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

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