All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.user.client.ui.CustomButton Maven / Gradle / Ivy

/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.gwt.user.client.ui;

import com.google.gwt.aria.client.PressedValue;
import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.safehtml.client.HasSafeHtml;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.annotations.IsSafeHtml;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;

/**
 * CustomButton is a base button class with built in support for a set number
 * of button faces.
 *
 * Each face has its own style modifier. For example, the state for down and
 * hovering is assigned the CSS modifier down-hovering. So, if the
 * button's overall style name is gwt-PushButton then when showing the
 * down-hovering face, the button's style is 
 * gwt-PushButton-down-hovering. The overall style name can be used to
 * change the style of the button irrespective of the current face.
 *
 * 

* Each button face can be assigned is own image, text, or html contents. If no * content is defined for a face, then the face will use the contents of another * face. For example, if down-hovering does not have defined * contents, it will use the contents defined by the down face. *

* *

* The supported faces are defined below: *

*

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
CSS style nameGetter methoddescription of facedefaults to contents of face
up {@link #getUpFace()}face shown when button is upnone
down {@link #getDownFace()}face shown when button is downup
up-hovering {@link #getUpHoveringFace()}face shown when button is up and hoveringup
up-disabled {@link #getUpDisabledFace()}face shown when button is up and disabledup
down-hovering {@link #getDownHoveringFace()}face shown when button is down and hoveringdown
down-disabled {@link #getDownDisabledFace()}face shown when button is down and disableddown
*

* *

Use in UiBinder Templates

* * When working with CustomButton subclasses in * {@link com.google.gwt.uibinder.client.UiBinder UiBinder} templates, you * can set text and assign ImageResources for their various faces via * child elements: *

*

*
<g:upFace> *
<g:downFace> *
<g:upHoveringFace> *
<g:downHoveringFace> *
<g:upDisabledFace> *
<g:downDisabledFace> *
* * Each face element can take an optional image attribute * and an html body. For example:
 * <ui:image field='downButton'/> <!-- define an {@link com.google.gwt.resources.client.ImageResource ImageResource} -->
 *
 * <g:PushButton ui:field='pushButton' enabled='true'>
 *   <g:upFace>
 *     <b>click me</b>
 *   </gwt:upFace>
 *   <g:upHoveringFace>
 *     <b>Click ME!</b>
 *   </gwt:upHoveringFace>
 *
 *   <g:downFace image='{downButton}'/>
 *   <g:downHoveringFace image='{downButton}'/>
 * </g:PushButton>
 * 
*/ public abstract class CustomButton extends ButtonBase { /** * Represents a button's face. Each face is associated with its own style * modifier and, optionally, its own contents html, text, or image. */ public abstract class Face implements HasHTML, HasSafeHtml { private static final String STYLENAME_HTML_FACE = "html-face"; private final Face delegateTo; private Element face; /** * Constructor for Face. Creates a new face that delegates to * the supplied face. * * @param delegateTo default content provider */ private Face(Face delegateTo) { this.delegateTo = delegateTo; } /** * Gets the face's contents as html. * * @return face's contents as html * */ @Override public String getHTML() { return getFace().getInnerHTML(); } /** * Gets the face's contents as text. * * @return face's contents as text * */ @Override public String getText() { return getFace().getInnerText(); } /** * Set the face's contents as html. * * @param html html to set as face's contents html */ @Override public void setHTML(SafeHtml html) { setHTML(html.asString()); } /** * Set the face's contents as html. * * @param html html to set as face's contents html * */ @Override public void setHTML(@IsSafeHtml String html) { face = DOM.createDiv(); UIObject.setStyleName(face, STYLENAME_HTML_FACE, true); face.setInnerHTML(html); updateButtonFace(); } /** * Set the face's contents as an image. * * @param image image to set as face contents */ public final void setImage(Image image) { face = image.getElement(); updateButtonFace(); } /** * Sets the face's contents as text. * * @param text text to set as face's contents */ @Override public final void setText(String text) { face = DOM.createDiv(); UIObject.setStyleName(face, STYLENAME_HTML_FACE, true); face.setInnerText(text); updateButtonFace(); } @Override public final String toString() { return this.getName(); } /** * Gets the ID associated with this face. This will be a bitwise and of all * of the attributes that comprise this face. */ abstract int getFaceID(); /** * Get the name of the face. This property is also used as a modifier on the * CustomButton style.

For instance, if the * CustomButton style is "gwt-PushButton" and the face name is * "up", then the CSS class name will be "gwt-PushButton-up". * * @return the face's name */ abstract String getName(); /** * Gets the contents associated with this face. */ private Element getFace() { if (face == null) { if (delegateTo == null) { // provide a default face as none was supplied. face = DOM.createDiv(); return face; } else { return delegateTo.getFace(); } } else { return face; } } private void updateButtonFace() { if (curFace != null && curFace.getFace() == this.getFace()) { setCurrentFaceElement(face); } } } private static final String STYLENAME_DEFAULT = "gwt-CustomButton"; /** * Pressed Attribute bit. */ private static final int DOWN_ATTRIBUTE = 1; /** * Hovering Attribute bit. */ private static final int HOVERING_ATTRIBUTE = 2; /** * Disabled Attribute bit. */ private static final int DISABLED_ATTRIBUTE = 4; /** * ID for up face. */ private static final int UP = 0; /** * ID for down face. */ private static final int DOWN = DOWN_ATTRIBUTE; /** * ID for upHovering face. */ private static final int UP_HOVERING = HOVERING_ATTRIBUTE; /** * ID for downHovering face. */ private static final int DOWN_HOVERING = DOWN_ATTRIBUTE | HOVERING_ATTRIBUTE; /** * ID for upDisabled face. */ private static final int UP_DISABLED = DISABLED_ATTRIBUTE; /** * ID for downDisabled face. */ private static final int DOWN_DISABLED = DOWN | DISABLED_ATTRIBUTE; /** * The button's current face element. */ private Element curFaceElement; /** * The button's current face. */ private Face curFace; /** * Face for up. */ private Face up; /** * Face for down. */ private Face down; /** * Face for downHover. */ private Face downHovering; /** * Face for upHover. */ private Face upHovering; /** * Face for upDisabled. */ private Face upDisabled; /** * Face for downDisabled. */ private Face downDisabled; /** * If true, this widget is capturing with the mouse held down. */ private boolean isCapturing; /** * If true, this widget has focus with the space bar down. */ private boolean isFocusing; /** * Used to decide whether to allow clicks to propagate up to the superclass * or container elements. */ private boolean allowClick; /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button */ public CustomButton(Image upImage) { this(); getUpFace().setImage(upImage); } /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button * @param handler the click handler */ public CustomButton(Image upImage, ClickHandler handler) { this(upImage); addClickHandler(handler); } /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button * @param listener the click listener * @deprecated Use {@link #CustomButton(Image, ClickHandler)} instead */ @Deprecated public CustomButton(Image upImage, ClickListener listener) { this(upImage); addClickListener(listener); } /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button * @param downImage image for the down face of the button */ public CustomButton(Image upImage, Image downImage) { this(upImage); getDownFace().setImage(downImage); } /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button * @param downImage image for the down face of the button * @param handler clickListener */ public CustomButton(Image upImage, Image downImage, ClickHandler handler) { this(upImage, handler); getDownFace().setImage(downImage); } /** * Constructor for CustomButton. * * @param upImage image for the default (up) face of the button * @param downImage image for the down face of the button * @param listener clickListener * @deprecated Use {@link #CustomButton(Image, Image, ClickHandler)} instead */ @Deprecated public CustomButton(Image upImage, Image downImage, ClickListener listener) { this(upImage, listener); getDownFace().setImage(downImage); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button */ public CustomButton(String upText) { this(); getUpFace().setText(upText); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button * @param handler the click handler */ public CustomButton(String upText, ClickHandler handler) { this(upText); addClickHandler(handler); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button * @param listener the click listener * @deprecated Use {@link #CustomButton(String, ClickListener)} instead */ @Deprecated public CustomButton(String upText, ClickListener listener) { this(upText); addClickListener(listener); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button * @param downText the text for the down face of the button */ public CustomButton(String upText, String downText) { this(upText); getDownFace().setText(downText); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button * @param downText the text for the down face of the button * @param handler the click handler */ public CustomButton(String upText, String downText, ClickHandler handler) { this(upText, downText); addClickHandler(handler); } /** * Constructor for CustomButton. * * @param upText the text for the default (up) face of the button * @param downText the text for the down face of the button * @param listener the click listener * @deprecated Use {@link #CustomButton(String, String, ClickHandler)} instead */ @Deprecated public CustomButton(String upText, String downText, ClickListener listener) { this(upText, downText); addClickListener(listener); } /** * Constructor for CustomButton. */ protected CustomButton() { // Use FocusPanel.impl rather than FocusWidget because only FocusPanel.impl // works across browsers to create a focusable element. super(FocusPanel.impl.createFocusable()); sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS | Event.KEYEVENTS); setUpFace(createFace(null, "up", UP)); setStyleName(STYLENAME_DEFAULT); // Add a11y role "button" Roles.getButtonRole().set(getElement()); } /** * Gets the downDisabled face of the button. * * @return the downDisabled face */ public final Face getDownDisabledFace() { if (downDisabled == null) { setDownDisabledFace(createFace(getDownFace(), "down-disabled", DOWN_DISABLED)); } return downDisabled; } /** * Gets the down face of the button. * * @return the down face */ public final Face getDownFace() { if (down == null) { setDownFace(createFace(getUpFace(), "down", DOWN)); } return down; } /** * Gets the downHovering face of the button. * * @return the downHovering face */ public final Face getDownHoveringFace() { if (downHovering == null) { setDownHoveringFace(createFace(getDownFace(), "down-hovering", DOWN_HOVERING)); } return downHovering; } /** * Gets the current face's html. * * @return current face's html */ @Override public String getHTML() { return getCurrentFace().getHTML(); } @Override public int getTabIndex() { return FocusPanel.impl.getTabIndex(getElement()); } /** * Gets the current face's text. * * @return current face's text */ @Override public String getText() { return getCurrentFace().getText(); } /** * Gets the upDisabled face of the button. * * @return the upDisabled face */ public final Face getUpDisabledFace() { if (upDisabled == null) { setUpDisabledFace(createFace(getUpFace(), "up-disabled", UP_DISABLED)); } return upDisabled; } /** * Gets the up face of the button. * * @return the up face */ public final Face getUpFace() { return up; } /** * Gets the upHovering face of the button. * * @return the upHovering face */ public final Face getUpHoveringFace() { if (upHovering == null) { setUpHoveringFace(createFace(getUpFace(), "up-hovering", UP_HOVERING)); } return upHovering; } @Override public void onBrowserEvent(Event event) { // Should not act on button if disabled. if (isEnabled() == false) { // This can happen when events are bubbled up from non-disabled children return; } int type = DOM.eventGetType(event); switch (type) { case Event.ONCLICK: // If clicks are currently disallowed, keep it from bubbling or being // passed to the superclass. if (!allowClick) { event.stopPropagation(); return; } break; case Event.ONMOUSEDOWN: if (event.getButton() == Event.BUTTON_LEFT) { setFocus(true); onClickStart(); DOM.setCapture(getElement()); isCapturing = true; // Prevent dragging (on some browsers); event.preventDefault(); } break; case Event.ONMOUSEUP: if (isCapturing) { isCapturing = false; DOM.releaseCapture(getElement()); if (isHovering() && event.getButton() == Event.BUTTON_LEFT) { onClick(); } } break; case Event.ONMOUSEMOVE: if (isCapturing) { // Prevent dragging (on other browsers); event.preventDefault(); } break; case Event.ONMOUSEOUT: Element to = DOM.eventGetToElement(event); if (getElement().isOrHasChild(DOM.eventGetTarget(event)) && (to == null || !getElement().isOrHasChild(to))) { if (isCapturing) { onClickCancel(); } setHovering(false); } break; case Event.ONMOUSEOVER: if (getElement().isOrHasChild(DOM.eventGetTarget(event))) { setHovering(true); if (isCapturing) { onClickStart(); } } break; case Event.ONBLUR: if (isFocusing) { isFocusing = false; onClickCancel(); } break; case Event.ONLOSECAPTURE: if (isCapturing) { isCapturing = false; onClickCancel(); } break; } super.onBrowserEvent(event); // Synthesize clicks based on keyboard events AFTER the normal key handling. if ((event.getTypeInt() & Event.KEYEVENTS) != 0) { char keyCode = (char) event.getKeyCode(); switch (type) { case Event.ONKEYDOWN: if (keyCode == ' ') { isFocusing = true; onClickStart(); } break; case Event.ONKEYUP: if (isFocusing && keyCode == ' ') { isFocusing = false; onClick(); } break; case Event.ONKEYPRESS: if (keyCode == '\n' || keyCode == '\r') { onClickStart(); onClick(); } break; } } } @Override public void setAccessKey(char key) { FocusPanel.impl.setAccessKey(getElement(), key); } /** * Sets whether this button is enabled. * * @param enabled true to enable the button, false * to disable it */ @Override public final void setEnabled(boolean enabled) { if (isEnabled() != enabled) { toggleDisabled(); super.setEnabled(enabled); if (!enabled) { cleanupCaptureState(); Roles.getButtonRole().removeAriaPressedState(getElement()); } else { setAriaPressed(getCurrentFace()); } } } @Override public void setFocus(boolean focused) { if (focused) { FocusPanel.impl.focus(getElement()); } else { FocusPanel.impl.blur(getElement()); } } @Override public void setHTML(SafeHtml html) { setHTML(html.asString()); } /** * Sets the current face's html. * * @param html html to set */ @Override public void setHTML(@IsSafeHtml String html) { getCurrentFace().setHTML(html); } @Override public void setTabIndex(int index) { FocusPanel.impl.setTabIndex(getElement(), index); } /** * Sets the current face's text. * * @param text text to set */ @Override public void setText(String text) { getCurrentFace().setText(text); } /** * Is this button down? * * @return true if the button is down */ protected boolean isDown() { return (DOWN_ATTRIBUTE & getCurrentFace().getFaceID()) > 0; } /** * Overridden on attach to ensure that a button face has been chosen before * the button is displayed. */ @Override protected void onAttach() { finishSetup(); super.onAttach(); } /** * Called when the user finishes clicking on this button. The default behavior * is to fire the click event to listeners. Subclasses that override * {@link #onClickStart()} should override this method to restore the normal * widget display. */ protected void onClick() { // Allow the click we're about to synthesize to pass through to the // superclass and containing elements. Element.dispatchEvent() is // synchronous, so we simply set and clear the flag within this method. allowClick = true; // Mouse coordinates are not always available (e.g., when the click is // caused by a keyboard event). NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false, false, false, false); getElement().dispatchEvent(evt); allowClick = false; } /** * Called when the user aborts a click in progress; for example, by dragging * the mouse outside of the button before releasing the mouse button. * Subclasses that override {@link #onClickStart()} should override this * method to restore the normal widget display. */ protected void onClickCancel() { } /** * Called when the user begins to click on this button. Subclasses may * override this method to display the start of the click visually; such * subclasses should also override {@link #onClick()} and * {@link #onClickCancel()} to restore normal visual state. Each * onClickStart will eventually be followed by either * onClick or onClickCancel, depending on whether * the click is completed. */ protected void onClickStart() { } @Override protected void onDetach() { super.onDetach(); cleanupCaptureState(); setHovering(false); } /** * Sets whether this button is down. * * @param down true to press the button, false * otherwise */ protected void setDown(boolean down) { if (down != isDown()) { toggleDown(); } } /** * Common setup between constructors. */ void finishSetup() { if (curFace == null) { setCurrentFace(getUpFace()); } } void fireClickListeners(@SuppressWarnings("unused") Event nativeEvent) { // TODO(ecc) Once event triggering is committed, should fire a native click event instead. fireEvent(new ClickEvent() { }); } /** * Gets the current face of the button. * * @return the current face */ Face getCurrentFace() { /* * Implementation note: Package protected so we can use it when testing the * button. */ finishSetup(); return curFace; } /** * Is the mouse hovering over this button? * * @return true if the mouse is hovering */ final boolean isHovering() { return (HOVERING_ATTRIBUTE & getCurrentFace().getFaceID()) > 0; } void setCurrentFace(Face newFace) { /* * Implementation note: default access for testing. */ if (curFace != newFace) { if (curFace != null) { removeStyleDependentName(curFace.getName()); } curFace = newFace; setCurrentFaceElement(newFace.getFace()); addStyleDependentName(curFace.getName()); if (isEnabled()) { setAriaPressed(newFace); } } } /** * Sets whether this button is hovering. * * @param hovering is this button hovering? */ final void setHovering(boolean hovering) { if (hovering != isHovering()) { toggleHover(); } } /** * Toggle the up/down attribute. */ void toggleDown() { int newFaceID = getCurrentFace().getFaceID() ^ DOWN_ATTRIBUTE; setCurrentFace(newFaceID); } /** * Resets internal state if this button can no longer service events. This can * occur when the widget becomes detached or disabled. */ private void cleanupCaptureState() { if (isCapturing || isFocusing) { DOM.releaseCapture(getElement()); isCapturing = false; isFocusing = false; onClickCancel(); } } private Face createFace(Face delegateTo, final String name, final int faceID) { return new Face(delegateTo) { @Override public String getName() { return name; } @Override int getFaceID() { return faceID; } }; } private Face getFaceFromID(int id) { switch (id) { case DOWN: return getDownFace(); case UP: return getUpFace(); case DOWN_HOVERING: return getDownHoveringFace(); case UP_HOVERING: return getUpHoveringFace(); case UP_DISABLED: return getUpDisabledFace(); case DOWN_DISABLED: return getDownDisabledFace(); default: throw new IllegalStateException(id + " is not a known face id."); } } private void setAriaPressed(Face newFace) { boolean pressed = (newFace.getFaceID() & DOWN_ATTRIBUTE) == 1; Roles.getButtonRole().setAriaPressedState(getElement(), PressedValue.of(pressed)); } /** * Sets the current face based on the faceID. * * @param faceID sets the new face of the button */ private void setCurrentFace(int faceID) { Face newFace = getFaceFromID(faceID); setCurrentFace(newFace); } private void setCurrentFaceElement(Element newFaceElement) { if (curFaceElement != newFaceElement) { if (curFaceElement != null) { getElement().removeChild(curFaceElement); } curFaceElement = newFaceElement; DOM.appendChild(getElement(), curFaceElement); } } /** * Sets the downDisabled face of the button. * * @param downDisabled downDisabled face */ private void setDownDisabledFace(Face downDisabled) { this.downDisabled = downDisabled; } /** * Sets the down face of the button. * * @param down the down face */ private void setDownFace(Face down) { this.down = down; } /** * Sets the downHovering face of the button. * * @param downHovering hoverDown face */ private void setDownHoveringFace(Face downHovering) { this.downHovering = downHovering; } /** * Sets the upDisabled face of the button. * * @param upDisabled upDisabled face */ private void setUpDisabledFace(Face upDisabled) { this.upDisabled = upDisabled; } /** * Sets the up face of the button. * * @param up up face */ private void setUpFace(Face up) { this.up = up; } /** * Sets the upHovering face of the button. * * @param upHovering upHovering face */ private void setUpHoveringFace(Face upHovering) { this.upHovering = upHovering; } /** * Toggle the disabled attribute. */ private void toggleDisabled() { // Toggle disabled. int newFaceID = getCurrentFace().getFaceID() ^ DISABLED_ATTRIBUTE; // Remove hovering. newFaceID &= ~HOVERING_ATTRIBUTE; // Sets the current face. setCurrentFace(newFaceID); } /** * Toggle the hovering attribute. */ private void toggleHover() { // Toggle hovering. int newFaceID = getCurrentFace().getFaceID() ^ HOVERING_ATTRIBUTE; // Remove disabled. newFaceID &= ~DISABLED_ATTRIBUTE; setCurrentFace(newFaceID); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy