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 28KB

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