/src/com/vaadin/client/ui/VCustomScrollTable.java
Java | 8224 lines | 5411 code | 933 blank | 1880 comment | 1400 complexity | f807ea2292c5164ecd39a40005efbcbf MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /*
- * 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.client.ui;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import com.google.gwt.core.client.JavaScriptObject;
- import com.google.gwt.core.client.Scheduler;
- import com.google.gwt.core.client.Scheduler.ScheduledCommand;
- 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.dom.client.Node;
- import com.google.gwt.dom.client.NodeList;
- import com.google.gwt.dom.client.Style;
- import com.google.gwt.dom.client.Style.Display;
- import com.google.gwt.dom.client.Style.Overflow;
- import com.google.gwt.dom.client.Style.Position;
- import com.google.gwt.dom.client.Style.TextAlign;
- import com.google.gwt.dom.client.Style.Unit;
- import com.google.gwt.dom.client.Style.Visibility;
- import com.google.gwt.dom.client.TableCellElement;
- import com.google.gwt.dom.client.TableRowElement;
- 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.FocusEvent;
- import com.google.gwt.event.dom.client.FocusHandler;
- import com.google.gwt.event.dom.client.KeyCodes;
- import com.google.gwt.event.dom.client.KeyDownEvent;
- import com.google.gwt.event.dom.client.KeyDownHandler;
- import com.google.gwt.event.dom.client.KeyPressEvent;
- import com.google.gwt.event.dom.client.KeyPressHandler;
- import com.google.gwt.event.dom.client.KeyUpEvent;
- import com.google.gwt.event.dom.client.KeyUpHandler;
- import com.google.gwt.event.dom.client.ScrollEvent;
- import com.google.gwt.event.dom.client.ScrollHandler;
- import com.google.gwt.event.logical.shared.CloseEvent;
- import com.google.gwt.event.logical.shared.CloseHandler;
- import com.google.gwt.event.shared.HandlerRegistration;
- import com.google.gwt.regexp.shared.MatchResult;
- import com.google.gwt.regexp.shared.RegExp;
- import com.google.gwt.user.client.Command;
- import com.google.gwt.user.client.DOM;
- import com.google.gwt.user.client.Event;
- import com.google.gwt.user.client.Event.NativePreviewEvent;
- import com.google.gwt.user.client.Event.NativePreviewHandler;
- import com.google.gwt.user.client.Timer;
- import com.google.gwt.user.client.Window;
- import com.google.gwt.user.client.ui.FlowPanel;
- import com.google.gwt.user.client.ui.HasWidgets;
- import com.google.gwt.user.client.ui.Panel;
- import com.google.gwt.user.client.ui.PopupPanel;
- import com.google.gwt.user.client.ui.UIObject;
- import com.google.gwt.user.client.ui.Widget;
- import com.vaadin.client.ApplicationConnection;
- import com.vaadin.client.BrowserInfo;
- import com.vaadin.client.ComponentConnector;
- import com.vaadin.client.ConnectorMap;
- import com.vaadin.client.DeferredWorker;
- import com.vaadin.client.Focusable;
- import com.vaadin.client.MouseEventDetailsBuilder;
- import com.vaadin.client.StyleConstants;
- import com.vaadin.client.TooltipInfo;
- import com.vaadin.client.UIDL;
- import com.vaadin.client.Util;
- import com.vaadin.client.VConsole;
- import com.vaadin.client.VTooltip;
- import com.vaadin.client.ui.VCustomScrollTable.VScrollTableBody.VScrollTableRow;
- import com.vaadin.client.ui.dd.DDUtil;
- import com.vaadin.client.ui.dd.VAbstractDropHandler;
- import com.vaadin.client.ui.dd.VAcceptCallback;
- import com.vaadin.client.ui.dd.VDragAndDropManager;
- import com.vaadin.client.ui.dd.VDragEvent;
- import com.vaadin.client.ui.dd.VHasDropHandler;
- import com.vaadin.client.ui.dd.VTransferable;
- import com.vaadin.shared.AbstractComponentState;
- import com.vaadin.shared.MouseEventDetails;
- import com.vaadin.shared.ui.dd.VerticalDropLocation;
- import com.vaadin.shared.ui.table.TableConstants;
- /**
- * VCustomScrollTable
- *
- * VCustomScrollTable 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 VCustomScrollTable 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
- */
- @SuppressWarnings("deprecation")
- public class VCustomScrollTable 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 {
- NONE(0), SINGLE(1), MULTI(2);
- private int id;
- private SelectMode(int id) {
- this.id = id;
- }
- public int getId() {
- return id;
- }
- }
- private static final String ROW_HEADER_COLUMN_KEY = "0";
- private static final double CACHE_RATE_DEFAULT = 2;
- /**
- * The default multi select mode where simple left clicks only selects one
- * item, CTRL+left click selects multiple items and SHIFT-left click selects
- * a range of items.
- */
- private static final int MULTISELECT_MODE_DEFAULT = 0;
- /**
- * The simple multiselect mode is what the table used to have before
- * ctrl/shift selections were added. That is that when this is set clicking
- * on an item selects/deselects the item and no ctrl/shift selections are
- * available.
- */
- private static final int MULTISELECT_MODE_SIMPLE = 1;
- /**
- * multiple of pagelength which component will cache when requesting more
- * rows
- */
- private double cache_rate = CACHE_RATE_DEFAULT;
- /**
- * fraction of pageLenght which can be scrolled without making new request
- */
- private double cache_react_rate = 0.75 * cache_rate;
- public static final char ALIGN_CENTER = 'c';
- public static final char ALIGN_LEFT = 'b';
- public static final char ALIGN_RIGHT = 'e';
- private static final int CHARCODE_SPACE = 32;
- private int firstRowInViewPort = 0;
- private int pageLength = 15;
- private int lastRequestedFirstvisible = 0; // to detect "serverside scroll"
- private int firstvisibleOnLastPage = -1; // To detect if the first visible
- // is on the last page
- /** For internal use only. May be removed or replaced in the future. */
- public boolean showRowHeaders = false;
- private String[] columnOrder;
- protected ApplicationConnection client;
- /** For internal use only. May be removed or replaced in the future. */
- public String paintableId;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean immediate;
- private boolean updatedReqRows = true;
- private boolean nullSelectionAllowed = true;
- private SelectMode selectMode = SelectMode.NONE;
- public final HashSet<String> selectedRowKeys = new HashSet<String>();
- /*
- * When scrolling and selecting at the same time, the selections are not in
- * sync with the server while retrieving new rows (until key is released).
- */
- private HashSet<Object> unSyncedselectionsBeforeRowFetch;
- /*
- * These are used when jumping between pages when pressing Home and End
- */
- /** For internal use only. May be removed or replaced in the future. */
- public boolean selectLastItemInNextRender = false;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean selectFirstItemInNextRender = false;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean focusFirstItemInNextRender = false;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean focusLastItemInNextRender = false;
- /**
- * The currently focused row.
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public VScrollTableRow focusedRow;
- /**
- * Helper to store selection range start in when using the keyboard
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public VScrollTableRow selectionRangeStart;
- /**
- * Flag for notifying when the selection has changed and should be sent to
- * the server
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public boolean selectionChanged = false;
- /*
- * The speed (in pixels) which the scrolling scrolls vertically/horizontally
- */
- private int scrollingVelocity = 10;
- private Timer scrollingVelocityTimer = null;
- /** For internal use only. May be removed or replaced in the future. */
- public String[] bodyActionKeys;
- private boolean enableDebug = false;
- private static final boolean hasNativeTouchScrolling = BrowserInfo.get()
- .isTouchDevice()
- && !BrowserInfo.get().requiresTouchScrollDelegate();
- private Set<String> noncollapsibleColumns;
- /**
- * 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)}
- * to avoid some rounding problems, e.g. round(2 * 19.8) / 2 = 20 but
- * round(3 * 19.8) / 3 = 19.66.
- */
- private double lastKnownRowHeight = Double.NaN;
- /**
- * Remember scroll position when getting detached to properly scroll back to
- * the location that there is data for if getting attached again.
- */
- private int detachedScrollPosition = 0;
- /**
- * Represents a select range of rows
- */
- private class SelectionRange {
- private VScrollTableRow startRow;
- private final int length;
- /**
- * Constuctor.
- */
- public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
- VScrollTableRow endRow;
- if (row2.isBefore(row1)) {
- startRow = row2;
- endRow = row1;
- } else {
- startRow = row1;
- endRow = row2;
- }
- length = endRow.getIndex() - startRow.getIndex() + 1;
- }
- public SelectionRange(VScrollTableRow row, int length) {
- startRow = row;
- this.length = length;
- }
- /*
- * (non-Javadoc)
- *
- * @see java.lang.Object#toString()
- */
- @Override
- public String toString() {
- return startRow.getKey() + "-" + length;
- }
- private boolean inRange(VScrollTableRow row) {
- return row.getIndex() >= startRow.getIndex()
- && row.getIndex() < startRow.getIndex() + length;
- }
- public Collection<SelectionRange> split(VScrollTableRow row) {
- assert row.isAttached();
- ArrayList<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
- int endOfFirstRange = row.getIndex() - 1;
- if (endOfFirstRange >= startRow.getIndex()) {
- // create range of first part unless its length is < 1
- ranges.add(new SelectionRange(startRow, endOfFirstRange
- - startRow.getIndex() + 1));
- }
- int startOfSecondRange = row.getIndex() + 1;
- if (getEndIndex() >= startOfSecondRange) {
- // create range of second part unless its length is < 1
- VScrollTableRow startOfRange = scrollBody
- .getRowByRowIndex(startOfSecondRange);
- if (startOfRange != null) {
- ranges.add(new SelectionRange(startOfRange, getEndIndex()
- - startOfSecondRange + 1));
- }
- }
- return ranges;
- }
- private int getEndIndex() {
- return startRow.getIndex() + length - 1;
- }
- }
- private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
- /** For internal use only. May be removed or replaced in the future. */
- public boolean initializedAndAttached = false;
- /**
- * Flag to indicate if a column width recalculation is needed due update.
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public boolean headerChangedDuringUpdate = false;
- /** For internal use only. May be removed or replaced in the future. */
- public final TableHead tHead = new TableHead();
- /** 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 FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel(
- true);
- private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
- @Override
- public void onKeyPress(KeyPressEvent keyPressEvent) {
- // This is used for Firefox only, since Firefox auto-repeat
- // works correctly only if we use a key press handler, other
- // browsers handle it correctly when using a key down handler
- if (!BrowserInfo.get().isGecko()) {
- return;
- }
- NativeEvent event = keyPressEvent.getNativeEvent();
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table
- // (prevents scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- // Key code in Firefox/onKeyPress is present only for
- // special keys, otherwise 0 is returned
- int keyCode = event.getKeyCode();
- if (keyCode == 0 && event.getCharCode() == ' ') {
- // Provide a keyCode for space to be compatible with
- // FireFox keypress event
- keyCode = CHARCODE_SPACE;
- }
- if (handleNavigation(keyCode,
- event.getCtrlKey() || event.getMetaKey(),
- event.getShiftKey())) {
- event.preventDefault();
- }
- startScrollingVelocityTimer();
- }
- }
- };
- private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
- @Override
- public void onKeyUp(KeyUpEvent keyUpEvent) {
- NativeEvent event = keyUpEvent.getNativeEvent();
- int keyCode = event.getKeyCode();
- if (!isFocusable()) {
- cancelScrollingVelocityTimer();
- } else if (isNavigationKey(keyCode)) {
- if (keyCode == getNavigationDownKey()
- || keyCode == getNavigationUpKey()) {
- /*
- * in multiselect mode the server may still have value from
- * previous page. Clear it unless doing multiselection or
- * just moving focus.
- */
- if (!event.getShiftKey() && !event.getCtrlKey()) {
- instructServerToForgetPreviousSelections();
- }
- sendSelectedRows();
- }
- cancelScrollingVelocityTimer();
- navKeyDown = false;
- }
- }
- };
- private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
- @Override
- public void onKeyDown(KeyDownEvent keyDownEvent) {
- NativeEvent event = keyDownEvent.getNativeEvent();
- // This is not used for Firefox
- if (BrowserInfo.get().isGecko()) {
- return;
- }
- if (!enabled) {
- // Cancel default keyboard events on a disabled Table
- // (prevents scrolling)
- event.preventDefault();
- } else if (hasFocus) {
- if (handleNavigation(event.getKeyCode(), event.getCtrlKey()
- || event.getMetaKey(), event.getShiftKey())) {
- navKeyDown = true;
- event.preventDefault();
- }
- startScrollingVelocityTimer();
- }
- }
- };
- /** For internal use only. May be removed or replaced in the future. */
- public int totalRows;
- private Set<String> collapsedColumns;
- /** For internal use only. May be removed or replaced in the future. */
- public final RowRequestHandler rowRequestHandler;
- /** For internal use only. May be removed or replaced in the future. */
- public VScrollTableBody scrollBody;
- private int firstvisible = 0;
- private boolean sortAscending;
- private String sortColumn;
- private String oldSortColumn;
- private boolean columnReordering;
- /**
- * This map contains captions and icon urls for actions like: * "33_c" ->
- * "Edit" * "33_i" -> "http://dom.com/edit.png"
- */
- private final HashMap<Object, String> actionMap = new HashMap<Object, String>();
- private String[] visibleColOrder;
- private boolean initialContentReceived = false;
- private Element scrollPositionElement;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean enabled;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean showColHeaders;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean showColFooters;
- /** flag to indicate that table body has changed */
- private boolean isNewBody = true;
- /**
- * Read from the "recalcWidths" -attribute. When it is true, the table will
- * recalculate the widths for columns - desirable in some cases. For #1983,
- * marked experimental. See also variable <code>refreshContentWidths</code>
- * in method {@link TableHead#updateCellsFromUIDL(UIDL)}.
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public boolean recalcWidths = false;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean rendering = false;
- private boolean hasFocus = false;
- private int dragmode;
- private int multiselectmode;
- /** For internal use only. May be removed or replaced in the future. */
- public int tabIndex;
- private TouchScrollDelegate touchScrollDelegate;
- /** For internal use only. May be removed or replaced in the future. */
- public int lastRenderedHeight;
- /**
- * 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;
- public int serverCacheLast = -1;
- /**
- * In several cases TreeTable depends on the scrollBody.lastRendered being
- * 'out of sync' while the update is being done. In those cases the sanity
- * check must be performed afterwards.
- */
- public boolean postponeSanityCheckForLastRendered;
- /** For internal use only. May be removed or replaced in the future. */
- public boolean sizeNeedsInit = true;
- /**
- * Used to recall the position of an open context menu if we need to close
- * and reopen it during a row update.
- * <p>
- * For internal use only. May be removed or replaced in the future.
- */
- public class ContextMenuDetails implements CloseHandler<PopupPanel> {
- public String rowKey;
- public int left;
- public int top;
- HandlerRegistration closeRegistration;
- public ContextMenuDetails(VContextMenu menu, String rowKey, int left,
- int top) {
- this.rowKey = rowKey;
- this.left = left;
- this.top = top;
- closeRegistration = menu.addCloseHandler(this);
- }
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- contextMenu = null;
- closeRegistration.removeHandler();
- }
- }
- /** For internal use only. May be removed or replaced in the future. */
- public ContextMenuDetails contextMenu = null;
- private boolean hadScrollBars = false;
- private HandlerRegistration addCloseHandler;
- /**
- * Changes to manage mouseDown and mouseUp
- */
- /**
- * The element where the last mouse down event was registered.
- */
- private Element lastMouseDownTarget;
- /**
- * Set to true by {@link #mouseUpPreviewHandler} if it gets a mouseup at the
- * same element as {@link #lastMouseDownTarget}.
- */
- private boolean mouseUpPreviewMatched = false;
- private HandlerRegistration mouseUpEventPreviewRegistration;
- /**
- * Previews events after a mousedown to detect where the following mouseup
- * hits.
- */
- private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() {
- @Override
- public void onPreviewNativeEvent(NativePreviewEvent event) {
- if (event.getTypeInt() == Event.ONMOUSEUP) {
- mouseUpEventPreviewRegistration.removeHandler();
- // Event's reported target not always correct if event
- // capture is in use
- Element elementUnderMouse = Util.getElementUnderMouse(event
- .getNativeEvent());
- if (lastMouseDownTarget != null
- && lastMouseDownTarget.isOrHasChild(elementUnderMouse)) {
- mouseUpPreviewMatched = true;
- } else {
- getLogger().log(
- Level.FINEST,
- "Ignoring mouseup from " + elementUnderMouse
- + " when mousedown was on "
- + lastMouseDownTarget);
- }
- }
- }
- };
- public VCustomScrollTable() {
- setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
- scrollBodyPanel.addFocusHandler(this);
- scrollBodyPanel.addBlurHandler(this);
- scrollBodyPanel.addScrollHandler(this);
- /*
- * Firefox auto-repeat works correctly only if we use a key press
- * handler, other browsers handle it correctly when using a key down
- * handler
- */
- if (BrowserInfo.get().isGecko()) {
- scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
- } else {
- scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
- }
- scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
- scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
- setStyleName(STYLENAME);
- add(tHead);
- add(scrollBodyPanel);
- add(tFoot);
- rowRequestHandler = new RowRequestHandler();
- }
- @Override
- public void setStyleName(String style) {
- updateStyleNames(style, false);
- }
- @Override
- public void setStylePrimaryName(String style) {
- updateStyleNames(style, true);
- }
- private void updateStyleNames(String newStyle, boolean isPrimary) {
- scrollBodyPanel
- .removeStyleName(getStylePrimaryName() + "-body-wrapper");
- scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
- if (scrollBody != null) {
- scrollBody.removeStyleName(getStylePrimaryName()
- + "-body-noselection");
- }
- if (isPrimary) {
- super.setStylePrimaryName(newStyle);
- } else {
- super.setStyleName(newStyle);
- }
- scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
- scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
- tHead.updateStyleNames(getStylePrimaryName());
- tFoot.updateStyleNames(getStylePrimaryName());
- if (scrollBody != null) {
- scrollBody.updateStyleNames(getStylePrimaryName());
- }
- }
- public void init(ApplicationConnection client) {
- this.client = client;
- // Add a handler to clear saved context menu details when the menu
- // closes. See #8526.
- addCloseHandler = client.getContextMenu().addCloseHandler(
- new CloseHandler<PopupPanel>() {
- @Override
- public void onClose(CloseEvent<PopupPanel> event) {
- contextMenu = null;
- }
- });
- }
- /**
- * 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) {
- top += Window.getScrollTop();
- left += Window.getScrollLeft();
- client.getContextMenu().showAt(this, left, top);
- 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
- * The width in pixels of the column before the resize event
- * @param newWidth
- * The width in pixels of the column after the resize event
- */
- private void fireColumnResizeEvent(String columnId, int originalWidth,
- int newWidth) {
- client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
- false);
- client.updateVariable(paintableId, "columnResizeEventPrev",
- originalWidth, false);
- client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
- immediate);
- }
- /**
- * Non-immediate variable update of column widths for a collection of
- * columns.
- *
- * @param columns
- * the columns to trigger the events for.
- */
- private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
- String[] newSizes = new String[columns.size()];
- int ix = 0;
- for (HeaderCell cell : columns) {
- newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
- }
- client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
- false);
- }
- /**
- * Moves the focus one step down
- *
- * @return Returns true if succeeded
- */
- private boolean moveFocusDown() {
- return moveFocusDown(0);
- }
- /**
- * Moves the focus down by 1+offset rows
- *
- * @return Returns true if succeeded, else false if the selection could not
- * be move downwards
- */
- private boolean moveFocusDown(int offset) {
- if (isSelectable()) {
- if (focusedRow == null && scrollBody.iterator().hasNext()) {
- // FIXME should focus first visible from top, not first rendered
- // ??
- return setRowFocus((VScrollTableRow) scrollBody.iterator()
- .next());
- } else {
- VScrollTableRow next = getNextRow(focusedRow, offset);
- if (next != null) {
- return setRowFocus(next);
- }
- }
- }
- return false;
- }
- /**
- * Moves the selection one step up
- *
- * @return Returns true if succeeded
- */
- private boolean moveFocusUp() {
- return moveFocusUp(0);
- }
- /**
- * 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()) {
- if (focusedRow == null && scrollBody.iterator().hasNext()) {
- // FIXME logic is exactly the same as in moveFocusDown, should
- // be the opposite??
- return setRowFocus((VScrollTableRow) scrollBody.iterator()
- .next());
- } else {
- VScrollTableRow prev = getPreviousRow(focusedRow, offset);
- if (prev != null) {
- return setRowFocus(prev);
- } else {
- VConsole.log("no previous available");
- }
- }
- }
- return false;
- }
- /**
- * Selects a row where the current selection head is
- *
- * @param ctrlSelect
- * Is the selection a ctrl+selection
- * @param shiftSelect
- * Is the selection a shift+selection
- * @return Returns truw
- */
- private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
- if (focusedRow != null) {
- // Arrows moves the selection and clears previous selections
- if (isSelectable() && !ctrlSelect && !shiftSelect) {
- deselectAll();
- focusedRow.toggleSelection();
- selectionRangeStart = focusedRow;
- } else if (isSelectable() && ctrlSelect && !shiftSelect) {
- // Ctrl+arrows moves selection head
- selectionRangeStart = focusedRow;
- // No selection, only selection head is moved
- } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
- // Shift+arrows selection selects a range
- focusedRow.toggleShiftSelection(shiftSelect);
- }
- }
- }
- /**
- * Sends the selection to the server if changed since the last update/visit.
- */
- protected void sendSelectedRows() {
- sendSelectedRows(immediate);
- }
- /**
- * 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
- */
- protected void sendSelectedRows(boolean immediately) {
- // Don't send anything if selection has not changed
- if (!selectionChanged) {
- return;
- }
- // Reset selection changed flag
- selectionChanged = false;
- // Note: changing the immediateness of this might require changes to
- // "clickEvent" immediateness also.
- if (isMultiSelectModeDefault()) {
- // Convert ranges to a set of strings
- Set<String> ranges = new HashSet<String>();
- for (SelectionRange range : selectedRowRanges) {
- ranges.add(range.toString());
- }
- // Send the selected row ranges
- client.updateVariable(paintableId, "selectedRanges",
- ranges.toArray(new String[selectedRowRanges.size()]), false);
- selectedRowRanges.clear();
- // clean selectedRowKeys so that they don't contain excess values
- for (Iterator<String> iterator = selectedRowKeys.iterator(); iterator
- .hasNext();) {
- String key = iterator.next();
- VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
- if (renderedRowByKey != null) {
- for (SelectionRange range : selectedRowRanges) {
- if (range.inRange(renderedRowByKey)) {
- iterator.remove();
- }
- }
- } else {
- // orphaned selected key, must be in a range, ignore
- iterator.remove();
- }
- }
- }
- // Send the selected rows
- client.updateVariable(paintableId, "selected",
- selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
- immediately);
- }
- /**
- * 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() {
- return KeyCodes.KEY_UP;
- }
- /**
- * 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() {
- return KeyCodes.KEY_DOWN;
- }
- /**
- * 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() {
- return KeyCodes.KEY_LEFT;
- }
- /**
- * 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() {
- return KeyCodes.KEY_RIGHT;
- }
- /**
- * 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() {
- return CHARCODE_SPACE;
- }
- /**
- * 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() {
- return KeyCodes.KEY_PAGEUP;
- }
- /**
- * 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() {
- return KeyCodes.KEY_PAGEDOWN;
- }
- /**
- * 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() {
- return KeyCodes.KEY_HOME;
- }
- /**
- * 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() {
- return KeyCodes.KEY_END;
- }
- /** For internal use only. May be removed or replaced in the future. */
- public void initializeRows(UIDL uidl, UIDL rowData) {
- if (scrollBody != null) {
- scrollBody.removeFromParent();
- }
- // Without this call the scroll position is messed up in IE even after
- // the lazy scroller has set the scroll position to the first visible
- // item
- scrollBodyPanel.getScrollPosition();
- scrollBody = createScrollBody();
- scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
- uidl.getIntAttribute("rows"));
- scrollBodyPanel.add(scrollBody);
- // New body starts scrolled to the left, make sure the header and footer
- // are also scrolled to the left
- tHead.setHorizontalScrollPosition(0);
- tFoot.setHorizontalScrollPosition(0);
- initialContentReceived = true;
- sizeNeedsInit = true;
- scrollBody.restoreRowVisibility();
- }
- /** For internal use only. May be removed or replaced in the future. */
- public void updateColumnProperties(UIDL uidl) {
- updateColumnOrder(uidl);
- updateCollapsedColumns(uidl);
- UIDL vc = uidl.getChildByTagName("visiblecolumns");
- if (vc != null) {
- tHead.updateCellsFromUIDL(vc);
- tFoot.updateCellsFromUIDL(vc);
- }
- updateHeader(uidl.getStringArrayAttribute("vcolorder"));
- updateFooter(uidl.getStringArrayAttribute("vcolorder"));
- if (uidl.hasVariable("noncollapsiblecolumns")) {
- noncollapsibleColumns = uidl
- .getStringArrayVariableAsSet("noncollapsiblecolumns");
- }
- }
- private void updateCollapsedColumns(UIDL uidl) {
- if (uidl.hasVariable("collapsedcolumns")) {
- tHead.setColumnCollapsingAllowed(true);
- collapsedColumns = uidl
- .getStringArrayVariableAsSet("collapsedcolumns");
- } else {
- tHead.setColumnCollapsingAllowed(false);
- }
- }
- private void updateColumnOrder(UIDL uidl) {
- if (uidl.hasVariable("columnorder")) {
- columnReordering = true;
- columnOrder = uidl.getStringArrayVariable("columnorder");
- } else {
- columnReordering = false;
- columnOrder = null;
- }
- }
- /** For internal use only. May be removed or replaced in the future. */
- public boolean selectSelectedRows(UIDL uidl) {
- boolean keyboardSelectionOverRowFetchInProgress = false;
- if (uidl.hasVariable("selected")) {
- final Set<String> selectedKeys = uidl
- .getStringArrayVariableAsSet("selected");
- removeUnselectedRowKeys(selectedKeys);
- if (scrollBody != null) {
- Iterator<Widget> iterator = scrollBody.iterator();
- while (iterator.hasNext()) {
- /*
- * Make the focus reflect to the server side state unless we
- * are currently selecting multiple rows with keyboard.
- */
- VScrollTableRow row = (VScrollTableRow) iterator.next();
- boolean selected = selectedKeys.contains(row.getKey());
- if (!selected
- && unSyncedselectionsBeforeRowFetch != null
- && unSyncedselectionsBeforeRowFetch.contains(row
- .getKey())) {
- selected = true;
- keyboardSelectionOverRowFetchInProgress = true;
- }
- if (selected && selectedKeys.size() == 1) {
- /*
- * If a single item is selected, move focus to the
- * selected row. (#10522)
- */
- setRowFocus(row);
- }
- if (selected != row.isSelected()) {
- row.toggleSelection();
- if (!isSingleSelectMode() && !selected) {
- // Update selection range in case a row is
- // unselected from the middle of a range - #8076
- removeRowFromUnsentSelectionRanges(row);
- }
- }
- }
- }
- }
- unSyncedselectionsBeforeRowFetch = null;
- return keyboardSelectionOverRowFetchInProgress;
- }
- private void removeUnselectedRowKeys(final Set<String> selectedKeys) {
- List<String> unselectedKeys = new ArrayList<String>(0);
- for (String key : selectedRowKeys) {
- if (!selectedKeys.contains(key)) {
- unselectedKeys.add(key);
- }
- }
- selectedRowKeys.removeAll(unselectedKeys);
- }
- /** For internal use only. May be removed or replaced in the future. */
- public void updateSortingProperties(UIDL uidl) {
- oldSortColumn = sortColumn;
- if (uidl.hasVariable("sortascending")) {
- sortAscending = uidl.getBooleanVariable("sortascending");
- sortColumn = uidl.getStringVariable("sortcolumn");
- }
- }
- /** For internal use only. May be removed or replaced in the future. */
- public void resizeSortedColumnForSortIndicator() {
- // Force recalculation of the captionContainer element inside the header
- // cell to accomodate for the size of the sort arrow.
- HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
- if (sortedHeader != null) {
- // Mark header as sorted now. Any earlier marking would lead to
- // columns with wrong sizes
- sortedHeader.setSorted(true);
- tHead.resizeCaptionContainer(sortedHeader);
- }
- // Also recalculate the width of the captionContainer element in the
- // previously sorted header, since this now has more room.
- HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
- if (oldSortedHeader != null) {
- tHead.resizeCaptionContainer(oldSortedHeader);
- }
- }
- private boolean lazyScrollerIsActive;
- private void disableLazyScroller() {
- lazyScrollerIsActive = false;
- scrollBodyPanel.getElement().getStyle().clearOverflowX();
- scrollBodyPanel.getElement().getStyle().clearOverflowY();
- }
- private void enableLazyScroller() {
- Scheduler.get().scheduleDeferred(lazyScroller);
- lazyScrollerIsActive = true;
- // prevent scrolling to jump in IE11
- scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN);
- scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN);
- }
- private boolean isLazyScrollerActive() {
- return lazyScrollerIsActive;
- }
- private ScheduledCommand lazyScroller = new ScheduledCommand() {
- @Override
- public void execute() {
- if (firstvisible >= 0) {
- firstRowInViewPort = firstvisible;
- if (firstvisibleOnLastPage > -1) {
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstvisibleOnLastPage));
- } else {
- scrollBodyPanel
- .setScrollPosition(measureRowHeightOffset(firstvisible));
- }
- …
Large files files are truncated, but you can click here to view the full file