Fixes event handling for CustomButton and subclasses:
- Mouse works like for a normal button; capture+focus on down click, click fires on release, button reacts to mouse enter/leave while held
- Keyboard support works like IE buttons; space bar "holds" the button down with click on key up; enter is a click on keypress
Review by: bruce
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1169 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/CustomButton.java b/user/src/com/google/gwt/user/client/ui/CustomButton.java
index acda760..adfeb4e 100644
--- a/user/src/com/google/gwt/user/client/ui/CustomButton.java
+++ b/user/src/com/google/gwt/user/client/ui/CustomButton.java
@@ -304,6 +304,16 @@
private Face downDisabled;
/**
+ * If <code>true</code>, this widget is capturing with the mouse held down.
+ */
+ private boolean isCapturing;
+
+ /**
+ * If <code>true</code>, this widget has focus with the space bar down.
+ */
+ private boolean isFocusing;
+
+ /**
* Constructor for <code>CustomButton</code>.
*
* @param upImage image for the default (up) face of the button
@@ -505,14 +515,86 @@
int type = DOM.eventGetType(event);
switch (type) {
+ case Event.ONMOUSEDOWN:
+ setFocus(true);
+ onClickStart();
+ DOM.setCapture(getElement());
+ isCapturing = true;
+ // Prevent dragging (on some browsers);
+ DOM.eventPreventDefault(event);
+ break;
+ case Event.ONMOUSEUP:
+ if (isCapturing) {
+ isCapturing = false;
+ DOM.releaseCapture(getElement());
+ if (isHovering()) {
+ onClick();
+ }
+ }
+ break;
+ case Event.ONMOUSEMOVE:
+ if (isCapturing) {
+ // Prevent dragging (on other browsers);
+ DOM.eventPreventDefault(event);
+ }
+ break;
case Event.ONMOUSEOUT:
- setHovering(false);
+ if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+ if (isCapturing) {
+ onClickCancel();
+ }
+ setHovering(false);
+ }
break;
case Event.ONMOUSEOVER:
- setHovering(true);
+ if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
+ setHovering(true);
+ if (isCapturing) {
+ onClickStart();
+ }
+ }
+ break;
+ case Event.ONCLICK:
+ // we handle clicks ourselves
+ return;
+ 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.
+ char keyCode = (char) DOM.eventGetKeyCode(event);
+ 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;
+ }
}
public void setAccessKey(char key) {
@@ -529,6 +611,9 @@
if (isEnabled() != enabled) {
toggleDisabled();
super.setEnabled(enabled);
+ if (!enabled) {
+ cleanupCaptureState();
+ }
}
}
@@ -581,6 +666,42 @@
}
/**
+ * 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() {
+ fireClickListeners();
+ }
+
+ /**
+ * 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
+ * <code>onClickStart</code> will eventually be followed by either
+ * <code>onClick</code> or <code>onClickCancel</code>, depending on
+ * whether the click is completed.
+ */
+ protected void onClickStart() {
+ }
+
+ protected void onDetach() {
+ super.onDetach();
+ cleanupCaptureState();
+ }
+
+ /**
* Sets whether this button is down.
*
* @param down <code>true</code> to press the button, <code>false</code>
@@ -612,9 +733,7 @@
* Implementation note: Package protected so we can use it when testing the
* button.
*/
-
finishSetup();
-
return curFace;
}
@@ -660,6 +779,19 @@
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) {
diff --git a/user/src/com/google/gwt/user/client/ui/PushButton.java b/user/src/com/google/gwt/user/client/ui/PushButton.java
index 66a8a80..239bee0 100644
--- a/user/src/com/google/gwt/user/client/ui/PushButton.java
+++ b/user/src/com/google/gwt/user/client/ui/PushButton.java
@@ -16,9 +16,6 @@
package com.google.gwt.user.client.ui;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-
/**
* A normal push button with custom styling.
*
@@ -39,8 +36,6 @@
private static final String STYLENAME_DEFAULT = "gwt-PushButton";
- private boolean waitingForMouseUp = false;
-
{
setStyleName(STYLENAME_DEFAULT);
}
@@ -62,6 +57,17 @@
}
/**
+ * Constructor for <code>PushButton</code>. The supplied image is used to
+ * construct the default face of the button.
+ *
+ * @param upImage image for the default (up) face of the button
+ * @param listener the click listener
+ */
+ public PushButton(Image upImage, ClickListener listener) {
+ super(upImage, listener);
+ }
+
+ /**
* Constructor for <code>PushButton</code>.
*
* @param upImage image for the default(up) face of the button
@@ -79,18 +85,7 @@
* @param listener clickListener
*/
public PushButton(Image upImage, Image downImage, ClickListener listener) {
- super(upImage, listener);
- }
-
- /**
- * Constructor for <code>PushButton</code>. The supplied image is used to
- * construct the default face of the button.
- *
- * @param upImage image for the default (up) face of the button
- * @param listener the click listener
- */
- public PushButton(Image upImage, ClickListener listener) {
- super(upImage, listener);
+ super(upImage, downImage, listener);
}
/**
@@ -134,37 +129,17 @@
public PushButton(String upText, String downText, ClickListener listener) {
super(upText, downText, listener);
}
- public void onBrowserEvent(Event event) {
- // Should not act on button if the button is disabled. This can happen
- // because an event is bubbled up from a non-disabled interior component.
- if (isEnabled() == false) {
- return;
- }
- int type = DOM.eventGetType(event);
- switch (type) {
- case Event.ONMOUSEDOWN:
- waitingForMouseUp = true;
- setDown(true);
- break;
- case Event.ONCLICK:
- // Must synthesize click events because when we have two separate face
- // elements for up/down, no click events are generated.
- return;
- case Event.ONMOUSEUP:
- if (waitingForMouseUp) {
- fireClickListeners();
- }
- waitingForMouseUp = false;
- setDown(false);
- break;
- case Event.ONMOUSEOUT:
- setDown(false);
- break;
- case Event.ONMOUSEOVER:
- if (waitingForMouseUp) {
- setDown(true);
- }
- }
- super.onBrowserEvent(event);
+
+ protected void onClick() {
+ setDown(false);
+ super.onClick();
+ }
+
+ protected void onClickCancel() {
+ setDown(false);
+ }
+
+ protected void onClickStart() {
+ setDown(true);
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/ToggleButton.java b/user/src/com/google/gwt/user/client/ui/ToggleButton.java
index c86a2d2..a4b6551 100644
--- a/user/src/com/google/gwt/user/client/ui/ToggleButton.java
+++ b/user/src/com/google/gwt/user/client/ui/ToggleButton.java
@@ -16,9 +16,6 @@
package com.google.gwt.user.client.ui;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Event;
-
/**
* A <code>ToggleButton</code> is a stylish stateful button which allows the
* user to toggle between <code>up</code> and <code>down</code> states.
@@ -61,6 +58,17 @@
}
/**
+ * Constructor for <code>ToggleButton</code>. The supplied image is used to
+ * construct the default face of the button.
+ *
+ * @param upImage image for the default (up) face of the button
+ * @param listener the click listener
+ */
+ public ToggleButton(Image upImage, ClickListener listener) {
+ super(upImage, listener);
+ }
+
+ /**
* Constructor for <code>ToggleButton</code>.
*
* @param upImage image for the default(up) face of the button
@@ -82,17 +90,6 @@
}
/**
- * Constructor for <code>ToggleButton</code>. The supplied image is used to
- * construct the default face of the button.
- *
- * @param upImage image for the default (up) face of the button
- * @param listener the click listener
- */
- public ToggleButton(Image upImage, ClickListener listener) {
- super(upImage, listener);
- }
-
- /**
* Constructor for <code>ToggleButton</code>. The supplied text is used to
* construct the default face of the button.
*
@@ -124,23 +121,17 @@
}
public boolean isDown() {
+ // Changes access to public.
return super.isDown();
}
-
- public void onBrowserEvent(Event event) {
- if (isEnabled() == false) {
- return;
- }
- int type = DOM.eventGetType(event);
- switch (type) {
- case Event.ONCLICK:
- toggleDown();
- break;
- }
- super.onBrowserEvent(event);
+
+ public void setDown(boolean down) {
+ // Changes access to public.
+ super.setDown(down);
}
-
- public void setDown(boolean isDown) {
- super.setDown(isDown);
+
+ protected void onClick() {
+ toggleDown();
+ super.onClick();
}
}