1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.vaadin.v7.client.ui;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Iterator;
24 import java.util.LinkedList;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.logging.Level;
30 import java.util.logging.Logger;
31
32 import com.google.gwt.core.client.JavaScriptObject;
33 import com.google.gwt.core.client.Scheduler;
34 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
35 import com.google.gwt.dom.client.Document;
36 import com.google.gwt.dom.client.Element;
37 import com.google.gwt.dom.client.NativeEvent;
38 import com.google.gwt.dom.client.Node;
39 import com.google.gwt.dom.client.NodeList;
40 import com.google.gwt.dom.client.Style;
41 import com.google.gwt.dom.client.Style.Display;
42 import com.google.gwt.dom.client.Style.Overflow;
43 import com.google.gwt.dom.client.Style.Position;
44 import com.google.gwt.dom.client.Style.TextAlign;
45 import com.google.gwt.dom.client.Style.Unit;
46 import com.google.gwt.dom.client.Style.Visibility;
47 import com.google.gwt.dom.client.TableCellElement;
48 import com.google.gwt.dom.client.TableRowElement;
49 import com.google.gwt.dom.client.TableSectionElement;
50 import com.google.gwt.dom.client.Touch;
51 import com.google.gwt.event.dom.client.BlurEvent;
52 import com.google.gwt.event.dom.client.BlurHandler;
53 import com.google.gwt.event.dom.client.FocusEvent;
54 import com.google.gwt.event.dom.client.FocusHandler;
55 import com.google.gwt.event.dom.client.KeyCodes;
56 import com.google.gwt.event.dom.client.KeyDownEvent;
57 import com.google.gwt.event.dom.client.KeyDownHandler;
58 import com.google.gwt.event.dom.client.KeyPressEvent;
59 import com.google.gwt.event.dom.client.KeyPressHandler;
60 import com.google.gwt.event.dom.client.KeyUpEvent;
61 import com.google.gwt.event.dom.client.KeyUpHandler;
62 import com.google.gwt.event.dom.client.ScrollEvent;
63 import com.google.gwt.event.dom.client.ScrollHandler;
64 import com.google.gwt.event.logical.shared.CloseEvent;
65 import com.google.gwt.event.logical.shared.CloseHandler;
66 import com.google.gwt.event.shared.HandlerRegistration;
67 import com.google.gwt.regexp.shared.MatchResult;
68 import com.google.gwt.regexp.shared.RegExp;
69 import com.google.gwt.user.client.Command;
70 import com.google.gwt.user.client.DOM;
71 import com.google.gwt.user.client.Event;
72 import com.google.gwt.user.client.Event.NativePreviewEvent;
73 import com.google.gwt.user.client.Event.NativePreviewHandler;
74 import com.google.gwt.user.client.Timer;
75 import com.google.gwt.user.client.Window;
76 import com.google.gwt.user.client.ui.FlowPanel;
77 import com.google.gwt.user.client.ui.Panel;
78 import com.google.gwt.user.client.ui.PopupPanel;
79 import com.google.gwt.user.client.ui.UIObject;
80 import com.google.gwt.user.client.ui.Widget;
81 import com.vaadin.client.ApplicationConnection;
82 import com.vaadin.client.BrowserInfo;
83 import com.vaadin.client.ComponentConnector;
84 import com.vaadin.client.ConnectorMap;
85 import com.vaadin.client.DeferredWorker;
86 import com.vaadin.client.Focusable;
87 import com.vaadin.client.HasChildMeasurementHintConnector.ChildMeasurementHint;
88 import com.vaadin.client.MouseEventDetailsBuilder;
89 import com.vaadin.client.StyleConstants;
90 import com.vaadin.client.TooltipInfo;
91 import com.vaadin.client.UIDL;
92 import com.vaadin.client.Util;
93 import com.vaadin.client.VTooltip;
94 import com.vaadin.client.WidgetUtil;
95 import com.vaadin.client.ui.Action;
96 import com.vaadin.client.ui.ActionOwner;
97 import com.vaadin.client.ui.FocusableScrollPanel;
98 import com.vaadin.client.ui.Icon;
99 import com.vaadin.client.ui.SubPartAware;
100 import com.vaadin.client.ui.TouchScrollDelegate;
101 import com.vaadin.client.ui.TreeAction;
102 import com.vaadin.client.ui.VContextMenu;
103 import com.vaadin.client.ui.VEmbedded;
104 import com.vaadin.client.ui.VOverlay;
105 import com.vaadin.client.ui.dd.DDUtil;
106 import com.vaadin.client.ui.dd.VAbstractDropHandler;
107 import com.vaadin.client.ui.dd.VAcceptCallback;
108 import com.vaadin.client.ui.dd.VDragAndDropManager;
109 import com.vaadin.client.ui.dd.VDragEvent;
110 import com.vaadin.client.ui.dd.VHasDropHandler;
111 import com.vaadin.client.ui.dd.VTransferable;
112 import com.vaadin.shared.AbstractComponentState;
113 import com.vaadin.shared.MouseEventDetails;
114 import com.vaadin.shared.ui.dd.VerticalDropLocation;
115 import com.vaadin.v7.client.ui.VScrollTablePatched.VScrollTableBody.VScrollTableRow;
116 import com.vaadin.v7.shared.ui.table.CollapseMenuContent;
117 import com.vaadin.v7.shared.ui.table.TableConstants;
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142 public class VScrollTablePatched extends FlowPanel
143 implements ScrollHandler, VHasDropHandler, FocusHandler,
144 BlurHandler, Focusable, ActionOwner, SubPartAware, DeferredWorker {
145
146 protected TableHead createTableHead() {
147 return new TableHead();
148 }
149
150 protected HeaderCell createHeaderCell(String colId, String headerText) {
151 return new HeaderCell(colId, headerText);
152 }
153
154 public boolean isVisibleInHierarchy() {
155 boolean isVisible = isVisible();
156 Widget current = getParent();
157 while (isVisible && current != null) {
158 isVisible &= current.isVisible();
159 current = current.getParent();
160 }
161 return isVisible;
162 }
163
164
165
166
167
168
169
170 private interface ContextMenuOwner {
171 public void showContextMenu(Event event);
172 }
173
174
175
176
177
178
179
180 private class TouchContextProvider {
181 private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
182 private Timer contextTouchTimeout;
183
184 private Event touchStart;
185 private int touchStartY;
186 private int touchStartX;
187
188 private ContextMenuOwner target;
189
190
191
192
193
194
195
196 public TouchContextProvider(ContextMenuOwner target) {
197 this.target = target;
198 }
199
200
201
202
203 public void cancel() {
204 if (contextTouchTimeout != null) {
205 contextTouchTimeout.cancel();
206 contextTouchTimeout = null;
207 }
208 touchStart = null;
209 }
210
211
212
213
214
215
216
217 public void handleTouchEvent(final Event event) {
218 int type = event.getTypeInt();
219
220 switch (type) {
221 case Event.ONCONTEXTMENU:
222 target.showContextMenu(event);
223 break;
224 case Event.ONTOUCHSTART:
225
226
227 touchStart = event;
228
229 Touch touch = event.getChangedTouches().get(0);
230 touchStartX = touch.getClientX();
231 touchStartY = touch.getClientY();
232
233 if (contextTouchTimeout == null) {
234 contextTouchTimeout = new Timer() {
235
236 @Override
237 public void run() {
238 if (touchStart != null) {
239
240
241 target.showContextMenu(touchStart);
242 event.preventDefault();
243 touchStart = null;
244 }
245 }
246 };
247 }
248 contextTouchTimeout.schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
249 break;
250 case Event.ONTOUCHCANCEL:
251 case Event.ONTOUCHEND:
252 cancel();
253 break;
254 case Event.ONTOUCHMOVE:
255 if (isSignificantMove(event)) {
256
257
258 cancel();
259 }
260 }
261 }
262
263
264
265
266
267
268
269
270
271
272 protected boolean isSignificantMove(Event event) {
273 if (touchStart == null) {
274
275 return false;
276 }
277
278
279
280 Touch touch = event.getChangedTouches().get(0);
281 int deltaX = touch.getClientX() - touchStartX;
282 int deltaY = touch.getClientY() - touchStartY;
283 int delta = deltaX * deltaX + deltaY * deltaY;
284
285
286
287 if (delta > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD
288 * TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
289 return true;
290 }
291 return false;
292 }
293 }
294
295 public static final String STYLENAME = "v-table";
296
297 public enum SelectMode {
298 NONE(0), SINGLE(1), MULTI(2);
299 private int id;
300
301 private SelectMode(int id) {
302 this.id = id;
303 }
304
305 public int getId() {
306 return id;
307 }
308 }
309
310 private static final String ROW_HEADER_COLUMN_KEY = "0";
311
312 private static final double CACHE_RATE_DEFAULT = 2;
313
314
315
316
317
318
319 private static final int MULTISELECT_MODE_DEFAULT = 0;
320
321
322
323
324
325
326
327 private static final int MULTISELECT_MODE_SIMPLE = 1;
328
329
330
331
332
333 private double cacheRate = CACHE_RATE_DEFAULT;
334
335
336
337 private double cacheReactRate = 0.75 * cacheRate;
338
339 public static final char ALIGN_CENTER = 'c';
340 public static final char ALIGN_LEFT = 'b';
341 public static final char ALIGN_RIGHT = 'e';
342 private static final int CHARCODE_SPACE = 32;
343 private int firstRowInViewPort = 0;
344 private int pageLength = 15;
345
346 private int lastRequestedFirstvisible = 0;
347
348
349 private int firstvisibleOnLastPage = -1;
350
351
352 public boolean showRowHeaders = false;
353
354 private String[] columnOrder;
355
356 protected ApplicationConnection client;
357
358
359 public String paintableId;
360
361
362 public boolean immediate;
363
364 private boolean updatedReqRows = true;
365
366 private boolean nullSelectionAllowed = true;
367
368 private SelectMode selectMode = SelectMode.NONE;
369
370 private boolean multiSelectTouchDetectionEnabled = true;
371
372 public final HashSet<String> selectedRowKeys = new HashSet<String>();
373
374
375
376
377
378 private HashSet<Object> unSyncedselectionsBeforeRowFetch;
379
380
381
382
383
384
385 public boolean selectLastItemInNextRender = false;
386
387 public boolean selectFirstItemInNextRender = false;
388
389 public boolean focusFirstItemInNextRender = false;
390
391 public boolean focusLastItemInNextRender = false;
392
393
394
395
396
397
398 public VScrollTableRow focusedRow;
399
400
401
402
403
404
405 public VScrollTableRow selectionRangeStart;
406
407
408
409
410
411
412
413 public boolean selectionChanged = false;
414
415
416
417
418 private int scrollingVelocity = 10;
419
420 private Timer scrollingVelocityTimer = null;
421
422
423 public String[] bodyActionKeys;
424
425 private boolean enableDebug = false;
426
427 private static final boolean HAS_NATIVE_TOUCH_SCROLLLING = BrowserInfo.get()
428 .isTouchDevice()
429 && !BrowserInfo.get().requiresTouchScrollDelegate();
430
431 private Set<String> noncollapsibleColumns;
432
433
434
435
436
437
438
439
440
441
442
443
444 private double lastKnownRowHeight = Double.NaN;
445
446
447
448
449
450 private int detachedScrollPosition = 0;
451
452
453
454
455 private class SelectionRange {
456 private VScrollTableRow startRow;
457 private final int length;
458
459
460
461
462 public SelectionRange(VScrollTableRow row1, VScrollTableRow row2) {
463 VScrollTableRow endRow;
464 if (row2.isBefore(row1)) {
465 startRow = row2;
466 endRow = row1;
467 } else {
468 startRow = row1;
469 endRow = row2;
470 }
471 length = endRow.getIndex() - startRow.getIndex() + 1;
472 }
473
474 public SelectionRange(VScrollTableRow row, int length) {
475 startRow = row;
476 this.length = length;
477 }
478
479
480
481
482
483
484
485 @Override
486 public String toString() {
487 return startRow.getKey() + "-" + length;
488 }
489
490 private boolean inRange(VScrollTableRow row) {
491 return row.getIndex() >= startRow.getIndex()
492 && row.getIndex() < startRow.getIndex() + length;
493 }
494
495 public Collection<SelectionRange> split(VScrollTableRow row) {
496 assert row.isAttached();
497 List<SelectionRange> ranges = new ArrayList<SelectionRange>(2);
498
499 int endOfFirstRange = row.getIndex() - 1;
500 if (endOfFirstRange >= startRow.getIndex()) {
501
502 ranges.add(new SelectionRange(startRow,
503 endOfFirstRange - startRow.getIndex() + 1));
504 }
505 int startOfSecondRange = row.getIndex() + 1;
506 if (getEndIndex() >= startOfSecondRange) {
507
508 VScrollTableRow startOfRange = scrollBody
509 .getRowByRowIndex(startOfSecondRange);
510 if (startOfRange != null) {
511 ranges.add(new SelectionRange(startOfRange,
512 getEndIndex() - startOfSecondRange + 1));
513 }
514 }
515 return ranges;
516 }
517
518 private int getEndIndex() {
519 return startRow.getIndex() + length - 1;
520 }
521
522 }
523
524 private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>();
525
526
527 public boolean initializedAndAttached = false;
528
529
530
531
532
533
534 public boolean headerChangedDuringUpdate = false;
535
536
537 public final TableHead tHead = createTableHead();
538
539
540 public final TableFooter tFoot = new TableFooter();
541
542
543 private ContextMenuOwner contextMenuOwner = new ContextMenuOwner() {
544
545 @Override
546 public void showContextMenu(Event event) {
547 int left = WidgetUtil.getTouchOrMouseClientX(event);
548 int top = WidgetUtil.getTouchOrMouseClientY(event);
549 boolean menuShown = handleBodyContextMenu(left, top);
550 if (menuShown) {
551 event.stopPropagation();
552 event.preventDefault();
553 }
554 }
555 };
556
557
558 private TouchContextProvider touchContextProvider = new TouchContextProvider(
559 contextMenuOwner);
560
561
562
563
564
565
566
567
568
569
570
571 public class FocusableScrollContextPanel extends FocusableScrollPanel {
572 @Override
573 public void onBrowserEvent(Event event) {
574 super.onBrowserEvent(event);
575 touchContextProvider.handleTouchEvent(event);
576 };
577
578 public FocusableScrollContextPanel(boolean useFakeFocusElement) {
579 super(useFakeFocusElement);
580 }
581 }
582
583
584 public final FocusableScrollContextPanel scrollBodyPanel = new FocusableScrollContextPanel(
585 true);
586
587 private KeyPressHandler navKeyPressHandler = new KeyPressHandler() {
588
589 @Override
590 public void onKeyPress(KeyPressEvent keyPressEvent) {
591
592
593
594 if (!useOldGeckoNavigation()) {
595 return;
596 }
597
598 NativeEvent event = keyPressEvent.getNativeEvent();
599 if (!enabled) {
600
601
602 event.preventDefault();
603 } else if (hasFocus()) {
604
605
606 int keyCode = event.getKeyCode();
607 if (keyCode == 0 && event.getCharCode() == ' ') {
608
609
610 keyCode = CHARCODE_SPACE;
611 }
612
613 if (handleNavigation(keyCode,
614 event.getCtrlKey() || event.getMetaKey(),
615 event.getShiftKey())) {
616 event.preventDefault();
617 }
618
619 startScrollingVelocityTimer();
620 }
621 }
622
623 };
624
625 private KeyUpHandler navKeyUpHandler = new KeyUpHandler() {
626
627 @Override
628 public void onKeyUp(KeyUpEvent keyUpEvent) {
629 NativeEvent event = keyUpEvent.getNativeEvent();
630 int keyCode = event.getKeyCode();
631
632 if (!isFocusable()) {
633 cancelScrollingVelocityTimer();
634 } else if (isNavigationKey(keyCode)) {
635 if (keyCode == getNavigationDownKey()
636 || keyCode == getNavigationUpKey()) {
637
638
639
640
641
642 if (!event.getShiftKey() && !event.getCtrlKey()) {
643 instructServerToForgetPreviousSelections();
644 }
645 sendSelectedRows();
646 }
647 cancelScrollingVelocityTimer();
648 navKeyDown = false;
649 }
650 }
651 };
652
653 private KeyDownHandler navKeyDownHandler = new KeyDownHandler() {
654
655 @Override
656 public void onKeyDown(KeyDownEvent keyDownEvent) {
657 NativeEvent event = keyDownEvent.getNativeEvent();
658
659 if (useOldGeckoNavigation()) {
660 return;
661 }
662
663 if (!enabled) {
664
665
666 event.preventDefault();
667 } else if (hasFocus()) {
668 if (handleNavigation(event.getKeyCode(),
669 event.getCtrlKey() || event.getMetaKey(),
670 event.getShiftKey())) {
671 navKeyDown = true;
672 event.preventDefault();
673 }
674
675 startScrollingVelocityTimer();
676 }
677 }
678 };
679
680
681 public int totalRows;
682
683 private Set<String> collapsedColumns;
684
685
686 public final RowRequestHandler rowRequestHandler;
687
688
689 public VScrollTableBody scrollBody;
690
691 private int firstvisible = 0;
692 private boolean sortAscending;
693 private String sortColumn;
694 private String oldSortColumn;
695 private boolean columnReordering;
696
697
698
699
700
701 private final Map<Object, String> actionMap = new HashMap<Object, String>();
702 private String[] visibleColOrder;
703 private boolean initialContentReceived = false;
704 private Element scrollPositionElement;
705
706
707 public boolean enabled;
708
709
710 public boolean showColHeaders;
711
712
713 public boolean showColFooters;
714
715
716 private boolean isNewBody = true;
717
718
719
720
721
722
723
724
725
726 public boolean recalcWidths = false;
727
728
729 public boolean rendering = false;
730
731 private boolean hasFocus = false;
732 private int dragmode;
733
734 protected int multiselectmode;
735
736
737
738
739 private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS;
740
741
742 public int tabIndex;
743
744 private TouchScrollDelegate touchScrollDelegate;
745
746
747 public int lastRenderedHeight;
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763 public int serverCacheFirst = -1;
764 public int serverCacheLast = -1;
765
766
767
768
769
770
771 public boolean postponeSanityCheckForLastRendered;
772
773
774 public boolean sizeNeedsInit = true;
775
776
777
778
779
780
781
782 public class ContextMenuDetails implements CloseHandler<PopupPanel> {
783 public String rowKey;
784 public int left;
785 public int top;
786 HandlerRegistration closeRegistration;
787
788 public ContextMenuDetails(VContextMenu menu, String rowKey, int left,
789 int top) {
790 this.rowKey = rowKey;
791 this.left = left;
792 this.top = top;
793 closeRegistration = menu.addCloseHandler(this);
794 }
795
796 @Override
797 public void onClose(CloseEvent<PopupPanel> event) {
798 contextMenu = null;
799 closeRegistration.removeHandler();
800 }
801 }
802
803
804 public ContextMenuDetails contextMenu = null;
805
806 private boolean hadScrollBars = false;
807
808 private HandlerRegistration addCloseHandler;
809
810
811
812
813
814
815
816 private Element lastMouseDownTarget;
817
818
819
820
821
822 private boolean mouseUpPreviewMatched = false;
823
824 private HandlerRegistration mouseUpEventPreviewRegistration;
825
826
827
828
829
830 private final NativePreviewHandler mouseUpPreviewHandler = new NativePreviewHandler() {
831
832 @Override
833 public void onPreviewNativeEvent(NativePreviewEvent event) {
834 if (event.getTypeInt() == Event.ONMOUSEUP) {
835 mouseUpEventPreviewRegistration.removeHandler();
836
837
838
839 Element elementUnderMouse = WidgetUtil
840 .getElementUnderMouse(event.getNativeEvent());
841 if (lastMouseDownTarget != null && lastMouseDownTarget
842 .isOrHasChild(elementUnderMouse)) {
843 mouseUpPreviewMatched = true;
844 } else {
845 getLogger().log(Level.FINEST,
846 "Ignoring mouseup from " + elementUnderMouse
847 + " when mousedown was on "
848 + lastMouseDownTarget);
849 }
850 }
851 }
852 };
853
854 public VScrollTablePatched() {
855 setMultiSelectMode(MULTISELECT_MODE_DEFAULT);
856
857 scrollBodyPanel.addFocusHandler(this);
858 scrollBodyPanel.addBlurHandler(this);
859
860 scrollBodyPanel.addScrollHandler(this);
861
862
863
864
865
866
867 if (useOldGeckoNavigation()) {
868 scrollBodyPanel.addKeyPressHandler(navKeyPressHandler);
869 } else {
870 scrollBodyPanel.addKeyDownHandler(navKeyDownHandler);
871 }
872 scrollBodyPanel.addKeyUpHandler(navKeyUpHandler);
873
874 scrollBodyPanel.sinkEvents(Event.TOUCHEVENTS | Event.ONCONTEXTMENU);
875
876 setStyleName(STYLENAME);
877
878 add(tHead);
879 add(scrollBodyPanel);
880 add(tFoot);
881
882 rowRequestHandler = new RowRequestHandler();
883 }
884
885
886
887
888
889
890 private boolean useOldGeckoNavigation() {
891 return BrowserInfo.get().isGecko()
892 && BrowserInfo.get().getGeckoVersion() < 65;
893 }
894
895 @Override
896 public void setStyleName(String style) {
897 updateStyleNames(style, false);
898 }
899
900 @Override
901 public void setStylePrimaryName(String style) {
902 updateStyleNames(style, true);
903 }
904
905 private void updateStyleNames(String newStyle, boolean isPrimary) {
906 scrollBodyPanel
907 .removeStyleName(getStylePrimaryName() + "-body-wrapper");
908 scrollBodyPanel.removeStyleName(getStylePrimaryName() + "-body");
909
910 if (scrollBody != null) {
911 scrollBody.removeStyleName(
912 getStylePrimaryName() + "-body-noselection");
913 }
914
915 if (isPrimary) {
916 super.setStylePrimaryName(newStyle);
917 } else {
918 super.setStyleName(newStyle);
919 }
920
921 scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body-wrapper");
922 scrollBodyPanel.addStyleName(getStylePrimaryName() + "-body");
923
924 tHead.updateStyleNames(getStylePrimaryName());
925 tFoot.updateStyleNames(getStylePrimaryName());
926
927 if (scrollBody != null) {
928 scrollBody.updateStyleNames(getStylePrimaryName());
929 }
930 }
931
932 public void init(ApplicationConnection client) {
933 this.client = client;
934
935
936 addCloseHandler = client.getContextMenu()
937 .addCloseHandler(new CloseHandler<PopupPanel>() {
938
939 @Override
940 public void onClose(CloseEvent<PopupPanel> event) {
941 contextMenu = null;
942 }
943 });
944 }
945
946
947
948
949
950
951
952
953
954
955 private boolean handleBodyContextMenu(int left, int top) {
956 if (enabled && bodyActionKeys != null) {
957 top += Window.getScrollTop();
958 left += Window.getScrollLeft();
959 client.getContextMenu().showAt(this, left, top);
960 return true;
961 }
962 return false;
963 }
964
965
966
967
968
969
970
971
972
973
974
975
976 private void fireColumnResizeEvent(String columnId, int originalWidth,
977 int newWidth) {
978 client.updateVariable(paintableId, "columnResizeEventColumn", columnId,
979 false);
980 client.updateVariable(paintableId, "columnResizeEventPrev",
981 originalWidth, false);
982 client.updateVariable(paintableId, "columnResizeEventCurr", newWidth,
983 immediate);
984
985 }
986
987
988
989
990
991
992
993
994 private void sendColumnWidthUpdates(Collection<HeaderCell> columns) {
995 String[] newSizes = new String[columns.size()];
996 int ix = 0;
997 for (HeaderCell cell : columns) {
998 newSizes[ix++] = cell.getColKey() + ":" + cell.getWidth();
999 }
1000 client.updateVariable(paintableId, "columnWidthUpdates", newSizes,
1001 false);
1002 }
1003
1004
1005
1006
1007
1008
1009 private boolean moveFocusDown() {
1010 return moveFocusDown(0);
1011 }
1012
1013
1014
1015
1016
1017
1018
1019 private boolean moveFocusDown(int offset) {
1020 if (isSelectable()) {
1021 if (focusedRow == null && scrollBody.iterator().hasNext()) {
1022
1023
1024 return setRowFocus(
1025 (VScrollTableRow) scrollBody.iterator().next());
1026 } else {
1027 VScrollTableRow next = getNextRow(focusedRow, offset);
1028 if (next != null) {
1029 return setRowFocus(next);
1030 }
1031 }
1032 }
1033
1034 return false;
1035 }
1036
1037
1038
1039
1040
1041
1042 private boolean moveFocusUp() {
1043 return moveFocusUp(0);
1044 }
1045
1046
1047
1048
1049
1050
1051
1052
1053 private boolean moveFocusUp(int offset) {
1054 if (isSelectable()) {
1055 if (focusedRow == null && scrollBody.iterator().hasNext()) {
1056
1057
1058 return setRowFocus(
1059 (VScrollTableRow) scrollBody.iterator().next());
1060 } else {
1061 VScrollTableRow prev = getPreviousRow(focusedRow, offset);
1062 if (prev != null) {
1063 return setRowFocus(prev);
1064 } else {
1065 getLogger().info("no previous available");
1066 }
1067 }
1068 }
1069
1070 return false;
1071 }
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082 private void selectFocusedRow(boolean ctrlSelect, boolean shiftSelect) {
1083 if (focusedRow != null) {
1084
1085 if (isSelectable() && !ctrlSelect && !shiftSelect) {
1086 deselectAll();
1087 focusedRow.toggleSelection();
1088 selectionRangeStart = focusedRow;
1089 } else if (isSelectable() && ctrlSelect && !shiftSelect) {
1090
1091 selectionRangeStart = focusedRow;
1092
1093 } else if (isMultiSelectModeAny() && !ctrlSelect && shiftSelect) {
1094
1095 focusedRow.toggleShiftSelection(shiftSelect);
1096 }
1097 }
1098 }
1099
1100
1101
1102
1103 protected void sendSelectedRows() {
1104 sendSelectedRows(immediate);
1105 }
1106
1107 private void updateFirstVisibleAndSendSelectedRows() {
1108 updateFirstVisibleRow();
1109 sendSelectedRows(immediate);
1110 }
1111
1112
1113
1114
1115
1116
1117
1118
1119 protected void sendSelectedRows(boolean immediately) {
1120
1121 if (!selectionChanged) {
1122 return;
1123 }
1124
1125
1126 selectionChanged = false;
1127
1128
1129
1130 if (isMultiSelectModeDefault()) {
1131
1132 Set<String> ranges = new HashSet<String>();
1133 for (SelectionRange range : selectedRowRanges) {
1134 ranges.add(range.toString());
1135 }
1136
1137
1138 client.updateVariable(paintableId, "selectedRanges",
1139 ranges.toArray(new String[selectedRowRanges.size()]),
1140 false);
1141 selectedRowRanges.clear();
1142
1143
1144 for (Iterator<String> iterator = selectedRowKeys
1145 .iterator(); iterator.hasNext();) {
1146 String key = iterator.next();
1147 VScrollTableRow renderedRowByKey = getRenderedRowByKey(key);
1148 if (renderedRowByKey != null) {
1149 for (SelectionRange range : selectedRowRanges) {
1150 if (range.inRange(renderedRowByKey)) {
1151 iterator.remove();
1152 }
1153 }
1154 } else {
1155
1156 iterator.remove();
1157 }
1158
1159 }
1160 }
1161
1162
1163 client.updateVariable(paintableId, "selected",
1164 selectedRowKeys.toArray(new String[selectedRowKeys.size()]),
1165 false);
1166
1167 if (immediately) {
1168 selectionSynchronizer.schedule(500);
1169 }
1170 }
1171
1172 private Timer selectionSynchronizer = new Timer() {
1173 @Override
1174 public void run() {
1175 client.sendPendingVariableChanges();
1176 }
1177 };
1178
1179
1180
1181
1182
1183
1184
1185
1186 protected int getNavigationUpKey() {
1187 return KeyCodes.KEY_UP;
1188 }
1189
1190
1191
1192
1193
1194
1195
1196
1197 protected int getNavigationDownKey() {
1198 return KeyCodes.KEY_DOWN;
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208 protected int getNavigationLeftKey() {
1209 return KeyCodes.KEY_LEFT;
1210 }
1211
1212
1213
1214
1215
1216
1217
1218
1219 protected int getNavigationRightKey() {
1220 return KeyCodes.KEY_RIGHT;
1221 }
1222
1223
1224
1225
1226
1227
1228
1229
1230 protected int getNavigationSelectKey() {
1231 return CHARCODE_SPACE;
1232 }
1233
1234
1235
1236
1237
1238
1239
1240
1241 protected int getNavigationPageUpKey() {
1242 return KeyCodes.KEY_PAGEUP;
1243 }
1244
1245
1246
1247
1248
1249
1250
1251
1252 protected int getNavigationPageDownKey() {
1253 return KeyCodes.KEY_PAGEDOWN;
1254 }
1255
1256
1257
1258
1259
1260
1261
1262
1263 protected int getNavigationStartKey() {
1264 return KeyCodes.KEY_HOME;
1265 }
1266
1267
1268
1269
1270
1271
1272
1273
1274 protected int getNavigationEndKey() {
1275 return KeyCodes.KEY_END;
1276 }
1277
1278
1279 public void initializeRows(UIDL uidl, UIDL rowData) {
1280 if (scrollBody != null) {
1281 scrollBody.removeFromParent();
1282 }
1283
1284
1285
1286
1287 int pos = scrollBodyPanel.getScrollPosition();
1288
1289
1290 if (pos == 0) {
1291 firstRowInViewPort = 0;
1292 }
1293
1294 scrollBody = createScrollBody();
1295
1296 scrollBody.renderInitialRows(rowData, uidl.getIntAttribute("firstrow"),
1297 uidl.getIntAttribute("rows"));
1298 scrollBodyPanel.add(scrollBody);
1299
1300 initialContentReceived = true;
1301 sizeNeedsInit = true;
1302 scrollBody.restoreRowVisibility();
1303
1304
1305 if (scrollBodyPanel != null) {
1306 VScrollTableRow row;
1307 if (selectedRowKeys.size() == 1) {
1308 String selectedRowKey = selectedRowKeys.iterator().next();
1309 row = getRenderedRowByKey(selectedRowKey);
1310 } else {
1311 row = (VScrollTableRow) scrollBody.iterator().next();
1312 }
1313 lazyRevertFocusToRow(row);
1314 }
1315 }
1316
1317
1318 public void updateColumnProperties(UIDL uidl) {
1319 updateColumnOrder(uidl);
1320
1321 updateCollapsedColumns(uidl);
1322
1323 UIDL vc = uidl.getChildByTagName("visiblecolumns");
1324 if (vc != null) {
1325 tHead.updateCellsFromUIDL(vc);
1326 tFoot.updateCellsFromUIDL(vc);
1327 }
1328
1329 updateHeader(uidl.getStringArrayAttribute("vcolorder"));
1330 updateFooter(uidl.getStringArrayAttribute("vcolorder"));
1331 if (uidl.hasVariable("noncollapsiblecolumns")) {
1332 noncollapsibleColumns = uidl
1333 .getStringArrayVariableAsSet("noncollapsiblecolumns");
1334 }
1335 }
1336
1337 private void updateCollapsedColumns(UIDL uidl) {
1338 if (uidl.hasVariable("collapsedcolumns")) {
1339 tHead.setColumnCollapsingAllowed(true);
1340 collapsedColumns = uidl
1341 .getStringArrayVariableAsSet("collapsedcolumns");
1342 } else {
1343 tHead.setColumnCollapsingAllowed(false);
1344 }
1345 }
1346
1347 private void updateColumnOrder(UIDL uidl) {
1348 if (uidl.hasVariable("columnorder")) {
1349 columnReordering = true;
1350 columnOrder = uidl.getStringArrayVariable("columnorder");
1351 } else {
1352 columnReordering = false;
1353 columnOrder = null;
1354 }
1355 }
1356
1357
1358 public boolean selectSelectedRows(UIDL uidl) {
1359 boolean keyboardSelectionOverRowFetchInProgress = false;
1360
1361 if (uidl.hasVariable("selected")) {
1362 final Set<String> selectedKeys = uidl
1363 .getStringArrayVariableAsSet("selected");
1364
1365
1366
1367 boolean rowsUnSelected = removeUnselectedRowKeys(selectedKeys);
1368 boolean updateFocus = rowsUnSelected || selectedRowKeys.isEmpty()
1369 || focusedRow == null;
1370 if (scrollBody != null) {
1371 for (Widget w : scrollBody) {
1372
1373
1374
1375
1376 VScrollTableRow row = (VScrollTableRow) w;
1377 boolean selected = selectedKeys.contains(row.getKey());
1378 if (!selected && unSyncedselectionsBeforeRowFetch != null
1379 && unSyncedselectionsBeforeRowFetch
1380 .contains(row.getKey())) {
1381 selected = true;
1382 keyboardSelectionOverRowFetchInProgress = true;
1383 }
1384 if (selected && selectedKeys.size() == 1 && updateFocus) {
1385
1386
1387
1388
1389 setRowFocus(row);
1390 }
1391
1392 if (selected != row.isSelected()) {
1393 row.toggleSelection();
1394
1395 if (!isSingleSelectMode() && !selected) {
1396
1397
1398 removeRowFromUnsentSelectionRanges(row);
1399 }
1400 }
1401 }
1402
1403 }
1404 }
1405 unSyncedselectionsBeforeRowFetch = null;
1406 return keyboardSelectionOverRowFetchInProgress;
1407 }
1408
1409 private boolean removeUnselectedRowKeys(final Set<String> selectedKeys) {
1410 List<String> unselectedKeys = new ArrayList<String>(0);
1411 for (String key : selectedRowKeys) {
1412 if (!selectedKeys.contains(key)) {
1413 unselectedKeys.add(key);
1414 }
1415 }
1416 return selectedRowKeys.removeAll(unselectedKeys);
1417 }
1418
1419
1420 public void updateSortingProperties(UIDL uidl) {
1421 oldSortColumn = sortColumn;
1422 if (uidl.hasVariable("sortascending")) {
1423 sortAscending = uidl.getBooleanVariable("sortascending");
1424 sortColumn = uidl.getStringVariable("sortcolumn");
1425 }
1426 }
1427
1428
1429 public void resizeSortedColumnForSortIndicator() {
1430
1431
1432 HeaderCell sortedHeader = tHead.getHeaderCell(sortColumn);
1433 if (sortedHeader != null) {
1434
1435
1436 sortedHeader.setSorted(true);
1437 tHead.resizeCaptionContainer(sortedHeader);
1438 }
1439
1440
1441 HeaderCell oldSortedHeader = tHead.getHeaderCell(oldSortColumn);
1442 if (oldSortedHeader != null) {
1443 tHead.resizeCaptionContainer(oldSortedHeader);
1444 }
1445 }
1446
1447 private boolean lazyScrollerIsActive;
1448
1449 private void disableLazyScroller() {
1450 lazyScrollerIsActive = false;
1451 scrollBodyPanel.getElement().getStyle().clearOverflowX();
1452 scrollBodyPanel.getElement().getStyle().clearOverflowY();
1453 }
1454
1455 private void enableLazyScroller() {
1456 Scheduler.get().scheduleDeferred(lazyScroller);
1457 lazyScrollerIsActive = true;
1458
1459 scrollBodyPanel.getElement().getStyle().setOverflowX(Overflow.HIDDEN);
1460 scrollBodyPanel.getElement().getStyle().setOverflowY(Overflow.HIDDEN);
1461 }
1462
1463 private boolean isLazyScrollerActive() {
1464 return lazyScrollerIsActive;
1465 }
1466
1467 private ScheduledCommand lazyScroller = new ScheduledCommand() {
1468
1469 @Override
1470 public void execute() {
1471 if (firstvisible >= 0) {
1472 firstRowInViewPort = firstvisible;
1473 if (firstvisibleOnLastPage > -1) {
1474 scrollBodyPanel.setScrollPosition(
1475 measureRowHeightOffset(firstvisibleOnLastPage));
1476 } else {
1477 scrollBodyPanel.setScrollPosition(
1478 measureRowHeightOffset(firstvisible));
1479 }
1480 }
1481 disableLazyScroller();
1482 }
1483 };
1484
1485
1486 public void updateFirstVisibleAndScrollIfNeeded(UIDL uidl) {
1487 firstvisible = uidl.hasVariable("firstvisible")
1488 ? uidl.getIntVariable("firstvisible")
1489 : 0;
1490 firstvisibleOnLastPage = uidl.hasVariable("firstvisibleonlastpage")
1491 ? uidl.getIntVariable("firstvisibleonlastpage")
1492 : -1;
1493 if (firstvisible != lastRequestedFirstvisible && scrollBody != null) {
1494
1495
1496
1497 lastRequestedFirstvisible = firstRowInViewPort;
1498
1499
1500
1501
1502 enableLazyScroller();
1503 }
1504 }
1505
1506 protected int measureRowHeightOffset(int rowIx) {
1507 return (int) (rowIx * scrollBody.getRowHeight());
1508 }
1509
1510
1511 public void updatePageLength(UIDL uidl) {
1512 int oldPageLength = pageLength;
1513 if (uidl.hasAttribute("pagelength")) {
1514 pageLength = uidl.getIntAttribute("pagelength");
1515 } else {
1516
1517 pageLength = totalRows;
1518 }
1519
1520 if (oldPageLength != pageLength && initializedAndAttached) {
1521
1522 sizeNeedsInit = true;
1523 }
1524 }
1525
1526
1527 public void updateSelectionProperties(UIDL uidl,
1528 AbstractComponentState state, boolean readOnly) {
1529 setMultiSelectMode(uidl.hasAttribute("multiselectmode")
1530 ? uidl.getIntAttribute("multiselectmode")
1531 : MULTISELECT_MODE_DEFAULT);
1532
1533 nullSelectionAllowed = uidl.hasAttribute("nsa")
1534 ? uidl.getBooleanAttribute("nsa")
1535 : true;
1536
1537 if (uidl.hasAttribute("selectmode")) {
1538 if (readOnly) {
1539 selectMode = SelectMode.NONE;
1540 } else if (uidl.getStringAttribute("selectmode").equals("multi")) {
1541 selectMode = SelectMode.MULTI;
1542 } else if (uidl.getStringAttribute("selectmode").equals("single")) {
1543 selectMode = SelectMode.SINGLE;
1544 } else {
1545 selectMode = SelectMode.NONE;
1546 }
1547 if (uidl.hasAttribute("touchdetection")) {
1548 multiSelectTouchDetectionEnabled = uidl
1549 .getBooleanAttribute("touchdetection");
1550 }
1551 }
1552 }
1553
1554
1555 public void updateDragMode(UIDL uidl) {
1556 dragmode = uidl.hasAttribute("dragmode")
1557 ? uidl.getIntAttribute("dragmode")
1558 : 0;
1559 if (BrowserInfo.get().isIE()) {
1560 if (dragmode > 0) {
1561 getElement().setPropertyJSO("onselectstart",
1562 getPreventTextSelectionIEHack());
1563 } else {
1564 getElement().setPropertyJSO("onselectstart", null);
1565 }
1566 }
1567 }
1568
1569
1570 public void updateTotalRows(UIDL uidl) {
1571 int newTotalRows = uidl.getIntAttribute("totalrows");
1572 if (newTotalRows != getTotalRows()) {
1573 if (scrollBody != null) {
1574 if (getTotalRows() == 0) {
1575 tHead.clear();
1576 tFoot.clear();
1577 }
1578 initializedAndAttached = false;
1579 initialContentReceived = false;
1580 isNewBody = true;
1581 }
1582 setTotalRows(newTotalRows);
1583 }
1584 }
1585
1586 protected void setTotalRows(int newTotalRows) {
1587 totalRows = newTotalRows;
1588 }
1589
1590 public int getTotalRows() {
1591 return totalRows;
1592 }
1593
1594
1595
1596
1597
1598
1599
1600 private int getHeaderPadding() {
1601 return scrollBody.getCellExtraWidth();
1602 }
1603
1604
1605
1606
1607
1608
1609
1610
1611 protected int getHierarchyColumnIndex() {
1612 return -1;
1613 }
1614
1615
1616
1617
1618 public void updateMaxIndent() {
1619 int oldIndent = scrollBody.getMaxIndent();
1620 scrollBody.calculateMaxIndent();
1621 if (oldIndent != scrollBody.getMaxIndent()) {
1622
1623 triggerLazyColumnAdjustment(true);
1624 }
1625 }
1626
1627
1628 public void focusRowFromBody() {
1629 if (selectedRowKeys.size() == 1) {
1630
1631 String selectedRowKey = selectedRowKeys.iterator().next();
1632 if (selectedRowKey != null) {
1633 VScrollTableRow renderedRow = getRenderedRowByKey(
1634 selectedRowKey);
1635 if (renderedRow == null || !renderedRow.isInViewPort()) {
1636 setRowFocus(
1637 scrollBody.getRowByRowIndex(firstRowInViewPort));
1638 } else {
1639 setRowFocus(renderedRow);
1640 }
1641 }
1642 } else {
1643
1644 setRowFocus(scrollBody.getRowByRowIndex(firstRowInViewPort));
1645 }
1646 }
1647
1648 protected VScrollTableBody createScrollBody() {
1649 return new VScrollTableBody();
1650 }
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660 public void selectLastRenderedRowInViewPort(boolean focusOnly) {
1661 int index = firstRowInViewPort + getFullyVisibleRowCount();
1662 VScrollTableRow lastRowInViewport = scrollBody.getRowByRowIndex(index);
1663 if (lastRowInViewport == null) {
1664
1665
1666 lastRowInViewport = scrollBody
1667 .getRowByRowIndex(scrollBody.getLastRendered());
1668 if (lastRowInViewport == null) {
1669 return;
1670 }
1671 }
1672 setRowFocus(lastRowInViewport);
1673 if (!focusOnly) {
1674 selectFocusedRow(false, multiselectPending);
1675 sendSelectedRows();
1676 }
1677 }
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687 public void selectFirstRenderedRowInViewPort(boolean focusOnly) {
1688 int index = firstRowInViewPort;
1689 VScrollTableRow firstInViewport = scrollBody.getRowByRowIndex(index);
1690 if (firstInViewport == null) {
1691
1692 return;
1693 }
1694 setRowFocus(firstInViewport);
1695 if (!focusOnly) {
1696 selectFocusedRow(false, multiselectPending);
1697 sendSelectedRows();
1698 }
1699 }
1700
1701
1702 public void setCacheRateFromUIDL(UIDL uidl) {
1703 setCacheRate(uidl.hasAttribute("cr") ? uidl.getDoubleAttribute("cr")
1704 : CACHE_RATE_DEFAULT);
1705 }
1706
1707 private void setCacheRate(double d) {
1708 if (cacheRate != d) {
1709 cacheRate = d;
1710 cacheReactRate = 0.75 * d;
1711 }
1712 }
1713
1714
1715 public void updateActionMap(UIDL mainUidl) {
1716 UIDL actionsUidl = mainUidl.getChildByTagName("actions");
1717 if (actionsUidl == null) {
1718 return;
1719 }
1720
1721 for (final Object child : actionsUidl) {
1722 final UIDL action = (UIDL) child;
1723 final String key = action.getStringAttribute("key");
1724 final String caption = action.getStringAttribute("caption");
1725 actionMap.put(key + "_c", caption);
1726 if (action.hasAttribute("icon")) {
1727
1728 actionMap.put(key + "_i", action.getStringAttribute("icon"));
1729 } else {
1730 actionMap.remove(key + "_i");
1731 }
1732 }
1733
1734 }
1735
1736 public String getActionCaption(String actionKey) {
1737 return actionMap.get(actionKey + "_c");
1738 }
1739
1740 public String getActionIcon(String actionKey) {
1741 return client.translateVaadinUri(actionMap.get(actionKey + "_i"));
1742 }
1743
1744 private void updateHeader(String[] strings) {
1745 if (strings == null) {
1746 return;
1747 }
1748
1749 int visibleCols = strings.length;
1750 int colIndex = 0;
1751 if (showRowHeaders) {
1752 tHead.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
1753 visibleCols++;
1754 visibleColOrder = new String[visibleCols];
1755 visibleColOrder[colIndex] = ROW_HEADER_COLUMN_KEY;
1756 colIndex++;
1757 } else {
1758 visibleColOrder = new String[visibleCols];
1759 tHead.removeCell(ROW_HEADER_COLUMN_KEY);
1760 }
1761
1762 for (final String cid : strings) {
1763 visibleColOrder[colIndex] = cid;
1764 tHead.enableColumn(cid, colIndex);
1765 colIndex++;
1766 }
1767
1768 tHead.setVisible(showColHeaders);
1769 setContainerHeight();
1770
1771 }
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781 private void updateFooter(String[] strings) {
1782 if (strings == null) {
1783 return;
1784 }
1785
1786
1787 int colIndex = 0;
1788 if (showRowHeaders) {
1789 tFoot.enableColumn(ROW_HEADER_COLUMN_KEY, colIndex);
1790 colIndex++;
1791 } else {
1792 tFoot.removeCell(ROW_HEADER_COLUMN_KEY);
1793 }
1794
1795 for (final String cid : strings) {
1796 tFoot.enableColumn(cid, colIndex);
1797 colIndex++;
1798 }
1799
1800 tFoot.setVisible(showColFooters);
1801 }
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813 public void updateBody(UIDL uidl, int firstRow, int reqRows) {
1814 int oldIndent = scrollBody.getMaxIndent();
1815 if (uidl == null || reqRows < 1) {
1816
1817 if (firstRow <= 0) {
1818 postponeSanityCheckForLastRendered = true;
1819 while (scrollBody.getLastRendered() > scrollBody
1820 .getFirstRendered()) {
1821 scrollBody.unlinkRow(false);
1822 }
1823 postponeSanityCheckForLastRendered = false;
1824 scrollBody.unlinkRow(false);
1825 }
1826 return;
1827 }
1828
1829 scrollBody.renderRows(uidl, firstRow, reqRows);
1830
1831 discardRowsOutsideCacheWindow();
1832 scrollBody.calculateMaxIndent();
1833 if (oldIndent != scrollBody.getMaxIndent()) {
1834
1835 headerChangedDuringUpdate = true;
1836 }
1837 }
1838
1839
1840 public void updateRowsInBody(UIDL partialRowUpdates) {
1841 if (partialRowUpdates == null) {
1842 return;
1843 }
1844 int firstRowIx = partialRowUpdates.getIntAttribute("firsturowix");
1845 int count = partialRowUpdates.getIntAttribute("numurows");
1846 scrollBody.unlinkRows(firstRowIx, count);
1847 scrollBody.insertRows(partialRowUpdates, firstRowIx, count);
1848 }
1849
1850
1851
1852
1853
1854 protected void discardRowsOutsideCacheWindow() {
1855 int firstRowToKeep = (int) (firstRowInViewPort
1856 - pageLength * cacheRate);
1857 int lastRowToKeep = (int) (firstRowInViewPort + pageLength
1858 + pageLength * cacheRate);
1859
1860 if (firstRowToKeep < 0) {
1861 firstRowToKeep = 0;
1862 }
1863 if (lastRowToKeep > totalRows) {
1864 lastRowToKeep = totalRows - 1;
1865 }
1866 debug("Client side calculated cache rows to keep: " + firstRowToKeep
1867 + "-" + lastRowToKeep);
1868
1869 if (serverCacheFirst != -1) {
1870 firstRowToKeep = serverCacheFirst;
1871 lastRowToKeep = serverCacheLast;
1872 debug("Server cache rows that override: " + serverCacheFirst + "-"
1873 + serverCacheLast);
1874 if (firstRowToKeep < scrollBody.getFirstRendered()
1875 || lastRowToKeep > scrollBody.getLastRendered()) {
1876 debug("*** Server wants us to keep " + serverCacheFirst + "-"
1877 + serverCacheLast + " but we only have rows "
1878 + scrollBody.getFirstRendered() + "-"
1879 + scrollBody.getLastRendered() + " rendered!");
1880 }
1881 }
1882 discardRowsOutsideOf(firstRowToKeep, lastRowToKeep);
1883
1884 scrollBody.fixSpacers();
1885
1886 scrollBody.restoreRowVisibility();
1887 }
1888
1889 private void discardRowsOutsideOf(int optimalFirstRow, int optimalLastRow) {
1890
1891
1892
1893
1894 int firstDiscarded = -1, lastDiscarded = -1;
1895 boolean cont = true;
1896 while (cont && scrollBody.getLastRendered() > optimalFirstRow
1897 && scrollBody.getFirstRendered() < optimalFirstRow) {
1898 if (firstDiscarded == -1) {
1899 firstDiscarded = scrollBody.getFirstRendered();
1900 }
1901
1902
1903 cont = scrollBody.unlinkRow(true);
1904 }
1905 if (firstDiscarded != -1) {
1906 lastDiscarded = scrollBody.getFirstRendered() - 1;
1907 debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
1908 }
1909 firstDiscarded = lastDiscarded = -1;
1910
1911 cont = true;
1912 while (cont && scrollBody.getLastRendered() > optimalLastRow) {
1913 if (lastDiscarded == -1) {
1914 lastDiscarded = scrollBody.getLastRendered();
1915 }
1916
1917
1918 cont = scrollBody.unlinkRow(false);
1919 }
1920 if (lastDiscarded != -1) {
1921 firstDiscarded = scrollBody.getLastRendered() + 1;
1922 debug("Discarded rows " + firstDiscarded + "-" + lastDiscarded);
1923 }
1924
1925 debug("Now in cache: " + scrollBody.getFirstRendered() + "-"
1926 + scrollBody.getLastRendered());
1927 }
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938 public void addAndRemoveRows(UIDL partialRowAdditions) {
1939 if (partialRowAdditions == null) {
1940 return;
1941 }
1942 if (partialRowAdditions.hasAttribute("hide")) {
1943 scrollBody.unlinkAndReindexRows(
1944 partialRowAdditions.getIntAttribute("firstprowix"),
1945 partialRowAdditions.getIntAttribute("numprows"));
1946 scrollBody.ensureCacheFilled();
1947 } else {
1948 if (partialRowAdditions.hasAttribute("delbelow")) {
1949 scrollBody.insertRowsDeleteBelow(partialRowAdditions,
1950 partialRowAdditions.getIntAttribute("firstprowix"),
1951 partialRowAdditions.getIntAttribute("numprows"));
1952 } else {
1953 scrollBody.insertAndReindexRows(partialRowAdditions,
1954 partialRowAdditions.getIntAttribute("firstprowix"),
1955 partialRowAdditions.getIntAttribute("numprows"));
1956 }
1957 }
1958
1959 discardRowsOutsideCacheWindow();
1960 }
1961
1962
1963
1964
1965
1966
1967
1968 private int getColIndexByKey(String colKey) {
1969
1970 if (ROW_HEADER_COLUMN_KEY.equals(colKey)) {
1971 return 0;
1972 }
1973 for (int i = 0; i < visibleColOrder.length; i++) {
1974 if (visibleColOrder[i].equals(colKey)) {
1975 return i;
1976 }
1977 }
1978 return -1;
1979 }
1980
1981 protected boolean isMultiSelectModeSimple() {
1982 return selectMode == SelectMode.MULTI
1983 && multiselectmode == MULTISELECT_MODE_SIMPLE;
1984 }
1985
1986 protected boolean isSingleSelectMode() {
1987 return selectMode == SelectMode.SINGLE;
1988 }
1989
1990 protected boolean isMultiSelectModeAny() {
1991 return selectMode == SelectMode.MULTI;
1992 }
1993
1994 protected boolean isMultiSelectModeDefault() {
1995 return selectMode == SelectMode.MULTI
1996 && multiselectmode == MULTISELECT_MODE_DEFAULT;
1997 }
1998
1999 protected void setMultiSelectMode(int multiselectmode) {
2000 if (BrowserInfo.get().isTouchDevice()
2001 && multiSelectTouchDetectionEnabled) {
2002
2003
2004 this.multiselectmode = MULTISELECT_MODE_SIMPLE;
2005 } else {
2006 this.multiselectmode = multiselectmode;
2007 }
2008
2009 }
2010
2011
2012 public boolean isSelectable() {
2013 return selectMode.getId() > SelectMode.NONE.getId();
2014 }
2015
2016 private boolean isCollapsedColumn(String colKey) {
2017 if (collapsedColumns == null) {
2018 return false;
2019 }
2020 if (collapsedColumns.contains(colKey)) {
2021 return true;
2022 }
2023 return false;
2024 }
2025
2026 private String getColKeyByIndex(int index) {
2027 return tHead.getHeaderCell(index).getColKey();
2028 }
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044 protected void setColWidth(int colIndex, int w, boolean isDefinedWidth) {
2045 final HeaderCell hcell = tHead.getHeaderCell(colIndex);
2046
2047
2048
2049
2050 int minWidth = hcell.getMinWidth(false, false);
2051 if (w < minWidth) {
2052 w = minWidth;
2053 }
2054
2055
2056 hcell.setWidth(w, isDefinedWidth);
2057
2058
2059 FooterCell fcell = tFoot.getFooterCell(colIndex);
2060 fcell.setWidth(w, isDefinedWidth);
2061
2062
2063 tHead.resizeCaptionContainer(hcell);
2064
2065
2066
2067
2068 minWidth = hcell.getMinWidth(true, false);
2069 if (w < minWidth) {
2070 w = minWidth;
2071 }
2072
2073
2074 scrollBody.setColWidth(colIndex, w);
2075 }
2076
2077 private int getColWidth(String colKey) {
2078 return tHead.getHeaderCell(colKey).getWidthWithIndent();
2079 }
2080
2081
2082
2083
2084
2085
2086
2087
2088 public VScrollTableRow getRenderedRowByKey(String key) {
2089 if (scrollBody != null) {
2090 for (Widget w : scrollBody) {
2091 VScrollTableRow r = (VScrollTableRow) w;
2092 if (r.getKey().equals(key)) {
2093 return r;
2094 }
2095 }
2096 }
2097 return null;
2098 }
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108 private VScrollTableRow getNextRow(VScrollTableRow row, int offset) {
2109 final Iterator<Widget> it = scrollBody.iterator();
2110 while (it.hasNext()) {
2111 VScrollTableRow r = (VScrollTableRow) it.next();
2112 if (r == row) {
2113 r = null;
2114 while (offset >= 0 && it.hasNext()) {
2115 r = (VScrollTableRow) it.next();
2116 offset--;
2117 }
2118 return r;
2119 }
2120 }
2121
2122 return null;
2123 }
2124
2125
2126
2127
2128
2129
2130
2131
2132 private VScrollTableRow getPreviousRow(VScrollTableRow row, int offset) {
2133 final Iterator<Widget> it = scrollBody.iterator();
2134 final Iterator<Widget> offsetIt = scrollBody.iterator();
2135 VScrollTableRow prev = null;
2136 while (it.hasNext()) {
2137 VScrollTableRow r = (VScrollTableRow) it.next();
2138 if (offset < 0) {
2139 prev = (VScrollTableRow) offsetIt.next();
2140 }
2141 if (r == row) {
2142 return prev;
2143 }
2144 offset--;
2145 }
2146
2147 return null;
2148 }
2149
2150 protected void reOrderColumn(String columnKey, int newIndex) {
2151
2152 final int oldIndex = getColIndexByKey(columnKey);
2153
2154
2155 tHead.moveCell(oldIndex, newIndex);
2156
2157
2158 scrollBody.moveCol(oldIndex, newIndex);
2159
2160
2161 tFoot.moveCell(oldIndex, newIndex);
2162
2163
2164
2165
2166
2167
2168
2169
2170 final String oldKeyOnNewIndex = visibleColOrder[newIndex];
2171 if (showRowHeaders) {
2172 newIndex--;
2173 }
2174
2175 for (String order : columnOrder) {
2176 if (order.equals(oldKeyOnNewIndex)) {
2177 break;
2178 }
2179 if (isCollapsedColumn(order)) {
2180 newIndex++;
2181 }
2182 }
2183
2184 final String[] newOrder = new String[columnOrder.length];
2185 for (int i = 0, j = 0; j < newOrder.length; i++) {
2186 if (j == newIndex) {
2187 newOrder[j] = columnKey;
2188 j++;
2189 }
2190 if (i == columnOrder.length) {
2191 break;
2192 }
2193 if (columnOrder[i].equals(columnKey)) {
2194 continue;
2195 }
2196 newOrder[j] = columnOrder[i];
2197 j++;
2198 }
2199 columnOrder = newOrder;
2200
2201 int i = showRowHeaders ? 1 : 0;
2202 for (int j = 0; j < newOrder.length; j++) {
2203 final String cid = newOrder[j];
2204 if (!isCollapsedColumn(cid)) {
2205 visibleColOrder[i++] = cid;
2206 }
2207 }
2208 client.updateVariable(paintableId, "columnorder", columnOrder, false);
2209 if (client.hasEventListeners(this,
2210 TableConstants.COLUMN_REORDER_EVENT_ID)) {
2211 client.sendPendingVariableChanges();
2212 }
2213 }
2214
2215 @Override
2216 protected void onDetach() {
2217 detachedScrollPosition = scrollBodyPanel.getScrollPosition();
2218 rowRequestHandler.cancel();
2219 super.onDetach();
2220
2221 if (scrollPositionElement != null) {
2222 final Element parent = DOM.getParent(scrollPositionElement);
2223 if (parent != null) {
2224 DOM.removeChild(parent, scrollPositionElement);
2225 }
2226 }
2227 }
2228
2229 @Override
2230 public void onAttach() {
2231 super.onAttach();
2232
2233 }
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247 public void sizeInit() {
2248 if (!isVisibleInHierarchy()) {
2249 return;
2250 }
2251
2252 sizeNeedsInit = false;
2253
2254 scrollBody.setContainerHeight();
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264 Iterator<Widget> headCells = tHead.iterator();
2265 Iterator<Widget> footCells = tFoot.iterator();
2266 int i = 0;
2267 int totalExplicitColumnsWidths = 0;
2268 int total = 0;
2269 float expandRatioDivider = 0;
2270
2271 final int[] widths = new int[tHead.visibleCells.size()];
2272
2273 tHead.enableBrowserIntelligence();
2274 tFoot.enableBrowserIntelligence();
2275
2276 int hierarchyColumnIndent = scrollBody != null
2277 ? scrollBody.getMaxIndent()
2278 : 0;
2279 HeaderCell hierarchyHeaderWithExpandRatio = null;
2280
2281
2282 while (headCells.hasNext()) {
2283 final HeaderCell hCell = (HeaderCell) headCells.next();
2284 final FooterCell fCell = (FooterCell) footCells.next();
2285 boolean needsIndent = hierarchyColumnIndent > 0
2286 && hCell.isHierarchyColumn();
2287 hCell.saveNaturalColumnWidthIfNotSaved(i);
2288 fCell.saveNaturalColumnWidthIfNotSaved(i);
2289 int w = hCell.getWidth();
2290 if (hCell.isDefinedWidth()) {
2291
2292 if (needsIndent && w < hierarchyColumnIndent) {
2293
2294 w = hierarchyColumnIndent;
2295 }
2296 totalExplicitColumnsWidths += w;
2297 } else {
2298 if (hCell.getExpandRatio() > 0) {
2299 expandRatioDivider += hCell.getExpandRatio();
2300 w = 0;
2301 if (needsIndent && w < hierarchyColumnIndent) {
2302 hierarchyHeaderWithExpandRatio = hCell;
2303
2304
2305 }
2306 } else {
2307
2308
2309
2310 int headerWidth = hCell.getNaturalColumnWidth(i);
2311 int footerWidth = fCell.getNaturalColumnWidth(i);
2312 w = headerWidth > footerWidth ? headerWidth : footerWidth;
2313 }
2314 if (w != 0) {
2315 hCell.setNaturalMinimumColumnWidth(w);
2316 fCell.setNaturalMinimumColumnWidth(w);
2317 }
2318 }
2319 widths[i] = w;
2320 total += w;
2321 i++;
2322 }
2323 if (hierarchyHeaderWithExpandRatio != null) {
2324 total += hierarchyColumnIndent;
2325 }
2326
2327 tHead.disableBrowserIntelligence();
2328 tFoot.disableBrowserIntelligence();
2329
2330 boolean willHaveScrollbarz = willHaveScrollbars();
2331
2332
2333 if (isDynamicWidth()) {
2334 int w = total;
2335 w += scrollBody.getCellExtraWidth() * visibleColOrder.length;
2336 if (willHaveScrollbarz) {
2337 w += WidgetUtil.getNativeScrollbarSize();
2338 }
2339 setContentWidth(w);
2340 }
2341
2342 int availW = scrollBody.getAvailableWidth();
2343 if (BrowserInfo.get().isIE()) {
2344
2345 availW = scrollBody.getAvailableWidth();
2346 }
2347 availW -= scrollBody.getCellExtraWidth() * visibleColOrder.length;
2348
2349 if (willHaveScrollbarz) {
2350 availW -= WidgetUtil.getNativeScrollbarSize();
2351 }
2352
2353
2354
2355 if (availW > total) {
2356
2357 int extraSpace = availW - total;
2358 if (hierarchyHeaderWithExpandRatio != null) {
2359
2360
2361
2362
2363
2364
2365
2366 extraSpace += hierarchyColumnIndent;
2367 }
2368 final int totalWidthR = total - totalExplicitColumnsWidths;
2369 int checksum = 0;
2370
2371 if (extraSpace == 1) {
2372
2373
2374
2375 headCells = tHead.iterator();
2376 i = 0;
2377 checksum = availW;
2378 while (headCells.hasNext()) {
2379 HeaderCell hc = (HeaderCell) headCells.next();
2380 if (!hc.isDefinedWidth()) {
2381 widths[i]++;
2382 break;
2383 }
2384 i++;
2385 }
2386
2387 } else if (expandRatioDivider > 0) {
2388 boolean setIndentToHierarchyHeader = false;
2389 if (hierarchyHeaderWithExpandRatio != null) {
2390
2391
2392 final int newSpace = Math.round((extraSpace
2393 * (hierarchyHeaderWithExpandRatio.getExpandRatio()
2394 / expandRatioDivider)));
2395 if (newSpace < hierarchyColumnIndent) {
2396
2397
2398
2399 setIndentToHierarchyHeader = true;
2400 extraSpace -= hierarchyColumnIndent;
2401 }
2402 }
2403
2404
2405
2406 headCells = tHead.iterator();
2407 i = 0;
2408 while (headCells.hasNext()) {
2409 HeaderCell hCell = (HeaderCell) headCells.next();
2410 if (hCell.getExpandRatio() > 0) {
2411 int w = widths[i];
2412 if (setIndentToHierarchyHeader
2413 && hierarchyHeaderWithExpandRatio
2414 .equals(hCell)) {
2415
2416
2417 w += hierarchyColumnIndent;
2418 } else {
2419 final int newSpace = Math
2420 .round((extraSpace * (hCell.getExpandRatio()
2421 / expandRatioDivider)));
2422 w += newSpace;
2423 }
2424 widths[i] = w;
2425 }
2426 checksum += widths[i];
2427 i++;
2428 }
2429 } else if (totalWidthR > 0) {
2430
2431
2432
2433
2434 headCells = tHead.iterator();
2435 i = 0;
2436 while (headCells.hasNext()) {
2437 HeaderCell hCell = (HeaderCell) headCells.next();
2438 if (!hCell.isDefinedWidth()) {
2439 int w = widths[i];
2440 final int newSpace = Math.round(
2441 (float) extraSpace * (float) w / totalWidthR);
2442 w += newSpace;
2443 widths[i] = w;
2444 }
2445 checksum += widths[i];
2446 i++;
2447 }
2448 }
2449
2450 if (extraSpace > 0 && checksum != availW) {
2451
2452
2453
2454
2455
2456 headCells = tHead.iterator();
2457 i = 0;
2458 while (headCells.hasNext()) {
2459 HeaderCell hc = (HeaderCell) headCells.next();
2460 if (!hc.isDefinedWidth()) {
2461 widths[i] += availW - checksum;
2462 break;
2463 }
2464 i++;
2465 }
2466 }
2467
2468 } else {
2469
2470 }
2471
2472
2473 i = 0;
2474 headCells = tHead.iterator();
2475 while (headCells.hasNext()) {
2476 final HeaderCell hCell = (HeaderCell) headCells.next();
2477
2478 final int w = widths[i];
2479 setColWidth(i, w, false);
2480
2481 i++;
2482 }
2483
2484 initializedAndAttached = true;
2485
2486 updatePageLength();
2487
2488
2489
2490
2491
2492 if (isDynamicHeight()) {
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503 if (pageLength == totalRows && pageLength == 0) {
2504 scrollBody.setHeight("0px");
2505 }
2506
2507 int bodyHeight;
2508 if (pageLength == totalRows) {
2509
2510
2511
2512
2513
2514
2515
2516 bodyHeight = scrollBody.getRequiredHeight();
2517 } else {
2518 bodyHeight = (int) Math
2519 .round(scrollBody.getRowHeight(true) * pageLength);
2520 }
2521 boolean needsSpaceForHorizontalSrollbar = (total > availW);
2522 if (needsSpaceForHorizontalSrollbar) {
2523 bodyHeight += WidgetUtil.getNativeScrollbarSize();
2524 }
2525 scrollBodyPanel.setHeight(bodyHeight + "px");
2526 WidgetUtil.runWebkitOverflowAutoFix(scrollBodyPanel.getElement());
2527 }
2528
2529 isNewBody = false;
2530
2531 if (firstvisible > 0) {
2532 enableLazyScroller();
2533 }
2534
2535 if (enabled) {
2536
2537 if (scrollBody.getLastRendered() + 1 < firstRowInViewPort
2538 + pageLength + (int) cacheReactRate * pageLength) {
2539 if (totalRows - 1 > scrollBody.getLastRendered()) {
2540
2541 int firstInNewSet = scrollBody.getLastRendered() + 1;
2542 int lastInNewSet = (int) (firstRowInViewPort + pageLength
2543 + cacheRate * pageLength);
2544 if (lastInNewSet > totalRows - 1) {
2545 lastInNewSet = totalRows - 1;
2546 }
2547 rowRequestHandler.triggerRowFetch(firstInNewSet,
2548 lastInNewSet - firstInNewSet + 1, 1);
2549 }
2550 }
2551 }
2552
2553
2554
2555
2556
2557 WidgetUtil
2558 .runWebkitOverflowAutoFixDeferred(scrollBodyPanel.getElement());
2559
2560 hadScrollBars = willHaveScrollbarz;
2561 }
2562
2563
2564
2565
2566
2567
2568
2569 protected boolean willHaveScrollbars() {
2570 if (isDynamicHeight()) {
2571 if (pageLength < totalRows) {
2572 return true;
2573 }
2574 } else {
2575 int fakeheight = (int) Math
2576 .round(scrollBody.getRowHeight() * totalRows);
2577 int availableHeight = scrollBodyPanel.getElement()
2578 .getPropertyInt("clientHeight");
2579 if (fakeheight > availableHeight) {
2580 return true;
2581 }
2582 }
2583 return false;
2584 }
2585
2586 private void announceScrollPosition() {
2587 if (scrollPositionElement == null) {
2588 scrollPositionElement = DOM.createDiv();
2589 scrollPositionElement
2590 .setClassName(getStylePrimaryName() + "-scrollposition");
2591 scrollPositionElement.getStyle().setPosition(Position.ABSOLUTE);
2592 scrollPositionElement.getStyle().setDisplay(Display.NONE);
2593 getElement().appendChild(scrollPositionElement);
2594 }
2595
2596 Style style = scrollPositionElement.getStyle();
2597 style.setMarginLeft(getElement().getOffsetWidth() / 2 - 80, Unit.PX);
2598 style.setMarginTop(-scrollBodyPanel.getOffsetHeight(), Unit.PX);
2599
2600
2601 int last = (firstRowInViewPort + pageLength);
2602 if (last > totalRows) {
2603 last = totalRows;
2604 }
2605 scrollPositionElement.setInnerHTML("<span>" + (firstRowInViewPort + 1)
2606 + " – " + (last) + "..." + "</span>");
2607 style.setDisplay(Display.BLOCK);
2608 }
2609
2610
2611 public void hideScrollPositionAnnotation() {
2612 if (scrollPositionElement != null) {
2613 scrollPositionElement.getStyle().setDisplay(Display.NONE);
2614 }
2615 }
2616
2617
2618 public boolean isScrollPositionVisible() {
2619 return scrollPositionElement != null && !scrollPositionElement
2620 .getStyle().getDisplay().equals(Display.NONE.toString());
2621 }
2622
2623
2624 public class RowRequestHandler extends Timer {
2625
2626 private int reqFirstRow = 0;
2627 private int reqRows = 0;
2628 private boolean isRequestHandlerRunning = false;
2629
2630 public void triggerRowFetch(int first, int rows) {
2631 setReqFirstRow(first);
2632 setReqRows(rows);
2633 deferRowFetch();
2634 }
2635
2636 public void triggerRowFetch(int first, int rows, int delay) {
2637 setReqFirstRow(first);
2638 setReqRows(rows);
2639 deferRowFetch(delay);
2640 }
2641
2642 public void deferRowFetch() {
2643 deferRowFetch(250);
2644 }
2645
2646 public boolean isRequestHandlerRunning() {
2647 return isRequestHandlerRunning;
2648 }
2649
2650 public void deferRowFetch(int msec) {
2651 isRequestHandlerRunning = true;
2652 if (reqRows > 0 && reqFirstRow < totalRows) {
2653 schedule(msec);
2654
2655
2656
2657 if (totalRows > pageLength && ((firstRowInViewPort
2658 + pageLength > scrollBody.getLastRendered())
2659 || (firstRowInViewPort < scrollBody
2660 .getFirstRendered()))) {
2661 announceScrollPosition();
2662 } else {
2663 hideScrollPositionAnnotation();
2664 }
2665 }
2666 }
2667
2668 public int getReqFirstRow() {
2669 return reqFirstRow;
2670 }
2671
2672 public void setReqFirstRow(int reqFirstRow) {
2673 if (reqFirstRow < 0) {
2674 this.reqFirstRow = 0;
2675 } else if (reqFirstRow >= totalRows) {
2676 this.reqFirstRow = totalRows - 1;
2677 } else {
2678 this.reqFirstRow = reqFirstRow;
2679 }
2680 }
2681
2682 public void setReqRows(int reqRows) {
2683 if (reqRows < 0) {
2684 this.reqRows = 0;
2685 } else if (reqFirstRow + reqRows > totalRows) {
2686 this.reqRows = totalRows - reqFirstRow;
2687 } else {
2688 this.reqRows = reqRows;
2689 }
2690 }
2691
2692 @Override
2693 public void run() {
2694
2695 if (client.getMessageSender().hasActiveRequest() || navKeyDown) {
2696
2697 getLogger().info("Postponed rowfetch");
2698 schedule(250);
2699 } else if (allRenderedRowsAreNew() && !updatedReqRows) {
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711 setReqFirstRow(
2712 (firstRowInViewPort - (int) (pageLength * cacheRate)));
2713 int last = firstRowInViewPort + (int) (cacheRate * pageLength)
2714 + pageLength - 1;
2715 if (last >= totalRows) {
2716 last = totalRows - 1;
2717 }
2718 setReqRows(last - getReqFirstRow() + 1);
2719 updatedReqRows = true;
2720 schedule(250);
2721
2722 } else {
2723
2724 int firstRendered = scrollBody.getFirstRendered();
2725 int lastRendered = scrollBody.getLastRendered();
2726 if (lastRendered > totalRows) {
2727 lastRendered = totalRows - 1;
2728 }
2729 boolean rendered = firstRendered >= 0 && lastRendered >= 0;
2730
2731 int firstToBeRendered = firstRendered;
2732
2733 if (reqFirstRow < firstToBeRendered) {
2734 firstToBeRendered = reqFirstRow;
2735 } else if (firstRowInViewPort
2736 - (int) (cacheRate * pageLength) > firstToBeRendered) {
2737 firstToBeRendered = firstRowInViewPort
2738 - (int) (cacheRate * pageLength);
2739 if (firstToBeRendered < 0) {
2740 firstToBeRendered = 0;
2741 }
2742 } else if (rendered && firstRendered + 1 < reqFirstRow
2743 && lastRendered + 1 < reqFirstRow) {
2744
2745
2746 firstToBeRendered = reqFirstRow;
2747 }
2748 if (firstToBeRendered + reqRows < firstRendered) {
2749
2750
2751
2752 setReqRows(firstRendered - firstToBeRendered);
2753 }
2754
2755 int lastToBeRendered = lastRendered;
2756 int lastReqRow = reqFirstRow + reqRows - 1;
2757
2758 if (lastReqRow > lastToBeRendered) {
2759 lastToBeRendered = lastReqRow;
2760 } else if (firstRowInViewPort + pageLength
2761 + pageLength * cacheRate < lastToBeRendered) {
2762 lastToBeRendered = (firstRowInViewPort + pageLength
2763 + (int) (pageLength * cacheRate));
2764 if (lastToBeRendered >= totalRows) {
2765 lastToBeRendered = totalRows - 1;
2766 }
2767
2768
2769 if (lastReqRow > lastToBeRendered) {
2770 setReqRows(lastToBeRendered - reqFirstRow);
2771 }
2772 } else if (rendered && lastRendered - 1 > lastReqRow
2773 && firstRendered - 1 > lastReqRow) {
2774
2775
2776 lastToBeRendered = lastReqRow;
2777 }
2778
2779 if (lastToBeRendered > totalRows) {
2780 lastToBeRendered = totalRows - 1;
2781 }
2782 if (reqFirstRow < firstToBeRendered
2783 || (reqFirstRow > firstToBeRendered
2784 && (reqFirstRow < firstRendered
2785 || reqFirstRow > lastRendered + 1))) {
2786 setReqFirstRow(firstToBeRendered);
2787 }
2788 if (lastRendered < lastToBeRendered
2789 && lastRendered + reqRows < lastToBeRendered) {
2790
2791
2792
2793 setReqRows(lastToBeRendered - lastRendered);
2794 } else if (lastToBeRendered >= firstRendered
2795 && reqFirstRow + reqRows < firstRendered) {
2796 setReqRows(lastToBeRendered - lastRendered);
2797 }
2798
2799 client.updateVariable(paintableId, "firstToBeRendered",
2800 firstToBeRendered, false);
2801 client.updateVariable(paintableId, "lastToBeRendered",
2802 lastToBeRendered, false);
2803
2804
2805
2806 if (firstRowInViewPort != firstvisible) {
2807
2808
2809 lastRequestedFirstvisible = firstRowInViewPort;
2810 client.updateVariable(paintableId, "firstvisible",
2811 firstRowInViewPort, false);
2812 }
2813
2814 client.updateVariable(paintableId, "reqfirstrow", reqFirstRow,
2815 false);
2816 client.updateVariable(paintableId, "reqrows", reqRows, true);
2817
2818 if (selectionChanged) {
2819 unSyncedselectionsBeforeRowFetch = new HashSet<Object>(
2820 selectedRowKeys);
2821 }
2822 isRequestHandlerRunning = false;
2823 }
2824 }
2825
2826
2827
2828
2829 public void refreshContent() {
2830 isRequestHandlerRunning = true;
2831 int first = (int) (firstRowInViewPort - pageLength * cacheRate);
2832 int reqRows = (int) (2 * pageLength * cacheRate + pageLength);
2833 if (first < 0) {
2834 reqRows = reqRows + first;
2835 first = 0;
2836 }
2837 setReqFirstRow(first);
2838 setReqRows(reqRows);
2839 run();
2840 }
2841 }
2842
2843 public class HeaderCell extends Widget {
2844
2845 Element td = DOM.createTD();
2846
2847 protected Element captionContainer = DOM.createDiv();
2848
2849 Element sortIndicator = DOM.createDiv();
2850
2851 Element colResizeWidget = DOM.createDiv();
2852
2853 Element floatingCopyOfHeaderCell;
2854
2855 private boolean sortable = false;
2856 private final String cid;
2857
2858 private boolean dragging;
2859 private Integer currentDragX = null;
2860
2861 private int dragStartX;
2862 private int colIndex;
2863 private int originalWidth;
2864
2865 private boolean isResizing;
2866
2867 private int headerX;
2868
2869 private boolean moved;
2870
2871 private int closestSlot;
2872
2873 private int width = -1;
2874
2875 private int naturalWidth = -1;
2876
2877 private char align = ALIGN_LEFT;
2878
2879 boolean definedWidth = false;
2880
2881 private float expandRatio = 0;
2882
2883 private boolean sorted;
2884
2885 public void setSortable(boolean b) {
2886 sortable = b;
2887
2888
2889
2890
2891
2892
2893 }
2894
2895
2896
2897
2898
2899
2900 public void resizeCaptionContainer(int rightSpacing) {
2901 int captionContainerWidth = width - colResizeWidget.getOffsetWidth()
2902 - rightSpacing;
2903
2904 if (td.getClassName().contains("-asc")
2905 || td.getClassName().contains("-desc")) {
2906
2907 captionContainerWidth -= sortIndicator.getOffsetWidth();
2908 }
2909
2910 if (captionContainerWidth < 0) {
2911 rightSpacing += captionContainerWidth;
2912 captionContainerWidth = 0;
2913 }
2914
2915 captionContainer.getStyle().setPropertyPx("width",
2916 captionContainerWidth);
2917
2918
2919 if (rightSpacing > 0) {
2920 colResizeWidget.getStyle().setMarginLeft(rightSpacing, Unit.PX);
2921 } else {
2922 colResizeWidget.getStyle().clearMarginLeft();
2923 }
2924 }
2925
2926 public void setNaturalMinimumColumnWidth(int w) {
2927 naturalWidth = w;
2928 }
2929
2930 public HeaderCell(String colId, String headerText) {
2931 cid = colId;
2932
2933 setText(headerText);
2934
2935 td.appendChild(colResizeWidget);
2936
2937
2938 captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
2939
2940 td.appendChild(sortIndicator);
2941 td.appendChild(captionContainer);
2942
2943 DOM.sinkEvents(td, Event.MOUSEEVENTS | Event.ONDBLCLICK
2944 | Event.ONCONTEXTMENU | Event.TOUCHEVENTS);
2945
2946 setElement(td);
2947
2948 setAlign(ALIGN_LEFT);
2949 }
2950
2951 protected void updateStyleNames(String primaryStyleName) {
2952 colResizeWidget.setClassName(primaryStyleName + "-resizer");
2953 sortIndicator.setClassName(primaryStyleName + "-sort-indicator");
2954 captionContainer
2955 .setClassName(primaryStyleName + "-caption-container");
2956 if (sorted) {
2957 if (sortAscending) {
2958 setStyleName(primaryStyleName + "-header-cell-asc");
2959 } else {
2960 setStyleName(primaryStyleName + "-header-cell-desc");
2961 }
2962 } else {
2963 setStyleName(primaryStyleName + "-header-cell");
2964 }
2965
2966 if (sortable) {
2967 addStyleName(primaryStyleName + "-header-sortable");
2968 }
2969
2970 final String alignPrefix = primaryStyleName
2971 + "-caption-container-align-";
2972
2973 switch (align) {
2974 case ALIGN_CENTER:
2975 captionContainer.addClassName(alignPrefix + "center");
2976 break;
2977 case ALIGN_RIGHT:
2978 captionContainer.addClassName(alignPrefix + "right");
2979 break;
2980 default:
2981 captionContainer.addClassName(alignPrefix + "left");
2982 break;
2983 }
2984
2985 }
2986
2987 public void disableAutoWidthCalculation() {
2988 definedWidth = true;
2989 expandRatio = 0;
2990 }
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002 public void setWidth(int w, boolean ensureDefinedWidth) {
3003 if (ensureDefinedWidth) {
3004 definedWidth = true;
3005
3006 expandRatio = 0;
3007 }
3008 if (width == -1) {
3009
3010 captionContainer.getStyle().clearOverflow();
3011 }
3012 width = w;
3013 if (w == -1) {
3014 captionContainer.getStyle().clearWidth();
3015 setWidth("");
3016 } else {
3017 tHead.resizeCaptionContainer(this);
3018
3019
3020
3021
3022
3023
3024 if (scrollBody != null) {
3025 int maxIndent = scrollBody.getMaxIndent();
3026 if (w < maxIndent && isHierarchyColumn()) {
3027 w = maxIndent;
3028 }
3029 int tdWidth = w + scrollBody.getCellExtraWidth();
3030 setWidth(tdWidth + "px");
3031 } else {
3032 Scheduler.get().scheduleDeferred(new Command() {
3033
3034 @Override
3035 public void execute() {
3036 int maxIndent = scrollBody.getMaxIndent();
3037 int tdWidth = width;
3038 if (tdWidth < maxIndent && isHierarchyColumn()) {
3039 tdWidth = maxIndent;
3040 }
3041 tdWidth += scrollBody.getCellExtraWidth();
3042 setWidth(tdWidth + "px");
3043 }
3044 });
3045 }
3046 }
3047 }
3048
3049 public void setUndefinedWidth() {
3050 definedWidth = false;
3051 if (!isResizing) {
3052 setWidth(-1, false);
3053 }
3054 }
3055
3056 private void setUndefinedWidthFlagOnly() {
3057 definedWidth = false;
3058 }
3059
3060
3061
3062
3063
3064
3065
3066 public boolean isDefinedWidth() {
3067 return definedWidth && width >= 0;
3068 }
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078 protected int getWidthWithIndent() {
3079 if (scrollBody != null && isHierarchyColumn()) {
3080 int maxIndent = scrollBody.getMaxIndent();
3081 if (maxIndent > width) {
3082 return maxIndent;
3083 }
3084 }
3085 return width;
3086 }
3087
3088
3089
3090
3091
3092
3093 public int getWidth() {
3094 return width;
3095 }
3096
3097
3098
3099
3100
3101
3102
3103 private boolean isHierarchyColumn() {
3104 int hierarchyColumnIndex = getHierarchyColumnIndex();
3105 return hierarchyColumnIndex >= 0
3106 && tHead.visibleCells.indexOf(this) == hierarchyColumnIndex;
3107 }
3108
3109 public void setText(String headerText) {
3110 DOM.setInnerHTML(captionContainer, headerText);
3111 }
3112
3113 public String getColKey() {
3114 return cid;
3115 }
3116
3117 protected void setSorted(boolean sorted) {
3118 this.sorted = sorted;
3119 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
3120 }
3121
3122
3123
3124
3125
3126 @Override
3127 public void onBrowserEvent(Event event) {
3128 if (enabled && event != null) {
3129 if (isResizing
3130 || event.getEventTarget().cast() == colResizeWidget) {
3131 if (dragging && (event.getTypeInt() == Event.ONMOUSEUP
3132 || event.getTypeInt() == Event.ONTOUCHEND)) {
3133
3134 handleCaptionEvent(event);
3135 } else {
3136 onResizeEvent(event);
3137 }
3138 } else {
3139
3140
3141
3142
3143
3144
3145 if (event.getTypeInt() == Event.ONMOUSEDOWN
3146 || event.getTypeInt() == Event.ONTOUCHSTART) {
3147 scrollBodyPanel.setFocus(true);
3148 }
3149 handleCaptionEvent(event);
3150 boolean stopPropagation = true;
3151 if (event.getTypeInt() == Event.ONCONTEXTMENU
3152 && !client.hasEventListeners(VScrollTablePatched.this,
3153 TableConstants.HEADER_CLICK_EVENT_ID)) {
3154
3155
3156 stopPropagation = false;
3157 }
3158 if (stopPropagation) {
3159 event.stopPropagation();
3160 event.preventDefault();
3161 }
3162 }
3163 }
3164 }
3165
3166 private void createFloatingCopy() {
3167 floatingCopyOfHeaderCell = DOM.createDiv();
3168 DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td));
3169 floatingCopyOfHeaderCell = DOM.getChild(floatingCopyOfHeaderCell,
3170 2);
3171
3172
3173
3174 String stylePrimaryName = VScrollTablePatched.this.getStylePrimaryName();
3175 StringBuilder sb = new StringBuilder();
3176 for (String s : VScrollTablePatched.this.getStyleName().split(" ")) {
3177 if (!s.equals(StyleConstants.UI_WIDGET)) {
3178 sb.append(s);
3179 if (s.equals(stylePrimaryName)) {
3180 sb.append("-header-drag ");
3181 } else {
3182 sb.append(' ');
3183 }
3184 }
3185 }
3186 floatingCopyOfHeaderCell.setClassName(sb.toString().trim());
3187
3188 floatingCopyOfHeaderCell.getStyle().setProperty("width", "auto");
3189 updateFloatingCopysPosition(DOM.getAbsoluteLeft(td),
3190 DOM.getAbsoluteTop(td));
3191 DOM.appendChild(VOverlay.getOverlayContainer(client),
3192 floatingCopyOfHeaderCell);
3193 }
3194
3195 private void updateFloatingCopysPosition(int x, int y) {
3196 x -= DOM.getElementPropertyInt(floatingCopyOfHeaderCell,
3197 "offsetWidth") / 2;
3198 floatingCopyOfHeaderCell.getStyle().setLeft(x, Unit.PX);
3199 if (y > 0) {
3200 floatingCopyOfHeaderCell.getStyle().setTop(y + 7, Unit.PX);
3201 }
3202 }
3203
3204 private void hideFloatingCopy() {
3205 floatingCopyOfHeaderCell.removeFromParent();
3206 floatingCopyOfHeaderCell = null;
3207 }
3208
3209
3210
3211
3212
3213
3214
3215
3216 private void fireHeaderClickedEvent(Event event) {
3217 if (client.hasEventListeners(VScrollTablePatched.this,
3218 TableConstants.HEADER_CLICK_EVENT_ID)) {
3219 MouseEventDetails details = MouseEventDetailsBuilder
3220 .buildMouseEventDetails(event);
3221 client.updateVariable(paintableId, "headerClickEvent",
3222 details.toString(), false);
3223 client.updateVariable(paintableId, "headerClickCID", cid, true);
3224 }
3225 }
3226
3227 protected void handleCaptionEvent(Event event) {
3228 switch (DOM.eventGetType(event)) {
3229 case Event.ONTOUCHSTART:
3230 case Event.ONMOUSEDOWN:
3231 if (columnReordering
3232 && WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
3233 if (event.getTypeInt() == Event.ONTOUCHSTART) {
3234
3235
3236
3237 event.stopPropagation();
3238 }
3239 dragging = true;
3240 currentDragX = WidgetUtil.getTouchOrMouseClientX(event);
3241 moved = false;
3242 colIndex = getColIndexByKey(cid);
3243 DOM.setCapture(getElement());
3244 headerX = tHead.getAbsoluteLeft();
3245
3246
3247 event.preventDefault();
3248 }
3249 break;
3250 case Event.ONMOUSEUP:
3251 case Event.ONTOUCHEND:
3252 case Event.ONTOUCHCANCEL:
3253 if (columnReordering
3254 && WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
3255 dragging = false;
3256 currentDragX = null;
3257 DOM.releaseCapture(getElement());
3258
3259 if (WidgetUtil.isTouchEvent(event)) {
3260
3261
3262
3263
3264 event.preventDefault();
3265 event.stopPropagation();
3266 }
3267 if (moved) {
3268 hideFloatingCopy();
3269 tHead.removeSlotFocus();
3270 if (closestSlot != colIndex
3271 && closestSlot != (colIndex + 1)) {
3272 if (closestSlot > colIndex) {
3273 reOrderColumn(cid, closestSlot - 1);
3274 } else {
3275 reOrderColumn(cid, closestSlot);
3276 }
3277 }
3278 moved = false;
3279 break;
3280 }
3281 }
3282
3283 if (!moved) {
3284
3285 if (sortable && WidgetUtil
3286 .isTouchEventOrLeftMouseButton(event)) {
3287 if (sortColumn.equals(cid)) {
3288
3289 client.updateVariable(paintableId, "sortascending",
3290 !sortAscending, false);
3291 } else {
3292
3293 client.updateVariable(paintableId, "sortcolumn",
3294 cid, false);
3295 }
3296
3297 scrollBodyPanel.setScrollPosition(0);
3298 firstvisible = 0;
3299 rowRequestHandler.setReqFirstRow(0);
3300 rowRequestHandler
3301 .setReqRows((int) (2 * pageLength * cacheRate
3302 + pageLength));
3303
3304
3305 rowRequestHandler.deferRowFetch();
3306 rowRequestHandler.cancel();
3307
3308 rowRequestHandler.run();
3309 }
3310 fireHeaderClickedEvent(event);
3311 if (WidgetUtil.isTouchEvent(event)) {
3312
3313
3314
3315
3316 event.preventDefault();
3317 event.stopPropagation();
3318 }
3319 break;
3320 }
3321 break;
3322 case Event.ONDBLCLICK:
3323 fireHeaderClickedEvent(event);
3324 break;
3325 case Event.ONTOUCHMOVE:
3326 case Event.ONMOUSEMOVE:
3327
3328
3329 int currentX = WidgetUtil.getTouchOrMouseClientX(event);
3330
3331 if (currentDragX == null || Math.abs(currentDragX
3332 - currentX) > VDragAndDropManager.MINIMUM_DISTANCE_TO_START_DRAG) {
3333 if (dragging && WidgetUtil
3334 .isTouchEventOrLeftMouseButton(event)) {
3335 if (event.getTypeInt() == Event.ONTOUCHMOVE) {
3336
3337
3338
3339 event.stopPropagation();
3340 }
3341 if (!moved) {
3342 createFloatingCopy();
3343 moved = true;
3344 }
3345
3346 final int clientX = WidgetUtil
3347 .getTouchOrMouseClientX(event);
3348 final int x = clientX
3349 + tHead.hTableWrapper.getScrollLeft();
3350 int slotX = headerX;
3351 closestSlot = colIndex;
3352 int closestDistance = -1;
3353 int start = 0;
3354 if (showRowHeaders) {
3355 start++;
3356 }
3357 final int visibleCellCount = tHead
3358 .getVisibleCellCount();
3359 for (int i = start; i <= visibleCellCount; i++) {
3360 if (i > 0) {
3361 final String colKey = getColKeyByIndex(i - 1);
3362
3363
3364
3365 slotX += getColWidth(colKey)
3366 + scrollBody.getCellExtraWidth();
3367 }
3368 final int dist = Math.abs(x - slotX);
3369 if (closestDistance == -1
3370 || dist < closestDistance) {
3371 closestDistance = dist;
3372 closestSlot = i;
3373 }
3374 }
3375 tHead.focusSlot(closestSlot);
3376
3377 updateFloatingCopysPosition(clientX, -1);
3378 }
3379 }
3380 break;
3381 default:
3382 break;
3383 }
3384 }
3385
3386 private void onResizeEvent(Event event) {
3387 switch (DOM.eventGetType(event)) {
3388 case Event.ONMOUSEDOWN:
3389 if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
3390 return;
3391 }
3392 isResizing = true;
3393 DOM.setCapture(getElement());
3394 dragStartX = DOM.eventGetClientX(event);
3395 colIndex = getColIndexByKey(cid);
3396 originalWidth = getWidthWithIndent();
3397 DOM.eventPreventDefault(event);
3398 break;
3399 case Event.ONMOUSEUP:
3400 if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
3401 return;
3402 }
3403 isResizing = false;
3404 DOM.releaseCapture(getElement());
3405 tHead.disableAutoColumnWidthCalculation(this);
3406
3407
3408
3409 HeaderCell lastCell = tHead
3410 .getHeaderCell(tHead.getVisibleCellCount() - 1);
3411 tHead.resizeCaptionContainer(lastCell);
3412 triggerLazyColumnAdjustment(true);
3413
3414 fireColumnResizeEvent(cid, originalWidth, getColWidth(cid));
3415 break;
3416 case Event.ONMOUSEMOVE:
3417 if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
3418 return;
3419 }
3420 if (isResizing) {
3421 final int deltaX = DOM.eventGetClientX(event) - dragStartX;
3422 if (deltaX == 0) {
3423 return;
3424 }
3425 tHead.disableAutoColumnWidthCalculation(this);
3426
3427 int newWidth = originalWidth + deltaX;
3428
3429 int minWidth = getMinWidth(true, false);
3430 if (newWidth < minWidth) {
3431
3432 newWidth = minWidth;
3433 }
3434 setColWidth(colIndex, newWidth, true);
3435 triggerLazyColumnAdjustment(false);
3436 forceRealignColumnHeaders();
3437 }
3438 break;
3439 default:
3440 break;
3441 }
3442 }
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454 private int getMinWidth(boolean includeIndent,
3455 boolean includeCellExtraWidth) {
3456 int minWidth = sortIndicator.getOffsetWidth();
3457 if (scrollBody != null) {
3458
3459 if (includeIndent && isHierarchyColumn()) {
3460 int maxIndent = scrollBody.getMaxIndent();
3461 if (minWidth < maxIndent) {
3462 minWidth = maxIndent;
3463 }
3464 }
3465 if (includeCellExtraWidth) {
3466 minWidth += scrollBody.getCellExtraWidth();
3467 }
3468 }
3469 return minWidth;
3470 }
3471
3472 public int getMinWidth() {
3473
3474 return getMinWidth(false, true);
3475 }
3476
3477 public String getCaption() {
3478 return DOM.getInnerText(captionContainer);
3479 }
3480
3481 public boolean isEnabled() {
3482 return getParent() != null;
3483 }
3484
3485 public void setAlign(char c) {
3486 align = c;
3487 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
3488 }
3489
3490 public char getAlign() {
3491 return align;
3492 }
3493
3494
3495
3496
3497
3498
3499
3500 protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) {
3501 if (naturalWidth < 0) {
3502
3503
3504
3505 int hw = captionContainer.getOffsetWidth() + getHeaderPadding();
3506 if (BrowserInfo.get().isGecko()) {
3507 hw += sortIndicator.getOffsetWidth();
3508 }
3509 if (columnIndex < 0) {
3510 columnIndex = 0;
3511 for (Widget w : tHead) {
3512 if (w == this) {
3513 break;
3514 }
3515 columnIndex++;
3516 }
3517 }
3518 final int cw = scrollBody.getColWidth(columnIndex);
3519 naturalWidth = (hw > cw ? hw : cw);
3520 }
3521 }
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533 public int getNaturalColumnWidth(int columnIndex) {
3534 final int iw = columnIndex == getHierarchyColumnIndex()
3535 ? scrollBody.getMaxIndent()
3536 : 0;
3537 saveNaturalColumnWidthIfNotSaved(columnIndex);
3538 if (isDefinedWidth()) {
3539 if (iw > width) {
3540 return iw;
3541 }
3542 return width;
3543 } else {
3544 if (iw > naturalWidth) {
3545
3546
3547 return iw;
3548 } else {
3549 return naturalWidth;
3550 }
3551 }
3552 }
3553
3554 public void setExpandRatio(float floatAttribute) {
3555 if (floatAttribute != expandRatio) {
3556 triggerLazyColumnAdjustment(false);
3557 }
3558 expandRatio = floatAttribute;
3559 }
3560
3561 public float getExpandRatio() {
3562 return expandRatio;
3563 }
3564
3565 public boolean isSorted() {
3566 return sorted;
3567 }
3568 }
3569
3570
3571
3572
3573
3574
3575 public class RowHeadersHeaderCell extends HeaderCell {
3576
3577 RowHeadersHeaderCell() {
3578 super(ROW_HEADER_COLUMN_KEY, "");
3579 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
3580 }
3581
3582 @Override
3583 protected void updateStyleNames(String primaryStyleName) {
3584 super.updateStyleNames(primaryStyleName);
3585 setStyleName(primaryStyleName + "-header-cell-rowheader");
3586 }
3587
3588 @Override
3589 protected void handleCaptionEvent(Event event) {
3590
3591
3592 }
3593 }
3594
3595 public class TableHead extends Panel implements ActionOwner {
3596
3597 private static final int WRAPPER_WIDTH = 900000;
3598
3599 List<Widget> visibleCells = new ArrayList<Widget>();
3600
3601 Map<String, HeaderCell> availableCells = new HashMap<String, HeaderCell>();
3602
3603 protected Element div = DOM.createDiv();
3604 Element hTableWrapper = DOM.createDiv();
3605 Element hTableContainer = DOM.createDiv();
3606 Element table = DOM.createTable();
3607 Element headerTableBody = DOM.createTBody();
3608 Element tr = DOM.createTR();
3609
3610 private final Element columnSelector = DOM.createDiv();
3611
3612 private int focusedSlot = -1;
3613
3614 public TableHead() {
3615 if (BrowserInfo.get().isIE()) {
3616 table.setPropertyInt("cellSpacing", 0);
3617 }
3618
3619 hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN);
3620 columnSelector.getStyle().setDisplay(Display.NONE);
3621
3622 DOM.appendChild(table, headerTableBody);
3623 DOM.appendChild(headerTableBody, tr);
3624 DOM.appendChild(hTableContainer, table);
3625 DOM.appendChild(hTableWrapper, hTableContainer);
3626 DOM.appendChild(div, hTableWrapper);
3627 DOM.appendChild(div, columnSelector);
3628 setElement(div);
3629
3630 DOM.sinkEvents(columnSelector, Event.ONCLICK);
3631
3632 availableCells.put(ROW_HEADER_COLUMN_KEY,
3633 new RowHeadersHeaderCell());
3634 }
3635
3636 protected void updateStyleNames(String primaryStyleName) {
3637 hTableWrapper.setClassName(primaryStyleName + "-header");
3638 columnSelector.setClassName(primaryStyleName + "-column-selector");
3639 setStyleName(primaryStyleName + "-header-wrap");
3640 for (HeaderCell c : availableCells.values()) {
3641 c.updateStyleNames(primaryStyleName);
3642 }
3643 }
3644
3645 public void resizeCaptionContainer(HeaderCell cell) {
3646 HeaderCell lastcell = getHeaderCell(visibleCells.size() - 1);
3647 int columnSelectorOffset = columnSelector.getOffsetWidth();
3648
3649 if (cell == lastcell && columnSelectorOffset > 0
3650 && !hasVerticalScrollbar()) {
3651
3652
3653 int columnTotalWidth = 0;
3654 for (Widget w : visibleCells) {
3655 int cellExtraWidth = w.getOffsetWidth();
3656 if (scrollBody != null
3657 && visibleCells
3658 .indexOf(w) == getHierarchyColumnIndex()
3659 && cellExtraWidth < scrollBody.getMaxIndent()) {
3660
3661
3662 columnTotalWidth += scrollBody.getMaxIndent();
3663 } else {
3664 columnTotalWidth += cellExtraWidth;
3665 }
3666 }
3667
3668 int divOffset = div.getOffsetWidth();
3669 if (columnTotalWidth >= divOffset - columnSelectorOffset) {
3670
3671
3672
3673
3674
3675 int offset = 0;
3676 int diff = divOffset - columnTotalWidth;
3677 if (diff < columnSelectorOffset && diff > 0) {
3678
3679
3680
3681
3682 offset = columnSelectorOffset - diff;
3683 } else {
3684
3685 offset = columnSelectorOffset;
3686 }
3687 lastcell.resizeCaptionContainer(offset);
3688 } else {
3689 cell.resizeCaptionContainer(0);
3690 }
3691 } else {
3692 cell.resizeCaptionContainer(0);
3693 }
3694 }
3695
3696 @Override
3697 public void clear() {
3698 for (String cid : availableCells.keySet()) {
3699 removeCell(cid);
3700 }
3701 availableCells.clear();
3702 availableCells.put(ROW_HEADER_COLUMN_KEY,
3703 new RowHeadersHeaderCell());
3704 }
3705
3706 public void updateCellsFromUIDL(UIDL uidl) {
3707 HashSet<String> updated = new HashSet<String>();
3708 boolean refreshContentWidths = initializedAndAttached
3709 && hadScrollBars != willHaveScrollbars();
3710 for (Object child : uidl) {
3711 final UIDL col = (UIDL) child;
3712 final String cid = col.getStringAttribute("cid");
3713 updated.add(cid);
3714
3715 String caption = buildCaptionHtmlSnippet(col);
3716 HeaderCell c = getHeaderCell(cid);
3717 if (c == null) {
3718 c = createHeaderCell(cid, caption);
3719 availableCells.put(cid, c);
3720 if (initializedAndAttached) {
3721
3722 initializedAndAttached = false;
3723 initialContentReceived = false;
3724 isNewBody = true;
3725 }
3726 } else {
3727 c.setText(caption);
3728
3729 }
3730
3731 c.setSorted(false);
3732 if (col.hasAttribute("sortable")) {
3733 c.setSortable(true);
3734 } else {
3735 c.setSortable(false);
3736 }
3737
3738
3739
3740 if (col.hasAttribute("align")) {
3741 c.setAlign(col.getStringAttribute("align").charAt(0));
3742 } else {
3743 c.setAlign(ALIGN_LEFT);
3744
3745 }
3746 if (col.hasAttribute("width") && !c.isResizing) {
3747
3748
3749 int width = col.getIntAttribute("width");
3750 int widthWithoutAddedIndent = width;
3751
3752
3753 int minWidth = c.getMinWidth(true, false);
3754 if (width < minWidth) {
3755 width = minWidth;
3756 }
3757 if (scrollBody != null && width != c.getWidthWithIndent()) {
3758
3759
3760
3761 final int newWidth = width;
3762 Scheduler.get().scheduleFinally(new ScheduledCommand() {
3763
3764 @Override
3765 public void execute() {
3766 final int colIx = getColIndexByKey(cid);
3767 setColWidth(colIx, newWidth, true);
3768 }
3769 });
3770 refreshContentWidths = true;
3771 } else {
3772
3773 minWidth = c.getMinWidth(false, false);
3774 if (widthWithoutAddedIndent < minWidth) {
3775 widthWithoutAddedIndent = minWidth;
3776 }
3777
3778 c.setWidth(widthWithoutAddedIndent, true);
3779 }
3780 } else if (col.hasAttribute("er")) {
3781 c.setExpandRatio(col.getFloatAttribute("er"));
3782 c.setUndefinedWidthFlagOnly();
3783 } else if (recalcWidths) {
3784 c.setUndefinedWidth();
3785
3786 } else {
3787 boolean hadExpandRatio = c.getExpandRatio() > 0;
3788 boolean hadDefinedWidth = c.isDefinedWidth();
3789 if (hadExpandRatio || hadDefinedWidth) {
3790
3791
3792
3793 c.setUndefinedWidth();
3794 c.setExpandRatio(0);
3795 refreshContentWidths = true;
3796 }
3797 }
3798
3799 if (col.hasAttribute("collapsed")) {
3800
3801
3802 if (c.isAttached()) {
3803 c.removeFromParent();
3804 headerChangedDuringUpdate = true;
3805 }
3806 }
3807 }
3808
3809 if (refreshContentWidths) {
3810
3811 Scheduler.get().scheduleFinally(new ScheduledCommand() {
3812
3813 @Override
3814 public void execute() {
3815 triggerLazyColumnAdjustment(true);
3816 }
3817 });
3818 }
3819
3820
3821 for (Iterator<String> cit = availableCells.keySet().iterator(); cit
3822 .hasNext();) {
3823 String cid = cit.next();
3824 if (!updated.contains(cid)) {
3825 removeCell(cid);
3826 cit.remove();
3827
3828
3829 initializedAndAttached = false;
3830 initialContentReceived = false;
3831 isNewBody = true;
3832 }
3833 }
3834 }
3835
3836 public void enableColumn(String cid, int index) {
3837 final HeaderCell c = getHeaderCell(cid);
3838 if (!c.isEnabled() || getHeaderCell(index) != c) {
3839 setHeaderCell(index, c);
3840 if (initializedAndAttached) {
3841 headerChangedDuringUpdate = true;
3842 }
3843 }
3844 }
3845
3846 public int getVisibleCellCount() {
3847 return visibleCells.size();
3848 }
3849
3850 public void setHorizontalScrollPosition(int scrollLeft) {
3851 hTableWrapper.setScrollLeft(scrollLeft);
3852 }
3853
3854 public void setColumnCollapsingAllowed(boolean cc) {
3855 if (cc) {
3856 columnSelector.getStyle().setDisplay(Display.BLOCK);
3857 } else {
3858 columnSelector.getStyle().setDisplay(Display.NONE);
3859 }
3860 }
3861
3862 public void disableBrowserIntelligence() {
3863 hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
3864 }
3865
3866 public void enableBrowserIntelligence() {
3867 hTableContainer.getStyle().clearWidth();
3868 }
3869
3870 public void setHeaderCell(int index, HeaderCell cell) {
3871 if (cell.isEnabled()) {
3872
3873 DOM.removeChild(tr, cell.getElement());
3874 orphan(cell);
3875 visibleCells.remove(cell);
3876 }
3877 if (index < visibleCells.size()) {
3878
3879 DOM.insertChild(tr, cell.getElement(), index);
3880 adopt(cell);
3881 visibleCells.add(index, cell);
3882 } else if (index == visibleCells.size()) {
3883
3884 DOM.appendChild(tr, cell.getElement());
3885 adopt(cell);
3886 visibleCells.add(cell);
3887 } else {
3888 throw new RuntimeException(
3889 "Header cells must be appended in order");
3890 }
3891 }
3892
3893 public HeaderCell getHeaderCell(int index) {
3894 if (index >= 0 && index < visibleCells.size()) {
3895 return (HeaderCell) visibleCells.get(index);
3896 } else {
3897 return null;
3898 }
3899 }
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910 public HeaderCell getHeaderCell(String cid) {
3911 return availableCells.get(cid);
3912 }
3913
3914 public void moveCell(int oldIndex, int newIndex) {
3915 final HeaderCell hCell = getHeaderCell(oldIndex);
3916 final Element cell = hCell.getElement();
3917
3918 visibleCells.remove(oldIndex);
3919 DOM.removeChild(tr, cell);
3920
3921 DOM.insertChild(tr, cell, newIndex);
3922 visibleCells.add(newIndex, hCell);
3923 }
3924
3925 @Override
3926 public Iterator<Widget> iterator() {
3927 return visibleCells.iterator();
3928 }
3929
3930 @Override
3931 public boolean remove(Widget w) {
3932 if (visibleCells.contains(w)) {
3933 visibleCells.remove(w);
3934 orphan(w);
3935 DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
3936 return true;
3937 }
3938 return false;
3939 }
3940
3941 public void removeCell(String colKey) {
3942 final HeaderCell c = getHeaderCell(colKey);
3943 remove(c);
3944 }
3945
3946 private void focusSlot(int index) {
3947 removeSlotFocus();
3948 if (index > 0) {
3949 Element child = tr.getChild(index - 1).getFirstChild().cast();
3950 child.setClassName(
3951 VScrollTablePatched.this.getStylePrimaryName() + "-resizer");
3952 child.addClassName(VScrollTablePatched.this.getStylePrimaryName()
3953 + "-focus-slot-right");
3954 } else {
3955 Element child = tr.getChild(index).getFirstChild().cast();
3956 child.setClassName(
3957 VScrollTablePatched.this.getStylePrimaryName() + "-resizer");
3958 child.addClassName(VScrollTablePatched.this.getStylePrimaryName()
3959 + "-focus-slot-left");
3960 }
3961 focusedSlot = index;
3962 }
3963
3964 private void removeSlotFocus() {
3965 if (focusedSlot < 0) {
3966 return;
3967 }
3968 if (focusedSlot == 0) {
3969 Element child = tr.getChild(focusedSlot).getFirstChild().cast();
3970 child.setClassName(
3971 VScrollTablePatched.this.getStylePrimaryName() + "-resizer");
3972 } else if (focusedSlot > 0) {
3973 Element child = tr.getChild(focusedSlot - 1).getFirstChild()
3974 .cast();
3975 child.setClassName(
3976 VScrollTablePatched.this.getStylePrimaryName() + "-resizer");
3977 }
3978 focusedSlot = -1;
3979 }
3980
3981 @Override
3982 public void onBrowserEvent(Event event) {
3983 if (enabled) {
3984 if (event.getEventTarget().cast() == columnSelector) {
3985 WidgetUtil.TextRectangle clientRect = WidgetUtil
3986 .getBoundingClientRect(columnSelector);
3987 final int left = (int) clientRect.getLeft();
3988 final int top = (int) (clientRect.getTop()
3989 + DOM.getElementPropertyInt(columnSelector,
3990 "offsetHeight"));
3991 client.getContextMenu().showAt(this, left, top);
3992 }
3993 }
3994 }
3995
3996 @Override
3997 protected void onDetach() {
3998 super.onDetach();
3999 if (client != null) {
4000 client.getContextMenu().ensureHidden(this);
4001 }
4002 }
4003
4004 class VisibleColumnAction extends Action {
4005
4006 String colKey;
4007 private boolean collapsed;
4008 private boolean noncollapsible = false;
4009 private VScrollTableRow currentlyFocusedRow;
4010
4011 public VisibleColumnAction(String colKey) {
4012 super(VScrollTablePatched.TableHead.this);
4013 this.colKey = colKey;
4014 caption = tHead.getHeaderCell(colKey).getCaption();
4015 currentlyFocusedRow = focusedRow;
4016 }
4017
4018 @Override
4019 public void execute() {
4020 if (noncollapsible) {
4021 return;
4022 }
4023 client.getContextMenu().hide();
4024
4025 if (collapsedColumns.contains(colKey)) {
4026 collapsedColumns.remove(colKey);
4027 } else {
4028 tHead.removeCell(colKey);
4029 collapsedColumns.add(colKey);
4030 triggerLazyColumnAdjustment(true);
4031 }
4032
4033
4034 client.updateVariable(
4035 paintableId, "collapsedcolumns", collapsedColumns
4036 .toArray(new String[collapsedColumns.size()]),
4037 false);
4038
4039 rowRequestHandler.refreshContent();
4040 lazyRevertFocusToRow(currentlyFocusedRow);
4041 }
4042
4043 public void setCollapsed(boolean b) {
4044 collapsed = b;
4045 }
4046
4047 public void setNoncollapsible(boolean b) {
4048 noncollapsible = b;
4049 }
4050
4051
4052
4053
4054
4055 @Override
4056 public String getHTML() {
4057 final StringBuilder buf = new StringBuilder();
4058 buf.append("<span class=\"");
4059 if (collapsed) {
4060 buf.append("v-off");
4061 } else {
4062 buf.append("v-on");
4063 }
4064 if (noncollapsible) {
4065 buf.append(" v-disabled");
4066 }
4067 buf.append("\">");
4068
4069 buf.append(super.getHTML());
4070 buf.append("</span>");
4071
4072 return buf.toString();
4073 }
4074
4075 }
4076
4077
4078
4079
4080
4081 @Override
4082 public Action[] getActions() {
4083 Object[] cols;
4084 if (columnReordering && columnOrder != null) {
4085 cols = columnOrder;
4086 } else {
4087
4088
4089 cols = visibleColOrder;
4090 cols = new Object[visibleColOrder.length
4091 + collapsedColumns.size()];
4092 int i;
4093 for (i = 0; i < visibleColOrder.length; i++) {
4094 cols[i] = visibleColOrder[i];
4095 }
4096 for (final String column : collapsedColumns) {
4097 cols[i++] = column;
4098 }
4099 }
4100 List<Action> actions = new ArrayList<Action>(cols.length);
4101
4102 for (Object col : cols) {
4103 final String cid = (String) col;
4104 boolean noncollapsible = noncollapsibleColumns.contains(cid);
4105
4106 if (noncollapsible
4107 && collapsibleMenuContent == CollapseMenuContent.COLLAPSIBLE_COLUMNS) {
4108 continue;
4109 }
4110
4111 final HeaderCell c = getHeaderCell(cid);
4112 final VisibleColumnAction a = new VisibleColumnAction(
4113 c.getColKey());
4114 a.setCaption(c.getCaption());
4115 if (!c.isEnabled()) {
4116 a.setCollapsed(true);
4117 }
4118 if (noncollapsible) {
4119 a.setNoncollapsible(true);
4120 }
4121 actions.add(a);
4122 }
4123 return actions.toArray(new Action[actions.size()]);
4124 }
4125
4126 @Override
4127 public ApplicationConnection getClient() {
4128 return client;
4129 }
4130
4131 @Override
4132 public String getPaintableId() {
4133 return paintableId;
4134 }
4135
4136
4137
4138
4139 public char[] getColumnAlignments() {
4140 final char[] aligns = new char[visibleCells.size()];
4141 int colIndex = 0;
4142 for (Widget w : visibleCells) {
4143 aligns[colIndex++] = ((HeaderCell) w).getAlign();
4144 }
4145 return aligns;
4146 }
4147
4148
4149
4150
4151
4152 public void disableAutoColumnWidthCalculation(HeaderCell source) {
4153 for (HeaderCell cell : availableCells.values()) {
4154 cell.disableAutoWidthCalculation();
4155 }
4156
4157
4158 List<HeaderCell> columns = new ArrayList<HeaderCell>(
4159 availableCells.values());
4160 columns.remove(source);
4161 sendColumnWidthUpdates(columns);
4162 forceRealignColumnHeaders();
4163 }
4164 }
4165
4166
4167
4168
4169 public class FooterCell extends Widget {
4170 private final Element td = DOM.createTD();
4171 private final Element captionContainer = DOM.createDiv();
4172 private char align = ALIGN_LEFT;
4173 private int width = -1;
4174 private float expandRatio = 0;
4175 private final String cid;
4176 boolean definedWidth = false;
4177 private int naturalWidth = -1;
4178
4179 public FooterCell(String colId, String headerText) {
4180 cid = colId;
4181
4182 setText(headerText);
4183
4184
4185 captionContainer.getStyle().setOverflow(Overflow.VISIBLE);
4186
4187 DOM.sinkEvents(captionContainer, Event.MOUSEEVENTS);
4188
4189 DOM.appendChild(td, captionContainer);
4190
4191 DOM.sinkEvents(td,
4192 Event.MOUSEEVENTS | Event.ONDBLCLICK | Event.ONCONTEXTMENU);
4193
4194 setElement(td);
4195
4196 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
4197 }
4198
4199 protected void updateStyleNames(String primaryStyleName) {
4200 captionContainer
4201 .setClassName(primaryStyleName + "-footer-container");
4202 }
4203
4204
4205
4206
4207
4208
4209
4210 public void setText(String footerText) {
4211 if (footerText == null || footerText.equals("")) {
4212 footerText = " ";
4213 }
4214
4215 DOM.setInnerHTML(captionContainer, footerText);
4216 }
4217
4218
4219
4220
4221
4222
4223
4224
4225 public void setAlign(char c) {
4226 if (align != c) {
4227 switch (c) {
4228 case ALIGN_CENTER:
4229 captionContainer.getStyle().setTextAlign(TextAlign.CENTER);
4230 break;
4231 case ALIGN_RIGHT:
4232 captionContainer.getStyle().setTextAlign(TextAlign.RIGHT);
4233 break;
4234 default:
4235 captionContainer.getStyle().setTextAlign(TextAlign.LEFT);
4236 break;
4237 }
4238 }
4239 align = c;
4240 }
4241
4242
4243
4244
4245
4246
4247 public char getAlign() {
4248 return align;
4249 }
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261 public void setWidth(int w, boolean ensureDefinedWidth) {
4262
4263 if (ensureDefinedWidth) {
4264 definedWidth = true;
4265
4266 expandRatio = 0;
4267 }
4268 if (width == w) {
4269 return;
4270 }
4271 if (width == -1) {
4272
4273 captionContainer.getStyle().clearOverflow();
4274 }
4275 width = w;
4276 if (w == -1) {
4277 captionContainer.getStyle().clearWidth();
4278 setWidth("");
4279 } else {
4280
4281
4282
4283
4284 final int borderWidths = 1;
4285
4286
4287 captionContainer.getStyle().setPropertyPx("width",
4288 Math.max(w - borderWidths, 0));
4289
4290
4291
4292
4293
4294
4295 if (scrollBody != null) {
4296 int maxIndent = scrollBody.getMaxIndent();
4297 if (w < maxIndent && tFoot.visibleCells
4298 .indexOf(this) == getHierarchyColumnIndex()) {
4299
4300 w = maxIndent;
4301 }
4302 int tdWidth = w + scrollBody.getCellExtraWidth()
4303 - borderWidths;
4304 setWidth(Math.max(tdWidth, 0) + "px");
4305 } else {
4306 Scheduler.get().scheduleDeferred(new Command() {
4307
4308 @Override
4309 public void execute() {
4310 int tdWidth = width;
4311 int maxIndent = scrollBody.getMaxIndent();
4312 if (tdWidth < maxIndent
4313 && tFoot.visibleCells.indexOf(
4314 this) == getHierarchyColumnIndex()) {
4315
4316 tdWidth = maxIndent;
4317 }
4318 tdWidth += scrollBody.getCellExtraWidth()
4319 - borderWidths;
4320 setWidth(Math.max(tdWidth, 0) + "px");
4321 }
4322 });
4323 }
4324 }
4325 }
4326
4327
4328
4329
4330 public void setUndefinedWidth() {
4331 definedWidth = false;
4332 setWidth(-1, false);
4333 }
4334
4335
4336
4337
4338
4339
4340
4341 public boolean isDefinedWidth() {
4342 return definedWidth && width >= 0;
4343 }
4344
4345
4346
4347
4348
4349
4350 public int getWidth() {
4351 return width;
4352 }
4353
4354
4355
4356
4357
4358
4359
4360 public void setExpandRatio(float floatAttribute) {
4361 expandRatio = floatAttribute;
4362 }
4363
4364
4365
4366
4367
4368
4369 public float getExpandRatio() {
4370 return expandRatio;
4371 }
4372
4373
4374
4375
4376
4377
4378 public boolean isEnabled() {
4379 return getParent() != null;
4380 }
4381
4382
4383
4384
4385
4386 @Override
4387 public void onBrowserEvent(Event event) {
4388 if (enabled && event != null) {
4389 handleCaptionEvent(event);
4390
4391 if (DOM.eventGetType(event) == Event.ONMOUSEUP) {
4392 scrollBodyPanel.setFocus(true);
4393 }
4394 boolean stopPropagation = true;
4395 if (event.getTypeInt() == Event.ONCONTEXTMENU
4396 && !client.hasEventListeners(VScrollTablePatched.this,
4397 TableConstants.FOOTER_CLICK_EVENT_ID)) {
4398
4399
4400 stopPropagation = false;
4401 }
4402 if (stopPropagation) {
4403 event.stopPropagation();
4404 event.preventDefault();
4405 }
4406 }
4407 }
4408
4409
4410
4411
4412
4413
4414
4415 protected void handleCaptionEvent(Event event) {
4416 if (event.getTypeInt() == Event.ONMOUSEUP
4417 || event.getTypeInt() == Event.ONDBLCLICK) {
4418 fireFooterClickedEvent(event);
4419 }
4420 }
4421
4422
4423
4424
4425
4426
4427
4428
4429 private void fireFooterClickedEvent(Event event) {
4430 if (client.hasEventListeners(VScrollTablePatched.this,
4431 TableConstants.FOOTER_CLICK_EVENT_ID)) {
4432 MouseEventDetails details = MouseEventDetailsBuilder
4433 .buildMouseEventDetails(event);
4434 client.updateVariable(paintableId, "footerClickEvent",
4435 details.toString(), false);
4436 client.updateVariable(paintableId, "footerClickCID", cid, true);
4437 }
4438 }
4439
4440
4441
4442
4443
4444
4445 public String getColKey() {
4446 return cid;
4447 }
4448
4449
4450
4451
4452
4453
4454
4455 protected void saveNaturalColumnWidthIfNotSaved(int columnIndex) {
4456 if (naturalWidth < 0) {
4457
4458
4459
4460 final int hw = ((Element) getElement().getLastChild())
4461 .getOffsetWidth() + getHeaderPadding();
4462 if (columnIndex < 0) {
4463 columnIndex = 0;
4464 for (Widget widget : tFoot) {
4465 if (widget == this) {
4466 break;
4467 }
4468 columnIndex++;
4469 }
4470 }
4471 final int cw = scrollBody.getColWidth(columnIndex);
4472 naturalWidth = (hw > cw ? hw : cw);
4473 }
4474 }
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486 public int getNaturalColumnWidth(int columnIndex) {
4487 final int iw = columnIndex == getHierarchyColumnIndex()
4488 ? scrollBody.getMaxIndent()
4489 : 0;
4490 saveNaturalColumnWidthIfNotSaved(columnIndex);
4491 if (isDefinedWidth()) {
4492 if (iw > width) {
4493 return iw;
4494 }
4495 return width;
4496 } else {
4497 if (iw > naturalWidth) {
4498 return iw;
4499 } else {
4500 return naturalWidth;
4501 }
4502 }
4503 }
4504
4505 public void setNaturalMinimumColumnWidth(int w) {
4506 naturalWidth = w;
4507 }
4508 }
4509
4510
4511
4512
4513
4514
4515 public class RowHeadersFooterCell extends FooterCell {
4516
4517 RowHeadersFooterCell() {
4518 super(ROW_HEADER_COLUMN_KEY, "");
4519 }
4520
4521 @Override
4522 protected void handleCaptionEvent(Event event) {
4523
4524
4525 }
4526 }
4527
4528
4529
4530
4531 public class TableFooter extends Panel {
4532
4533 private static final int WRAPPER_WIDTH = 900000;
4534
4535 List<Widget> visibleCells = new ArrayList<Widget>();
4536 Map<String, FooterCell> availableCells = new HashMap<String, FooterCell>();
4537
4538 Element div = DOM.createDiv();
4539 Element hTableWrapper = DOM.createDiv();
4540 Element hTableContainer = DOM.createDiv();
4541 Element table = DOM.createTable();
4542 Element headerTableBody = DOM.createTBody();
4543 Element tr = DOM.createTR();
4544
4545 public TableFooter() {
4546
4547 hTableWrapper.getStyle().setOverflow(Overflow.HIDDEN);
4548
4549 DOM.appendChild(table, headerTableBody);
4550 DOM.appendChild(headerTableBody, tr);
4551 DOM.appendChild(hTableContainer, table);
4552 DOM.appendChild(hTableWrapper, hTableContainer);
4553 DOM.appendChild(div, hTableWrapper);
4554 setElement(div);
4555
4556 availableCells.put(ROW_HEADER_COLUMN_KEY,
4557 new RowHeadersFooterCell());
4558
4559 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
4560 }
4561
4562 protected void updateStyleNames(String primaryStyleName) {
4563 hTableWrapper.setClassName(primaryStyleName + "-footer");
4564 setStyleName(primaryStyleName + "-footer-wrap");
4565 for (FooterCell c : availableCells.values()) {
4566 c.updateStyleNames(primaryStyleName);
4567 }
4568 }
4569
4570 @Override
4571 public void clear() {
4572 for (String cid : availableCells.keySet()) {
4573 removeCell(cid);
4574 }
4575 availableCells.clear();
4576 availableCells.put(ROW_HEADER_COLUMN_KEY,
4577 new RowHeadersFooterCell());
4578 }
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588 @Override
4589 public boolean remove(Widget w) {
4590 if (visibleCells.contains(w)) {
4591 visibleCells.remove(w);
4592 orphan(w);
4593 DOM.removeChild(DOM.getParent(w.getElement()), w.getElement());
4594 return true;
4595 }
4596 return false;
4597 }
4598
4599
4600
4601
4602
4603
4604
4605 @Override
4606 public Iterator<Widget> iterator() {
4607 return visibleCells.iterator();
4608 }
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618 public FooterCell getFooterCell(String cid) {
4619 return availableCells.get(cid);
4620 }
4621
4622
4623
4624
4625
4626
4627
4628
4629 public FooterCell getFooterCell(int index) {
4630 if (index < visibleCells.size()) {
4631 return (FooterCell) visibleCells.get(index);
4632 } else {
4633 return null;
4634 }
4635 }
4636
4637
4638
4639
4640
4641
4642
4643 public void updateCellsFromUIDL(UIDL uidl) {
4644 HashSet<String> updated = new HashSet<String>();
4645 for (Object child : uidl) {
4646 final UIDL col = (UIDL) child;
4647 final String cid = col.getStringAttribute("cid");
4648 updated.add(cid);
4649
4650 String caption = col.hasAttribute("fcaption")
4651 ? col.getStringAttribute("fcaption")
4652 : "";
4653 FooterCell c = getFooterCell(cid);
4654 if (c == null) {
4655 c = new FooterCell(cid, caption);
4656 availableCells.put(cid, c);
4657 if (initializedAndAttached) {
4658
4659 initializedAndAttached = false;
4660 initialContentReceived = false;
4661 isNewBody = true;
4662 }
4663 } else {
4664 c.setText(caption);
4665 }
4666
4667 if (col.hasAttribute("align")) {
4668 c.setAlign(col.getStringAttribute("align").charAt(0));
4669 } else {
4670 c.setAlign(ALIGN_LEFT);
4671
4672 }
4673 if (col.hasAttribute("width")) {
4674 if (scrollBody == null || isNewBody) {
4675
4676
4677
4678 final int width = col.getIntAttribute("width");
4679 c.setWidth(width, true);
4680 }
4681 } else if (recalcWidths) {
4682 c.setUndefinedWidth();
4683 }
4684 if (col.hasAttribute("er")) {
4685 c.setExpandRatio(col.getFloatAttribute("er"));
4686 }
4687 if (col.hasAttribute("collapsed")) {
4688
4689
4690 if (c.isAttached()) {
4691 c.removeFromParent();
4692 headerChangedDuringUpdate = true;
4693 }
4694 }
4695 }
4696
4697
4698 for (Iterator<String> cit = availableCells.keySet().iterator(); cit
4699 .hasNext();) {
4700 String cid = cit.next();
4701 if (!updated.contains(cid)) {
4702 removeCell(cid);
4703 cit.remove();
4704 }
4705 }
4706 }
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716 public void setFooterCell(int index, FooterCell cell) {
4717 if (cell.isEnabled()) {
4718
4719 DOM.removeChild(tr, cell.getElement());
4720 orphan(cell);
4721 visibleCells.remove(cell);
4722 }
4723 if (index < visibleCells.size()) {
4724
4725 DOM.insertChild(tr, cell.getElement(), index);
4726 adopt(cell);
4727 visibleCells.add(index, cell);
4728 } else if (index == visibleCells.size()) {
4729
4730 DOM.appendChild(tr, cell.getElement());
4731 adopt(cell);
4732 visibleCells.add(cell);
4733 } else {
4734 throw new RuntimeException(
4735 "Header cells must be appended in order");
4736 }
4737 }
4738
4739
4740
4741
4742
4743
4744
4745 public void removeCell(String colKey) {
4746 final FooterCell c = getFooterCell(colKey);
4747 remove(c);
4748 }
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758 public void enableColumn(String cid, int index) {
4759 final FooterCell c = getFooterCell(cid);
4760 if (!c.isEnabled() || getFooterCell(index) != c) {
4761 setFooterCell(index, c);
4762 if (initializedAndAttached) {
4763 headerChangedDuringUpdate = true;
4764 }
4765 }
4766 }
4767
4768
4769
4770
4771 public void disableBrowserIntelligence() {
4772 hTableContainer.getStyle().setWidth(WRAPPER_WIDTH, Unit.PX);
4773 }
4774
4775
4776
4777
4778 public void enableBrowserIntelligence() {
4779 hTableContainer.getStyle().clearWidth();
4780 }
4781
4782
4783
4784
4785
4786
4787
4788
4789 public void setHorizontalScrollPosition(int scrollLeft) {
4790 hTableWrapper.setScrollLeft(scrollLeft);
4791 }
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801 public void moveCell(int oldIndex, int newIndex) {
4802 final FooterCell hCell = getFooterCell(oldIndex);
4803 final Element cell = hCell.getElement();
4804
4805 visibleCells.remove(oldIndex);
4806 DOM.removeChild(tr, cell);
4807
4808 DOM.insertChild(tr, cell, newIndex);
4809 visibleCells.add(newIndex, hCell);
4810 }
4811 }
4812
4813
4814
4815
4816
4817
4818
4819 public class VScrollTableBody extends Panel {
4820 protected VScrollTableRow createScrollTableRow(UIDL uidl, char[] aligns) {
4821 return new VScrollTableRow(uidl, aligns);
4822 }
4823
4824 protected VScrollTableRow createScrollTableRow() {
4825 return new VScrollTableRow();
4826 }
4827
4828 public static final int DEFAULT_ROW_HEIGHT = 24;
4829
4830 private double rowHeight = -1;
4831
4832 public final LinkedList<Widget> renderedRows = new LinkedList<Widget>();
4833
4834
4835
4836
4837
4838
4839 private boolean tBodyMeasurementsDone = false;
4840
4841 Element preSpacer = DOM.createDiv();
4842 Element postSpacer = DOM.createDiv();
4843
4844 Element container = DOM.createDiv();
4845
4846 TableSectionElement tBodyElement = Document.get().createTBodyElement();
4847 Element table = DOM.createTable();
4848
4849 private int firstRendered;
4850 private int lastRendered;
4851
4852 private char[] aligns;
4853
4854 protected VScrollTableBody() {
4855 constructDOM();
4856 setElement(container);
4857 }
4858
4859 public void setLastRendered(int lastRendered) {
4860 if (totalRows >= 0 && lastRendered > totalRows) {
4861 getLogger().info("setLastRendered: " + this.lastRendered + " -> "
4862 + lastRendered);
4863 this.lastRendered = totalRows - 1;
4864 } else {
4865 this.lastRendered = lastRendered;
4866 }
4867 }
4868
4869 public int getLastRendered() {
4870
4871 return lastRendered;
4872 }
4873
4874 public int getFirstRendered() {
4875
4876 return firstRendered;
4877 }
4878
4879 public VScrollTableRow getRowByRowIndex(int indexInTable) {
4880 int internalIndex = indexInTable - firstRendered;
4881 if (internalIndex >= 0 && internalIndex < renderedRows.size()) {
4882 return (VScrollTableRow) renderedRows.get(internalIndex);
4883 } else {
4884 return null;
4885 }
4886 }
4887
4888
4889
4890
4891 public int getRequiredHeight() {
4892 return preSpacer.getOffsetHeight() + postSpacer.getOffsetHeight()
4893 + WidgetUtil.getRequiredHeight(table);
4894 }
4895
4896 private void constructDOM() {
4897 if (BrowserInfo.get().isIE()) {
4898 table.setPropertyInt("cellSpacing", 0);
4899 }
4900
4901 table.appendChild(tBodyElement);
4902 DOM.appendChild(container, preSpacer);
4903 DOM.appendChild(container, table);
4904 DOM.appendChild(container, postSpacer);
4905 if (BrowserInfo.get().requiresTouchScrollDelegate()) {
4906 NodeList<Node> childNodes = container.getChildNodes();
4907 for (int i = 0; i < childNodes.getLength(); i++) {
4908 Element item = (Element) childNodes.getItem(i);
4909 item.getStyle().setProperty("webkitTransform",
4910 "translate3d(0,0,0)");
4911 }
4912 }
4913 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
4914 }
4915
4916 protected void updateStyleNames(String primaryStyleName) {
4917 table.setClassName(primaryStyleName + "-table");
4918 preSpacer.setClassName(primaryStyleName + "-row-spacer");
4919 postSpacer.setClassName(primaryStyleName + "-row-spacer");
4920 for (Widget w : renderedRows) {
4921 VScrollTableRow row = (VScrollTableRow) w;
4922 row.updateStyleNames(primaryStyleName);
4923 }
4924 }
4925
4926 public int getAvailableWidth() {
4927 int availW = scrollBodyPanel.getOffsetWidth() - getBorderWidth();
4928 return availW;
4929 }
4930
4931 public void renderInitialRows(UIDL rowData, int firstIndex, int rows) {
4932 firstRendered = firstIndex;
4933 setLastRendered(firstIndex + rows - 1);
4934 aligns = tHead.getColumnAlignments();
4935 for (final Object child : rowData) {
4936 final VScrollTableRow row = createRow((UIDL) child, aligns);
4937 addRow(row);
4938 }
4939 if (isAttached()) {
4940 fixSpacers();
4941 }
4942 }
4943
4944 public void renderRows(UIDL rowData, int firstIndex, int rows) {
4945
4946 aligns = tHead.getColumnAlignments();
4947 final Iterator<?> it = rowData.iterator();
4948 if (firstIndex == lastRendered + 1) {
4949 while (it.hasNext()) {
4950 final VScrollTableRow row = prepareRow((UIDL) it.next());
4951 addRow(row);
4952 setLastRendered(lastRendered + 1);
4953 }
4954 fixSpacers();
4955 } else if (firstIndex + rows == firstRendered) {
4956 final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
4957 int i = rows;
4958
4959 while (it.hasNext()) {
4960 i--;
4961 rowArray[i] = prepareRow((UIDL) it.next());
4962 }
4963
4964 for (i = 0; i < rows; i++) {
4965 addRowBeforeFirstRendered(rowArray[i]);
4966 firstRendered--;
4967 }
4968 } else {
4969
4970
4971
4972
4973
4974
4975 boolean temp = postponeSanityCheckForLastRendered;
4976 postponeSanityCheckForLastRendered = true;
4977 while (lastRendered + 1 > firstRendered) {
4978 unlinkRow(false);
4979 }
4980 postponeSanityCheckForLastRendered = temp;
4981 VScrollTableRow row = prepareRow((UIDL) it.next());
4982 firstRendered = firstIndex;
4983 setLastRendered(firstIndex - 1);
4984 addRow(row);
4985 setLastRendered(lastRendered + 1);
4986 setContainerHeight();
4987 fixSpacers();
4988
4989 while (it.hasNext()) {
4990 addRow(prepareRow((UIDL) it.next()));
4991 setLastRendered(lastRendered + 1);
4992 }
4993
4994 fixSpacers();
4995 }
4996
4997
4998
4999 ensureCacheFilled();
5000 }
5001
5002
5003
5004
5005
5006
5007 protected void ensureCacheFilled() {
5008
5009
5010
5011
5012 if (isLazyScrollerActive()) {
5013 return;
5014 }
5015
5016 int reactFirstRow = (int) (firstRowInViewPort
5017 - pageLength * cacheReactRate);
5018 int reactLastRow = (int) (firstRowInViewPort + pageLength
5019 + pageLength * cacheReactRate);
5020 if (reactFirstRow < 0) {
5021 reactFirstRow = 0;
5022 }
5023 if (reactLastRow >= totalRows) {
5024 reactLastRow = totalRows - 1;
5025 }
5026 if (lastRendered < reactFirstRow || firstRendered > reactLastRow) {
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036 rowRequestHandler.triggerRowFetch(reactFirstRow,
5037 reactLastRow - reactFirstRow + 1, 1);
5038 } else if (lastRendered < reactLastRow) {
5039
5040 rowRequestHandler.triggerRowFetch(lastRendered + 1,
5041 reactLastRow - lastRendered, 1);
5042 } else if (firstRendered > reactFirstRow) {
5043
5044
5045
5046
5047
5048
5049
5050
5051 rowRequestHandler.triggerRowFetch(reactFirstRow,
5052 firstRendered - reactFirstRow, 1);
5053 }
5054 }
5055
5056
5057
5058
5059
5060
5061
5062
5063
5064
5065 protected List<VScrollTableRow> insertRows(UIDL rowData, int firstIndex,
5066 int rows) {
5067 aligns = tHead.getColumnAlignments();
5068 final Iterator<?> it = rowData.iterator();
5069 List<VScrollTableRow> insertedRows = new ArrayList<VScrollTableRow>();
5070
5071 if (firstIndex == lastRendered + 1) {
5072 while (it.hasNext()) {
5073 final VScrollTableRow row = prepareRow((UIDL) it.next());
5074 addRow(row);
5075 insertedRows.add(row);
5076 if (postponeSanityCheckForLastRendered) {
5077 lastRendered++;
5078 } else {
5079 setLastRendered(lastRendered + 1);
5080 }
5081 }
5082 fixSpacers();
5083 } else if (firstIndex + rows == firstRendered) {
5084 final VScrollTableRow[] rowArray = new VScrollTableRow[rows];
5085 int i = rows;
5086 while (it.hasNext()) {
5087 i--;
5088 rowArray[i] = prepareRow((UIDL) it.next());
5089 }
5090 for (i = 0; i < rows; i++) {
5091 addRowBeforeFirstRendered(rowArray[i]);
5092 insertedRows.add(rowArray[i]);
5093 firstRendered--;
5094 }
5095 } else {
5096
5097 int ix = firstIndex;
5098 while (it.hasNext()) {
5099 VScrollTableRow row = prepareRow((UIDL) it.next());
5100 insertRowAt(row, ix);
5101 insertedRows.add(row);
5102 if (postponeSanityCheckForLastRendered) {
5103 lastRendered++;
5104 } else {
5105 setLastRendered(lastRendered + 1);
5106 }
5107 ix++;
5108 }
5109 fixSpacers();
5110 }
5111 return insertedRows;
5112 }
5113
5114 protected List<VScrollTableRow> insertAndReindexRows(UIDL rowData,
5115 int firstIndex, int rows) {
5116 List<VScrollTableRow> inserted = insertRows(rowData, firstIndex,
5117 rows);
5118 int actualIxOfFirstRowAfterInserted = firstIndex + rows
5119 - firstRendered;
5120 for (int ix = actualIxOfFirstRowAfterInserted; ix < renderedRows
5121 .size(); ix++) {
5122 VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
5123 r.setIndex(r.getIndex() + rows);
5124 }
5125 setContainerHeight();
5126 return inserted;
5127 }
5128
5129 protected void insertRowsDeleteBelow(UIDL rowData, int firstIndex,
5130 int rows) {
5131 unlinkAllRowsStartingAt(firstIndex);
5132 insertRows(rowData, firstIndex, rows);
5133 setContainerHeight();
5134 }
5135
5136
5137
5138
5139
5140
5141
5142
5143
5144
5145 private VScrollTableRow prepareRow(UIDL uidl) {
5146 final VScrollTableRow row = createRow(uidl, aligns);
5147 row.initCellWidths();
5148 return row;
5149 }
5150
5151 protected VScrollTableRow createRow(UIDL uidl, char[] aligns2) {
5152 if (uidl.hasAttribute("gen_html")) {
5153
5154 return new VScrollTableGeneratedRow(uidl, aligns2);
5155 }
5156 return createScrollTableRow(uidl, aligns2);
5157 }
5158
5159 private void addRowBeforeFirstRendered(VScrollTableRow row) {
5160 row.setIndex(firstRendered - 1);
5161 if (row.isSelected()) {
5162 row.addStyleName("v-selected");
5163 }
5164 tBodyElement.insertBefore(row.getElement(),
5165 tBodyElement.getFirstChild());
5166 adopt(row);
5167 renderedRows.add(0, row);
5168 }
5169
5170 private void addRow(VScrollTableRow row) {
5171 row.setIndex(firstRendered + renderedRows.size());
5172 if (row.isSelected()) {
5173 row.addStyleName("v-selected");
5174 }
5175 tBodyElement.appendChild(row.getElement());
5176
5177
5178 renderedRows.add(row);
5179 adopt(row);
5180 }
5181
5182 private void insertRowAt(VScrollTableRow row, int index) {
5183 row.setIndex(index);
5184 if (row.isSelected()) {
5185 row.addStyleName("v-selected");
5186 }
5187 if (index > 0) {
5188 VScrollTableRow sibling = getRowByRowIndex(index - 1);
5189 tBodyElement.insertAfter(row.getElement(),
5190 sibling.getElement());
5191 } else {
5192 VScrollTableRow sibling = getRowByRowIndex(index);
5193 tBodyElement.insertBefore(row.getElement(),
5194 sibling.getElement());
5195 }
5196 adopt(row);
5197 int actualIx = index - firstRendered;
5198 renderedRows.add(actualIx, row);
5199 }
5200
5201 @Override
5202 public Iterator<Widget> iterator() {
5203 return renderedRows.iterator();
5204 }
5205
5206
5207
5208
5209 protected boolean unlinkRow(boolean fromBeginning) {
5210 if (lastRendered - firstRendered < 0) {
5211 return false;
5212 }
5213 int actualIx;
5214 if (fromBeginning) {
5215 actualIx = 0;
5216 firstRendered++;
5217 } else {
5218 actualIx = renderedRows.size() - 1;
5219 if (postponeSanityCheckForLastRendered) {
5220 --lastRendered;
5221 } else {
5222 setLastRendered(lastRendered - 1);
5223 }
5224 }
5225 if (actualIx >= 0) {
5226 unlinkRowAtActualIndex(actualIx);
5227 fixSpacers();
5228 return true;
5229 }
5230 return false;
5231 }
5232
5233 protected void unlinkRows(int firstIndex, int count) {
5234 if (count < 1) {
5235 return;
5236 }
5237 if (firstRendered > firstIndex
5238 && firstRendered < firstIndex + count) {
5239 count = count - (firstRendered - firstIndex);
5240 firstIndex = firstRendered;
5241 }
5242 int lastIndex = firstIndex + count - 1;
5243 if (lastRendered < lastIndex) {
5244 lastIndex = lastRendered;
5245 }
5246 for (int ix = lastIndex; ix >= firstIndex; ix--) {
5247 unlinkRowAtActualIndex(actualIndex(ix));
5248 if (postponeSanityCheckForLastRendered) {
5249
5250 lastRendered--;
5251 } else {
5252 setLastRendered(lastRendered - 1);
5253 }
5254 }
5255 fixSpacers();
5256 }
5257
5258 protected void unlinkAndReindexRows(int firstIndex, int count) {
5259 unlinkRows(firstIndex, count);
5260 int actualFirstIx = firstIndex - firstRendered;
5261 for (int ix = actualFirstIx; ix < renderedRows.size(); ix++) {
5262 VScrollTableRow r = (VScrollTableRow) renderedRows.get(ix);
5263 r.setIndex(r.getIndex() - count);
5264 }
5265 setContainerHeight();
5266 }
5267
5268 protected void unlinkAllRowsStartingAt(int index) {
5269 if (firstRendered > index) {
5270 index = firstRendered;
5271 }
5272 for (int ix = renderedRows.size() - 1; ix >= index; ix--) {
5273 unlinkRowAtActualIndex(actualIndex(ix));
5274 setLastRendered(lastRendered - 1);
5275 }
5276 fixSpacers();
5277 }
5278
5279 private int actualIndex(int index) {
5280 return index - firstRendered;
5281 }
5282
5283 private void unlinkRowAtActualIndex(int index) {
5284 final VScrollTableRow toBeRemoved = (VScrollTableRow) renderedRows
5285 .get(index);
5286 tBodyElement.removeChild(toBeRemoved.getElement());
5287 orphan(toBeRemoved);
5288 renderedRows.remove(index);
5289 }
5290
5291 @Override
5292 public boolean remove(Widget w) {
5293 throw new UnsupportedOperationException();
5294 }
5295
5296
5297
5298
5299
5300 private void setContainerHeight() {
5301 fixSpacers();
5302 container.getStyle().setHeight(measureRowHeightOffset(totalRows),
5303 Unit.PX);
5304 }
5305
5306 private void fixSpacers() {
5307 int prepx = measureRowHeightOffset(firstRendered);
5308 if (prepx < 0) {
5309 prepx = 0;
5310 }
5311 preSpacer.getStyle().setPropertyPx("height", prepx);
5312 int postpx;
5313 if (pageLength == 0 && totalRows == pageLength) {
5314
5315
5316
5317
5318
5319
5320 postpx = measureRowHeightOffset(1);
5321 } else {
5322 postpx = measureRowHeightOffset(totalRows - 1)
5323 - measureRowHeightOffset(lastRendered);
5324 }
5325
5326 if (postpx < 0) {
5327 postpx = 0;
5328 }
5329 postSpacer.getStyle().setPropertyPx("height", postpx);
5330 }
5331
5332 public double getRowHeight() {
5333 return getRowHeight(false);
5334 }
5335
5336 public double getRowHeight(boolean forceUpdate) {
5337 if (tBodyMeasurementsDone && !forceUpdate) {
5338 return rowHeight;
5339 } else {
5340 if (tBodyElement.getRows().getLength() > 0 && getTableHeight() > 0) {
5341 int tableHeight = getTableHeight();
5342 int rowCount = tBodyElement.getRows().getLength();
5343 rowHeight = tableHeight / (double) rowCount;
5344 } else {
5345
5346 if (!Double.isNaN(lastKnownRowHeight)) {
5347
5348 if (BrowserInfo.get().isIE()) {
5349
5350
5351
5352
5353
5354
5355
5356
5357 getTableHeight();
5358 }
5359 rowHeight = lastKnownRowHeight;
5360 } else if (isAttached()) {
5361
5362 VScrollTableRow scrollTableRow = createScrollTableRow();
5363 tBodyElement.appendChild(scrollTableRow.getElement());
5364 getRowHeight(forceUpdate);
5365 tBodyElement.removeChild(scrollTableRow.getElement());
5366 } else {
5367
5368 return DEFAULT_ROW_HEIGHT;
5369 }
5370 }
5371 lastKnownRowHeight = rowHeight;
5372 tBodyMeasurementsDone = true;
5373 return rowHeight;
5374 }
5375 }
5376
5377 public int getTableHeight() {
5378 return table.getOffsetHeight();
5379 }
5380
5381
5382
5383
5384
5385
5386
5387 public int getColWidth(int columnIndex) {
5388 if (tBodyMeasurementsDone) {
5389 if (renderedRows.isEmpty()) {
5390
5391 return 0;
5392 }
5393 for (Widget row : renderedRows) {
5394 if (!(row instanceof VScrollTableGeneratedRow)) {
5395 TableRowElement tr = row.getElement().cast();
5396
5397 if (columnIndex < tr.getChildCount()) {
5398 Element wrapperdiv = tr.getCells()
5399 .getItem(columnIndex).getFirstChildElement()
5400 .cast();
5401 return wrapperdiv.getOffsetWidth();
5402 }
5403 }
5404 }
5405 return 0;
5406 } else {
5407 return 0;
5408 }
5409 }
5410
5411
5412
5413
5414
5415
5416
5417
5418
5419
5420
5421
5422
5423
5424
5425
5426 public void setColWidth(int colIndex, int w) {
5427 for (Widget row : renderedRows) {
5428 ((VScrollTableRow) row).setCellWidth(colIndex, w);
5429 }
5430 }
5431
5432 private int cellExtraWidth = -1;
5433
5434
5435
5436
5437 private int getCellExtraWidth() {
5438 if (!isVisibleInHierarchy()) {
5439 return 0;
5440 }
5441 if (cellExtraWidth < 0) {
5442 detectExtrawidth();
5443 }
5444 return cellExtraWidth;
5445 }
5446
5447
5448
5449
5450
5451
5452
5453
5454
5455
5456
5457 protected int getMaxIndent() {
5458 return 0;
5459 }
5460
5461
5462
5463
5464
5465
5466
5467 protected void calculateMaxIndent() {
5468
5469 }
5470
5471 private void detectExtrawidth() {
5472 NodeList<TableRowElement> rows = tBodyElement.getRows();
5473 if (rows.getLength() == 0) {
5474
5475 VScrollTableRow scrollTableRow = createScrollTableRow();
5476 scrollTableRow.updateStyleNames(
5477 VScrollTablePatched.this.getStylePrimaryName());
5478 tBodyElement.appendChild(scrollTableRow.getElement());
5479 detectExtrawidth();
5480 tBodyElement.removeChild(scrollTableRow.getElement());
5481 } else {
5482 boolean noCells = false;
5483 TableRowElement item = rows.getItem(0);
5484 TableCellElement firstTD = item.getCells().getItem(0);
5485 if (firstTD == null) {
5486
5487
5488 noCells = true;
5489 VScrollTableRow next = (VScrollTableRow) iterator().next();
5490 boolean sorted = tHead.getHeaderCell(0) != null
5491 ? tHead.getHeaderCell(0).isSorted()
5492 : false;
5493 next.addCell(null, "", ALIGN_LEFT, "", true, sorted);
5494 firstTD = item.getCells().getItem(0);
5495 }
5496 com.google.gwt.dom.client.Element wrapper = firstTD
5497 .getFirstChildElement();
5498 cellExtraWidth = firstTD.getOffsetWidth()
5499 - wrapper.getOffsetWidth();
5500 if (noCells) {
5501 firstTD.getParentElement().removeChild(firstTD);
5502 }
5503 }
5504 }
5505
5506 public void moveCol(int oldIndex, int newIndex) {
5507
5508
5509 for (Widget w : this) {
5510 final VScrollTableRow row = (VScrollTableRow) w;
5511
5512 final Element td = DOM.getChild(row.getElement(), oldIndex);
5513 if (td != null) {
5514 DOM.removeChild(row.getElement(), td);
5515
5516 DOM.insertChild(row.getElement(), td, newIndex);
5517 }
5518 }
5519
5520 }
5521
5522
5523
5524
5525
5526 private void restoreRowVisibility() {
5527 for (Widget row : renderedRows) {
5528 row.getElement().getStyle().setProperty("visibility", "");
5529 }
5530 }
5531
5532 public int indexOf(Widget row) {
5533 int relIx = -1;
5534 for (int ix = 0; ix < renderedRows.size(); ix++) {
5535 if (renderedRows.get(ix) == row) {
5536 relIx = ix;
5537 break;
5538 }
5539 }
5540 if (relIx >= 0) {
5541 return firstRendered + relIx;
5542 }
5543 return -1;
5544 }
5545
5546 public class VScrollTableRow extends Panel
5547 implements ActionOwner, ContextMenuOwner {
5548
5549 private static final int TOUCHSCROLL_TIMEOUT = 500;
5550 private static final int DRAGMODE_MULTIROW = 2;
5551 protected List<Widget> childWidgets = new ArrayList<Widget>();
5552 private boolean selected = false;
5553 protected final int rowKey;
5554
5555 private String[] actionKeys = null;
5556 private final TableRowElement rowElement;
5557 private int index;
5558 private Event touchStart;
5559
5560 private static final int TOUCH_CONTEXT_MENU_TIMEOUT = 500;
5561 private Timer contextTouchTimeout;
5562 private Timer dragTouchTimeout;
5563 private int touchStartY;
5564 private int touchStartX;
5565
5566 private TouchContextProvider touchContextProvider = new TouchContextProvider(
5567 this);
5568
5569 private TooltipInfo tooltipInfo = null;
5570 private Map<TableCellElement, TooltipInfo> cellToolTips = new HashMap<TableCellElement, TooltipInfo>();
5571 private boolean isDragging = false;
5572 protected String rowStyle = null;
5573 protected boolean applyZeroWidthFix = true;
5574
5575 private VScrollTableRow(int rowKey) {
5576 this.rowKey = rowKey;
5577 rowElement = Document.get().createTRElement();
5578 setElement(rowElement);
5579 DOM.sinkEvents(getElement(),
5580 Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONDBLCLICK
5581 | Event.ONCONTEXTMENU
5582 | VTooltip.TOOLTIP_EVENTS);
5583 }
5584
5585 public VScrollTableRow(UIDL uidl, char[] aligns) {
5586 this(uidl.getIntAttribute("key"));
5587
5588
5589
5590
5591
5592 getElement().getStyle().setProperty("visibility", "hidden");
5593
5594 rowStyle = uidl.getStringAttribute("rowstyle");
5595 updateStyleNames(VScrollTablePatched.this.getStylePrimaryName());
5596
5597 String rowDescription = uidl.getStringAttribute("rowdescr");
5598 if (rowDescription != null && !rowDescription.equals("")) {
5599 tooltipInfo = new TooltipInfo(rowDescription, null, this);
5600 } else {
5601 tooltipInfo = null;
5602 }
5603
5604 tHead.getColumnAlignments();
5605 int col = 0;
5606 int visibleColumnIndex = -1;
5607
5608
5609 if (showRowHeaders) {
5610 boolean sorted = tHead.getHeaderCell(col).isSorted();
5611 addCell(uidl, buildCaptionHtmlSnippet(uidl), aligns[col++],
5612 "rowheader", true, sorted);
5613 visibleColumnIndex++;
5614 }
5615
5616 if (uidl.hasAttribute("al")) {
5617 actionKeys = uidl.getStringArrayAttribute("al");
5618 }
5619
5620 addCellsFromUIDL(uidl, aligns, col, visibleColumnIndex);
5621
5622 if (uidl.hasAttribute("selected") && !isSelected()) {
5623 toggleSelection();
5624 }
5625 }
5626
5627 protected void updateStyleNames(String primaryStyleName) {
5628
5629 if (getStylePrimaryName().contains("odd")) {
5630 setStyleName(primaryStyleName + "-row-odd");
5631 } else {
5632 setStyleName(primaryStyleName + "-row");
5633 }
5634
5635 if (rowStyle != null) {
5636 addStyleName(primaryStyleName + "-row-" + rowStyle);
5637 }
5638
5639 for (int i = 0; i < rowElement.getChildCount(); i++) {
5640 TableCellElement cell = (TableCellElement) rowElement
5641 .getChild(i);
5642 updateCellStyleNames(cell, primaryStyleName);
5643 }
5644 }
5645
5646 public TooltipInfo getTooltipInfo() {
5647 return tooltipInfo;
5648 }
5649
5650
5651
5652
5653 public VScrollTableRow() {
5654 this(0);
5655 addCell(null, "_", 'b', "", true, false);
5656 }
5657
5658 protected void initCellWidths() {
5659 final int cells = tHead.getVisibleCellCount();
5660 for (int i = 0; i < cells; i++) {
5661 int w = VScrollTablePatched.this.getColWidth(getColKeyByIndex(i));
5662 if (w < 0) {
5663 w = 0;
5664 }
5665 setCellWidth(i, w);
5666 }
5667 }
5668
5669 protected void setCellWidth(int cellIx, int width) {
5670 final Element cell = DOM.getChild(getElement(), cellIx);
5671 Style wrapperStyle = cell.getFirstChildElement().getStyle();
5672 int wrapperWidth = width;
5673 if (BrowserInfo.get().isSafariOrIOS()
5674 || BrowserInfo.get().isOpera10()) {
5675
5676
5677
5678
5679
5680 if (applyZeroWidthFix && width == 0) {
5681 wrapperWidth = 1;
5682 wrapperStyle.setMarginRight(-1, Unit.PX);
5683 } else {
5684 wrapperStyle.clearMarginRight();
5685 }
5686 }
5687 wrapperStyle.setPropertyPx("width", wrapperWidth);
5688 cell.getStyle().setPropertyPx("width", width);
5689 }
5690
5691 protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
5692 int visibleColumnIndex) {
5693 for (final Object cell : uidl) {
5694 visibleColumnIndex++;
5695
5696 String columnId = visibleColOrder[visibleColumnIndex];
5697
5698 String style = "";
5699 if (uidl.hasAttribute("style-" + columnId)) {
5700 style = uidl.getStringAttribute("style-" + columnId);
5701 }
5702
5703 String description = null;
5704 if (uidl.hasAttribute("descr-" + columnId)) {
5705 description = uidl
5706 .getStringAttribute("descr-" + columnId);
5707 }
5708
5709 boolean sorted = tHead.getHeaderCell(col).isSorted();
5710 if (cell instanceof String) {
5711 addCell(uidl, cell.toString(), aligns[col++], style,
5712 isRenderHtmlInCells(), sorted, description);
5713 } else {
5714 final ComponentConnector cellContent = client
5715 .getPaintable((UIDL) cell);
5716
5717 addCell(uidl, cellContent.getWidget(), aligns[col++],
5718 style, sorted, description);
5719 }
5720 }
5721 }
5722
5723
5724
5725
5726
5727
5728
5729 protected boolean isRenderHtmlInCells() {
5730 return false;
5731 }
5732
5733
5734
5735
5736
5737
5738 public boolean isInViewPort() {
5739 int absoluteTop = getAbsoluteTop();
5740 int absoluteBottom = absoluteTop + getOffsetHeight();
5741 int viewPortTop = scrollBodyPanel.getAbsoluteTop();
5742 int viewPortBottom = viewPortTop
5743 + scrollBodyPanel.getOffsetHeight();
5744 return absoluteBottom > viewPortTop
5745 && absoluteTop < viewPortBottom;
5746 }
5747
5748
5749
5750
5751
5752
5753
5754
5755 public boolean isBefore(VScrollTableRow row1) {
5756 return getIndex() < row1.getIndex();
5757 }
5758
5759
5760
5761
5762
5763
5764
5765 private void setIndex(int indexInWholeTable) {
5766 index = indexInWholeTable;
5767 boolean isOdd = indexInWholeTable % 2 == 0;
5768
5769
5770
5771
5772
5773
5774 String primaryStyleName = getStylePrimaryName();
5775 if (primaryStyleName != null && !primaryStyleName.equals("")) {
5776 removeStyleName(getStylePrimaryName());
5777 }
5778 if (!isOdd) {
5779 addStyleName(VScrollTablePatched.this.getStylePrimaryName()
5780 + "-row-odd");
5781 } else {
5782 addStyleName(
5783 VScrollTablePatched.this.getStylePrimaryName() + "-row");
5784 }
5785 }
5786
5787 public int getIndex() {
5788 return index;
5789 }
5790
5791 @Override
5792 protected void onDetach() {
5793 super.onDetach();
5794 client.getContextMenu().ensureHidden(this);
5795 }
5796
5797 public String getKey() {
5798 return String.valueOf(rowKey);
5799 }
5800
5801 public void addCell(UIDL rowUidl, String text, char align,
5802 String style, boolean textIsHTML, boolean sorted) {
5803 addCell(rowUidl, text, align, style, textIsHTML, sorted, null);
5804 }
5805
5806 public void addCell(UIDL rowUidl, String text, char align,
5807 String style, boolean textIsHTML, boolean sorted,
5808 String description) {
5809
5810 final TableCellElement td = DOM.createTD().cast();
5811 initCellWithText(text, align, style, textIsHTML, sorted,
5812 description, td);
5813 }
5814
5815 protected void initCellWithText(String text, char align,
5816 String style, boolean textIsHTML, boolean sorted,
5817 String description, final TableCellElement td) {
5818 final Element container = DOM.createDiv();
5819 container.setClassName(VScrollTablePatched.this.getStylePrimaryName()
5820 + "-cell-wrapper");
5821
5822 td.setClassName(VScrollTablePatched.this.getStylePrimaryName()
5823 + "-cell-content");
5824
5825 if (style != null && !style.equals("")) {
5826 td.addClassName(VScrollTablePatched.this.getStylePrimaryName()
5827 + "-cell-content-" + style);
5828 }
5829
5830 if (sorted) {
5831 td.addClassName(VScrollTablePatched.this.getStylePrimaryName()
5832 + "-cell-content-sorted");
5833 }
5834
5835 if (textIsHTML) {
5836 container.setInnerHTML(text);
5837 } else {
5838 container.setInnerText(text);
5839 }
5840 setAlign(align, container);
5841 setTooltip(td, description);
5842
5843 td.appendChild(container);
5844 getElement().appendChild(td);
5845 }
5846
5847 protected void updateCellStyleNames(TableCellElement td,
5848 String primaryStyleName) {
5849 Element container = td.getFirstChild().cast();
5850 container.setClassName(primaryStyleName + "-cell-wrapper");
5851
5852
5853
5854
5855 String className = td.getClassName();
5856 String oldPrimaryName = className.split("-cell-content")[0];
5857 td.setClassName(
5858 className.replaceAll(oldPrimaryName, primaryStyleName));
5859 }
5860
5861 public void addCell(UIDL rowUidl, Widget w, char align,
5862 String style, boolean sorted, String description) {
5863 final TableCellElement td = DOM.createTD().cast();
5864 initCellWithWidget(w, align, style, sorted, td);
5865 setTooltip(td, description);
5866 }
5867
5868 private void setTooltip(TableCellElement td, String description) {
5869 if (description != null && !description.equals("")) {
5870 TooltipInfo info = new TooltipInfo(description, null, this);
5871 cellToolTips.put(td, info);
5872 } else {
5873 cellToolTips.remove(td);
5874 }
5875
5876 }
5877
5878 private void setAlign(char align, final Element container) {
5879 switch (align) {
5880 case ALIGN_CENTER:
5881 container.getStyle().setProperty("textAlign", "center");
5882 break;
5883 case ALIGN_LEFT:
5884 container.getStyle().setProperty("textAlign", "left");
5885 break;
5886 case ALIGN_RIGHT:
5887 default:
5888 container.getStyle().setProperty("textAlign", "right");
5889 break;
5890 }
5891 }
5892
5893 protected void initCellWithWidget(Widget w, char align,
5894 String style, boolean sorted, final TableCellElement td) {
5895 final Element container = DOM.createDiv();
5896 String className = VScrollTablePatched.this.getStylePrimaryName()
5897 + "-cell-content";
5898 if (style != null && !style.equals("")) {
5899 className += " " + VScrollTablePatched.this.getStylePrimaryName()
5900 + "-cell-content-" + style;
5901 }
5902 if (sorted) {
5903 className += " " + VScrollTablePatched.this.getStylePrimaryName()
5904 + "-cell-content-sorted";
5905 }
5906 td.setClassName(className);
5907 container.setClassName(VScrollTablePatched.this.getStylePrimaryName()
5908 + "-cell-wrapper");
5909 setAlign(align, container);
5910 td.appendChild(container);
5911 getElement().appendChild(td);
5912
5913
5914 w.removeFromParent();
5915 container.appendChild(w.getElement());
5916 adopt(w);
5917 childWidgets.add(w);
5918 }
5919
5920 @Override
5921 public Iterator<Widget> iterator() {
5922 return childWidgets.iterator();
5923 }
5924
5925 @Override
5926 public boolean remove(Widget w) {
5927 if (childWidgets.contains(w)) {
5928 orphan(w);
5929 DOM.removeChild(DOM.getParent(w.getElement()),
5930 w.getElement());
5931 childWidgets.remove(w);
5932 return true;
5933 } else {
5934 return false;
5935 }
5936 }
5937
5938
5939
5940
5941
5942
5943
5944
5945
5946
5947
5948 private boolean handleClickEvent(Event event, Element targetTdOrTr,
5949 boolean immediate) {
5950 if (!client.hasEventListeners(VScrollTablePatched.this,
5951 TableConstants.ITEM_CLICK_EVENT_ID)) {
5952
5953 return false;
5954 }
5955
5956
5957 client.updateVariable(paintableId, "clickedKey", "" + rowKey,
5958 false);
5959
5960 if (getElement() == targetTdOrTr.getParentElement()) {
5961
5962 int childIndex = DOM.getChildIndex(getElement(),
5963 targetTdOrTr);
5964 String colKey = null;
5965 colKey = tHead.getHeaderCell(childIndex).getColKey();
5966 client.updateVariable(paintableId, "clickedColKey", colKey,
5967 false);
5968 }
5969
5970 MouseEventDetails details = MouseEventDetailsBuilder
5971 .buildMouseEventDetails(event);
5972
5973 client.updateVariable(paintableId, "clickEvent",
5974 details.toString(), immediate);
5975
5976 return true;
5977 }
5978
5979 public TooltipInfo getTooltip(
5980 com.google.gwt.dom.client.Element target) {
5981
5982 TooltipInfo info = null;
5983 final Element targetTdOrTr = getTdOrTr(target);
5984 if (targetTdOrTr != null && "td".equals(
5985 targetTdOrTr.getTagName().toLowerCase(Locale.ROOT))) {
5986 TableCellElement td = (TableCellElement) targetTdOrTr
5987 .cast();
5988 info = cellToolTips.get(td);
5989 }
5990
5991 if (info == null) {
5992 info = tooltipInfo;
5993 }
5994
5995 return info;
5996 }
5997
5998 private Element getTdOrTr(Element target) {
5999 Element thisTrElement = getElement();
6000 if (target == thisTrElement) {
6001
6002 return target;
6003 }
6004
6005
6006 Element element = target;
6007 while (element != null
6008 && element.getParentElement() != thisTrElement) {
6009 element = element.getParentElement();
6010 }
6011 return element;
6012 }
6013
6014
6015
6016
6017
6018
6019 private boolean handleTouchEvent(final Event event) {
6020
6021 boolean touchEventHandled = false;
6022
6023 if (enabled && HAS_NATIVE_TOUCH_SCROLLLING) {
6024 touchContextProvider.handleTouchEvent(event);
6025
6026 final Element targetTdOrTr = getEventTargetTdOrTr(event);
6027 final int type = event.getTypeInt();
6028
6029 switch (type) {
6030 case Event.ONTOUCHSTART:
6031 touchEventHandled = true;
6032 touchStart = event;
6033 isDragging = false;
6034 Touch touch = event.getChangedTouches().get(0);
6035
6036
6037 touchStartX = touch.getClientX();
6038 touchStartY = touch.getClientY();
6039
6040 if (dragmode != 0) {
6041 if (dragTouchTimeout == null) {
6042 dragTouchTimeout = new Timer() {
6043
6044 @Override
6045 public void run() {
6046 if (touchStart != null) {
6047
6048
6049 isDragging = true;
6050 }
6051 }
6052 };
6053 }
6054 dragTouchTimeout.schedule(TOUCHSCROLL_TIMEOUT);
6055 }
6056
6057 if (actionKeys != null) {
6058 if (contextTouchTimeout == null) {
6059 contextTouchTimeout = new Timer() {
6060
6061 @Override
6062 public void run() {
6063 if (touchStart != null) {
6064
6065
6066 showContextMenu(touchStart);
6067 event.preventDefault();
6068 touchStart = null;
6069 }
6070 }
6071 };
6072 }
6073 contextTouchTimeout
6074 .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
6075 event.stopPropagation();
6076 }
6077 break;
6078 case Event.ONTOUCHMOVE:
6079 touchEventHandled = true;
6080 if (isSignificantMove(event)) {
6081 if (contextTouchTimeout != null) {
6082
6083
6084
6085 contextTouchTimeout.cancel();
6086 contextTouchTimeout = null;
6087 }
6088 if (!isDragging && dragTouchTimeout != null) {
6089
6090
6091 dragTouchTimeout.cancel();
6092 dragTouchTimeout = null;
6093 }
6094
6095 if (dragmode != 0 && touchStart != null
6096 && isDragging) {
6097 event.preventDefault();
6098 event.stopPropagation();
6099 startRowDrag(touchStart, type, targetTdOrTr);
6100 }
6101 touchStart = null;
6102 }
6103 break;
6104 case Event.ONTOUCHEND:
6105 case Event.ONTOUCHCANCEL:
6106 touchEventHandled = true;
6107 if (contextTouchTimeout != null) {
6108 contextTouchTimeout.cancel();
6109 }
6110 if (dragTouchTimeout != null) {
6111 dragTouchTimeout.cancel();
6112 }
6113 if (touchStart != null) {
6114 if (!BrowserInfo.get().isAndroid()) {
6115 event.preventDefault();
6116 WidgetUtil.simulateClickFromTouchEvent(
6117 touchStart, this);
6118 }
6119 event.stopPropagation();
6120 touchStart = null;
6121 }
6122 isDragging = false;
6123 break;
6124 }
6125 }
6126 return touchEventHandled;
6127 }
6128
6129
6130
6131
6132
6133 @Override
6134 public void onBrowserEvent(final Event event) {
6135
6136 final boolean touchEventHandled = handleTouchEvent(event);
6137
6138 if (enabled && !touchEventHandled) {
6139 final int type = event.getTypeInt();
6140 final Element targetTdOrTr = getEventTargetTdOrTr(event);
6141 if (type == Event.ONCONTEXTMENU) {
6142 showContextMenu(event);
6143 if (enabled && (actionKeys != null
6144 || client.hasEventListeners(VScrollTablePatched.this,
6145 TableConstants.ITEM_CLICK_EVENT_ID))) {
6146
6147
6148
6149
6150
6151 event.stopPropagation();
6152 event.preventDefault();
6153 }
6154 return;
6155 }
6156
6157 boolean targetCellOrRowFound = targetTdOrTr != null;
6158
6159 switch (type) {
6160 case Event.ONDBLCLICK:
6161 if (targetCellOrRowFound) {
6162 handleClickEvent(event, targetTdOrTr, true);
6163 }
6164 break;
6165 case Event.ONMOUSEUP:
6166
6167
6168
6169
6170
6171
6172
6173 if (mouseUpPreviewMatched && lastMouseDownTarget != null
6174 && lastMouseDownTarget == getElementTdOrTr(
6175 WidgetUtil
6176 .getElementUnderMouse(event))) {
6177
6178
6179 if (targetCellOrRowFound) {
6180
6181
6182
6183
6184 boolean clickEventSent = handleClickEvent(event,
6185 targetTdOrTr, false);
6186
6187 if (event.getButton() == Event.BUTTON_LEFT
6188 && isSelectable()) {
6189
6190
6191 if ((event.getCtrlKey()
6192 || event.getMetaKey())
6193 && event.getShiftKey()
6194 && isMultiSelectModeDefault()) {
6195 toggleShiftSelection(false);
6196 setRowFocus(this);
6197
6198
6199 } else if ((event.getCtrlKey()
6200 || event.getMetaKey())
6201 && isMultiSelectModeDefault()) {
6202 boolean wasSelected = isSelected();
6203 toggleSelection();
6204 setRowFocus(this);
6205
6206
6207
6208
6209 selectionRangeStart = this;
6210 if (wasSelected) {
6211 removeRowFromUnsentSelectionRanges(
6212 this);
6213 }
6214
6215 } else if ((event.getCtrlKey()
6216 || event.getMetaKey())
6217 && isSingleSelectMode()) {
6218
6219
6220 if (!isSelected() || (isSelected()
6221 && nullSelectionAllowed)) {
6222
6223 if (!isSelected()) {
6224 deselectAll();
6225 }
6226
6227 toggleSelection();
6228 setRowFocus(this);
6229 }
6230
6231 } else if (event.getShiftKey()
6232 && isMultiSelectModeDefault()) {
6233
6234 toggleShiftSelection(true);
6235
6236 } else {
6237
6238 boolean currentlyJustThisRowSelected = selectedRowKeys
6239 .size() == 1
6240 && selectedRowKeys
6241 .contains(getKey());
6242
6243 if (!currentlyJustThisRowSelected) {
6244 if (isSingleSelectMode()
6245 || isMultiSelectModeDefault()) {
6246
6247
6248
6249
6250
6251
6252
6253
6254
6255
6256
6257 deselectAll();
6258 }
6259 toggleSelection();
6260 } else if ((isSingleSelectMode()
6261 || isMultiSelectModeSimple())
6262 && nullSelectionAllowed) {
6263 toggleSelection();
6264 }
6265
6266
6267
6268
6269
6270 selectionRangeStart = this;
6271 setRowFocus(this);
6272 }
6273
6274
6275 if (BrowserInfo.get().isIE()) {
6276 ((Element) event.getEventTarget()
6277 .cast()).setPropertyJSO(
6278 "onselectstart", null);
6279 }
6280
6281 sendSelectedRows(false);
6282 }
6283
6284
6285
6286
6287
6288
6289 if (immediate || clickEventSent) {
6290 client.sendPendingVariableChanges();
6291 }
6292 }
6293 }
6294 mouseUpPreviewMatched = false;
6295 lastMouseDownTarget = null;
6296 break;
6297 case Event.ONTOUCHEND:
6298 case Event.ONTOUCHCANCEL:
6299 if (touchStart != null) {
6300
6301
6302
6303
6304 WidgetUtil.simulateClickFromTouchEvent(touchStart,
6305 this);
6306 touchStart = null;
6307 }
6308 touchContextProvider.cancel();
6309 break;
6310 case Event.ONTOUCHMOVE:
6311 if (isSignificantMove(event)) {
6312
6313
6314
6315
6316
6317 if (dragmode != 0 && touchStart != null
6318 && (TouchScrollDelegate
6319 .getActiveScrollDelegate() == null)) {
6320 startRowDrag(touchStart, type, targetTdOrTr);
6321 }
6322 touchContextProvider.cancel();
6323
6324
6325
6326
6327 touchStart = null;
6328 }
6329
6330 break;
6331 case Event.ONTOUCHSTART:
6332 touchStart = event;
6333 Touch touch = event.getChangedTouches().get(0);
6334
6335
6336 touchStartX = touch.getClientX();
6337 touchStartY = touch.getClientY();
6338
6339
6340
6341 touchStart.preventDefault();
6342 if (dragmode != 0 || actionKeys != null) {
6343 new Timer() {
6344
6345 @Override
6346 public void run() {
6347 TouchScrollDelegate activeScrollDelegate = TouchScrollDelegate
6348 .getActiveScrollDelegate();
6349
6350
6351
6352
6353
6354
6355
6356 if (activeScrollDelegate != null) {
6357 if (activeScrollDelegate.isMoved()) {
6358
6359
6360
6361
6362
6363
6364
6365 touchStart = null;
6366 } else {
6367
6368
6369
6370
6371
6372
6373 activeScrollDelegate
6374 .stopScrolling();
6375 }
6376 }
6377 }
6378 }.schedule(TOUCHSCROLL_TIMEOUT);
6379
6380 if (contextTouchTimeout == null
6381 && actionKeys != null) {
6382 contextTouchTimeout = new Timer() {
6383
6384 @Override
6385 public void run() {
6386 if (touchStart != null) {
6387 showContextMenu(touchStart);
6388 touchStart = null;
6389 }
6390 }
6391 };
6392 }
6393 if (contextTouchTimeout != null) {
6394 contextTouchTimeout.cancel();
6395 contextTouchTimeout
6396 .schedule(TOUCH_CONTEXT_MENU_TIMEOUT);
6397 }
6398 }
6399 break;
6400 case Event.ONMOUSEDOWN:
6401
6402
6403
6404
6405
6406 lastMouseDownTarget = getElementTdOrTr(
6407 WidgetUtil.getElementUnderMouse(event));
6408 mouseUpPreviewMatched = false;
6409 mouseUpEventPreviewRegistration = Event
6410 .addNativePreviewHandler(mouseUpPreviewHandler);
6411
6412 if (targetCellOrRowFound) {
6413 setRowFocus(this);
6414 if (dragmode != 0 && (event
6415 .getButton() == NativeEvent.BUTTON_LEFT)) {
6416 ensureFocus();
6417 startRowDrag(event, type, targetTdOrTr);
6418
6419 } else if (event.getCtrlKey() || event.getShiftKey()
6420 || event.getMetaKey()
6421 && isMultiSelectModeDefault()) {
6422 ensureFocus();
6423
6424 event.preventDefault();
6425
6426
6427 if (BrowserInfo.get().isIE()) {
6428 ((Element) event.getEventTarget().cast())
6429 .setPropertyJSO("onselectstart",
6430 getPreventTextSelectionIEHack());
6431 }
6432
6433 event.stopPropagation();
6434 }
6435 }
6436 break;
6437 case Event.ONMOUSEOUT:
6438 break;
6439 default:
6440 break;
6441 }
6442 }
6443 super.onBrowserEvent(event);
6444 }
6445
6446 private boolean isSignificantMove(Event event) {
6447 if (touchStart == null) {
6448
6449 return false;
6450 }
6451
6452
6453
6454
6455 Touch touch = event.getChangedTouches().get(0);
6456 if (Math.abs(touch.getClientX()
6457 - touchStartX) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
6458 return true;
6459 }
6460 if (Math.abs(touch.getClientY()
6461 - touchStartY) > TouchScrollDelegate.SIGNIFICANT_MOVE_THRESHOLD) {
6462 return true;
6463 }
6464 return false;
6465 }
6466
6467
6468
6469
6470
6471
6472
6473 private boolean rowKeyIsSelected(int rowKey) {
6474
6475 if (selectedRowKeys.contains("" + rowKey)) {
6476 return true;
6477 }
6478
6479
6480 for (SelectionRange r : selectedRowRanges) {
6481 if (r.inRange(getRenderedRowByKey("" + rowKey))) {
6482 return true;
6483 }
6484 }
6485 return false;
6486 }
6487
6488 protected void startRowDrag(Event event, final int type,
6489 Element targetTdOrTr) {
6490 VTransferable transferable = new VTransferable();
6491 transferable.setDragSource(ConnectorMap.get(client)
6492 .getConnector(VScrollTablePatched.this));
6493 transferable.setData("itemId", "" + rowKey);
6494 NodeList<TableCellElement> cells = rowElement.getCells();
6495 for (int i = 0; i < cells.getLength(); i++) {
6496 if (cells.getItem(i).isOrHasChild(targetTdOrTr)) {
6497 HeaderCell headerCell = tHead.getHeaderCell(i);
6498 transferable.setData("propertyId", headerCell.cid);
6499 break;
6500 }
6501 }
6502
6503 VDragEvent ev = VDragAndDropManager.get()
6504 .startDrag(transferable, event, true);
6505 if (dragmode == DRAGMODE_MULTIROW && isMultiSelectModeAny()
6506 && rowKeyIsSelected(rowKey)) {
6507
6508
6509 ev.createDragImage(scrollBody.tBodyElement, true);
6510
6511
6512 Element dragImage = ev.getDragImage();
6513 int i = 0;
6514 for (Widget next : scrollBody) {
6515 Element child = (Element) dragImage.getChild(i++);
6516
6517 if (!rowKeyIsSelected(
6518 ((VScrollTableRow) next).rowKey)) {
6519 child.getStyle().setVisibility(Visibility.HIDDEN);
6520 }
6521 }
6522 } else {
6523 ev.createDragImage(getElement(), true);
6524 }
6525 if (type == Event.ONMOUSEDOWN) {
6526 event.preventDefault();
6527 }
6528 event.stopPropagation();
6529 }
6530
6531
6532
6533
6534
6535
6536
6537
6538
6539
6540
6541 private Element getEventTargetTdOrTr(Event event) {
6542 final Element eventTarget = event.getEventTarget().cast();
6543 return getElementTdOrTr(eventTarget);
6544 }
6545
6546 private Element getElementTdOrTr(Element element) {
6547
6548 Widget widget = WidgetUtil.findWidget(element, null);
6549
6550 if (widget != this) {
6551
6552
6553
6554
6555
6556
6557 while (widget != null && widget.getParent() != this) {
6558 widget = widget.getParent();
6559 }
6560
6561 if (!(widget instanceof VLabel)
6562 && !(widget instanceof VEmbedded)
6563 && !(widget instanceof VTextField
6564 && ((VTextField) widget).isReadOnly())
6565 && !(widget instanceof com.vaadin.client.ui.VLabel)
6566 && !(widget instanceof com.vaadin.client.ui.VTextField
6567 && ((com.vaadin.client.ui.VTextField) widget).isReadOnly())) {
6568 return null;
6569 }
6570 }
6571 return getTdOrTr(element);
6572 }
6573
6574 @Override
6575 public void showContextMenu(Event event) {
6576 if (enabled && actionKeys != null) {
6577
6578 int left = WidgetUtil.getTouchOrMouseClientX(event)
6579 + Window.getScrollLeft();
6580 int top = WidgetUtil.getTouchOrMouseClientY(event)
6581 + Window.getScrollTop();
6582 showContextMenu(left, top);
6583 }
6584 }
6585
6586 public void showContextMenu(int left, int top) {
6587 VContextMenu menu = client.getContextMenu();
6588 contextMenu = new ContextMenuDetails(menu, getKey(), left, top);
6589 menu.showAt(this, left, top);
6590 }
6591
6592
6593
6594
6595
6596
6597 public boolean isSelected() {
6598 return selected;
6599 }
6600
6601
6602
6603
6604 public void toggleSelection() {
6605 selected = !selected;
6606 selectionChanged = true;
6607 if (selected) {
6608 selectedRowKeys.add(String.valueOf(rowKey));
6609 addStyleName("v-selected");
6610 } else {
6611 removeStyleName("v-selected");
6612 selectedRowKeys.remove(String.valueOf(rowKey));
6613 }
6614 }
6615
6616
6617
6618
6619
6620
6621
6622
6623 private void toggleShiftSelection(boolean deselectPrevious) {
6624
6625
6626
6627
6628
6629 if (isSingleSelectMode()) {
6630
6631 deselectAll();
6632 toggleSelection();
6633 return;
6634 }
6635
6636
6637 VScrollTableRow endRow = this;
6638 VScrollTableRow startRow = selectionRangeStart;
6639 if (startRow == null) {
6640 startRow = focusedRow;
6641 selectionRangeStart = focusedRow;
6642
6643
6644 if (startRow == null) {
6645 startRow = (VScrollTableRow) scrollBody.iterator()
6646 .next();
6647 setRowFocus(endRow);
6648 }
6649 } else if (!startRow.isSelected()) {
6650
6651
6652 startRow = (VScrollTableRow) scrollBody.iterator().next();
6653 setRowFocus(endRow);
6654 }
6655
6656
6657 if (deselectPrevious) {
6658 deselectAll();
6659 }
6660
6661
6662
6663 if (!startRow.isBefore(endRow)) {
6664 VScrollTableRow tmp = startRow;
6665 startRow = endRow;
6666 endRow = tmp;
6667 }
6668 SelectionRangeablePatched.html#SelectionRange">SelectionRange range = new SelectionRange(startRow, endRow);
6669
6670 for (Widget w : scrollBody) {
6671 VScrollTableRow row = (VScrollTableRow) w;
6672 if (range.inRange(row)) {
6673 if (!row.isSelected()) {
6674 row.toggleSelection();
6675 }
6676 selectedRowKeys.add(row.getKey());
6677 }
6678 }
6679
6680
6681 if (startRow != endRow) {
6682 selectedRowRanges.add(range);
6683 }
6684 }
6685
6686
6687
6688
6689
6690
6691
6692 @Override
6693 public Action[] getActions() {
6694 if (actionKeys == null) {
6695 return new Action[] {};
6696 }
6697 final Action[] actions = new Action[actionKeys.length];
6698 for (int i = 0; i < actions.length; i++) {
6699 final String actionKey = actionKeys[i];
6700 final TreeAction a = new TreeAction(this,
6701 String.valueOf(rowKey), actionKey) {
6702
6703 @Override
6704 public void execute() {
6705 super.execute();
6706 lazyRevertFocusToRow(VScrollTableRow.this);
6707 }
6708 };
6709 a.setCaption(getActionCaption(actionKey));
6710 a.setIconUrl(getActionIcon(actionKey));
6711 actions[i] = a;
6712 }
6713 return actions;
6714 }
6715
6716 @Override
6717 public ApplicationConnection getClient() {
6718 return client;
6719 }
6720
6721 @Override
6722 public String getPaintableId() {
6723 return paintableId;
6724 }
6725
6726 public Widget getWidgetForPaintable() {
6727 return this;
6728 }
6729 }
6730
6731 protected class VScrollTableGeneratedRow extends VScrollTableRow {
6732
6733 private boolean spanColumns;
6734 private boolean htmlContentAllowed;
6735
6736 public VScrollTableGeneratedRow(UIDL uidl, char[] aligns) {
6737 super(uidl, aligns);
6738 addStyleName("v-table-generated-row");
6739 }
6740
6741 public boolean isSpanColumns() {
6742 return spanColumns;
6743 }
6744
6745 @Override
6746 protected void initCellWidths() {
6747 if (spanColumns) {
6748 setSpannedColumnWidthAfterDOMFullyInited();
6749 } else {
6750 super.initCellWidths();
6751 }
6752 }
6753
6754 private void setSpannedColumnWidthAfterDOMFullyInited() {
6755
6756
6757
6758 Scheduler.get().scheduleDeferred(new ScheduledCommand() {
6759
6760 @Override
6761 public void execute() {
6762 if (showRowHeaders) {
6763 setCellWidth(0, tHead.getHeaderCell(0)
6764 .getWidthWithIndent());
6765 calcAndSetSpanWidthOnCell(1);
6766 } else {
6767 calcAndSetSpanWidthOnCell(0);
6768 }
6769 }
6770 });
6771 }
6772
6773 @Override
6774 protected boolean isRenderHtmlInCells() {
6775 return htmlContentAllowed;
6776 }
6777
6778 @Override
6779 protected void addCellsFromUIDL(UIDL uidl, char[] aligns, int col,
6780 int visibleColumnIndex) {
6781 htmlContentAllowed = uidl.getBooleanAttribute("gen_html");
6782 spanColumns = uidl.getBooleanAttribute("gen_span");
6783
6784 if (spanColumns) {
6785 int colCount = uidl.getChildCount();
6786
6787 for (final Object cell : uidl) {
6788 if (cell instanceof String) {
6789 addSpannedCell(uidl, cell.toString(), aligns[0], "",
6790 htmlContentAllowed, false, null, colCount);
6791 } else {
6792 final ComponentConnector cellContent = client.getPaintable((UIDL) cell);
6793 addSpannedCell(uidl, cellContent.getWidget(), aligns[0], "",
6794 false, colCount);
6795 }
6796 break;
6797 }
6798 } else {
6799 super.addCellsFromUIDL(uidl, aligns, col,
6800 visibleColumnIndex);
6801 }
6802 }
6803
6804 private void addSpannedCell(UIDL rowUidl, Widget w, char align,
6805 String style, boolean sorted, int colCount) {
6806 TableCellElement td = DOM.createTD().cast();
6807 td.setColSpan(colCount);
6808 initCellWithWidget(w, align, style, sorted, td);
6809 }
6810
6811 private void addSpannedCell(UIDL rowUidl, String text, char align,
6812 String style, boolean textIsHTML, boolean sorted,
6813 String description, int colCount) {
6814
6815 final TableCellElement td = DOM.createTD().cast();
6816 td.setColSpan(colCount);
6817 initCellWithText(text, align, style, textIsHTML, sorted,
6818 description, td);
6819 }
6820
6821 @Override
6822 protected void setCellWidth(int cellIx, int width) {
6823 if (isSpanColumns()) {
6824 if (showRowHeaders) {
6825 if (cellIx == 0) {
6826 super.setCellWidth(0, width);
6827 } else {
6828
6829
6830 calcAndSetSpanWidthOnCell(1);
6831 }
6832 } else {
6833
6834 calcAndSetSpanWidthOnCell(0);
6835 }
6836 } else {
6837 super.setCellWidth(cellIx, width);
6838 }
6839 }
6840
6841 private void calcAndSetSpanWidthOnCell(final int cellIx) {
6842 int spanWidth = 0;
6843 for (int ix = (showRowHeaders ? 1 : 0); ix < tHead
6844 .getVisibleCellCount(); ix++) {
6845 spanWidth += tHead.getHeaderCell(ix).getOffsetWidth();
6846 }
6847 WidgetUtil.setWidthExcludingPaddingAndBorder(
6848 (Element) getElement().getChild(cellIx), spanWidth, 13,
6849 false);
6850 }
6851 }
6852
6853
6854
6855
6856
6857
6858
6859
6860
6861 protected void ensureFocus() {
6862 if (!hasFocus()) {
6863 scrollBodyPanel.setFocus(true);
6864 }
6865
6866 }
6867
6868 }
6869
6870
6871
6872
6873 public void deselectAll() {
6874 for (Widget w : scrollBody) {
6875 VScrollTableRow row = (VScrollTableRow) w;
6876 if (row.isSelected()) {
6877 row.toggleSelection();
6878 }
6879 }
6880
6881 selectedRowKeys.clear();
6882 selectedRowRanges.clear();
6883
6884
6885 instructServerToForgetPreviousSelections();
6886 }
6887
6888
6889
6890
6891
6892 private void instructServerToForgetPreviousSelections() {
6893 client.updateVariable(paintableId, "clearSelections", true, false);
6894 }
6895
6896
6897
6898
6899 public void updatePageLength() {
6900
6901 if (!isVisibleInHierarchy() || !enabled) {
6902 return;
6903 }
6904
6905 if (scrollBody == null) {
6906 return;
6907 }
6908
6909 if (isDynamicHeight()) {
6910 return;
6911 }
6912
6913 int rowHeight = (int) Math.round(scrollBody.getRowHeight());
6914 int bodyH = scrollBodyPanel.getOffsetHeight();
6915 int rowsAtOnce = bodyH / rowHeight;
6916 boolean anotherPartlyVisible = ((bodyH % rowHeight) != 0);
6917 if (anotherPartlyVisible) {
6918 rowsAtOnce++;
6919 }
6920 if (pageLength != rowsAtOnce) {
6921 pageLength = rowsAtOnce;
6922 client.updateVariable(paintableId, "pagelength", pageLength, false);
6923
6924 if (!rendering) {
6925 int currentlyVisible = scrollBody.getLastRendered()
6926 - scrollBody.getFirstRendered();
6927 if (currentlyVisible < pageLength
6928 && currentlyVisible < totalRows) {
6929
6930 scrollBodyPanel.setScrollPosition(scrollTop + 1);
6931 scrollBodyPanel.setScrollPosition(scrollTop - 1);
6932 }
6933
6934 sizeNeedsInit = true;
6935 }
6936 }
6937
6938 }
6939
6940
6941 public void updateWidth() {
6942 if (!isVisibleInHierarchy()) {
6943
6944
6945
6946
6947
6948 return;
6949 }
6950
6951 if (!isDynamicWidth()) {
6952 int innerPixels = getOffsetWidth() - getBorderWidth();
6953 if (innerPixels < 0) {
6954 innerPixels = 0;
6955 }
6956 setContentWidth(innerPixels);
6957
6958
6959 triggerLazyColumnAdjustment(false);
6960
6961 } else {
6962
6963 sizeNeedsInit = true;
6964
6965
6966 triggerLazyColumnAdjustment(false);
6967 }
6968
6969
6970
6971
6972
6973 setProperTabIndex();
6974 }
6975
6976 private static final int LAZY_COLUMN_ADJUST_TIMEOUT = 300;
6977
6978 private final Timer lazyAdjustColumnWidths = new Timer() {
6979
6980 private int formerWidth;
6981 private boolean formerScrolling;
6982
6983
6984
6985
6986
6987
6988
6989 @Override
6990 public void run() {
6991 if (scrollBody == null || !isVisibleInHierarchy()) {
6992
6993
6994 triggerLazyColumnAdjustment(false);
6995 return;
6996 }
6997
6998
6999 int width = scrollBody.getAvailableWidth();
7000 boolean scrolling = willHaveScrollbars();
7001 if (width != formerWidth || scrolling != formerScrolling) {
7002 formerWidth = width;
7003 formerScrolling = scrolling;
7004 } else {
7005 return;
7006 }
7007
7008 int usedMinimumWidth = 0;
7009 int totalExplicitColumnsWidths = 0;
7010 float expandRatioDivider = 0;
7011 int colIndex = 0;
7012
7013 int hierarchyColumnIndent = scrollBody.getMaxIndent();
7014 int hierarchyColumnIndex = getHierarchyColumnIndex();
7015 HeaderCell hierarchyHeaderInNeedOfFurtherHandling = null;
7016
7017 for (Widget widget : tHead) {
7018 final HeaderCell hCell = (HeaderCell) widget;
7019 boolean hasIndent = hierarchyColumnIndent > 0
7020 && hCell.isHierarchyColumn();
7021 if (hCell.isDefinedWidth()) {
7022
7023
7024 int w = hCell.getWidth();
7025 if (hasIndent && w < hierarchyColumnIndent) {
7026
7027 w = hierarchyColumnIndent;
7028 hierarchyHeaderInNeedOfFurtherHandling = hCell;
7029 }
7030 totalExplicitColumnsWidths += w;
7031 usedMinimumWidth += w;
7032 } else {
7033
7034 int naturalColumnWidth = hCell
7035 .getNaturalColumnWidth(colIndex);
7036
7037
7038
7039
7040
7041 if (hCell.getExpandRatio() > 0) {
7042 naturalColumnWidth = 0;
7043 }
7044 usedMinimumWidth += naturalColumnWidth;
7045 expandRatioDivider += hCell.getExpandRatio();
7046 if (hasIndent) {
7047 hierarchyHeaderInNeedOfFurtherHandling = hCell;
7048 }
7049 }
7050 colIndex++;
7051 }
7052
7053 int availW = scrollBody.getAvailableWidth();
7054
7055 availW = scrollBody.getAvailableWidth();
7056 int visibleCellCount = tHead.getVisibleCellCount();
7057 int totalExtraWidth = scrollBody.getCellExtraWidth()
7058 * visibleCellCount;
7059 if (willHaveScrollbars()) {
7060 totalExtraWidth += WidgetUtil.getNativeScrollbarSize();
7061
7062 scrollBodyPanel.getElement().getStyle().clearOverflowY();
7063 } else {
7064
7065
7066
7067 scrollBodyPanel.getElement().getStyle()
7068 .setOverflowY(Overflow.HIDDEN);
7069 }
7070
7071 availW -= totalExtraWidth;
7072 int forceScrollBodyWidth = -1;
7073
7074 int extraSpace = availW - usedMinimumWidth;
7075 if (extraSpace < 0) {
7076 if (getTotalRows() == 0) {
7077
7078
7079
7080
7081
7082
7083
7084
7085 forceScrollBodyWidth = usedMinimumWidth + totalExtraWidth;
7086 }
7087 extraSpace = 0;
7088
7089 scrollBodyPanel.getElement().getStyle().clearOverflowX();
7090 } else {
7091
7092
7093
7094
7095 scrollBodyPanel.getElement().getStyle()
7096 .setOverflowX(Overflow.HIDDEN);
7097 }
7098
7099 if (forceScrollBodyWidth > 0) {
7100 scrollBody.container.getStyle().setWidth(forceScrollBodyWidth,
7101 Unit.PX);
7102 } else {
7103
7104
7105 scrollBody.container.getStyle().clearWidth();
7106 }
7107
7108 int totalUndefinedNaturalWidths = usedMinimumWidth
7109 - totalExplicitColumnsWidths;
7110
7111 if (hierarchyHeaderInNeedOfFurtherHandling != null
7112 && !hierarchyHeaderInNeedOfFurtherHandling
7113 .isDefinedWidth()) {
7114
7115 int w = hierarchyHeaderInNeedOfFurtherHandling
7116 .getNaturalColumnWidth(hierarchyColumnIndex);
7117 int newSpace = Math.round(w + (float) extraSpace * (float) w
7118 / totalUndefinedNaturalWidths);
7119 if (newSpace >= hierarchyColumnIndent) {
7120
7121 hierarchyHeaderInNeedOfFurtherHandling = null;
7122 } else {
7123
7124 totalExplicitColumnsWidths += hierarchyColumnIndent;
7125 usedMinimumWidth -= w - hierarchyColumnIndent;
7126 totalUndefinedNaturalWidths = usedMinimumWidth
7127 - totalExplicitColumnsWidths;
7128 expandRatioDivider += hierarchyHeaderInNeedOfFurtherHandling
7129 .getExpandRatio();
7130 extraSpace = Math.max(availW - usedMinimumWidth, 0);
7131 }
7132 }
7133
7134
7135 colIndex = 0;
7136 int checksum = 0;
7137 for (Widget widget : tHead) {
7138 HeaderCell hCell = (HeaderCell) widget;
7139 if (hCell.isResizing) {
7140 continue;
7141 }
7142 if (!hCell.isDefinedWidth()) {
7143 int w = hCell.getNaturalColumnWidth(colIndex);
7144 int newSpace;
7145 if (expandRatioDivider > 0) {
7146
7147 if (hCell.getExpandRatio() > 0) {
7148 w = 0;
7149 }
7150 newSpace = Math.round((w + extraSpace
7151 * hCell.getExpandRatio() / expandRatioDivider));
7152 } else {
7153 if (hierarchyHeaderInNeedOfFurtherHandling == hCell) {
7154
7155 newSpace = hierarchyColumnIndent;
7156 } else if (totalUndefinedNaturalWidths != 0) {
7157
7158 newSpace = Math.round(w + (float) extraSpace
7159 * (float) w / totalUndefinedNaturalWidths);
7160 } else {
7161 newSpace = w;
7162 }
7163 }
7164 checksum += newSpace;
7165 setColWidth(colIndex, newSpace, false);
7166
7167 } else {
7168 if (hierarchyHeaderInNeedOfFurtherHandling == hCell) {
7169
7170 checksum += hierarchyColumnIndent;
7171 setColWidth(colIndex, hierarchyColumnIndent, false);
7172 } else {
7173 int cellWidth = hCell.getWidthWithIndent();
7174 checksum += cellWidth;
7175 if (hCell.isHierarchyColumn()) {
7176
7177
7178 setColWidth(colIndex, cellWidth, true);
7179 }
7180 }
7181 }
7182 colIndex++;
7183 }
7184
7185 if (extraSpace > 0 && checksum != availW) {
7186
7187
7188
7189
7190
7191 colIndex = 0;
7192 for (Widget widget : tHead) {
7193 HeaderCell hc = (HeaderCell) widget;
7194 if (!hc.isResizing && !hc.isDefinedWidth()) {
7195 setColWidth(colIndex,
7196 hc.getWidthWithIndent() + availW - checksum,
7197 false);
7198 break;
7199 }
7200 colIndex++;
7201 }
7202 }
7203
7204 if (isDynamicHeight() && totalRows == pageLength) {
7205
7206
7207 int bodyHeight = scrollBody.getRequiredHeight();
7208 boolean needsSpaceForHorizontalScrollbar = (availW < usedMinimumWidth);
7209 if (needsSpaceForHorizontalScrollbar) {
7210 bodyHeight += WidgetUtil.getNativeScrollbarSize();
7211 }
7212 int heightBefore = getOffsetHeight();
7213 scrollBodyPanel.setHeight(bodyHeight + "px");
7214
7215 if (heightBefore != getOffsetHeight()) {
7216 Util.notifyParentOfSizeChange(VScrollTablePatched.this, false);
7217 }
7218 }
7219
7220 forceRealignColumnHeaders();
7221 }
7222
7223 };
7224
7225 private void forceRealignColumnHeaders() {
7226 if (BrowserInfo.get().isIE()) {
7227
7228
7229
7230
7231
7232
7233 onScroll(null);
7234 }
7235 }
7236
7237
7238
7239
7240
7241
7242 private void setContentWidth(int pixels) {
7243 tHead.setWidth(pixels + "px");
7244 scrollBodyPanel.setWidth(pixels + "px");
7245 tFoot.setWidth(pixels + "px");
7246 }
7247
7248 private int borderWidth = -1;
7249
7250
7251
7252
7253 private int getBorderWidth() {
7254 if (borderWidth < 0) {
7255 borderWidth = WidgetUtil.measureHorizontalPaddingAndBorder(
7256 scrollBodyPanel.getElement(), 2);
7257 if (borderWidth < 0) {
7258 borderWidth = 0;
7259 }
7260 }
7261 return borderWidth;
7262 }
7263
7264
7265
7266
7267
7268 private int containerHeight;
7269
7270 private void setContainerHeight() {
7271 if (!isDynamicHeight()) {
7272
7273
7274
7275
7276
7277
7278
7279 if (BrowserInfo.get().isAndroid23()) {
7280 getElement().getStyle().setDisplay(Display.BLOCK);
7281 }
7282
7283 containerHeight = getOffsetHeight();
7284 containerHeight -= showColHeaders ? tHead.getOffsetHeight() : 0;
7285 containerHeight -= tFoot.getOffsetHeight();
7286 containerHeight -= getContentAreaBorderHeight();
7287 if (containerHeight < 0) {
7288 containerHeight = 0;
7289 }
7290
7291 scrollBodyPanel.setHeight(containerHeight + "px");
7292
7293 if (BrowserInfo.get().isAndroid23()) {
7294 getElement().getStyle().clearDisplay();
7295 }
7296 }
7297 }
7298
7299 private int contentAreaBorderHeight = -1;
7300 private int scrollLeft;
7301 private int scrollTop;
7302
7303
7304 public VScrollTableDropHandler dropHandler;
7305
7306 private boolean navKeyDown;
7307
7308
7309 public boolean multiselectPending;
7310
7311
7312 public CollapseMenuContent collapsibleMenuContent;
7313
7314
7315
7316
7317 private int getContentAreaBorderHeight() {
7318 if (contentAreaBorderHeight < 0) {
7319
7320 scrollBodyPanel.getElement().getStyle()
7321 .setOverflow(Overflow.HIDDEN);
7322 int oh = scrollBodyPanel.getOffsetHeight();
7323 int ch = scrollBodyPanel.getElement()
7324 .getPropertyInt("clientHeight");
7325 contentAreaBorderHeight = oh - ch;
7326 scrollBodyPanel.getElement().getStyle().setOverflow(Overflow.AUTO);
7327 }
7328 return contentAreaBorderHeight;
7329 }
7330
7331 @Override
7332 public void setHeight(String height) {
7333 if (height.isEmpty()
7334 && !getElement().getStyle().getHeight().isEmpty()) {
7335
7336
7337
7338
7339 sizeNeedsInit = true;
7340 }
7341 super.setHeight(height);
7342 }
7343
7344
7345 public void updateHeight() {
7346 setContainerHeight();
7347
7348 if (initializedAndAttached) {
7349 updatePageLength();
7350 }
7351
7352 triggerLazyColumnAdjustment(false);
7353
7354
7355
7356
7357
7358 setProperTabIndex();
7359
7360 }
7361
7362
7363
7364
7365
7366
7367
7368 @Override
7369 public void setVisible(boolean visible) {
7370 if (isVisible() != visible) {
7371 super.setVisible(visible);
7372 if (initializedAndAttached) {
7373 if (visible) {
7374 Scheduler.get().scheduleDeferred(new Command() {
7375
7376 @Override
7377 public void execute() {
7378 scrollBodyPanel.setScrollPosition(
7379 measureRowHeightOffset(firstRowInViewPort));
7380 }
7381 });
7382 }
7383 }
7384 }
7385 }
7386
7387
7388
7389
7390
7391
7392
7393
7394 protected String buildCaptionHtmlSnippet(UIDL uidl) {
7395 String s = uidl.hasAttribute("caption")
7396 ? uidl.getStringAttribute("caption")
7397 : "";
7398 if (uidl.hasAttribute("icon")) {
7399 Icon icon = client.getIcon(uidl.getStringAttribute("icon"));
7400 icon.setAlternateText("icon");
7401 s = icon.getElement().getString() + s;
7402 }
7403 return s;
7404 }
7405
7406
7407
7408 private void updateFirstVisibleRow() {
7409 scrollTop = scrollBodyPanel.getScrollPosition();
7410 firstRowInViewPort = calcFirstRowInViewPort();
7411 int maxFirstRow = totalRows - pageLength;
7412 if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) {
7413 firstRowInViewPort = maxFirstRow;
7414 }
7415 lastRequestedFirstvisible = firstRowInViewPort;
7416 client.updateVariable(paintableId, "firstvisible", firstRowInViewPort,
7417 false);
7418 }
7419
7420
7421
7422
7423
7424
7425 @Override
7426 public void onScroll(ScrollEvent event) {
7427
7428
7429 if (isLazyScrollerActive()) {
7430 return;
7431 }
7432
7433 scrollLeft = scrollBodyPanel.getElement().getScrollLeft();
7434 scrollTop = scrollBodyPanel.getScrollPosition();
7435
7436
7437
7438
7439
7440
7441
7442
7443 if (!initializedAndAttached || !isAttached()) {
7444 return;
7445 }
7446 if (!enabled) {
7447 scrollBodyPanel.setScrollPosition(
7448 measureRowHeightOffset(firstRowInViewPort));
7449 return;
7450 }
7451
7452 rowRequestHandler.cancel();
7453
7454 if (BrowserInfo.get().isSafariOrIOS() && event != null
7455 && scrollTop == 0) {
7456
7457
7458
7459 Scheduler.get().scheduleDeferred(new Command() {
7460
7461 @Override
7462 public void execute() {
7463 onScroll(null);
7464 }
7465 });
7466 return;
7467 }
7468
7469
7470 tHead.setHorizontalScrollPosition(scrollLeft);
7471
7472
7473 tFoot.setHorizontalScrollPosition(scrollLeft);
7474
7475 if (totalRows == 0) {
7476
7477 return;
7478 }
7479
7480 firstRowInViewPort = calcFirstRowInViewPort();
7481 int maxFirstRow = totalRows - pageLength;
7482 if (firstRowInViewPort > maxFirstRow && maxFirstRow >= 0) {
7483 firstRowInViewPort = maxFirstRow;
7484 }
7485
7486 int postLimit = (int) (firstRowInViewPort + (pageLength - 1)
7487 + pageLength * cacheReactRate);
7488 if (postLimit > totalRows - 1) {
7489 postLimit = totalRows - 1;
7490 }
7491 int preLimit = (int) (firstRowInViewPort
7492 - pageLength * cacheReactRate);
7493 if (preLimit < 0) {
7494 preLimit = 0;
7495 }
7496 final int lastRendered = scrollBody.getLastRendered();
7497 final int firstRendered = scrollBody.getFirstRendered();
7498
7499 if (postLimit <= lastRendered && preLimit >= firstRendered) {
7500
7501
7502
7503 lastRequestedFirstvisible = firstRowInViewPort;
7504 client.updateVariable(paintableId, "firstvisible",
7505 firstRowInViewPort, false);
7506 return;
7507 }
7508
7509 if (allRenderedRowsAreNew()) {
7510
7511 rowRequestHandler.setReqFirstRow(
7512 (firstRowInViewPort - (int) (pageLength * cacheRate)));
7513 int last = firstRowInViewPort + (int) (cacheRate * pageLength)
7514 + pageLength - 1;
7515 if (last >= totalRows) {
7516 last = totalRows - 1;
7517 }
7518 rowRequestHandler
7519 .setReqRows(last - rowRequestHandler.getReqFirstRow() + 1);
7520 updatedReqRows = false;
7521 rowRequestHandler.deferRowFetch();
7522 return;
7523 }
7524 if (preLimit < firstRendered) {
7525
7526
7527 rowRequestHandler.setReqFirstRow(
7528 (int) (firstRowInViewPort - pageLength * cacheRate));
7529 rowRequestHandler.setReqRows(
7530 firstRendered - rowRequestHandler.getReqFirstRow());
7531 rowRequestHandler.deferRowFetch();
7532
7533 return;
7534 }
7535 if (postLimit > lastRendered) {
7536
7537 int reqRows = (int) ((firstRowInViewPort + pageLength
7538 + pageLength * cacheRate) - lastRendered);
7539 rowRequestHandler.triggerRowFetch(lastRendered + 1, reqRows);
7540 }
7541 }
7542
7543 private boolean allRenderedRowsAreNew() {
7544 int firstRowInViewPort = calcFirstRowInViewPort();
7545 int firstRendered = scrollBody.getFirstRendered();
7546 int lastRendered = scrollBody.getLastRendered();
7547 return (firstRowInViewPort - pageLength * cacheRate > lastRendered
7548 || firstRowInViewPort + pageLength
7549 + pageLength * cacheRate < firstRendered);
7550 }
7551
7552 protected int calcFirstRowInViewPort() {
7553 return (int) Math.ceil(scrollTop / scrollBody.getRowHeight());
7554 }
7555
7556 @Override
7557 public VScrollTableDropHandler getDropHandler() {
7558 return dropHandler;
7559 }
7560
7561 private static class TableDDDetails {
7562 int overkey = -1;
7563 VerticalDropLocation dropLocation;
7564 String colkey;
7565
7566 @Override
7567 public boolean equals(Object obj) {
7568 if (obj instanceof TableDDDetails) {
7569 TableDDDetails other = (TableDDDetails) obj;
7570 return dropLocation == other.dropLocation
7571 && overkey == other.overkey
7572 && ((colkey != null && colkey.equals(other.colkey))
7573 || (colkey == null && other.colkey == null));
7574 }
7575 return false;
7576 }
7577
7578 @Override
7579 public int hashCode() {
7580 final int prime = 31;
7581 int result = 1;
7582 result = prime * result
7583 + ((dropLocation == null) ? 0 : dropLocation.hashCode());
7584 result = prime * result + overkey;
7585 result = prime * result
7586 + ((colkey == null) ? 0 : colkey.hashCode());
7587 return result;
7588 }
7589 }
7590
7591 public class VScrollTableDropHandler extends VAbstractDropHandler {
7592
7593 private static final String ROWSTYLEBASE = "v-table-row-drag-";
7594 private TableDDDetails dropDetails;
7595 private TableDDDetails lastEmphasized;
7596
7597 @Override
7598 public void dragEnter(VDragEvent drag) {
7599 updateDropDetails(drag);
7600 super.dragEnter(drag);
7601 }
7602
7603 private void updateDropDetails(VDragEvent drag) {
7604 dropDetails = new TableDDDetails();
7605 Element elementOver = drag.getElementOver();
7606
7607 Class<? extends Widget> clazz = getRowClass();
7608 VScrollTableRow row = null;
7609 if (clazz != null) {
7610 row = WidgetUtil.findWidget(elementOver, clazz);
7611 }
7612 if (row != null) {
7613 dropDetails.overkey = row.rowKey;
7614 Element tr = row.getElement();
7615 Element element = elementOver;
7616 while (element != null && element.getParentElement() != tr) {
7617 element = element.getParentElement();
7618 }
7619 int childIndex = DOM.getChildIndex(tr, element);
7620 dropDetails.colkey = tHead.getHeaderCell(childIndex)
7621 .getColKey();
7622 dropDetails.dropLocation = DDUtil.getVerticalDropLocation(
7623 row.getElement(), drag.getCurrentGwtEvent(), 0.2);
7624 }
7625
7626 drag.getDropDetails().put("itemIdOver", dropDetails.overkey + "");
7627 drag.getDropDetails().put("detail",
7628 dropDetails.dropLocation != null
7629 ? dropDetails.dropLocation.toString()
7630 : null);
7631
7632 }
7633
7634 private Class<? extends Widget> getRowClass() {
7635
7636
7637 Iterator<Widget> it = scrollBody.iterator();
7638 if (it.hasNext()) {
7639 return it.next().getClass();
7640 }
7641 return null;
7642 }
7643
7644 @Override
7645 public void dragOver(VDragEvent drag) {
7646 TableDDDetails oldDetails = dropDetails;
7647 updateDropDetails(drag);
7648 if (!oldDetails.equals(dropDetails)) {
7649 deEmphasis();
7650 final TableDDDetails newDetails = dropDetails;
7651 VAcceptCallback cb = new VAcceptCallback() {
7652
7653 @Override
7654 public void accepted(VDragEvent event) {
7655 if (newDetails.equals(dropDetails)) {
7656 dragAccepted(event);
7657 }
7658
7659
7660
7661 }
7662 };
7663 validate(cb, drag);
7664 }
7665 }
7666
7667 @Override
7668 public void dragLeave(VDragEvent drag) {
7669 deEmphasis();
7670 super.dragLeave(drag);
7671 }
7672
7673 @Override
7674 public boolean drop(VDragEvent drag) {
7675 deEmphasis();
7676 return super.drop(drag);
7677 }
7678
7679 private void deEmphasis() {
7680 UIObject.setStyleName(getElement(), getStylePrimaryName() + "-drag",
7681 false);
7682 if (lastEmphasized == null) {
7683 return;
7684 }
7685 for (Widget w : scrollBody.renderedRows) {
7686 VScrollTableRow row = (VScrollTableRow) w;
7687 if (lastEmphasized != null
7688 && row.rowKey == lastEmphasized.overkey) {
7689 String stylename = ROWSTYLEBASE
7690 + lastEmphasized.dropLocation.toString()
7691 .toLowerCase(Locale.ROOT);
7692 VScrollTableRow.setStyleName(row.getElement(), stylename,
7693 false);
7694 lastEmphasized = null;
7695 return;
7696 }
7697 }
7698 }
7699
7700
7701
7702
7703
7704 private void emphasis(TableDDDetails details) {
7705 deEmphasis();
7706 UIObject.setStyleName(getElement(), getStylePrimaryName() + "-drag",
7707 true);
7708
7709 for (Widget w : scrollBody.renderedRows) {
7710 VScrollTableRow row = (VScrollTableRow) w;
7711 if (details != null && details.overkey == row.rowKey) {
7712 String stylename = ROWSTYLEBASE + details.dropLocation
7713 .toString().toLowerCase(Locale.ROOT);
7714 VScrollTableRow.setStyleName(row.getElement(), stylename,
7715 true);
7716 lastEmphasized = details;
7717 return;
7718 }
7719 }
7720 }
7721
7722 @Override
7723 protected void dragAccepted(VDragEvent drag) {
7724 emphasis(dropDetails);
7725 }
7726
7727 @Override
7728 public ComponentConnector getConnector() {
7729 return ConnectorMap.get(client).getConnector(VScrollTablePatched.this);
7730 }
7731
7732 @Override
7733 public ApplicationConnection getApplicationConnection() {
7734 return client;
7735 }
7736
7737 }
7738
7739 protected VScrollTableRow getFocusedRow() {
7740 return focusedRow;
7741 }
7742
7743
7744
7745
7746
7747
7748
7749
7750 public boolean setRowFocus(VScrollTableRow row) {
7751
7752 if (!isSelectable()) {
7753 return false;
7754 }
7755
7756
7757 if (focusedRow != null && focusedRow != row) {
7758 focusedRow.removeStyleName(getStylePrimaryName() + "-focus");
7759 }
7760
7761 if (row != null) {
7762
7763 row.addStyleName(getStylePrimaryName() + "-focus");
7764
7765
7766
7767
7768 if (row == focusedRow) {
7769 return false;
7770 }
7771
7772
7773 focusedRow = row;
7774
7775 if (hasFocus()) {
7776 ensureRowIsVisible(row);
7777 }
7778
7779 return true;
7780 }
7781
7782 return false;
7783 }
7784
7785
7786
7787
7788
7789
7790
7791 private void ensureRowIsVisible(VScrollTableRow row) {
7792 if (BrowserInfo.get().isTouchDevice()) {
7793
7794
7795 return;
7796 }
7797
7798
7799
7800
7801 WidgetUtil.scrollIntoViewVertically(row.getElement());
7802 }
7803
7804
7805
7806
7807
7808
7809
7810
7811
7812
7813
7814
7815 protected boolean handleNavigation(int keycode, boolean ctrl,
7816 boolean shift) {
7817 if (keycode == KeyCodes.KEY_TAB || keycode == KeyCodes.KEY_SHIFT) {
7818
7819 return false;
7820 }
7821
7822
7823 if (!isSelectable() && keycode == getNavigationDownKey()) {
7824 scrollBodyPanel.setScrollPosition(
7825 scrollBodyPanel.getScrollPosition() + scrollingVelocity);
7826 return true;
7827 } else if (keycode == getNavigationDownKey()) {
7828 if (isMultiSelectModeAny() && moveFocusDown()) {
7829 selectFocusedRow(ctrl, shift);
7830
7831 } else if (isSingleSelectMode() && !shift && moveFocusDown()) {
7832 selectFocusedRow(ctrl, shift);
7833 }
7834 return true;
7835 }
7836
7837
7838 if (!isSelectable() && keycode == getNavigationUpKey()) {
7839 scrollBodyPanel.setScrollPosition(
7840 scrollBodyPanel.getScrollPosition() - scrollingVelocity);
7841 return true;
7842 } else if (keycode == getNavigationUpKey()) {
7843 if (isMultiSelectModeAny() && moveFocusUp()) {
7844 selectFocusedRow(ctrl, shift);
7845 } else if (isSingleSelectMode() && !shift && moveFocusUp()) {
7846 selectFocusedRow(ctrl, shift);
7847 }
7848 return true;
7849 }
7850
7851 if (keycode == getNavigationLeftKey()) {
7852
7853 scrollBodyPanel.setHorizontalScrollPosition(
7854 scrollBodyPanel.getHorizontalScrollPosition()
7855 - scrollingVelocity);
7856 return true;
7857
7858 } else if (keycode == getNavigationRightKey()) {
7859
7860 scrollBodyPanel.setHorizontalScrollPosition(
7861 scrollBodyPanel.getHorizontalScrollPosition()
7862 + scrollingVelocity);
7863 return true;
7864 }
7865
7866
7867 if (isSelectable() && keycode == getNavigationSelectKey()) {
7868 if (isSingleSelectMode()) {
7869 boolean wasSelected = focusedRow.isSelected();
7870 deselectAll();
7871 if (!wasSelected || !nullSelectionAllowed) {
7872 focusedRow.toggleSelection();
7873 }
7874 } else {
7875 focusedRow.toggleSelection();
7876 removeRowFromUnsentSelectionRanges(focusedRow);
7877 }
7878
7879 sendSelectedRows();
7880 return true;
7881 }
7882
7883
7884 if (keycode == getNavigationPageDownKey()) {
7885 if (isSelectable()) {
7886
7887
7888
7889
7890
7891
7892 if (!isFocusAtTheEndOfTable()) {
7893 VScrollTableRow lastVisibleRowInViewPort = scrollBody
7894 .getRowByRowIndex(firstRowInViewPort
7895 + getFullyVisibleRowCount() - 1);
7896 if (lastVisibleRowInViewPort != null
7897 && lastVisibleRowInViewPort != focusedRow) {
7898
7899
7900 setRowFocus(lastVisibleRowInViewPort);
7901 selectFocusedRow(ctrl, shift);
7902 updateFirstVisibleAndSendSelectedRows();
7903 } else {
7904 int indexOfToBeFocused = focusedRow.getIndex()
7905 + getFullyVisibleRowCount();
7906 if (indexOfToBeFocused >= totalRows) {
7907 indexOfToBeFocused = totalRows - 1;
7908 }
7909 VScrollTableRow toBeFocusedRow = scrollBody
7910 .getRowByRowIndex(indexOfToBeFocused);
7911
7912 if (toBeFocusedRow != null) {
7913
7914
7915
7916 setRowFocus(toBeFocusedRow);
7917 selectFocusedRow(ctrl, shift);
7918
7919 updateFirstVisibleAndSendSelectedRows();
7920 } else {
7921
7922
7923
7924 selectLastItemInNextRender = true;
7925 multiselectPending = shift;
7926 scrollByPagelength(1);
7927 }
7928 }
7929 }
7930 } else {
7931
7932 scrollByPagelength(1);
7933 }
7934 return true;
7935 }
7936
7937
7938 if (keycode == getNavigationPageUpKey()) {
7939 if (isSelectable()) {
7940
7941
7942
7943
7944
7945
7946 if (!isFocusAtTheBeginningOfTable()) {
7947 VScrollTableRow firstVisibleRowInViewPort = scrollBody
7948 .getRowByRowIndex(firstRowInViewPort);
7949 if (firstVisibleRowInViewPort != null
7950 && firstVisibleRowInViewPort != focusedRow) {
7951
7952
7953 setRowFocus(firstVisibleRowInViewPort);
7954 selectFocusedRow(ctrl, shift);
7955 updateFirstVisibleAndSendSelectedRows();
7956 } else {
7957 int indexOfToBeFocused = focusedRow.getIndex()
7958 - getFullyVisibleRowCount();
7959 if (indexOfToBeFocused < 0) {
7960 indexOfToBeFocused = 0;
7961 }
7962 VScrollTableRow toBeFocusedRow = scrollBody
7963 .getRowByRowIndex(indexOfToBeFocused);
7964
7965 if (toBeFocusedRow != null) {
7966
7967
7968 setRowFocus(toBeFocusedRow);
7969 selectFocusedRow(ctrl, shift);
7970
7971 updateFirstVisibleAndSendSelectedRows();
7972 } else {
7973
7974
7975
7976
7977 selectFirstItemInNextRender = true;
7978 multiselectPending = shift;
7979 scrollByPagelength(-1);
7980 }
7981 }
7982 }
7983 } else {
7984
7985 scrollByPagelength(-1);
7986 }
7987
7988 return true;
7989 }
7990
7991
7992 if (keycode == getNavigationStartKey()) {
7993 scrollBodyPanel.setScrollPosition(0);
7994 if (isSelectable()) {
7995 if (focusedRow != null && focusedRow.getIndex() == 0) {
7996 return false;
7997 } else {
7998 VScrollTableRow rowByRowIndex = (VScrollTableRow) scrollBody
7999 .iterator().next();
8000 if (rowByRowIndex.getIndex() == 0) {
8001 setRowFocus(rowByRowIndex);
8002 selectFocusedRow(ctrl, shift);
8003 updateFirstVisibleAndSendSelectedRows();
8004 } else {
8005
8006 if (ctrl) {
8007 focusFirstItemInNextRender = true;
8008 } else {
8009 selectFirstItemInNextRender = true;
8010 multiselectPending = shift;
8011 }
8012 }
8013 }
8014 }
8015 return true;
8016 }
8017
8018
8019 if (keycode == getNavigationEndKey()) {
8020 scrollBodyPanel.setScrollPosition(scrollBody.getOffsetHeight());
8021 if (isSelectable()) {
8022 final int lastRendered = scrollBody.getLastRendered();
8023 if (lastRendered + 1 == totalRows) {
8024 VScrollTableRow rowByRowIndex = scrollBody
8025 .getRowByRowIndex(lastRendered);
8026 if (focusedRow != rowByRowIndex) {
8027 setRowFocus(rowByRowIndex);
8028 selectFocusedRow(ctrl, shift);
8029 updateFirstVisibleAndSendSelectedRows();
8030 }
8031 } else {
8032 if (ctrl) {
8033 focusLastItemInNextRender = true;
8034 } else {
8035 selectLastItemInNextRender = true;
8036 multiselectPending = shift;
8037 }
8038 }
8039 }
8040 return true;
8041 }
8042
8043 return false;
8044 }
8045
8046 private boolean isFocusAtTheBeginningOfTable() {
8047 return focusedRow.getIndex() == 0;
8048 }
8049
8050 private boolean isFocusAtTheEndOfTable() {
8051 return focusedRow.getIndex() + 1 >= totalRows;
8052 }
8053
8054 private int getFullyVisibleRowCount() {
8055 return (int) (scrollBodyPanel.getOffsetHeight()
8056 / scrollBody.getRowHeight());
8057 }
8058
8059 private void scrollByPagelength(int i) {
8060 int pixels = i * scrollBodyPanel.getOffsetHeight();
8061 int newPixels = scrollBodyPanel.getScrollPosition() + pixels;
8062 if (newPixels < 0) {
8063 newPixels = 0;
8064 }
8065
8066 scrollBodyPanel.setScrollPosition(newPixels);
8067 }
8068
8069
8070
8071
8072
8073
8074
8075
8076
8077 @Override
8078 public void onFocus(FocusEvent event) {
8079 if (isFocusable()) {
8080 hasFocus = true;
8081
8082
8083 if (focusedRow == null) {
8084 focusRowFromBody();
8085 } else {
8086 setRowFocus(focusedRow);
8087 }
8088 }
8089 }
8090
8091
8092
8093
8094
8095
8096
8097
8098
8099 @Override
8100 public void onBlur(BlurEvent event) {
8101 onBlur();
8102 }
8103
8104 private void onBlur() {
8105 hasFocus = false;
8106 navKeyDown = false;
8107
8108 if (BrowserInfo.get().isIE()) {
8109
8110
8111
8112
8113
8114 Element focusedElement = WidgetUtil.getFocusedElement();
8115 if (focusedElement != null
8116 && focusedElement != scrollBodyPanel.getFocusElement()
8117 && Util.getConnectorForElement(client, getParent(),
8118 focusedElement) == ConnectorMap.get(client)
8119 .getConnector(this)) {
8120
8121
8122
8123
8124 focus();
8125 return;
8126 }
8127 }
8128
8129 if (isFocusable()) {
8130
8131 setRowFocus(null);
8132 }
8133 }
8134
8135
8136
8137
8138
8139
8140
8141 public void removeRowFromUnsentSelectionRanges(VScrollTableRow row) {
8142 Collection<SelectionRange> newRanges = null;
8143 for (Iterator<SelectionRange> iterator = selectedRowRanges
8144 .iterator(); iterator.hasNext();) {
8145 SelectionRange range = iterator.next();
8146 if (range.inRange(row)) {
8147
8148 Collection<SelectionRange> splitranges = range.split(row);
8149 if (newRanges == null) {
8150 newRanges = new ArrayList<SelectionRange>();
8151 }
8152 newRanges.addAll(splitranges);
8153 iterator.remove();
8154 }
8155 }
8156 if (newRanges != null) {
8157 selectedRowRanges.addAll(newRanges);
8158 }
8159 }
8160
8161
8162
8163
8164
8165
8166 public boolean isFocusable() {
8167 if (scrollBody != null && enabled) {
8168 return !(!hasHorizontalScrollbar() && !hasVerticalScrollbar()
8169 && !isSelectable());
8170 }
8171 return false;
8172 }
8173
8174 private boolean hasHorizontalScrollbar() {
8175 return scrollBody.getOffsetWidth() > scrollBodyPanel.getOffsetWidth();
8176 }
8177
8178 private boolean hasVerticalScrollbar() {
8179 return scrollBody.getOffsetHeight() > scrollBodyPanel.getOffsetHeight();
8180 }
8181
8182
8183
8184
8185
8186
8187
8188 @Override
8189 public void focus() {
8190 if (isFocusable()) {
8191 scrollBodyPanel.focus();
8192 }
8193 }
8194
8195
8196
8197
8198
8199
8200
8201
8202
8203
8204
8205
8206
8207 public void setProperTabIndex() {
8208 int storedScrollTop = 0;
8209 int storedScrollLeft = 0;
8210
8211 if (BrowserInfo.get().getOperaVersion() >= 11) {
8212
8213 storedScrollTop = scrollBodyPanel.getScrollPosition();
8214 storedScrollLeft = scrollBodyPanel.getHorizontalScrollPosition();
8215 }
8216
8217 if (tabIndex == 0 && !isFocusable()) {
8218 scrollBodyPanel.setTabIndex(-1);
8219 } else {
8220 scrollBodyPanel.setTabIndex(tabIndex);
8221 }
8222
8223 if (BrowserInfo.get().getOperaVersion() >= 11) {
8224
8225 scrollBodyPanel.setScrollPosition(storedScrollTop);
8226 scrollBodyPanel.setHorizontalScrollPosition(storedScrollLeft);
8227 }
8228 }
8229
8230 public void startScrollingVelocityTimer() {
8231 if (scrollingVelocityTimer == null) {
8232 scrollingVelocityTimer = new Timer() {
8233
8234 @Override
8235 public void run() {
8236 scrollingVelocity++;
8237 }
8238 };
8239 scrollingVelocityTimer.scheduleRepeating(100);
8240 }
8241 }
8242
8243 public void cancelScrollingVelocityTimer() {
8244 if (scrollingVelocityTimer != null) {
8245
8246 scrollingVelocityTimer.cancel();
8247 scrollingVelocityTimer = null;
8248 scrollingVelocity = 10;
8249 }
8250 }
8251
8252
8253
8254
8255
8256
8257 private boolean isNavigationKey(int keyCode) {
8258 return keyCode == getNavigationUpKey()
8259 || keyCode == getNavigationLeftKey()
8260 || keyCode == getNavigationRightKey()
8261 || keyCode == getNavigationDownKey()
8262 || keyCode == getNavigationPageUpKey()
8263 || keyCode == getNavigationPageDownKey()
8264 || keyCode == getNavigationEndKey()
8265 || keyCode == getNavigationStartKey();
8266 }
8267
8268 public void lazyRevertFocusToRow(
8269 final VScrollTableRow currentlyFocusedRow) {
8270 Scheduler.get().scheduleFinally(new ScheduledCommand() {
8271 @Override
8272 public void execute() {
8273 if (currentlyFocusedRow != null) {
8274 setRowFocus(currentlyFocusedRow);
8275 } else {
8276 getLogger().info("no row?");
8277 focusRowFromBody();
8278 }
8279 scrollBody.ensureFocus();
8280 }
8281 });
8282 }
8283
8284 @Override
8285 public Action[] getActions() {
8286 if (bodyActionKeys == null) {
8287 return new Action[] {};
8288 }
8289 final Action[] actions = new Action[bodyActionKeys.length];
8290 for (int i = 0; i < actions.length; i++) {
8291 final String actionKey = bodyActionKeys[i];
8292 Action bodyAction = new TreeAction(this, null, actionKey);
8293 bodyAction.setCaption(getActionCaption(actionKey));
8294 bodyAction.setIconUrl(getActionIcon(actionKey));
8295 actions[i] = bodyAction;
8296 }
8297 return actions;
8298 }
8299
8300 @Override
8301 public ApplicationConnection getClient() {
8302 return client;
8303 }
8304
8305 @Override
8306 public String getPaintableId() {
8307 return paintableId;
8308 }
8309
8310
8311
8312
8313
8314
8315
8316
8317 private static native JavaScriptObject getPreventTextSelectionIEHack()
8318
8319
8320 ;
8321
8322 public void triggerLazyColumnAdjustment(boolean now) {
8323 lazyAdjustColumnWidths.cancel();
8324 if (now) {
8325 lazyAdjustColumnWidths.run();
8326 } else {
8327 lazyAdjustColumnWidths.schedule(LAZY_COLUMN_ADJUST_TIMEOUT);
8328 }
8329 }
8330
8331 private boolean isDynamicWidth() {
8332 ComponentConnector paintable = ConnectorMap.get(client)
8333 .getConnector(this);
8334 return paintable.isUndefinedWidth();
8335 }
8336
8337 private boolean isDynamicHeight() {
8338 ComponentConnector paintable = ConnectorMap.get(client)
8339 .getConnector(this);
8340 if (paintable == null) {
8341
8342
8343
8344 return false;
8345 }
8346 return paintable.isUndefinedHeight();
8347 }
8348
8349 private void debug(String msg) {
8350 if (enableDebug) {
8351 getLogger().severe(msg);
8352 }
8353 }
8354
8355 public Widget getWidgetForPaintable() {
8356 return this;
8357 }
8358
8359 private static final String SUBPART_HEADER = "header";
8360 private static final String SUBPART_FOOTER = "footer";
8361 private static final String SUBPART_ROW = "row";
8362 private static final String SUBPART_COL = "col";
8363
8364
8365
8366
8367 private static final RegExp SUBPART_HEADER_REGEXP = RegExp
8368 .compile(SUBPART_HEADER + "\\[(\\d+)\\]");
8369
8370
8371
8372
8373 private static final RegExp SUBPART_FOOTER_REGEXP = RegExp
8374 .compile(SUBPART_FOOTER + "\\[(\\d+)\\]");
8375
8376 private static final RegExp SUBPART_ROW_REGEXP = RegExp
8377 .compile(SUBPART_ROW + "\\[(\\d+)]");
8378
8379
8380
8381 private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp.compile(
8382 SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL + "\\[(\\d+)\\]");
8383
8384 @Override
8385 public com.google.gwt.user.client.Element getSubPartElement(
8386 String subPart) {
8387 if (SUBPART_ROW_COL_REGEXP.test(subPart)) {
8388 MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart);
8389 int rowIx = Integer.valueOf(result.getGroup(1));
8390 int colIx = Integer.valueOf(result.getGroup(2));
8391 VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
8392 if (row != null) {
8393 Element rowElement = row.getElement();
8394 if (colIx < rowElement.getChildCount()) {
8395 return rowElement.getChild(colIx).getFirstChild().cast();
8396 }
8397 }
8398
8399 } else if (SUBPART_ROW_REGEXP.test(subPart)) {
8400 MatchResult result = SUBPART_ROW_REGEXP.exec(subPart);
8401 int rowIx = Integer.valueOf(result.getGroup(1));
8402 VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx);
8403 if (row != null) {
8404 return row.getElement();
8405 }
8406
8407 } else if (SUBPART_HEADER_REGEXP.test(subPart)) {
8408 MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart);
8409 int headerIx = Integer.valueOf(result.getGroup(1));
8410 HeaderCell headerCell = tHead.getHeaderCell(headerIx);
8411 if (headerCell != null) {
8412 return headerCell.getElement();
8413 }
8414
8415 } else if (SUBPART_FOOTER_REGEXP.test(subPart)) {
8416 MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart);
8417 int footerIx = Integer.valueOf(result.getGroup(1));
8418 FooterCell footerCell = tFoot.getFooterCell(footerIx);
8419 if (footerCell != null) {
8420 return footerCell.getElement();
8421 }
8422 }
8423
8424 return null;
8425 }
8426
8427 @Override
8428 public String getSubPartName(
8429 com.google.gwt.user.client.Element subElement) {
8430 Widget widget = WidgetUtil.findWidget(subElement, null);
8431 if (widget instanceof HeaderCell) {
8432 return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget)
8433 + "]";
8434 } else if (widget instanceof FooterCell) {
8435 return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget)
8436 + "]";
8437 } else if (widget instanceof VScrollTableRow) {
8438
8439 VScrollTableRow row = (VScrollTableRow) widget;
8440 int rowIx = scrollBody.indexOf(row);
8441 if (rowIx >= 0) {
8442 int colIx = -1;
8443 for (int ix = 0; ix < row.getElement().getChildCount(); ix++) {
8444 if (row.getElement().getChild(ix)
8445 .isOrHasChild(subElement)) {
8446 colIx = ix;
8447 break;
8448 }
8449 }
8450 if (colIx >= 0) {
8451 return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "["
8452 + colIx + "]";
8453 }
8454 return SUBPART_ROW + "[" + rowIx + "]";
8455 }
8456 }
8457
8458 return null;
8459 }
8460
8461
8462
8463
8464 public void onUnregister() {
8465 if (addCloseHandler != null) {
8466 addCloseHandler.removeHandler();
8467 }
8468 }
8469
8470
8471
8472
8473 @Override
8474 public boolean isWorkPending() {
8475 return lazyAdjustColumnWidths.isRunning();
8476 }
8477
8478 private static Logger getLogger() {
8479 return Logger.getLogger(VScrollTable.class.getName());
8480 }
8481
8482 public ChildMeasurementHint getChildMeasurementHint() {
8483 return childMeasurementHint;
8484 }
8485
8486 public void setChildMeasurementHint(ChildMeasurementHint hint) {
8487 childMeasurementHint = hint;
8488 }
8489
8490 private boolean hasFocus() {
8491 if (hasFocus && BrowserInfo.get().isIE()) {
8492 com.google.gwt.user.client.Element focusedElement = Util
8493 .getIEFocusedElement();
8494 if (!getElement().isOrHasChild(focusedElement)) {
8495
8496 getLogger().warning(
8497 "IE did not send a blur event, firing manually");
8498 onBlur();
8499 }
8500 }
8501 return hasFocus;
8502 }
8503
8504 }