From 7a243926ca0b744f62078b78706ee6e71421ba33 Mon Sep 17 00:00:00 2001 From: Heikki Ohinmaa Date: Thu, 7 Aug 2014 13:27:14 +0300 Subject: Context menu is not shown in Table body on mobile browsers (#13694) Includes updated tests. Change-Id: I928b888302a2cd22b7c94600fd4f8b7608ba0ca8 --- client/src/com/vaadin/client/ui/VScrollTable.java | 227 +++++++++++++++++++--- 1 file changed, 197 insertions(+), 30 deletions(-) (limited to 'client') diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 6a1e8664ed..c1e83ef08b 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -47,8 +47,6 @@ import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.dom.client.Touch; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; -import com.google.gwt.event.dom.client.ContextMenuEvent; -import com.google.gwt.event.dom.client.ContextMenuHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; @@ -129,6 +127,137 @@ public class VScrollTable extends FlowPanel implements HasWidgets, ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, ActionOwner, SubPartAware, DeferredWorker { + /** + * Simple interface for parts of the table capable of owning a context menu. + * + * @since 7.2 + * @author Vaadin Ltd + */ + private interface ContextMenuOwner { + public void showContextMenu(Event event); + } + + /** + * Handles showing context menu on "long press" from a touch screen. + * + * @since 7.2 + * @author Vaadin Ltd + */ + private class TouchContextProvider { + private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; + private Timer contextTouchTimeout; + + private Event touchStart; + private int touchStartY; + private int touchStartX; + + private ContextMenuOwner target; + + /** + * Initializes a handler for a certain context menu owner. + * + * @param target + * the owner of the context menu + */ + public TouchContextProvider(ContextMenuOwner target) { + this.target = target; + } + + /** + * Cancels the current context touch timeout. + */ + public void cancel() { + if (contextTouchTimeout != null) { + contextTouchTimeout.cancel(); + contextTouchTimeout = null; + } + touchStart = null; + } + + /** + * A function to handle touch context events in a table. + * + * @param event + * browser event to handle + */ + public void handleTouchEvent(final Event event) { + int type = event.getTypeInt(); + + switch (type) { + case Event.ONCONTEXTMENU: + target.showContextMenu(event); + break; + case Event.ONTOUCHSTART: + // save position to fields, touches in events are same + // instance during the operation. + touchStart = event; + + Touch touch = event.getChangedTouches().get(0); + touchStartX = touch.getClientX(); + touchStartY = touch.getClientY(); + + if (contextTouchTimeout == null) { + contextTouchTimeout = new Timer() { + + @Override + public void run() { + if (touchStart != null) { + // Open the context menu if finger + // is held in place long enough. + target.showContextMenu(touchStart); + event.preventDefault(); + touchStart = null; + } + } + }; + } + contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT); + break; + case Event.ONTOUCHCANCEL: + case Event.ONTOUCHEND: + cancel(); + break; + case Event.ONTOUCHMOVE: + if (isSignificantMove(event)) { + // Moved finger before the context menu timer + // expired, so let the browser handle the event. + cancel(); + } + } + } + + /** + * Calculates how many pixels away the user's finger has traveled. This + * reduces the chance of small non-intentional movements from canceling + * the long press detection. + * + * @param event + * the Event for which to check the move distance + * @return true if this is considered an intentional move by the user + */ + protected boolean isSignificantMove(Event event) { + if (touchStart == null) { + // no touch start + return false; + } + + // Calculate the distance between touch start and the current touch + // position + Touch touch = event.getChangedTouches().get(0); + int deltaX = touch.getClientX() - touchStartX; + int deltaY = touch.getClientY() - touchStartY; + int delta = deltaX * deltaX + deltaY * deltaY; + + // Compare to the square of the significant move threshold to remove + // the need for a square root + if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD + * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) { + return true; + } + return false; + } + } + public static final String STYLENAME = "v-table"; public enum SelectMode { @@ -372,8 +501,49 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** For internal use only. May be removed or replaced in the future. */ public final TableFooter tFoot = new TableFooter(); + /** Handles context menu for table body */ + private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() { + + @Override + public void showContextMenu(Event event) { + int left = Util.getTouchOrMouseClientX(event); + int top = Util.getTouchOrMouseClientY(event); + boolean menuShown = handleBodyContextMenu(left, top); + if (menuShown) { + event.stopPropagation(); + event.preventDefault(); + } + } + }; + + /** Handles touch events to display a context menu for table body */ + private TouchContextProvider touchContextProvider = new TouchContextProvider( + contextMenuOwner); + + /** + * For internal use only. May be removed or replaced in the future. + * + * Overwrites onBrowserEvent function on FocusableScrollPanel to give event + * access to touchContextProvider. Has to be public to give TableConnector + * access to the scrollBodyPanel field. + * + * @since 7.2 + * @author Vaadin Ltd + */ + public class FocusableScrollContextPanel extends FocusableScrollPanel { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + touchContextProvider.handleTouchEvent(event); + }; + + public FocusableScrollContextPanel(boolean useFakeFocusElement) { + super(useFakeFocusElement); + } + } + /** For internal use only. May be removed or replaced in the future. */ - public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel( + public final FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel( true); private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { @@ -613,16 +783,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } scrollBodyPanel.addKeyUpHandler(navKeyUpHandler); - scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS); - - scrollBodyPanel.sinkEvents(Event.ONCONTEXTMENU); - scrollBodyPanel.addDomHandler(new ContextMenuHandler() { - - @Override - public void onContextMenu(ContextMenuEvent event) { - handleBodyContextMenu(event); - } - }, ContextMenuEvent.getType()); + scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU); setStyleName(STYLENAME); @@ -684,19 +845,23 @@ public class VScrollTable extends FlowPanel implements HasWidgets, }); } - private void handleBodyContextMenu(ContextMenuEvent event) { + /** + * Handles a context menu event on table body. + * + * @param left + * left position of the context menu + * @param top + * top position of the context menu + * @return true if a context menu was shown, otherwise false + */ + private boolean handleBodyContextMenu(int left, int top) { if (enabled && bodyActionKeys != null) { - int left = Util.getTouchOrMouseClientX(event.getNativeEvent()); - int top = Util.getTouchOrMouseClientY(event.getNativeEvent()); top += Window.getScrollTop(); left += Window.getScrollLeft(); client.getContextMenu().showAt(this, left, top); - - // Only prevent browser context menu if there are action handlers - // registered - event.stopPropagation(); - event.preventDefault(); + return true; } + return false; } /** @@ -2420,7 +2585,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, int firstRendered = scrollBody.getFirstRendered(); int lastRendered = scrollBody.getLastRendered(); - if (lastRendered > totalRows) { lastRendered = totalRows - 1; } @@ -5137,7 +5301,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return -1; } - public class VScrollTableRow extends Panel implements ActionOwner { + public class VScrollTableRow extends Panel implements ActionOwner, + ContextMenuOwner { private static final int TOUCHSCROLL_TIMEOUT = 100; private static final int DRAGMODE_MULTIROW = 2; @@ -5155,6 +5320,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private Timer dragTouchTimeout; private int touchStartY; private int touchStartX; + + private TouchContextProvider touchContextProvider = new TouchContextProvider( + this); + private TooltipInfo tooltipInfo = null; private Map cellToolTips = new HashMap(); private boolean isDragging = false; @@ -5610,6 +5779,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, boolean touchEventHandled = false; if (enabled && hasNativeTouchScrolling) { + touchContextProvider.handleTouchEvent(event); + final Element targetTdOrTr = getEventTargetTdOrTr(event); final int type = event.getTypeInt(); @@ -5870,9 +6041,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, Util.simulateClickFromTouchEvent(touchStart, this); touchStart = null; } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } + touchContextProvider.cancel(); break; case Event.ONTOUCHMOVE: if (isSignificantMove(event)) { @@ -5887,9 +6056,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, .getActiveScrollDelegate() == null)) { startRowDrag(touchStart, type, targetTdOrTr); } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } + touchContextProvider.cancel(); /* * Avoid clicks and drags by clearing touch start * flag. @@ -6125,6 +6292,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return getTdOrTr(eventTarget); } + @Override public void showContextMenu(Event event) { if (enabled && actionKeys != null) { // Show context menu if there are registered action handlers @@ -6291,7 +6459,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, public Widget getWidgetForPaintable() { return this; } - } protected class VScrollTableGeneratedRow extends VScrollTableRow { -- cgit v1.2.3