You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MultiSelectionRenderer.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.v7.client.widget.grid.selection;
  17. import java.util.Collection;
  18. import java.util.HashSet;
  19. import com.google.gwt.animation.client.AnimationScheduler;
  20. import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
  21. import com.google.gwt.animation.client.AnimationScheduler.AnimationHandle;
  22. import com.google.gwt.core.client.GWT;
  23. import com.google.gwt.dom.client.BrowserEvents;
  24. import com.google.gwt.dom.client.Element;
  25. import com.google.gwt.dom.client.NativeEvent;
  26. import com.google.gwt.dom.client.TableElement;
  27. import com.google.gwt.dom.client.TableRowElement;
  28. import com.google.gwt.dom.client.TableSectionElement;
  29. import com.google.gwt.event.dom.client.ClickEvent;
  30. import com.google.gwt.event.dom.client.ClickHandler;
  31. import com.google.gwt.event.dom.client.MouseDownEvent;
  32. import com.google.gwt.event.dom.client.MouseDownHandler;
  33. import com.google.gwt.event.dom.client.TouchStartEvent;
  34. import com.google.gwt.event.dom.client.TouchStartHandler;
  35. import com.google.gwt.event.shared.HandlerRegistration;
  36. import com.google.gwt.user.client.Event;
  37. import com.google.gwt.user.client.Event.NativePreviewEvent;
  38. import com.google.gwt.user.client.Event.NativePreviewHandler;
  39. import com.google.gwt.user.client.ui.CheckBox;
  40. import com.vaadin.client.WidgetUtil;
  41. import com.vaadin.v7.client.renderers.ClickableRenderer;
  42. import com.vaadin.v7.client.widget.grid.CellReference;
  43. import com.vaadin.v7.client.widget.grid.RendererCellReference;
  44. import com.vaadin.v7.client.widget.grid.events.GridEnabledEvent;
  45. import com.vaadin.v7.client.widget.grid.events.GridEnabledHandler;
  46. import com.vaadin.v7.client.widget.grid.selection.SelectionModel.Multi.Batched;
  47. import com.vaadin.v7.client.widgets.Escalator.AbstractRowContainer;
  48. import com.vaadin.v7.client.widgets.Grid;
  49. /**
  50. * Renderer showing multi selection check boxes.
  51. *
  52. * @author Vaadin Ltd
  53. * @param <T>
  54. * the type of the associated grid
  55. * @since 7.4
  56. */
  57. public class MultiSelectionRenderer<T>
  58. extends ClickableRenderer<Boolean, CheckBox> {
  59. private static final String SELECTION_CHECKBOX_CLASSNAME = "-selection-checkbox";
  60. /** The size of the autoscroll area, both top and bottom. */
  61. private static final int SCROLL_AREA_GRADIENT_PX = 100;
  62. /** The maximum number of pixels per second to autoscroll. */
  63. private static final int SCROLL_TOP_SPEED_PX_SEC = 500;
  64. /**
  65. * The minimum area where the grid doesn't scroll while the pointer is
  66. * pressed.
  67. */
  68. private static final int MIN_NO_AUTOSCROLL_AREA_PX = 50;
  69. /**
  70. * Handler for MouseDown and TouchStart events for selection checkboxes.
  71. *
  72. * @since 7.5
  73. */
  74. private final class CheckBoxEventHandler implements MouseDownHandler,
  75. TouchStartHandler, ClickHandler, GridEnabledHandler {
  76. private final CheckBox checkBox;
  77. /**
  78. * @param checkBox
  79. * checkbox widget for this handler
  80. */
  81. private CheckBoxEventHandler(CheckBox checkBox) {
  82. this.checkBox = checkBox;
  83. }
  84. @Override
  85. public void onMouseDown(MouseDownEvent event) {
  86. if (checkBox.isEnabled()) {
  87. if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
  88. startDragSelect(event.getNativeEvent(),
  89. checkBox.getElement());
  90. }
  91. }
  92. }
  93. @Override
  94. public void onTouchStart(TouchStartEvent event) {
  95. if (checkBox.isEnabled()) {
  96. startDragSelect(event.getNativeEvent(), checkBox.getElement());
  97. }
  98. }
  99. @Override
  100. public void onClick(ClickEvent event) {
  101. // Clicking is already handled with MultiSelectionRenderer
  102. event.preventDefault();
  103. event.stopPropagation();
  104. }
  105. @Override
  106. public void onEnabled(boolean enabled) {
  107. checkBox.setEnabled(enabled);
  108. }
  109. }
  110. /**
  111. * This class's main objective is to listen when to stop autoscrolling, and
  112. * make sure everything stops accordingly.
  113. */
  114. private class TouchEventHandler implements NativePreviewHandler {
  115. @Override
  116. public void onPreviewNativeEvent(final NativePreviewEvent event) {
  117. switch (event.getTypeInt()) {
  118. case Event.ONTOUCHSTART: {
  119. if (event.getNativeEvent().getTouches().length() == 1) {
  120. /*
  121. * Something has dropped a touchend/touchcancel and the
  122. * scroller is most probably running amok. Let's cancel it
  123. * and pretend that everything's going as expected
  124. *
  125. * Because this is a preview, this code is run before the
  126. * event handler in MultiSelectionRenderer.onBrowserEvent.
  127. * Therefore, we can simply kill everything and let that
  128. * method restart things as they should.
  129. */
  130. autoScrollHandler.stop();
  131. /*
  132. * Related TODO: investigate why iOS seems to ignore a
  133. * touchend/touchcancel when frames are dropped, and/or if
  134. * something can be done about that.
  135. */
  136. }
  137. break;
  138. }
  139. case Event.ONTOUCHMOVE:
  140. event.cancel();
  141. break;
  142. case Event.ONTOUCHEND:
  143. case Event.ONTOUCHCANCEL:
  144. /*
  145. * Remember: targetElement is always where touchstart started,
  146. * not where the finger is pointing currently.
  147. */
  148. final Element targetElement = Element
  149. .as(event.getNativeEvent().getEventTarget());
  150. if (isInFirstColumn(targetElement)) {
  151. removeNativeHandler();
  152. event.cancel();
  153. }
  154. break;
  155. }
  156. }
  157. private boolean isInFirstColumn(final Element element) {
  158. if (element == null) {
  159. return false;
  160. }
  161. final Element tbody = getTbodyElement();
  162. if (tbody == null || !tbody.isOrHasChild(element)) {
  163. return false;
  164. }
  165. /*
  166. * The null-parent in the while clause is in the case where element
  167. * is an immediate tr child in the tbody. Should never happen in
  168. * internal code, but hey...
  169. */
  170. Element cursor = element;
  171. while (cursor.getParentElement() != null
  172. && cursor.getParentElement().getParentElement() != tbody) {
  173. cursor = cursor.getParentElement();
  174. }
  175. final Element tr = cursor.getParentElement();
  176. return tr.getFirstChildElement().equals(cursor);
  177. }
  178. }
  179. /**
  180. * This class's responsibility is to
  181. * <ul>
  182. * <li>scroll the table while a pointer is kept in a scrolling zone and
  183. * <li>select rows whenever a pointer is "activated" on a selection cell
  184. * </ul>
  185. * <p>
  186. * <em>Techical note:</em> This class is an AnimationCallback because we
  187. * need a timer: when the finger is kept in place while the grid scrolls, we
  188. * still need to be able to make new selections. So, instead of relying on
  189. * events (which won't be fired, since the pointer isn't necessarily
  190. * moving), we do this check on each frame while the pointer is "active"
  191. * (mouse is pressed, finger is on screen).
  192. */
  193. private class AutoScrollerAndSelector implements AnimationCallback {
  194. /**
  195. * If the acceleration gradient area is smaller than this, autoscrolling
  196. * will be disabled (it becomes too quick to accelerate to be usable).
  197. */
  198. private static final int GRADIENT_MIN_THRESHOLD_PX = 10;
  199. /**
  200. * The speed at which the gradient area recovers, once scrolling in that
  201. * direction has started.
  202. */
  203. private static final int SCROLL_AREA_REBOUND_PX_PER_SEC = 1;
  204. private static final double SCROLL_AREA_REBOUND_PX_PER_MS = SCROLL_AREA_REBOUND_PX_PER_SEC
  205. / 1000.0d;
  206. /**
  207. * The lowest y-coordinate on the {@link Event#getClientY() client} from
  208. * where we need to start scrolling towards the top.
  209. */
  210. private int topBound = -1;
  211. /**
  212. * The highest y-coordinate on the {@link Event#getClientY() client}
  213. * from where we need to scrolling towards the bottom.
  214. */
  215. private int bottomBound = -1;
  216. /**
  217. * <code>true</code> if the pointer is selecting, <code>false</code> if
  218. * the pointer is deselecting.
  219. */
  220. private final boolean selectionPaint;
  221. /**
  222. * The area where the selection acceleration takes place. If &lt;
  223. * {@link #GRADIENT_MIN_THRESHOLD_PX}, autoscrolling is disabled
  224. */
  225. private final int gradientArea;
  226. /**
  227. * The number of pixels per seconds we currently are scrolling (negative
  228. * is towards the top, positive is towards the bottom).
  229. */
  230. private double scrollSpeed = 0;
  231. private double prevTimestamp = 0;
  232. /**
  233. * This field stores fractions of pixels to scroll, to make sure that
  234. * we're able to scroll less than one px per frame.
  235. */
  236. private double pixelsToScroll = 0.0d;
  237. /** Should this animator be running. */
  238. private boolean running = false;
  239. /** The handle in which this instance is running. */
  240. private AnimationHandle handle;
  241. /** The pointer's pageX coordinate of the first click. */
  242. private int initialPageX = -1;
  243. /** The pointer's pageY coordinate. */
  244. private int pageY;
  245. /** The logical index of the row that was most recently modified. */
  246. private int lastModifiedLogicalRow = -1;
  247. /** @see #doScrollAreaChecks(int) */
  248. private int finalTopBound;
  249. /** @see #doScrollAreaChecks(int) */
  250. private int finalBottomBound;
  251. private boolean scrollAreaShouldRebound = false;
  252. private final int bodyAbsoluteTop;
  253. private final int bodyAbsoluteBottom;
  254. public AutoScrollerAndSelector(final int topBound,
  255. final int bottomBound, final int gradientArea,
  256. final boolean selectionPaint) {
  257. finalTopBound = topBound;
  258. finalBottomBound = bottomBound;
  259. this.gradientArea = gradientArea;
  260. this.selectionPaint = selectionPaint;
  261. bodyAbsoluteTop = getBodyClientTop();
  262. bodyAbsoluteBottom = getBodyClientBottom();
  263. }
  264. @Override
  265. public void execute(final double timestamp) {
  266. final double timeDiff = timestamp - prevTimestamp;
  267. prevTimestamp = timestamp;
  268. reboundScrollArea(timeDiff);
  269. pixelsToScroll += scrollSpeed * (timeDiff / 1000.0d);
  270. final int intPixelsToScroll = (int) pixelsToScroll;
  271. pixelsToScroll -= intPixelsToScroll;
  272. if (intPixelsToScroll != 0) {
  273. grid.setScrollTop(grid.getScrollTop() + intPixelsToScroll);
  274. }
  275. int constrainedPageY = Math.max(bodyAbsoluteTop,
  276. Math.min(bodyAbsoluteBottom, pageY));
  277. int logicalRow = getLogicalRowIndex(grid, WidgetUtil
  278. .getElementFromPoint(initialPageX, constrainedPageY));
  279. int incrementOrDecrement = (logicalRow > lastModifiedLogicalRow) ? 1
  280. : -1;
  281. /*
  282. * Both pageY and initialPageX have their initialized (and
  283. * unupdated) values while the cursor hasn't moved since the first
  284. * invocation. This will lead to logicalRow being -1, until the
  285. * pointer has been moved.
  286. */
  287. while (logicalRow != -1 && lastModifiedLogicalRow != logicalRow) {
  288. lastModifiedLogicalRow += incrementOrDecrement;
  289. setSelected(lastModifiedLogicalRow, selectionPaint);
  290. }
  291. reschedule();
  292. }
  293. /**
  294. * If the scroll are has been offset by the pointer starting out there,
  295. * move it back a bit
  296. */
  297. private void reboundScrollArea(double timeDiff) {
  298. if (!scrollAreaShouldRebound) {
  299. return;
  300. }
  301. int reboundPx = (int) Math
  302. .ceil(SCROLL_AREA_REBOUND_PX_PER_MS * timeDiff);
  303. if (topBound < finalTopBound) {
  304. topBound += reboundPx;
  305. topBound = Math.min(topBound, finalTopBound);
  306. updateScrollSpeed(pageY);
  307. } else if (bottomBound > finalBottomBound) {
  308. bottomBound -= reboundPx;
  309. bottomBound = Math.max(bottomBound, finalBottomBound);
  310. updateScrollSpeed(pageY);
  311. }
  312. }
  313. private void updateScrollSpeed(final int pointerPageY) {
  314. final double ratio;
  315. if (pointerPageY < topBound) {
  316. final double distance = pointerPageY - topBound;
  317. ratio = Math.max(-1, distance / gradientArea);
  318. } else if (pointerPageY > bottomBound) {
  319. final double distance = pointerPageY - bottomBound;
  320. ratio = Math.min(1, distance / gradientArea);
  321. } else {
  322. ratio = 0;
  323. }
  324. scrollSpeed = ratio * SCROLL_TOP_SPEED_PX_SEC;
  325. }
  326. public void start(int logicalRowIndex) {
  327. running = true;
  328. setSelected(logicalRowIndex, selectionPaint);
  329. lastModifiedLogicalRow = logicalRowIndex;
  330. reschedule();
  331. }
  332. public void stop() {
  333. running = false;
  334. if (handle != null) {
  335. handle.cancel();
  336. handle = null;
  337. }
  338. }
  339. private void reschedule() {
  340. if (running && gradientArea >= GRADIENT_MIN_THRESHOLD_PX) {
  341. handle = AnimationScheduler.get().requestAnimationFrame(this,
  342. grid.getElement());
  343. }
  344. }
  345. public void updatePointerCoords(int pageX, int pageY) {
  346. doScrollAreaChecks(pageY);
  347. updateScrollSpeed(pageY);
  348. this.pageY = pageY;
  349. if (initialPageX == -1) {
  350. initialPageX = pageX;
  351. }
  352. }
  353. /**
  354. * This method checks whether the first pointer event started in an area
  355. * that would start scrolling immediately, and does some actions
  356. * accordingly.
  357. * <p>
  358. * If it is, that scroll area will be offset "beyond" the pointer (above
  359. * if pointer is towards the top, otherwise below).
  360. * <p>
  361. * <span style="font-size:smaller">*) This behavior will change in
  362. * future patches (henrik paul 2.7.2014)</span>
  363. */
  364. private void doScrollAreaChecks(int pageY) {
  365. /*
  366. * The first run makes sure that neither scroll position is
  367. * underneath the finger, but offset to either direction from
  368. * underneath the pointer.
  369. */
  370. if (topBound == -1) {
  371. topBound = Math.min(finalTopBound, pageY);
  372. bottomBound = Math.max(finalBottomBound, pageY);
  373. } else {
  374. /*
  375. * Subsequent runs make sure that the scroll area grows (but
  376. * doesn't shrink) with the finger, but no further than the
  377. * final bound.
  378. */
  379. int oldTopBound = topBound;
  380. if (topBound < finalTopBound) {
  381. topBound = Math.max(topBound,
  382. Math.min(finalTopBound, pageY));
  383. }
  384. int oldBottomBound = bottomBound;
  385. if (bottomBound > finalBottomBound) {
  386. bottomBound = Math.min(bottomBound,
  387. Math.max(finalBottomBound, pageY));
  388. }
  389. final boolean topDidNotMove = oldTopBound == topBound;
  390. final boolean bottomDidNotMove = oldBottomBound == bottomBound;
  391. final boolean wasVerticalMovement = pageY != this.pageY;
  392. scrollAreaShouldRebound = (topDidNotMove && bottomDidNotMove
  393. && wasVerticalMovement);
  394. }
  395. }
  396. }
  397. /**
  398. * This class makes sure that pointer movemenets are registered and
  399. * delegated to the autoscroller so that it can:
  400. * <ul>
  401. * <li>modify the speed in which we autoscroll.
  402. * <li>"paint" a new row with the selection.
  403. * </ul>
  404. * Essentially, when a pointer is pressed on the selection column, a native
  405. * preview handler is registered (so that selection gestures can happen
  406. * outside of the selection column). The handler itself makes sure that it's
  407. * detached when the pointer is "lifted".
  408. */
  409. private class AutoScrollHandler {
  410. private AutoScrollerAndSelector autoScroller;
  411. /** The registration info for {@link #scrollPreviewHandler} */
  412. private HandlerRegistration handlerRegistration;
  413. private final NativePreviewHandler scrollPreviewHandler = new NativePreviewHandler() {
  414. @Override
  415. public void onPreviewNativeEvent(final NativePreviewEvent event) {
  416. if (autoScroller == null) {
  417. stop();
  418. return;
  419. }
  420. final NativeEvent nativeEvent = event.getNativeEvent();
  421. int pageY = 0;
  422. int pageX = 0;
  423. switch (event.getTypeInt()) {
  424. case Event.ONMOUSEMOVE:
  425. case Event.ONTOUCHMOVE:
  426. pageY = WidgetUtil.getTouchOrMouseClientY(nativeEvent);
  427. pageX = WidgetUtil.getTouchOrMouseClientX(nativeEvent);
  428. autoScroller.updatePointerCoords(pageX, pageY);
  429. break;
  430. case Event.ONMOUSEUP:
  431. case Event.ONTOUCHEND:
  432. case Event.ONTOUCHCANCEL:
  433. stop();
  434. break;
  435. }
  436. }
  437. };
  438. /**
  439. * The top bound, as calculated from the {@link Event#getClientY()
  440. * client} coordinates.
  441. */
  442. private int topBound = -1;
  443. /**
  444. * The bottom bound, as calculated from the {@link Event#getClientY()
  445. * client} coordinates.
  446. */
  447. private int bottomBound = -1;
  448. /** The size of the autoscroll acceleration area. */
  449. private int gradientArea;
  450. public void start(int logicalRowIndex) {
  451. SelectionModel<T> model = grid.getSelectionModel();
  452. if (model instanceof Batched) {
  453. Batched<?> batchedModel = (Batched<?>) model;
  454. batchedModel.startBatchSelect();
  455. }
  456. /*
  457. * bounds are updated whenever the autoscroll cycle starts, to make
  458. * sure that the widget hasn't changed in size, moved around, or
  459. * whatnot.
  460. */
  461. updateScrollBounds();
  462. assert handlerRegistration == null : "handlerRegistration was not null";
  463. assert autoScroller == null : "autoScroller was not null";
  464. handlerRegistration = Event
  465. .addNativePreviewHandler(scrollPreviewHandler);
  466. autoScroller = new AutoScrollerAndSelector(topBound, bottomBound,
  467. gradientArea, !isSelected(logicalRowIndex));
  468. autoScroller.start(logicalRowIndex);
  469. }
  470. private void updateScrollBounds() {
  471. final int topBorder = getBodyClientTop();
  472. final int bottomBorder = getBodyClientBottom();
  473. topBound = topBorder + SCROLL_AREA_GRADIENT_PX;
  474. bottomBound = bottomBorder - SCROLL_AREA_GRADIENT_PX;
  475. gradientArea = SCROLL_AREA_GRADIENT_PX;
  476. // modify bounds if they're too tightly packed
  477. if (bottomBound - topBound < MIN_NO_AUTOSCROLL_AREA_PX) {
  478. int adjustment = MIN_NO_AUTOSCROLL_AREA_PX
  479. - (bottomBound - topBound);
  480. topBound -= adjustment / 2;
  481. bottomBound += adjustment / 2;
  482. gradientArea -= adjustment / 2;
  483. }
  484. }
  485. public void stop() {
  486. if (handlerRegistration != null) {
  487. handlerRegistration.removeHandler();
  488. handlerRegistration = null;
  489. }
  490. if (autoScroller != null) {
  491. autoScroller.stop();
  492. autoScroller = null;
  493. }
  494. SelectionModel<T> model = grid.getSelectionModel();
  495. if (model instanceof Batched) {
  496. Batched<?> batchedModel = (Batched<?>) model;
  497. batchedModel.commitBatchSelect();
  498. }
  499. removeNativeHandler();
  500. }
  501. }
  502. private final Grid<T> grid;
  503. private HandlerRegistration nativePreviewHandlerRegistration;
  504. private final AutoScrollHandler autoScrollHandler = new AutoScrollHandler();
  505. public MultiSelectionRenderer(final Grid<T> grid) {
  506. this.grid = grid;
  507. }
  508. @Override
  509. public void destroy() {
  510. if (nativePreviewHandlerRegistration != null) {
  511. removeNativeHandler();
  512. }
  513. }
  514. @Override
  515. public CheckBox createWidget() {
  516. final CheckBox checkBox = GWT.create(CheckBox.class);
  517. checkBox.setStylePrimaryName(
  518. grid.getStylePrimaryName() + SELECTION_CHECKBOX_CLASSNAME);
  519. CheckBoxEventHandler handler = new CheckBoxEventHandler(checkBox);
  520. // Sink events
  521. checkBox.sinkBitlessEvent(BrowserEvents.MOUSEDOWN);
  522. checkBox.sinkBitlessEvent(BrowserEvents.TOUCHSTART);
  523. checkBox.sinkBitlessEvent(BrowserEvents.CLICK);
  524. // Add handlers
  525. checkBox.addMouseDownHandler(handler);
  526. checkBox.addTouchStartHandler(handler);
  527. checkBox.addClickHandler(handler);
  528. grid.addHandler(handler, GridEnabledEvent.TYPE);
  529. checkBox.setEnabled(grid.isEnabled());
  530. return checkBox;
  531. }
  532. @Override
  533. public void render(final RendererCellReference cell, final Boolean data,
  534. CheckBox checkBox) {
  535. checkBox.setValue(data, false);
  536. checkBox.setEnabled(grid.isEnabled() && !grid.isEditorActive()
  537. && grid.isUserSelectionAllowed());
  538. }
  539. @Override
  540. public Collection<String> getConsumedEvents() {
  541. final HashSet<String> events = new HashSet<String>();
  542. /*
  543. * this column's first interest is only to attach a NativePreventHandler
  544. * that does all the magic. These events are the beginning of that
  545. * cycle.
  546. */
  547. events.add(BrowserEvents.MOUSEDOWN);
  548. events.add(BrowserEvents.TOUCHSTART);
  549. return events;
  550. }
  551. @Override
  552. public boolean onBrowserEvent(final CellReference<?> cell,
  553. final NativeEvent event) {
  554. if (BrowserEvents.TOUCHSTART.equals(event.getType())
  555. || (BrowserEvents.MOUSEDOWN.equals(event.getType())
  556. && event.getButton() == NativeEvent.BUTTON_LEFT)) {
  557. startDragSelect(event, Element.as(event.getEventTarget()));
  558. return true;
  559. }
  560. return false;
  561. }
  562. private void startDragSelect(NativeEvent event, final Element target) {
  563. injectNativeHandler();
  564. int logicalRowIndex = getLogicalRowIndex(grid, target);
  565. autoScrollHandler.start(logicalRowIndex);
  566. event.preventDefault();
  567. event.stopPropagation();
  568. }
  569. private void injectNativeHandler() {
  570. removeNativeHandler();
  571. nativePreviewHandlerRegistration = Event
  572. .addNativePreviewHandler(new TouchEventHandler());
  573. }
  574. private void removeNativeHandler() {
  575. if (nativePreviewHandlerRegistration != null) {
  576. nativePreviewHandlerRegistration.removeHandler();
  577. nativePreviewHandlerRegistration = null;
  578. }
  579. }
  580. private int getLogicalRowIndex(Grid<T> grid, final Element target) {
  581. if (target == null) {
  582. return -1;
  583. }
  584. /*
  585. * We can't simply go backwards until we find a <tr> first element,
  586. * because of the table-in-table scenario. We need to, unfortunately, go
  587. * up from our known root.
  588. */
  589. final Element tbody = getTbodyElement();
  590. Element tr = tbody.getFirstChildElement();
  591. while (tr != null) {
  592. if (tr.isOrHasChild(target)) {
  593. final Element td = tr.getFirstChildElement();
  594. assert td != null : "Cell has disappeared";
  595. final Element checkbox = td.getFirstChildElement();
  596. assert checkbox != null : "Checkbox has disappeared";
  597. return ((AbstractRowContainer) grid.getEscalator().getBody())
  598. .getLogicalRowIndex((TableRowElement) tr);
  599. }
  600. tr = tr.getNextSiblingElement();
  601. }
  602. return -1;
  603. }
  604. private TableElement getTableElement() {
  605. final Element root = grid.getElement();
  606. final Element tablewrapper = Element.as(root.getChild(2));
  607. if (tablewrapper != null) {
  608. return TableElement.as(tablewrapper.getFirstChildElement());
  609. } else {
  610. return null;
  611. }
  612. }
  613. private TableSectionElement getTbodyElement() {
  614. TableElement table = getTableElement();
  615. if (table != null) {
  616. return table.getTBodies().getItem(0);
  617. } else {
  618. return null;
  619. }
  620. }
  621. private TableSectionElement getTheadElement() {
  622. TableElement table = getTableElement();
  623. if (table != null) {
  624. return table.getTHead();
  625. } else {
  626. return null;
  627. }
  628. }
  629. private TableSectionElement getTfootElement() {
  630. TableElement table = getTableElement();
  631. if (table != null) {
  632. return table.getTFoot();
  633. } else {
  634. return null;
  635. }
  636. }
  637. /** Get the "top" of an element in relation to "client" coordinates. */
  638. private int getClientTop(final Element e) {
  639. return e.getAbsoluteTop();
  640. }
  641. private int getBodyClientBottom() {
  642. return getClientTop(getTfootElement()) - 1;
  643. }
  644. private int getBodyClientTop() {
  645. // Off by one pixel miscalculation. possibly border related.
  646. return getClientTop(grid.getElement())
  647. + getTheadElement().getOffsetHeight() + 1;
  648. }
  649. protected boolean isSelected(final int logicalRow) {
  650. return grid.isSelected(grid.getDataSource().getRow(logicalRow));
  651. }
  652. protected void setSelected(final int logicalRow, final boolean select) {
  653. if (!grid.isUserSelectionAllowed()) {
  654. return;
  655. }
  656. T row = grid.getDataSource().getRow(logicalRow);
  657. if (select) {
  658. grid.select(row);
  659. } else {
  660. grid.deselect(row);
  661. }
  662. }
  663. }