From 8d470c9802ee33d6dfaf26adad7b2269b665bf44 Mon Sep 17 00:00:00 2001 From: Heikki Ohinmaa Date: Wed, 23 Jul 2014 11:38:27 +0300 Subject: [PATCH] Context menu is not shown in Table body on mobile browsers (#13694) Change-Id: I095401164a931f3a69dea91037a6cbf35b197121 --- .../com/vaadin/client/ui/VScrollTable.java | 520 +++++++++++------- .../components/table/TabletContextMenu.java | 139 +++++ 2 files changed, 449 insertions(+), 210 deletions(-) create mode 100644 uitest/src/com/vaadin/tests/components/table/TabletContextMenu.java diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index ba90c0dfed..af469949a8 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -1,12 +1,12 @@ /* * Copyright 2000-2014 Vaadin Ltd. - * + * * 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 @@ -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; @@ -103,31 +101,160 @@ import com.vaadin.shared.ui.table.TableConstants; /** * VScrollTable - * + * * VScrollTable is a FlowPanel having two widgets in it: * TableHead component * * ScrollPanel - * + * * TableHead contains table's header and widgets + logic for resizing, * reordering and hiding columns. - * + * * ScrollPanel contains VScrollTableBody object which handles content. To save * some bandwidth and to improve clients responsiveness with loads of data, in * VScrollTableBody all rows are not necessary rendered. There are "spacers" in * VScrollTableBody to use the exact same space as non-rendered rows would use. * This way we can use seamlessly traditional scrollbars and scrolling to fetch * more rows instead of "paging". - * + * * In VScrollTable we listen to scroll events. On horizontal scrolling we also * update TableHeads scroll position which has its scrollbars hidden. On * vertical scroll events we will check if we are reaching the end of area where * we have rows rendered and - * + * * TODO implement unregistering for child components in Cells */ public class VScrollTable extends FlowPanel implements HasWidgets, ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, ActionOwner, SubPartAware { + /** + * Simple interface for parts of the table capable of owning a context menu. + * + * @author Vaadin Ltd + */ + private interface ContextMenuOwner { + public void showContextMenu(Event event); + } + + /** + * Handles showing context menu on "long press" from a touch screen. + * + * @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 { @@ -264,7 +391,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * The last known row height used to preserve the height of a table with * custom row heights and a fixed page length after removing the last row * from the table. - * + * * A new VScrollTableBody instance is created every time the number of rows * changes causing {@link VScrollTableBody#rowHeight} to be discarded and * the height recalculated by {@link VScrollTableBody#getRowHeight(boolean)} @@ -308,7 +435,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see java.lang.Object#toString() */ @@ -369,9 +496,34 @@ 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. */ public final FocusableScrollPanel scrollBodyPanel = new FocusableScrollPanel( - true); + true) { + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + touchContextProvider.handleTouchEvent(event); + }; + }; private KeyPressHandler navKeyPressHandler = new KeyPressHandler() { @@ -533,14 +685,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Values (serverCacheFirst+serverCacheLast) sent by server that tells which * rows (indexes) are in the server side cache (page buffer). -1 means * unknown. The server side cache row MUST MATCH the client side cache rows. - * + * * If the client side cache contains additional rows with e.g. buttons, it * will cause out of sync when such a button is pressed. - * + * * If the server side cache contains additional rows with e.g. buttons, * scrolling in the client will cause empty buttons to be rendered * (cached=true request for non-existing components) - * + * * For internal use only. May be removed or replaced in the future. */ public int serverCacheFirst = -1; @@ -610,16 +762,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); @@ -681,25 +824,29 @@ 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; } /** * Fires a column resize event which sends the resize information to the * server. - * + * * @param columnId * The columnId of the column which was resized * @param originalWidth @@ -721,7 +868,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Non-immediate variable update of column widths for a collection of * columns. - * + * * @param columns * the columns to trigger the events for. */ @@ -737,7 +884,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Moves the focus one step down - * + * * @return Returns true if succeeded */ private boolean moveFocusDown() { @@ -746,7 +893,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Moves the focus down by 1+offset rows - * + * * @return Returns true if succeeded, else false if the selection could not * be move downwards */ @@ -770,7 +917,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Moves the selection one step up - * + * * @return Returns true if succeeded */ private boolean moveFocusUp() { @@ -779,10 +926,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Moves the focus row upwards - * + * * @return Returns true if succeeded, else false if the selection could not * be move upwards - * + * */ private boolean moveFocusUp(int offset) { if (isSelectable()) { @@ -806,7 +953,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Selects a row where the current selection head is - * + * * @param ctrlSelect * Is the selection a ctrl+selection * @param shiftSelect @@ -841,7 +988,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Sends the selection to the server if it has been changed since the last * update/visit. - * + * * @param immediately * set to true to immediately send the rows */ @@ -898,7 +1045,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key that moves the selection head upwards. By default it is the * up arrow key but by overriding this you can change the key to whatever * you want. - * + * * @return The keycode of the key */ protected int getNavigationUpKey() { @@ -909,7 +1056,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key that moves the selection head downwards. By default it is the * down arrow key but by overriding this you can change the key to whatever * you want. - * + * * @return The keycode of the key */ protected int getNavigationDownKey() { @@ -920,7 +1067,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key that scrolls to the left in the table. By default it is the * left arrow key but by overriding this you can change the key to whatever * you want. - * + * * @return The keycode of the key */ protected int getNavigationLeftKey() { @@ -931,7 +1078,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key that scroll to the right on the table. By default it is the * right arrow key but by overriding this you can change the key to whatever * you want. - * + * * @return The keycode of the key */ protected int getNavigationRightKey() { @@ -942,7 +1089,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key that selects an item in the table. By default it is the space * bar key but by overriding this you can change the key to whatever you * want. - * + * * @return */ protected int getNavigationSelectKey() { @@ -953,7 +1100,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key the moves the selection one page up in the table. By default * this is the Page Up key but by overriding this you can change the key to * whatever you want. - * + * * @return */ protected int getNavigationPageUpKey() { @@ -964,7 +1111,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key the moves the selection one page down in the table. By * default this is the Page Down key but by overriding this you can change * the key to whatever you want. - * + * * @return */ protected int getNavigationPageDownKey() { @@ -975,7 +1122,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key the moves the selection to the beginning of the table. By * default this is the Home key but by overriding this you can change the * key to whatever you want. - * + * * @return */ protected int getNavigationStartKey() { @@ -986,7 +1133,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Get the key the moves the selection to the end of the table. By default * this is the End key but by overriding this you can change the key to * whatever you want. - * + * * @return */ protected int getNavigationEndKey() { @@ -1093,9 +1240,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * The focus is no longer on a selected row. Move * focus to the selected row. (#10522) - * + * * Don't do this for multiselect (#13341). - * + * * Checking the selection mode here instead of in * setRowFocus allows keyboard shift+downarrow * selection to work as expected. @@ -1302,7 +1449,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the extra space that is given to the header column when column * width is determined by header text. - * + * * @return extra space in pixels */ private int getHeaderPadding() { @@ -1313,7 +1460,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This method exists for the needs of {@link VTreeTable} only. Not part of * the official API, extend at your own risk. May be removed or * replaced in the future. - * + * * @return index of TreeTable's hierarchy column, or -1 if not applicable */ protected int getHierarchyColumnIndex() { @@ -1359,7 +1506,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Selects the last row visible in the table *

* For internal use only. May be removed or replaced in the future. - * + * * @param focusOnly * Should the focus only be moved to the last row */ @@ -1386,7 +1533,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Selects the first row visible in the table *

* For internal use only. May be removed or replaced in the future. - * + * * @param focusOnly * Should the focus only be moved to the first row */ @@ -1485,7 +1632,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, *

* Update headers whould be called before this method is called! *

- * + * * @param strings */ private void updateFooter(String[] strings) { @@ -1514,7 +1661,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * For internal use only. May be removed or replaced in the future. - * + * * @param uidl * which contains row data * @param firstRow @@ -1643,7 +1790,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * on the commands in the UIDL. *

* For internal use only. May be removed or replaced in the future. - * + * * @param partialRowAdditions * the UIDL containing row updates. */ @@ -1673,7 +1820,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Gives correct column index for given column key ("cid" in UIDL). - * + * * @param colKey * @return column index of visible columns, -1 if column not visible */ @@ -1741,9 +1888,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Note: not part of the official API, extend at your own risk. May be * removed or replaced in the future. - * + * * Sets the indicated column's width for headers and scrollBody alike. - * + * * @param colIndex * index of the modified column * @param w @@ -1791,7 +1938,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Get a rendered row by its key - * + * * @param key * The key to search with * @return @@ -1812,10 +1959,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the next row to the given row - * + * * @param row * The row to calculate from - * + * * @return The next row or null if no row exists */ private VScrollTableRow getNextRow(VScrollTableRow row, int offset) { @@ -1838,7 +1985,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the previous row from the given row - * + * * @param row * The row to calculate from * @return The previous row or null if no row exists @@ -1950,13 +2097,13 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Run only once when component is attached and received its initial * content. This function: - * + * * * Syncs headers and bodys "natural widths and saves the values. - * + * * * Sets proper width and height - * + * * * Makes deferred request to get some cache rows - * + * * For internal use only. May be removed or replaced in the future. */ public void sizeInit() { @@ -1968,7 +2115,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * We will use browsers table rendering algorithm to find proper column * widths. If content and header take less space than available, we will * divide extra space relatively to each column which has not width set. - * + * * Overflow pixels are added to last column. */ @@ -2267,7 +2414,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Note: this method is not part of official API although declared as * protected. Extend at your own risk. - * + * * @return true if content area will have scrollbars visible. */ protected boolean willHaveScrollbars() { @@ -2657,7 +2804,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Sets width to the header cell. This width should not include any * possible indent modifications that are present in * {@link VScrollTableBody#getMaxIndent()}. - * + * * @param w * required width of the cell sans indentations * @param ensureDefinedWidth @@ -2720,7 +2867,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Detects if width is fixed by developer on server side or resized to * current width by user. - * + * * @return true if defined, false if "natural" width */ public boolean isDefinedWidth() { @@ -2729,10 +2876,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * This method exists for the needs of {@link VTreeTable} only. - * + * * Returns the pixels width of the header cell. This includes the * indent, if applicable. - * + * * @return The width in pixels */ protected int getWidthWithIndent() { @@ -2747,7 +2894,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the pixels width of the header cell. - * + * * @return The width in pixels */ public int getWidth() { @@ -2756,7 +2903,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * This method exists for the needs of {@link VTreeTable} only. - * + * * @return true if this is hierarcyColumn's header cell, * false otherwise */ @@ -2870,7 +3017,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Fires a header click event after the user has clicked a column header * cell - * + * * @param event * The click event */ @@ -3082,7 +3229,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the smallest possible cell width in pixels. - * + * * @param includeIndent * - width should include hierarchy column indent if * applicable (VTreeTable only) @@ -3134,10 +3281,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Detects the natural minimum width for the column of this header cell. * If column is resized by user or the width is defined by server the * actual width is returned. Else the natural min width is returned. - * + * * @param columnIndex * column index hint, if -1 (unknown) it will be detected - * + * * @return */ public int getNaturalColumnWidth(int columnIndex) { @@ -3198,7 +3345,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * HeaderCell that is header cell for row headers. - * + * * Reordering disabled and clicking on it resets sorting. */ public class RowHeadersHeaderCell extends HeaderCell { @@ -3527,9 +3674,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Get's HeaderCell by it's column Key. - * + * * Note that this returns HeaderCell even if it is currently collapsed. - * + * * @param cid * Column key of accessed HeaderCell * @return HeaderCell @@ -3822,7 +3969,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Sets the text of the footer - * + * * @param footerText * The text in the footer */ @@ -3836,7 +3983,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Set alignment of the text in the cell - * + * * @param c * The alignment which can be ALIGN_CENTER, ALIGN_LEFT, * ALIGN_RIGHT @@ -3860,7 +4007,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Get the alignment of the text int the cell - * + * * @return Returns either ALIGN_CENTER, ALIGN_LEFT or ALIGN_RIGHT */ public char getAlign() { @@ -3871,7 +4018,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Sets the width of the cell. This width should not include any * possible indent modifications that are present in * {@link VScrollTableBody#getMaxIndent()}. - * + * * @param w * The width of the cell * @param ensureDefinedWidth @@ -3953,7 +4100,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Detects if width is fixed by developer on server side or resized to * current width by user. - * + * * @return true if defined, false if "natural" width */ public boolean isDefinedWidth() { @@ -3962,7 +4109,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the pixels width of the footer cell. - * + * * @return The width in pixels */ public int getWidth() { @@ -3971,7 +4118,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Sets the expand ratio of the cell - * + * * @param floatAttribute * The expand ratio */ @@ -3981,7 +4128,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the expand ration of the cell - * + * * @return The expand ratio */ public float getExpandRatio() { @@ -3990,7 +4137,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Is the cell enabled? - * + * * @return True if enabled else False */ public boolean isEnabled() { @@ -4026,7 +4173,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Handles a event on the captions - * + * * @param event * The event to handle */ @@ -4040,7 +4187,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Fires a footer click event after the user has clicked a column footer * cell - * + * * @param event * The click event */ @@ -4057,7 +4204,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the column key of the column - * + * * @return The column key */ public String getColKey() { @@ -4068,10 +4215,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Detects the natural minimum width for the column of this header cell. * If column is resized by user or the width is defined by server the * actual width is returned. Else the natural min width is returned. - * + * * @param columnIndex * column index hint, if -1 (unknown) it will be detected - * + * * @return */ public int getNaturalColumnWidth(int columnIndex) { @@ -4117,7 +4264,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * HeaderCell that is header cell for row headers. - * + * * Reordering disabled and clicking on it resets sorting. */ public class RowHeadersFooterCell extends FooterCell { @@ -4187,7 +4334,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see * com.google.gwt.user.client.ui.Panel#remove(com.google.gwt.user.client * .ui.Widget) @@ -4206,7 +4353,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see com.google.gwt.user.client.ui.HasWidgets#iterator() */ @@ -4217,10 +4364,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Gets a footer cell which represents the given columnId - * + * * @param cid * The columnId - * + * * @return The cell */ public FooterCell getFooterCell(String cid) { @@ -4229,7 +4376,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Gets a footer cell by using a column index - * + * * @param index * The index of the column * @return The Cell @@ -4244,7 +4391,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Updates the cells contents when updateUIDL request is received - * + * * @param uidl * The UIDL */ @@ -4315,7 +4462,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Set a footer cell for a specified column index - * + * * @param index * The index * @param cell @@ -4346,7 +4493,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Remove a cell by using the columnId - * + * * @param colKey * The columnId to remove */ @@ -4357,7 +4504,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Enable a column (Sets the footer cell) - * + * * @param cid * The columnId * @param index @@ -4390,7 +4537,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Set the horizontal position in the cell in the footer. This is done * when a horizontal scrollbar is present. - * + * * @param scrollLeft * The value of the leftScroll */ @@ -4400,7 +4547,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Swap cells when the column are dragged - * + * * @param oldIndex * The old index of the cell * @param newIndex @@ -4422,7 +4569,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This Panel can only contain VScrollTableRow type of widgets. This * "simulates" very large table, keeping spacers which take room of * unrendered rows. - * + * */ public class VScrollTableBody extends Panel { @@ -4615,7 +4762,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * #8040 - scroll position is completely changed since the * latest request, so request a new set of rows. - * + * * TODO: We should probably check whether the fetched rows match * the current scroll position right when they arrive, so as to * not waste time rendering a set of rows that will never be @@ -4630,7 +4777,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } else if (firstRendered > reactFirstRow) { /* * Branch for fetching cache above visible area. - * + * * If cache needed for both before and after visible area, this * will be rendered after-cache is received and rendered. So in * some rare situations the table may make two cache visits to @@ -4643,7 +4790,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Inserts rows as provided in the rowData starting at firstIndex. - * + * * @param rowData * @param firstIndex * @param rows @@ -4725,9 +4872,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This method is used to instantiate new rows for this table. It * automatically sets correct widths to rows cells and assigns correct * client reference for child widgets. - * + * * This method can be called only after table has been initialized - * + * * @param uidl */ private VScrollTableRow prepareRow(UIDL uidl) { @@ -4968,7 +5115,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Returns the width available for column content. - * + * * @param columnIndex * @return */ @@ -4994,14 +5141,14 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Sets the content width of a column. - * + * * Due IE limitation, we must set the width to a wrapper elements inside * table cells (with overflow hidden, which does not work on td * elements). - * + * * To get this work properly crossplatform, we will also set the width * of td. - * + * * @param colIndex * @param w */ @@ -5027,9 +5174,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * This method exists for the needs of {@link VTreeTable} only. May be * removed or replaced in the future.

Returns the maximum * indent of the hierarcyColumn, if applicable. - * + * * @see {@link VScrollTable#getHierarchyColumnIndex()} - * + * * @return maximum indent in pixels */ protected int getMaxIndent() { @@ -5120,7 +5267,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; @@ -5131,13 +5279,15 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private String[] actionKeys = null; private final TableRowElement rowElement; private int index; - private Event touchStart; - private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500; - private Timer contextTouchTimeout; + private Event touchStart; 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; @@ -5295,7 +5445,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Overriding this and returning true causes all text cells to be * rendered as HTML. - * + * * @return always returns false in the default implementation */ protected boolean isRenderHtmlInCells() { @@ -5304,7 +5454,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Detects whether row is visible in tables viewport. - * + * * @return */ public boolean isInViewPort() { @@ -5320,7 +5470,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Makes a check based on indexes whether the row is before the * compared row. - * + * * @param row1 * @return true if this rows index is smaller than in the row1 */ @@ -5331,7 +5481,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Sets the index of the row in the whole table. Currently used just * to set even/odd classname - * + * * @param indexInWholeTable */ private void setIndex(int indexInWholeTable) { @@ -5510,7 +5660,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * If there are registered click listeners, sends a click event and * returns true. Otherwise, does nothing and returns false. - * + * * @param event * @param targetTdOrTr * @param immediate @@ -5585,7 +5735,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Special handler for touch devices that support native scrolling - * + * * @return Whether the event was handled by this method. */ private boolean handleTouchEvent(final Event event) { @@ -5593,6 +5743,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(); @@ -5623,37 +5775,10 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT); } - - if (actionKeys != null) { - 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. - showContextMenu(touchStart); - event.preventDefault(); - touchStart = null; - } - } - }; - } - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - } break; case Event.ONTOUCHMOVE: touchEventHandled = true; if (isSignificantMove(event)) { - if (contextTouchTimeout != null) { - // Moved finger before the context menu timer - // expired, so let the browser handle this as a - // scroll. - contextTouchTimeout.cancel(); - contextTouchTimeout = null; - } if (!isDragging && dragTouchTimeout != null) { // Moved finger before the drag timer expired, // so let the browser handle this as a scroll. @@ -5673,9 +5798,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, case Event.ONTOUCHEND: case Event.ONTOUCHCANCEL: touchEventHandled = true; - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - } if (dragTouchTimeout != null) { dragTouchTimeout.cancel(); } @@ -5853,9 +5975,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)) { @@ -5870,9 +5990,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. @@ -5929,25 +6047,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } }.schedule(TOUCHSCROLL_TIMEOUT); - - if (contextTouchTimeout == null - && actionKeys != null) { - contextTouchTimeout = new Timer() { - - @Override - public void run() { - if (touchStart != null) { - showContextMenu(touchStart); - touchStart = null; - } - } - }; - } - if (contextTouchTimeout != null) { - contextTouchTimeout.cancel(); - contextTouchTimeout - .schedule(TOUCH_CONTEXT_MENU_TIMEOUT); - } } break; case Event.ONMOUSEDOWN: @@ -6008,7 +6107,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Checks if the row represented by the row key has been selected - * + * * @param key * The generated row key */ @@ -6078,7 +6177,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * target of the event should not be handled. If the event target is * the row directly this method returns the TR element instead of * the TD. - * + * * @param event * @return TD or TR element that the event targets (the actual event * target is this element or a child of it) @@ -6108,6 +6207,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 @@ -6127,7 +6227,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Has the row been selected? - * + * * @return Returns true if selected, else false */ public boolean isSelected() { @@ -6152,7 +6252,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Is called when a user clicks an item when holding SHIFT key down. * This will select a new range from the last focused row - * + * * @param deselectPrevious * Should the previous selected range be deselected */ @@ -6221,7 +6321,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see com.vaadin.client.ui.IActionOwner#getActions () */ @@ -6399,7 +6499,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Ensure the component has a focus. - * + * * TODO the current implementation simply always calls focus for the * component. In case the Table at some point implements focus/blur * listeners, this method needs to be evolved to conditionally call @@ -6593,7 +6693,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, if (getTotalRows() == 0) { /* * Too wide header combined with no rows in the table. - * + * * No horizontal scrollbars would be displayed because * there's no rows that grows too wide causing the * scrollBody container div to overflow. Must explicitely @@ -6744,7 +6844,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * helper to set pixel size of head and body part - * + * * @param pixels */ private void setContentWidth(int pixels) { @@ -6900,7 +7000,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Helper function to build html snippet for column or row headers - * + * * @param uidl * possibly with values caption and icon * @return html snippet containing possibly an icon + caption text @@ -6933,7 +7033,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, scrollTop = scrollBodyPanel.getScrollPosition(); /* * #6970 - IE sometimes fires scroll events for a detached table. - * + * * FIXME initializedAndAttached should probably be renamed - its name * doesn't seem to reflect its semantics. onDetach() doesn't set it to * false, and changing that might break something else, so we need to @@ -7225,7 +7325,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Moves the selection head to a specific row - * + * * @param row * The row to where the selection head should move * @return Returns true if focus was moved successfully, else false @@ -7266,7 +7366,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Ensures that the row is visible - * + * * @param row * The row to ensure is visible */ @@ -7281,7 +7381,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Handles the keyboard events handled by the table - * + * * @param event * The keyboard event received * @return true iff the navigation event was handled @@ -7538,7 +7638,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event * .dom.client.FocusEvent) @@ -7560,7 +7660,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see * com.google.gwt.event.dom.client.BlurHandler#onBlur(com.google.gwt.event * .dom.client.BlurEvent) @@ -7598,7 +7698,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Removes a key from a range if the key is found in a selected range - * + * * @param key * The key to remove */ @@ -7624,7 +7724,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /** * Can the Table be focused? - * + * * @return True if the table can be focused, else false */ public boolean isFocusable() { @@ -7644,7 +7744,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, /* * (non-Javadoc) - * + * * @see com.vaadin.client.Focusable#focus() */ @@ -7713,7 +7813,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } /** - * + * * @param keyCode * @return true if the given keyCode is used by the table for navigation */ @@ -7774,7 +7874,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Add this to the element mouse down event by using element.setPropertyJSO * ("onselectstart",applyDisableTextSelectionIEHack()); Remove it then again * when the mouse is depressed in the mouse up event. - * + * * @return Returns the JSO preventing text selection */ private static native JavaScriptObject getPreventTextSelectionIEHack() diff --git a/uitest/src/com/vaadin/tests/components/table/TabletContextMenu.java b/uitest/src/com/vaadin/tests/components/table/TabletContextMenu.java new file mode 100644 index 0000000000..193cb499a7 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/table/TabletContextMenu.java @@ -0,0 +1,139 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * 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.vaadin.tests.components.table; + +import com.vaadin.event.Action; +import com.vaadin.event.Action.Handler; +import com.vaadin.event.ShortcutAction; +import com.vaadin.server.VaadinRequest; +import com.vaadin.tests.components.AbstractTestUI; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Table; + +/** + * A test UI for context menus on different parts of a VSCrollTable. + * + * This UI has no attached unit test due to the poor support of touch events on + * Selenium. + * + * @since + * @author Vaadin Ltd + */ +public class TabletContextMenu extends AbstractTestUI { + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#setup(com.vaadin.server. + * VaadinRequest) + */ + @Override + protected void setup(VaadinRequest request) { + setSizeFull(); + + HorizontalLayout layout = new HorizontalLayout(); + layout.setSizeFull(); + layout.setSpacing(true); + addComponent(layout); + + Table table1 = createTable("no scrolling, has context menu"); + addActionHandler(table1); + table1.addItem(); + layout.addComponent(table1); + + Table table2 = createTable("should scroll, has context menu"); + for (int i = 0; i < 100; ++i) { + table2.addItem(); + } + addActionHandler(table2); + layout.addComponent(table2); + + Table table3 = createTable("no scrolling, no context menu"); + table3.addItem(); + layout.addComponent(table3); + + Table table4 = createTable("should scroll, no context menu"); + for (int i = 0; i < 100; ++i) { + table4.addItem(); + } + layout.addComponent(table4); + } + + private Table createTable(String caption) { + Table table = new Table(caption); + table.setImmediate(true); + + table.addContainerProperty("column1", String.class, "test"); + table.setSizeFull(); + table.setHeight("500px"); + table.setSelectable(true); + + return table; + } + + private void addActionHandler(Table table) { + table.addActionHandler(new Handler() { + + Action tabNext = new ShortcutAction("Shift", + ShortcutAction.KeyCode.TAB, null); + Action tabPrev = new ShortcutAction("Shift+Tab", + ShortcutAction.KeyCode.TAB, + new int[] { ShortcutAction.ModifierKey.SHIFT }); + Action curDown = new ShortcutAction("Down", + ShortcutAction.KeyCode.ARROW_DOWN, null); + Action curUp = new ShortcutAction("Up", + ShortcutAction.KeyCode.ARROW_UP, null); + Action enter = new ShortcutAction("Enter", + ShortcutAction.KeyCode.ENTER, null); + Action add = new ShortcutAction("Add Below", + ShortcutAction.KeyCode.A, null); + Action delete = new ShortcutAction("Delete", + ShortcutAction.KeyCode.DELETE, null); + + @Override + public void handleAction(Action action, Object sender, Object target) { + System.out.println(action.getCaption()); + } + + @Override + public Action[] getActions(Object target, Object sender) { + return new Action[] { tabNext, tabPrev, curDown, curUp, enter, + add, delete }; + } + }); + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTestDescription() + */ + @Override + protected String getTestDescription() { + return "Make sure empty table parts have context menu on touch screen devices"; + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.tests.components.AbstractTestUI#getTicketNumber() + */ + @Override + protected Integer getTicketNumber() { + return 13694; + } + +} -- 2.39.5