1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Used for representing data or components in a pageable and selectable
17 table."""
18
19 import logging
20
21 from warnings import warn
22
23 try:
24 from collections import OrderedDict
25 except ImportError:
26 from ordereddict import OrderedDict
27
28 from muntjac.ui.field import IField
29 from muntjac.ui.field_factory import IFieldFactory
30 from muntjac.ui.form import Form
31 from muntjac.ui.default_field_factory import DefaultFieldFactory
32 from muntjac.ui.component_container import IComponentContainer
33 from muntjac.ui.component import IComponent, Event as ComponentEvent
34
35 from muntjac.data.property import IValueChangeNotifier, IValueChangeListener
36 from muntjac.event.mouse_events import ClickEvent
37 from muntjac.event import action
38 from muntjac.event.data_bound_transferable import DataBoundTransferable
39 from muntjac.event.dd.drop_target import IDropTarget
40 from muntjac.event.dd.drag_source import IDragSource
41
42 from muntjac.terminal.key_mapper import KeyMapper
43 from muntjac.terminal.gwt.client.mouse_event_details import MouseEventDetails
44 from muntjac.terminal.gwt.client.ui.v_scroll_table import VScrollTable
45
46 from muntjac.data import container
47
48 from muntjac.event.dd.acceptcriteria.server_side_criterion import \
49 ServerSideCriterion
50
51 from muntjac.ui.abstract_select import \
52 AbstractSelect, MultiSelectMode, AbstractSelectTargetDetails
53
54 from muntjac.event.item_click_event import \
55 ItemClickEvent, IItemClickNotifier, IItemClickSource, ITEM_CLICK_METHOD,\
56 IItemClickListener
57
58 from muntjac.data.util.indexed_container import \
59 ItemSetChangeEvent, IndexedContainer
60
61 from muntjac.util import clsname, OrderedSet
62
63
64 logger = logging.getLogger(__name__)
65
66
67 -class Table(AbstractSelect,
68 container.ISortable, IItemClickSource, IItemClickNotifier,
69 IDragSource, IDropTarget):
70 """C{Table} is used for representing data or components in a
71 pageable and selectable table.
72
73 Scalability of the Table is largely dictated by the container. A table
74 does not have a limit for the number of items and is just as fast with
75 hundreds of thousands of items as with just a few. The current GWT
76 implementation with scrolling however limits the number of rows to
77 around 500000, depending on the browser and the pixel height of rows.
78
79 Components in a Table will not have their caption nor icon rendered.
80
81 @author: Vaadin Ltd.
82 @author: Richard Lincoln
83 @version: 1.1.2
84 """
85
86 CLIENT_WIDGET = None
87
88 CELL_KEY = 0
89 CELL_HEADER = 1
90 CELL_ICON = 2
91 CELL_ITEMID = 3
92 CELL_GENERATED_ROW = 4
93 CELL_FIRSTCOL = 5
94
95
96 ALIGN_LEFT = 'b'
97
98
99 ALIGN_CENTER = 'c'
100
101
102 ALIGN_RIGHT = 'e'
103
104
105 COLUMN_HEADER_MODE_HIDDEN = -1
106
107
108 COLUMN_HEADER_MODE_ID = 0
109
110
111
112 COLUMN_HEADER_MODE_EXPLICIT = 1
113
114
115
116
117
118
119 COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2
120
121
122
123 ROW_HEADER_MODE_HIDDEN = -1
124
125
126 ROW_HEADER_MODE_ID = AbstractSelect.ITEM_CAPTION_MODE_ID
127
128
129 ROW_HEADER_MODE_ITEM = AbstractSelect.ITEM_CAPTION_MODE_ITEM
130
131
132
133
134 ROW_HEADER_MODE_INDEX = AbstractSelect.ITEM_CAPTION_MODE_INDEX
135
136
137 ROW_HEADER_MODE_EXPLICIT = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT
138
139
140
141 ROW_HEADER_MODE_PROPERTY = AbstractSelect.ITEM_CAPTION_MODE_PROPERTY
142
143
144 ROW_HEADER_MODE_ICON_ONLY = AbstractSelect.ITEM_CAPTION_MODE_ICON_ONLY
145
146
147
148
149 ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = \
150 AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID
151
152
153 _CACHE_RATE_DEFAULT = 2
154
155 _ROW_HEADER_COLUMN_KEY = '0'
156
157 _ROW_HEADER_FAKE_PROPERTY_ID = object()
158
159
160 - def __init__(self, caption=None, dataSource=None):
161 """Creates a new table with caption and connect it to a IContainer.
162 """
163
164 self._columnCollapsingAllowed = False
165
166
167 self._columnReorderingAllowed = False
168
169
170 self._columnIdMap = KeyMapper()
171
172
173 self._visibleColumns = list()
174
175
176 self._collapsedColumns = set()
177
178
179 self._columnHeaders = dict()
180
181
182 self._columnFooters = dict()
183
184
185 self._columnIcons = dict()
186
187
188 self._columnAlignments = dict()
189
190
191
192 self._columnWidths = dict()
193
194
195 self._columnGenerators = OrderedDict()
196
197
198 self._pageLength = 15
199
200
201 self._currentPageFirstItemId = None
202
203
204 self._currentPageFirstItemIndex = 0
205
206
207 self._selectable = False
208
209
210 self._columnHeaderMode = self.COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID
211
212
213 self._columnFootersVisible = False
214
215
216 self._rowCaptionsAreHidden = True
217
218
219 self._pageBuffer = None
220
221
222
223 self._listenedProperties = None
224
225
226
227 self._visibleComponents = None
228
229
230 self._actionHandlers = None
231
232
233 self._actionMapper = None
234
235
236 self._fieldFactory = DefaultFieldFactory.get()
237
238
239 self._editable = False
240
241
242 self._sortAscending = True
243
244
245 self._sortContainerPropertyId = None
246
247
248
249 self._sortDisabled = False
250
251
252
253
254 self._reqRowsToPaint = -1
255
256
257
258
259 self._reqFirstRowToPaint = -1
260
261 self._firstToBeRenderedInClient = -1
262
263 self._lastToBeRenderedInClient = -1
264
265 self._isContentRefreshesEnabled = True
266
267 self._pageBufferFirstIndex = 0
268
269 self._containerChangeToBeRendered = False
270
271
272 self._cellStyleGenerator = None
273
274
275 self._itemDescriptionGenerator = None
276
277
278
279 self.alwaysRecalculateColumnWidths = False
280
281 self._cacheRate = self._CACHE_RATE_DEFAULT
282
283 self._dragMode = TableDragMode.NONE
284
285 self._dropHandler = None
286
287 self._multiSelectMode = MultiSelectMode.DEFAULT
288
289 self._rowCacheInvalidated = False
290
291 self._rowGenerator = None
292
293 self._associatedProperties = dict()
294
295 self._painted = False
296
297
298 super(Table, self).__init__()
299
300 self.setRowHeaderMode(self.ROW_HEADER_MODE_HIDDEN)
301
302 if caption is not None:
303 self.setCaption(caption)
304
305 if dataSource is not None:
306 self.setContainerDataSource(dataSource)
307
308
310 """Gets the array of visible column id:s, including generated columns.
311
312 The columns are show in the order of their appearance in this array.
313
314 @return: an array of currently visible propertyIds and generated column
315 ids.
316 """
317 if self._visibleColumns is None:
318 return None
319
320 return list(self._visibleColumns)
321
322
324 """Sets the array of visible column property ids.
325
326 The columns are show in the order of their appearance in this array.
327
328 @param visibleColumns:
329 the array of shown property ids.
330 """
331
332 if visibleColumns is None:
333 raise ValueError, 'Can not set visible columns to null value'
334
335
336
337
338
339 properties = self.getContainerPropertyIds()
340 for vc in visibleColumns:
341 if vc is None:
342 raise ValueError, 'Ids must be non-nulls'
343 elif (vc not in properties) and (vc not in self._columnGenerators):
344 raise ValueError('Ids must exist in the IContainer '
345 'or as a generated column , missing id: ' + str(vc))
346
347
348
349 newVC = list()
350 for vc in visibleColumns:
351 newVC.append(vc)
352
353
354 if self._visibleColumns is not None:
355 disabledHere = self.disableContentRefreshing()
356 try:
357 for col in visibleColumns:
358 if col not in newVC:
359 self.setColumnHeader(col, None)
360 self.setColumnAlignment(col, None)
361 self.setColumnIcon(col, None)
362 finally:
363 if disabledHere:
364 self.enableContentRefreshing(False)
365
366 self._visibleColumns = newVC
367
368
369 self.refreshRowCache()
370
371
373 """Gets the headers of the columns.
374
375 The headers match the property id:s given my the set visible column
376 headers. The table must be set in either L{COLUMN_HEADER_MODE_EXPLICIT}
377 or L{COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
378 headers. In the defaults mode any nulls in the headers array are
379 replaced with id.__str__.
380
381 @return: the array of column headers.
382 """
383 if self._columnHeaders is None:
384 return None
385
386 headers = list()
387
388 for col in self._visibleColumns:
389 headers.append( self.getColumnHeader(col) )
390
391 return headers
392
393
395 """Sets the headers of the columns.
396
397 The headers match the property id:s given my the set visible column
398 headers. The table must be set in either L{COLUMN_HEADER_MODE_EXPLICIT}
399 or L{COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
400 headers. In the defaults mode any nulls in the headers array are
401 replaced with id.toString() outputs when rendering.
402
403 @param columnHeaders:
404 the Array of column headers that match the
405 L{getVisibleColumns} method.
406 """
407 if len(columnHeaders) != len(self._visibleColumns):
408 raise ValueError,('The length of the headers array must '
409 'match the number of visible columns')
410
411 self._columnHeaders.clear()
412
413 i = 0
414 it = iter(self._visibleColumns)
415 while i < len(columnHeaders):
416 try:
417 self._columnHeaders[it.next()] = columnHeaders[i]
418 i += 1
419 except StopIteration:
420 break
421
422 self.requestRepaint()
423
424
426 """Gets the icons of the columns.
427
428 The icons in headers match the property ids given my the set visible
429 column headers. The table must be set in either
430 L{COLUMN_HEADER_MODE_EXPLICIT} or
431 L{COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
432 headers with icons.
433
434 @return: the array of icons that match the L{getVisibleColumns}.
435 """
436 if self._columnIcons is None:
437 return None
438
439 icons = list()
440 for col in self._visibleColumns:
441 icons.append( self._columnIcons[col] )
442
443 return icons
444
445
447 """Sets the icons of the columns.
448
449 The icons in headers match the property id:s given my the set visible
450 column headers. The table must be set in either
451 L{COLUMN_HEADER_MODE_EXPLICIT} or
452 L{COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID} mode to show the
453 headers with icons.
454
455 @param columnIcons: the array of icons that match the
456 L{getVisibleColumns}.
457 """
458 if len(columnIcons) != len(self._visibleColumns):
459 raise ValueError('The length of the icons array must '
460 'match the number of visible columns')
461
462 del self._columnIcons[:]
463
464
465
466 for i, col in enumerate(self._visibleColumns):
467 self._columnIcons[col] = columnIcons[i]
468
469
470 self.requestRepaint()
471
472
474 """Gets the array of column alignments.
475
476 The items in the array must match the properties identified by
477 L{getVisibleColumns}. The possible values for the alignments
478 include:
479
480 - L{ALIGN_LEFT}: Left alignment
481 - L{ALIGN_CENTER}: Centered
482 - L{ALIGN_RIGHT}: Right alignment
483
484 The alignments default to L{ALIGN_LEFT}: any null values are
485 rendered as align lefts.
486
487 @return: the column alignments array.
488 """
489 if self._columnAlignments is None:
490 return None
491
492 alignments = list()
493
494 for col in self._visibleColumns:
495 alignments.append( self.getColumnAlignment(col) )
496
497 return alignments
498
499
501 """Sets the column alignments.
502
503 The items in the array must match the properties identified by
504 L{getVisibleColumns}. The possible values for the alignments
505 include:
506
507 - L{ALIGN_LEFT}: Left alignmen
508 - L{ALIGN_CENTER}: Centered
509 - L{ALIGN_RIGHT}: Right alignment
510
511 The alignments default to L{ALIGN_LEFT}
512
513 @param columnAlignments:
514 the column alignments array.
515 """
516 if len(columnAlignments) != len(self._visibleColumns):
517 raise ValueError('The length of the alignments array '
518 'must match the number of visible columns')
519
520
521 for i, a in enumerate(columnAlignments):
522 if ((a is not None)
523 and (a is not self.ALIGN_LEFT)
524 and (a is not self.ALIGN_CENTER)
525 and (a is not self.ALIGN_RIGHT)):
526 raise ValueError('Column ' + str(i) + ' aligment \''
527 + a + '\' is invalid')
528
529
530 newCA = dict()
531
532 i = 0
533 it = iter(self._visibleColumns)
534 while i < len(columnAlignments):
535 try:
536 newCA[ it.next() ] = columnAlignments[i]
537 i += 1
538 except StopIteration:
539 break
540
541 self._columnAlignments = newCA
542
543
544
545 self.refreshRenderedCells()
546
547
549 """Sets columns width (in pixels). Theme may not necessary respect
550 very small or very big values. Setting width to -1 (default) means
551 that theme will make decision of width.
552
553 Column can either have a fixed width or expand ratio. The latter one
554 set is used. See @link L{setColumnExpandRatio}.
555
556 @param propertyId:
557 colunmns property id
558 @param width:
559 width to be reserved for colunmns content
560 """
561 if propertyId is None:
562
563
564 propertyId = self._ROW_HEADER_FAKE_PROPERTY_ID
565
566 if width < 0:
567 if propertyId in self._columnWidths:
568 del self._columnWidths[propertyId]
569 else:
570 self._columnWidths[propertyId] = int(width)
571
572 self.requestRepaint()
573
574
576 """Sets the column expand ratio for given column.
577
578 Expand ratios can be defined to customize the way how excess space is
579 divided among columns. Table can have excess space if it has its width
580 defined and there is horizontally more space than columns consume
581 naturally. Excess space is the space that is not used by columns with
582 explicit width (see L{setColumnWidth}) or with
583 natural width (no width nor expand ratio).
584
585 By default (without expand ratios) the excess space is divided
586 proportionally to columns natural widths.
587
588 Only expand ratios of visible columns are used in final calculations.
589
590 Column can either have a fixed width or expand ratio. The latter one
591 set is used.
592
593 A column with expand ratio is considered to be minimum width by
594 default (if no excess space exists). The minimum width is defined
595 by terminal implementation.
596
597 If terminal implementation supports re-sizable columns the column
598 becomes fixed width column if users resizes the column.
599
600 @param propertyId:
601 columns property id
602 @param expandRatio:
603 the expandRatio used to divide excess space for this column
604 """
605 if expandRatio < 0:
606 if propertyId in self._columnWidths:
607 del self._columnWidths[propertyId]
608 else:
609 self._columnWidths[propertyId] = float(expandRatio)
610
611
613 width = self._columnWidths.get(propertyId)
614 if (width is None) or (not isinstance(width, float)):
615 return -1
616
617 return float(width)
618
619
621 """Gets the pixel width of column
622
623 @return: width of column or -1 when value not set
624 """
625 if propertyId is None:
626
627
628 propertyId = self._ROW_HEADER_FAKE_PROPERTY_ID
629
630 width = self._columnWidths.get(propertyId)
631 if (width is None) or (not isinstance(width, int)):
632 return -1
633
634 return int(width)
635
636
637 - def getPageLength(self):
638 """Gets the page length.
639
640 Setting page length 0 disables paging.
641
642 @return: the length of one page.
643 """
644 return self._pageLength
645
646
647 - def setPageLength(self, pageLength):
648 """Sets the page length.
649
650 Setting page length 0 disables paging. The page length defaults to 15.
651
652 If Table has width set (L{setWidth} ) the client
653 side may update the page length automatically the correct value.
654
655 @param pageLength:
656 the length of one page.
657 """
658 if (pageLength >= 0) and (self._pageLength != pageLength):
659 self._pageLength = pageLength
660
661 self.refreshRowCache()
662
663
665 """This method adjusts a possible caching mechanism of table
666 implementation.
667
668 Table component may fetch and render some rows outside visible area.
669 With complex tables (for example containing layouts and components),
670 the client side may become unresponsive. Setting the value lower, UI
671 will become more responsive. With higher values scrolling in client
672 will hit server less frequently.
673
674 The amount of cached rows will be cacheRate multiplied with pageLength
675 L{setPageLength} both below and above visible area.
676
677 @param cacheRate:
678 a value over 0 (fastest rendering time). Higher value will
679 cache more rows on server (smoother scrolling). Default
680 value is 2.
681 """
682 if cacheRate < 0:
683 raise ValueError, 'cacheRate cannot be less than zero'
684
685 if self._cacheRate != cacheRate:
686 self._cacheRate = cacheRate
687 self.requestRepaint()
688
689
691 """@see: L{setCacheRate}
692
693 @return: the current cache rate value
694 """
695 return self._cacheRate
696
697
699 """Getter for property currentPageFirstItem.
700
701 @return: the Value of property currentPageFirstItem.
702 """
703
704 if isinstance(self.items, container.IIndexed):
705 index = self.getCurrentPageFirstItemIndex()
706 idd = None
707 if (index >= 0) and (index < len(self)):
708 idd = self.getIdByIndex(index)
709
710 if (idd is not None) and (idd != self._currentPageFirstItemId):
711 self._currentPageFirstItemId = idd
712
713
714 if self._currentPageFirstItemId is None:
715 self._currentPageFirstItemId = self.firstItemId()
716
717 return self._currentPageFirstItemId
718
719
722
723
724 - def setCurrentPageFirstItemId(self, currentPageFirstItemId):
725 """Setter for property currentPageFirstItemId.
726
727 @param currentPageFirstItemId:
728 the new value of property currentPageFirstItemId.
729 """
730
731 index = -1
732 if isinstance(self.items, container.IIndexed):
733 index = self.indexOfId(currentPageFirstItemId)
734 else:
735
736
737 idd = self.firstItemId()
738 while (idd is not None) and (idd != currentPageFirstItemId):
739 index += 1
740 idd = self.nextItemId(idd)
741 if idd is None:
742 index = -1
743
744
745 if index >= 0:
746
747
748
749 maxIndex = len(self) - self._pageLength
750 if maxIndex < 0:
751 maxIndex = 0
752
753 if index > maxIndex:
754 self.setCurrentPageFirstItemIndex(maxIndex)
755 return
756
757 self._currentPageFirstItemId = currentPageFirstItemId
758 self._currentPageFirstItemIndex = index
759
760
761 self.refreshRowCache()
762
763
766
767
769 """Gets the icon Resource for the specified column.
770
771 @param propertyId:
772 the propertyId identifying the column.
773 @return: the icon for the specified column; null if the column has
774 no icon set, or if the column is not visible.
775 """
776 return self._columnIcons.get(propertyId)
777
778
780 """Sets the icon Resource for the specified column.
781
782 Throws ValueError if the specified column is not
783 visible.
784
785 @param propertyId:
786 the propertyId identifying the column.
787 @param icon:
788 the icon Resource to set.
789 """
790 if icon is None:
791 if propertyId in self._columnIcons:
792 del self._columnIcons[propertyId]
793 else:
794 self._columnIcons[propertyId] = icon
795
796
797 self.requestRepaint()
798
799
818
819
821 """Sets the column header for the specified column;
822
823 @param propertyId:
824 the propertyId identifying the column.
825 @param header:
826 the header to set.
827 """
828 if header is None:
829 if propertyId in self._columnHeaders:
830 del self._columnHeaders[propertyId]
831 else:
832 self._columnHeaders[propertyId] = header
833
834 self.requestRepaint()
835
836
838 """Gets the specified column's alignment.
839
840 @param propertyId:
841 the propertyID identifying the column.
842 @return: the specified column's alignment if it as one; null otherwise.
843 """
844 a = self._columnAlignments.get(propertyId)
845 return self.ALIGN_LEFT if a is None else a
846
847
849 """Sets the specified column's alignment.
850
851 Throws IllegalArgumentException if the alignment is not one of
852 the following: L{ALIGN_LEFT}, L{ALIGN_CENTER} or L{ALIGN_RIGHT}
853
854 @param propertyId:
855 the propertyID identifying the column.
856 @param alignment:
857 the desired alignment.
858 """
859
860 if (alignment is not None
861 and (alignment is not self.ALIGN_LEFT)
862 and (alignment is not self.ALIGN_CENTER)
863 and (alignment is not self.ALIGN_RIGHT)):
864 raise ValueError('Column alignment \'' + alignment
865 + '\' is not supported.')
866
867 if (alignment is None) or (alignment == self.ALIGN_LEFT):
868 if propertyId in self._columnAlignments:
869 del self._columnAlignments[propertyId]
870 else:
871 self._columnAlignments[propertyId] = alignment
872
873
874
875 self.refreshRenderedCells()
876
877
879 """Checks if the specified column is collapsed.
880
881 @param propertyId:
882 the propertyID identifying the column.
883 @return: true if the column is collapsed; false otherwise;
884 """
885 return ((self._collapsedColumns is not None)
886 and (propertyId in self._collapsedColumns))
887
888
890 """Sets whether the specified column is collapsed or not.
891
892 @param propertyId:
893 the propertyID identifying the column.
894 @param collapsed:
895 the desired collapsedness.
896 @raise ValueError:
897 if column collapsing is not allowed
898 """
899 if not self.isColumnCollapsingAllowed():
900 raise ValueError, 'Column collapsing not allowed!'
901
902 if collapsed:
903 self._collapsedColumns.add(propertyId)
904 else:
905 if propertyId in self._collapsedColumns:
906 self._collapsedColumns.remove(propertyId)
907
908
909 self.refreshRowCache()
910
911
913 """Checks if column collapsing is allowed.
914
915 @return: true if columns can be collapsed; false otherwise.
916 """
917 return self._columnCollapsingAllowed
918
919
921 """Sets whether column collapsing is allowed or not.
922
923 @param collapsingAllowed:
924 specifies whether column collapsing is allowed.
925 """
926 self._columnCollapsingAllowed = collapsingAllowed
927
928 if not collapsingAllowed:
929 self._collapsedColumns.clear()
930
931
932
933 self.refreshRenderedCells()
934
935
937 """Checks if column reordering is allowed.
938
939 @return: true if columns can be reordered; false otherwise.
940 """
941 return self._columnReorderingAllowed
942
943
945 """Sets whether column reordering is allowed or not.
946
947 @param columnReorderingAllowed:
948 specifies whether column reordering is allowed.
949 """
950 if columnReorderingAllowed != self._columnReorderingAllowed:
951 self._columnReorderingAllowed = columnReorderingAllowed
952 self.requestRepaint()
953
954
956
957
958
959
960
961 if (columnOrder is None) or (not self.isColumnReorderingAllowed()):
962 return
963
964 newOrder = list()
965 for order in columnOrder:
966 if (order is not None) and (order in self._visibleColumns):
967 self._visibleColumns.remove(order)
968 newOrder.append(order)
969
970 for columnId in self._visibleColumns:
971 if columnId not in newOrder:
972 newOrder.append(columnId)
973
974 self._visibleColumns = newOrder
975
976
977 self.refreshRowCache()
978
979
981 """Getter for property currentPageFirstItem.
982
983 @return: the Value of property currentPageFirstItem.
984 """
985 return self._currentPageFirstItemIndex
986
987
988 - def setCurrentPageFirstItemIndex(self, newIndex,
989 needsPageBufferReset=True):
990 """Setter for property currentPageFirstItem.
991
992 @param newIndex:
993 the New value of property currentPageFirstItem.
994 """
995 if newIndex < 0:
996 newIndex = 0
997
998
999
1000 size = self.size()
1001
1002
1003
1004
1005 maxIndex = size - self._pageLength
1006 if maxIndex < 0:
1007 maxIndex = 0
1008
1009
1010 if newIndex > maxIndex:
1011 newIndex = maxIndex
1012
1013
1014 if isinstance(self.items, container.IIndexed):
1015 try:
1016 self._currentPageFirstItemId = self.getIdByIndex(newIndex)
1017 except IndexError:
1018 self._currentPageFirstItemId = None
1019
1020 self._currentPageFirstItemIndex = newIndex
1021 else:
1022
1023
1024
1025
1026 self._currentPageFirstItemId = self.firstItemId()
1027
1028
1029 while ((self._currentPageFirstItemIndex < newIndex)
1030 and (not self.isLastId(self._currentPageFirstItemId))):
1031 self._currentPageFirstItemIndex += 1
1032 self._currentPageFirstItemId = \
1033 self.nextItemId(self._currentPageFirstItemId)
1034
1035
1036 if self.isLastId(self._currentPageFirstItemId):
1037 self._currentPageFirstItemIndex = size - 1
1038
1039
1040 while ((self._currentPageFirstItemIndex > newIndex)
1041 and (not self.isFirstId(self._currentPageFirstItemId))):
1042 self._currentPageFirstItemIndex -= 1
1043 self._currentPageFirstItemId = \
1044 self.prevItemId(self._currentPageFirstItemId)
1045
1046
1047 if self.isFirstId(self._currentPageFirstItemId):
1048 self._currentPageFirstItemIndex = 0
1049
1050
1051 while ((self._currentPageFirstItemIndex < newIndex)
1052 and (not self.isLastId(self._currentPageFirstItemId))):
1053 self._currentPageFirstItemIndex += 1
1054 self._currentPageFirstItemId = \
1055 self.nextItemId(self._currentPageFirstItemId)
1056
1057
1058
1059 if self.isLastId(self._currentPageFirstItemId):
1060 newIndex = self._currentPageFirstItemIndex = size - 1
1061
1062 if needsPageBufferReset:
1063
1064 self.refreshRowCache()
1065
1066
1068 """Getter for property pageBuffering.
1069
1070 @deprecated: functionality is not needed in ajax rendering model
1071
1072 @return: the value of property pageBuffering.
1073 """
1074 return True
1075
1076
1077 - def setPageBufferingEnabled(self, pageBuffering):
1078 """Setter for property pageBuffering.
1079
1080 @deprecated: functionality is not needed in ajax rendering model
1081
1082 @param pageBuffering:
1083 the new value of property pageBuffering.
1084 """
1085 warn('functionality is not needed in ajax rendering model',
1086 DeprecationWarning)
1087
1088
1090 """Getter for property selectable.
1091
1092 The table is not selectable by default.
1093
1094 @return: the Value of property selectable.
1095 """
1096 return self._selectable
1097
1098
1100 """Setter for property selectable.
1101
1102 The table is not selectable by default.
1103
1104 @param selectable:
1105 the new value of property selectable.
1106 """
1107 if self._selectable != selectable:
1108 self._selectable = selectable
1109 self.requestRepaint()
1110
1111
1113 """Getter for property columnHeaderMode.
1114
1115 @return: the value of property columnHeaderMode.
1116 """
1117 return self._columnHeaderMode
1118
1119
1121 """Setter for property columnHeaderMode.
1122
1123 @param columnHeaderMode:
1124 the new value of property columnHeaderMode.
1125 """
1126 if (columnHeaderMode != self._columnHeaderMode
1127 and columnHeaderMode >= self.COLUMN_HEADER_MODE_HIDDEN
1128 and (columnHeaderMode <=
1129 self.COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID)):
1130 self._columnHeaderMode = columnHeaderMode
1131 self.requestRepaint()
1132
1133
1135 """Refreshes the rows in the internal cache. Only if
1136 L{resetPageBuffer} is called before this then all values are
1137 guaranteed to be recreated.
1138 """
1139 if self.getParent() is None:
1140 return
1141
1142 if not self._isContentRefreshesEnabled:
1143 return
1144
1145
1146 pagelen = self.getPageLength()
1147 firstIndex = self.getCurrentPageFirstItemIndex()
1148 rows = totalRows = self.size()
1149 if (rows > 0) and (firstIndex >= 0):
1150 rows -= firstIndex
1151
1152 if (pagelen > 0) and (pagelen < rows):
1153 rows = pagelen
1154
1155
1156 if (self._lastToBeRenderedInClient -
1157 self._firstToBeRenderedInClient > 0):
1158 rows = (self._lastToBeRenderedInClient -
1159 self._firstToBeRenderedInClient) + 1
1160
1161 if self._firstToBeRenderedInClient >= 0:
1162 if self._firstToBeRenderedInClient < totalRows:
1163 firstIndex = self._firstToBeRenderedInClient
1164 else:
1165 firstIndex = totalRows - 1
1166 else:
1167
1168 self._firstToBeRenderedInClient = firstIndex
1169
1170 if totalRows > 0:
1171 if (rows + firstIndex) > totalRows:
1172 rows = totalRows - firstIndex
1173 else:
1174 rows = 0
1175
1176 self._pageBuffer = self.getVisibleCellsNoCache(firstIndex, rows, True)
1177 if rows > 0:
1178 self._pageBufferFirstIndex = firstIndex
1179 self.setRowCacheInvalidated(True)
1180 self.requestRepaint()
1181
1182
1184 """Requests that the Table should be repainted as soon as possible.
1185
1186 Note that a C{Table} does not necessarily repaint its contents when
1187 this method has been called. See L{refreshRowCache} for forcing an
1188 update of the contents.
1189 """
1190
1191 super(Table, self).requestRepaint()
1192
1193
1195 totalCachedRows = len(self._pageBuffer[self.CELL_ITEMID])
1196 totalRows = self.size()
1197 firstIndexInPageBuffer = firstIndex - self._pageBufferFirstIndex
1198
1199
1200
1201
1202
1203 if firstIndexInPageBuffer + rows > totalCachedRows:
1204 rows = totalCachedRows - firstIndexInPageBuffer
1205
1206
1207
1208 self.unregisterComponentsAndPropertiesInRows(firstIndex, rows)
1209
1210
1211
1212 newCachedRowCount = totalCachedRows
1213 if newCachedRowCount + self._pageBufferFirstIndex > totalRows:
1214 newCachedRowCount = totalRows - self._pageBufferFirstIndex
1215
1216
1217
1218 firstAppendedRowInPageBuffer = totalCachedRows - rows
1219 firstAppendedRow = \
1220 firstAppendedRowInPageBuffer + self._pageBufferFirstIndex
1221
1222
1223
1224
1225 maxRowsToRender = totalRows - firstAppendedRow
1226 rowsToAdd = rows
1227 if rowsToAdd > maxRowsToRender:
1228 rowsToAdd = maxRowsToRender
1229
1230 cells = None
1231 if rowsToAdd > 0:
1232 cells = self.getVisibleCellsNoCache(firstAppendedRow, rowsToAdd,
1233 False)
1234
1235
1236
1237
1238 pbl = len(self._pageBuffer)
1239 newPageBuffer = [([None] * newCachedRowCount) for _ in range(pbl)]
1240 for i in range(pbl):
1241 for row in range(firstIndexInPageBuffer):
1242
1243 newPageBuffer[i][row] = self._pageBuffer[i][row]
1244
1245 for row in range(firstIndexInPageBuffer,
1246 firstAppendedRowInPageBuffer):
1247
1248 newPageBuffer[i][row] = self._pageBuffer[i][row + rows]
1249
1250 for row in range(firstAppendedRowInPageBuffer, newCachedRowCount):
1251
1252
1253 newPageBuffer[i][row] = cells[i][row - firstAppendedRowInPageBuffer]
1254
1255 self._pageBuffer = newPageBuffer
1256
1257
1259 cells = self.getVisibleCellsNoCache(firstIndex, rows, False)
1260 cacheIx = firstIndex - self._pageBufferFirstIndex
1261
1262 totalCachedRows = len(self._pageBuffer[self.CELL_ITEMID])
1263 end = min(cacheIx + rows, totalCachedRows)
1264 for ix in range(cacheIx, end):
1265 for i in range(len(self._pageBuffer)):
1266 self._pageBuffer[i][ix] = cells[i][ix - cacheIx]
1267 return cells
1268
1269
1271 """@param firstIndex:
1272 The position where new rows should be inserted
1273 @param rows:
1274 The maximum number of rows that should be inserted at
1275 position firstIndex. Less rows will be inserted if the
1276 page buffer is too small.
1277 """
1278 logger.debug(
1279 'Insert %d rows at index %d to existing page buffer requested'
1280 % (rows, firstIndex))
1281
1282
1283
1284 minPageBufferIndex = (self.getCurrentPageFirstItemIndex()
1285 - (self.getPageLength() * self.getCacheRate()))
1286 if minPageBufferIndex < 0:
1287 minPageBufferIndex = 0
1288
1289 maxPageBufferIndex = (self.getCurrentPageFirstItemIndex()
1290 + (self.getPageLength() * (1 + self.getCacheRate())))
1291 maxBufferSize = maxPageBufferIndex - minPageBufferIndex
1292
1293 if self.getPageLength() == 0:
1294
1295 maxBufferSize = len(self._pageBuffer[0]) + rows
1296
1297
1298
1299
1300 currentlyCachedRowCount = len(self._pageBuffer[self.CELL_ITEMID])
1301
1302
1303
1304
1305
1306
1307 firstIndexInPageBuffer = firstIndex - self._pageBufferFirstIndex
1308
1309
1310 if firstIndexInPageBuffer + rows > maxBufferSize:
1311 rows = maxBufferSize - firstIndexInPageBuffer
1312
1313
1314
1315
1316
1317
1318 firstCacheRowToRemoveInPageBuffer = firstIndexInPageBuffer
1319
1320
1321
1322
1323 numberOfOldRowsAfterInsertedRows = (maxBufferSize
1324 - firstIndexInPageBuffer - rows)
1325 if numberOfOldRowsAfterInsertedRows > 0:
1326 firstCacheRowToRemoveInPageBuffer += \
1327 numberOfOldRowsAfterInsertedRows
1328
1329 if firstCacheRowToRemoveInPageBuffer <= currentlyCachedRowCount:
1330
1331
1332 self.unregisterComponentsAndPropertiesInRows(
1333 firstCacheRowToRemoveInPageBuffer
1334 + self._pageBufferFirstIndex,
1335 (currentlyCachedRowCount
1336 - firstCacheRowToRemoveInPageBuffer)
1337 + self._pageBufferFirstIndex)
1338
1339
1340 newCachedRowCount = currentlyCachedRowCount
1341 if (maxBufferSize == 0) or (currentlyCachedRowCount < maxBufferSize):
1342 newCachedRowCount = currentlyCachedRowCount + rows
1343 if maxBufferSize > 0 and newCachedRowCount > maxBufferSize:
1344 newCachedRowCount = maxBufferSize
1345
1346
1347 cells = self.getVisibleCellsNoCache(firstIndex, rows, False)
1348
1349
1350
1351
1352 pbl = len(self._pageBuffer)
1353 newPageBuffer = [([None] * newCachedRowCount) for _ in range(pbl)]
1354
1355 for i in range(pbl):
1356 for row in range(firstIndexInPageBuffer):
1357
1358 newPageBuffer[i][row] = self._pageBuffer[i][row]
1359
1360 for row in range(firstIndexInPageBuffer,
1361 firstIndexInPageBuffer + rows):
1362
1363 newPageBuffer[i][row] = cells[i][row - firstIndexInPageBuffer]
1364
1365 for row in range(firstIndexInPageBuffer + rows, newCachedRowCount):
1366
1367 newPageBuffer[i][row] = self._pageBuffer[i][row - rows]
1368
1369 self._pageBuffer = newPageBuffer
1370
1371 logger.debug('Page Buffer now contains %d rows (%d - %d)' %
1372 (len(self._pageBuffer[self.CELL_ITEMID]),
1373 self._pageBufferFirstIndex,
1374 (self._pageBufferFirstIndex
1375 + len(self._pageBuffer[self.CELL_ITEMID]) - 1)))
1376
1377 return cells
1378
1379
1381 """Render rows with index "firstIndex" to "firstIndex+rows-1" to a new
1382 buffer.
1383
1384 Reuses values from the current page buffer if the rows are found there.
1385 """
1386 logger.debug('Render visible cells for rows %d - %d' %
1387 (firstIndex, (firstIndex + rows - 1)))
1388 colids = self.getVisibleColumns()
1389 cols = len(colids)
1390
1391 oldListenedProperties = self._listenedProperties
1392 oldVisibleComponents = self._visibleComponents
1393
1394 if replaceListeners:
1395
1396
1397 self._listenedProperties = set()
1398 self._visibleComponents = set()
1399
1400 cells = [([None] * rows) for _ in range(cols + self.CELL_FIRSTCOL)]
1401 if rows == 0:
1402 self.unregisterPropertiesAndComponents(oldListenedProperties,
1403 oldVisibleComponents)
1404 return cells
1405
1406
1407 if isinstance(self.items, container.IIndexed):
1408 idd = self.getIdByIndex(firstIndex)
1409 else:
1410 idd = self.firstItemId()
1411 for i in range(firstIndex):
1412 idd = self.nextItemId(idd)
1413
1414 headmode = self.getRowHeaderMode()
1415 iscomponent = [None] * cols
1416 for i in range(cols):
1417 iscomponent[i] = ((colids[i] in self._columnGenerators)
1418 or issubclass(self.getType(colids[i]), IComponent))
1419
1420 if (self._pageBuffer is not None
1421 and len(self._pageBuffer[self.CELL_ITEMID]) > 0):
1422 firstIndexNotInCache = (self._pageBufferFirstIndex
1423 + len(self._pageBuffer[self.CELL_ITEMID]))
1424 else:
1425 firstIndexNotInCache = -1
1426
1427
1428 filledRows = 0
1429 i = 0
1430 while i < rows and idd is not None:
1431 cells[self.CELL_ITEMID][i] = idd
1432 cells[self.CELL_KEY][i] = self.itemIdMapper.key(idd)
1433 if headmode != self.ROW_HEADER_MODE_HIDDEN:
1434 if headmode == self.ROW_HEADER_MODE_INDEX:
1435 cells[self.CELL_HEADER][i] = str(i + firstIndex + 1)
1436 else:
1437 cells[self.CELL_HEADER][i] = self.getItemCaption(idd)
1438
1439 cells[self.CELL_ICON][i] = self.getItemIcon(idd)
1440
1441 if self._rowGenerator is not None:
1442 generatedRow = self._rowGenerator.generateRow(self, idd)
1443 else:
1444 generatedRow = None
1445 cells[self.CELL_GENERATED_ROW][i] = generatedRow
1446
1447 collapsed = 0
1448 for j in range(cols):
1449 if self.isColumnCollapsed(colids[j]):
1450 collapsed += 1
1451 continue
1452 p = None
1453 value = ''
1454 isGeneratedRow = generatedRow is not None
1455 isGeneratedColumn = colids[j] in self._columnGenerators
1456 isGenerated = isGeneratedRow or isGeneratedColumn
1457
1458 if not isGenerated:
1459 p = self.getContainerProperty(idd, colids[j])
1460
1461 if isGeneratedRow:
1462 if generatedRow.isSpanColumns() and j > 0:
1463 value = None
1464 elif (generatedRow.isSpanColumns() and j == 0
1465 and isinstance(generatedRow.getValue(),IComponent)):
1466 value = generatedRow.getValue()
1467 elif len(generatedRow.getText()) > j:
1468 value = generatedRow.getText()[j]
1469 else:
1470
1471 index = firstIndex + i
1472 if (p is not None) or isGenerated:
1473 indexInOldBuffer = index - self._pageBufferFirstIndex
1474 if (index < firstIndexNotInCache
1475 and index >= self._pageBufferFirstIndex
1476 and self._pageBuffer[self.CELL_GENERATED_ROW][indexInOldBuffer] is None
1477 and self._pageBuffer[self.CELL_ITEMID][indexInOldBuffer] == idd):
1478
1479
1480
1481 value = self._pageBuffer[self.CELL_FIRSTCOL + j][indexInOldBuffer]
1482 if (not isGeneratedColumn and iscomponent[j]
1483 or (not isinstance(value, IComponent))):
1484 self.listenProperty(p, oldListenedProperties)
1485 elif isGeneratedColumn:
1486 cg = self._columnGenerators[colids[j]]
1487 value = cg.generateCell(self, idd, colids[j])
1488 if (value is not None
1489 and not isinstance(value, IComponent)
1490 and not isinstance(value, basestring)):
1491
1492
1493 value = str(value)
1494 elif iscomponent[j]:
1495 value = p.getValue()
1496 self.listenProperty(p, oldListenedProperties)
1497 elif p is not None:
1498 value = self.getPropertyValue(idd, colids[j], p)
1499
1500
1501
1502
1503
1504
1505 if not isinstance(value, IComponent):
1506 self.listenProperty(p, oldListenedProperties)
1507 else:
1508 value = self.getPropertyValue(idd, colids[j], None)
1509
1510 if isinstance(value, IComponent):
1511 self.registerComponent(value)
1512
1513 cells[self.CELL_FIRSTCOL + j - collapsed][i] = value
1514
1515
1516 if isinstance(self.items, container.IIndexed):
1517 index = firstIndex + i + 1
1518 if index < self.size():
1519 idd = self.getIdByIndex(index)
1520 else:
1521 idd = None
1522 else:
1523 idd = self.nextItemId(idd)
1524
1525 filledRows += 1
1526 i += 1
1527
1528
1529 if filledRows != len(cells[0]):
1530 temp = [[None] * filledRows] * len(cells)
1531 for i in range(len(cells)):
1532 for j in range(filledRows):
1533 temp[i][j] = cells[i][j]
1534 cells = temp
1535
1536 self.unregisterPropertiesAndComponents(oldListenedProperties,
1537 oldVisibleComponents)
1538
1539 return cells
1540
1541
1548
1549
1560
1561
1563 """@param firstIx:
1564 Index of the first row to process. Global index, not
1565 relative to page buffer.
1566 @param count:
1567 """
1568 logger.debug("Unregistering components in rows %d - %d" %
1569 (firstIx, (firstIx + count - 1)))
1570 colids = self.getVisibleColumns()
1571 if (self._pageBuffer is not None
1572 and len(self._pageBuffer[self.CELL_ITEMID]) > 0):
1573 bufSize = len(self._pageBuffer[self.CELL_ITEMID])
1574 ix = firstIx - self._pageBufferFirstIndex
1575 ix = 0 if ix < 0 else ix
1576 if ix < bufSize:
1577 count = bufSize - ix if count > bufSize - ix else count
1578 for i in range(count):
1579 for c in range(len(colids)):
1580 col = self.CELL_FIRSTCOL + c
1581 cellVal = self._pageBuffer[col][i + ix]
1582 if (isinstance(cellVal, IComponent)
1583 and cellVal in self._visibleComponents):
1584 self._visibleComponents.remove(cellVal)
1585 self.unregisterComponent(cellVal)
1586 else:
1587 r = self._pageBuffer[self.CELL_ITEMID][i + ix]
1588 p = self.getContainerProperty(r, colids[c])
1589 if (isinstance(p, IValueChangeNotifier)
1590 and p in self._listenedProperties):
1591 self._listenedProperties.remove(p)
1592 p.removeListener(self)
1593
1594
1597 """Helper method to remove listeners and maintain correct component
1598 hierarchy. Detaches properties and components if those are no more
1599 rendered in client.
1600
1601 @param oldListenedProperties:
1602 set of properties that where listened in last render
1603 @param oldVisibleComponents:
1604 set of components that where attached in last render
1605 """
1606 if oldVisibleComponents is not None:
1607 for c in oldVisibleComponents:
1608 if c not in self._visibleComponents:
1609 self.unregisterComponent(c)
1610
1611 if oldListenedProperties is not None:
1612 for o in oldListenedProperties:
1613 if o not in self._listenedProperties:
1614 o.removeListener(self, IValueChangeListener)
1615
1616
1618 """This method cleans up a IComponent that has been generated when
1619 Table is in editable mode. The component needs to be detached from
1620 its parent and if it is a field, it needs to be detached from its
1621 property data source in order to allow garbage collection to take
1622 care of removing the unused component from memory.
1623
1624 Override this method and C{getPropertyValue}
1625 with custom logic if you need to deal with buffered fields.
1626
1627 @see: L{getPropertyValue}
1628
1629 @param component:
1630 a set of components that should be unregistered.
1631 """
1632
1633
1634 component.setParent(None)
1635
1636
1637 if isinstance(component, IField):
1638 field = component
1639 associatedProperty = \
1640 self._associatedProperties.pop(component, None)
1641 if (associatedProperty is not None
1642 and field.getPropertyDataSource() == associatedProperty):
1643
1644
1645 field.setPropertyDataSource(None)
1646
1647
1649 """Refreshes the current page contents.
1650
1651 @deprecated: should not need to be used
1652 """
1653 warn('should not need to be used', DeprecationWarning)
1654
1655
1657 """Sets the row header mode.
1658
1659 The mode can be one of the following ones:
1660
1661 - L{ROW_HEADER_MODE_HIDDEN}: The row captions are hidden.
1662 - L{ROW_HEADER_MODE_ID}: Items Id-objects C{__str__}
1663 is used as row caption.
1664 - L{ROW_HEADER_MODE_ITEM}: Item-objects C{__str__}
1665 is used as row caption.
1666 - L{ROW_HEADER_MODE_PROPERTY}: Property set with
1667 L{setItemCaptionPropertyId} is used as row header.
1668 - L{ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID}: Items Id-objects
1669 C{__str__} is used as row header. If caption is explicitly
1670 specified, it overrides the id-caption.
1671 - L{ROW_HEADER_MODE_EXPLICIT}: The row headers must be explicitly
1672 specified.
1673 - L{ROW_HEADER_MODE_INDEX}: The index of the item is used as row
1674 caption. The index mode can only be used with the containers
1675 implementing C{container.IIndexed} interface.
1676
1677 The default value is L{ROW_HEADER_MODE_HIDDEN}
1678
1679 @param mode:
1680 the one of the modes listed above.
1681 """
1682 if self.ROW_HEADER_MODE_HIDDEN == mode:
1683 self._rowCaptionsAreHidden = True
1684 else:
1685 self._rowCaptionsAreHidden = False
1686 self.setItemCaptionMode(mode)
1687
1688
1689
1690 self.refreshRenderedCells()
1691
1692
1694 """Gets the row header mode.
1695
1696 @return: the row header mode.
1697 @see: L{setRowHeaderMode}
1698 """
1699 if self._rowCaptionsAreHidden:
1700 return self.ROW_HEADER_MODE_HIDDEN
1701 else:
1702 return self.getItemCaptionMode()
1703
1704
1706 """Adds the new row to table and fill the visible cells (except
1707 generated columns) with given values.
1708
1709 @param args: tuple of the form
1710 - (cells, itemId)
1711 1. the Object array that is used for filling the visible
1712 cells new row. The types must be settable to visible
1713 column property types.
1714 2. the Id the new row. If null, a new id is automatically
1715 assigned. If given, the table cant already have a item
1716 with given id.
1717 @return: Returns item id for the new row. Returns null if operation
1718 fails.
1719 """
1720 nargs = len(args)
1721 if nargs < 2:
1722 return super(Table, self).addItem(*args)
1723 elif nargs == 2:
1724 cells, itemId = args
1725
1726 availableCols = list()
1727 for idd in self._visibleColumns:
1728 if idd not in self._columnGenerators:
1729 availableCols.append(idd)
1730
1731
1732 if len(cells) != len(availableCols):
1733 return None
1734
1735
1736 if itemId is None:
1737 itemId = self.items.addItem()
1738 if itemId is None:
1739 return None
1740 item = self.items.getItem(itemId)
1741 else:
1742 item = self.items.addItem(itemId)
1743
1744 if item is None:
1745 return None
1746
1747
1748 for i in range(len(availableCols)):
1749 item.getItemProperty( availableCols[i] ).setValue(cells[i])
1750
1751 if not isinstance(self.items, container.IItemSetChangeNotifier):
1752 self.refreshRowCache()
1753
1754 return itemId
1755 else:
1756 raise ValueError, 'too many arguments'
1757
1758
1760 """Discards and recreates the internal row cache. Call this if you make
1761 changes that affect the rows but the information about the changes are
1762 not automatically propagated to the Table.
1763
1764 Do not call this e.g. if you have updated the data model through a
1765 Property. These types of changes are automatically propagated to the
1766 Table.
1767
1768 A typical case when this is needed is if you update a generator (e.g.
1769 CellStyleGenerator) and want to ensure that the rows are redrawn with
1770 new styles.
1771
1772 I{Note that calling this method is not cheap so avoid calling it
1773 unnecessarily.}
1774 """
1775 self.resetPageBuffer()
1776 self.refreshRenderedCells()
1777
1778
1780
1781 self.disableContentRefreshing()
1782
1783 if newDataSource is None:
1784 newDataSource = IndexedContainer()
1785
1786
1787
1788 if isinstance(newDataSource, container.IOrdered):
1789 super(Table, self).setContainerDataSource(newDataSource)
1790 else:
1791 raise NotImplementedError
1792
1793
1794
1795
1796 self._currentPageFirstItemId = None
1797 self._currentPageFirstItemIndex = 0
1798
1799
1800 if self._collapsedColumns is not None:
1801 self._collapsedColumns.clear()
1802
1803
1804
1805 col = list()
1806 for idd in self.getContainerPropertyIds():
1807 if (self._columnGenerators is None
1808 or idd not in self._columnGenerators):
1809 col.append(idd)
1810
1811
1812 if (self._columnGenerators is not None
1813 and len(self._columnGenerators) > 0):
1814 col.extend(self._columnGenerators.keys())
1815
1816 self.setVisibleColumns(col)
1817
1818
1819 self.resetPageBuffer()
1820
1821 self.enableContentRefreshing(True)
1822
1823
1825 """Gets items ids from a range of key values
1826 """
1827 ids = set()
1828 for _ in range(length):
1829
1830
1831 assert itemId is not None
1832 ids.add(itemId)
1833 itemId = self.nextItemId(itemId)
1834 return ids
1835
1836
1838 """Handles selection if selection is a multiselection
1839
1840 @param variables:
1841 The variables
1842 """
1843 ka = variables.get('selected')
1844 ranges = variables.get('selectedRanges')
1845
1846 renderedItemIds = self.getCurrentlyRenderedItemIds()
1847
1848 newValue = set(self.getValue())
1849
1850 if 'clearSelections' in variables:
1851
1852 newValue.clear()
1853 else:
1854
1855
1856 newValue = newValue.difference(renderedItemIds)
1857
1858
1859
1860
1861 for i in range(len(ka)):
1862
1863 idd = self.itemIdMapper.get( ka[i] )
1864 if (not self.isNullSelectionAllowed()
1865 and (idd is None)
1866 or (idd == self.getNullSelectionItemId())):
1867
1868 self.requestRepaint()
1869 elif idd is not None and self.containsId(idd):
1870 newValue.add(idd)
1871
1872
1873 if ranges is not None:
1874 for rnge in ranges:
1875 if len(rnge) > 0:
1876 split = rnge.split('-')
1877 startItemId = self.itemIdMapper.get(split[0])
1878 length = int(split[1])
1879 ids = self.getItemIdsInRange(startItemId, length)
1880 newValue.update(ids)
1881
1882 if not self.isNullSelectionAllowed() and len(newValue) == 0:
1883
1884 self.requestRepaint()
1885 return
1886
1887 self.setValue(newValue, True)
1888
1889
1891 ids = set()
1892 if self._pageBuffer is not None:
1893 for i in range(len( self._pageBuffer[self.CELL_ITEMID] )):
1894 ids.add( self._pageBuffer[self.CELL_ITEMID][i] )
1895 return ids
1896
1897
1898
1900 """Invoked when the value of a variable has changed.
1901
1902 @see: L{Select.changeVariables}
1903 """
1904 clientNeedsContentRefresh = False
1905
1906 self.handleClickEvent(variables)
1907
1908 self.handleColumnResizeEvent(variables)
1909
1910 self.handleColumnWidthUpdates(variables)
1911
1912 self.disableContentRefreshing()
1913
1914 if not self.isSelectable() and 'selected' in variables:
1915
1916
1917 variables = dict(variables)
1918 del variables['selected']
1919
1920
1921
1922 elif (self.isSelectable()
1923 and self.isMultiSelect()
1924 and 'selected' in variables
1925 and self._multiSelectMode == MultiSelectMode.DEFAULT):
1926 self.handleSelectedItems(variables)
1927 variables = dict(variables)
1928 del variables['selected']
1929
1930 super(Table, self).changeVariables(source, variables)
1931
1932
1933 if 'pagelength' in variables:
1934
1935 self._pageLength = variables.get('pagelength')
1936
1937
1938 if 'firstvisible' in variables:
1939 value = variables.get('firstvisible')
1940 if value is not None:
1941 self.setCurrentPageFirstItemIndex(int(value), False)
1942
1943
1944 if 'reqfirstrow' in variables or 'reqrows' in variables:
1945
1946 try:
1947 self._firstToBeRenderedInClient = \
1948 variables.get('firstToBeRendered')
1949 self._lastToBeRenderedInClient = \
1950 variables.get('lastToBeRendered')
1951 except Exception:
1952
1953 logger.info('Could not parse the first and/or last rows.')
1954
1955
1956
1957 if not self._containerChangeToBeRendered:
1958 value = variables.get('reqfirstrow')
1959 if value is not None:
1960 self._reqFirstRowToPaint = int(value)
1961 value = variables.get('reqrows')
1962 if value is not None:
1963 self._reqRowsToPaint = int(value)
1964
1965 if (self._reqFirstRowToPaint
1966 + self._reqRowsToPaint > self.size()):
1967 self._reqRowsToPaint = \
1968 self.size() - self._reqFirstRowToPaint
1969
1970 logger.debug("Client wants rows %d - %d" %
1971 (self._reqFirstRowToPaint,
1972 (self._reqFirstRowToPaint + self._reqRowsToPaint - 1)))
1973 clientNeedsContentRefresh = True
1974
1975 if not self._sortDisabled:
1976
1977 doSort = False
1978 if 'sortcolumn' in variables:
1979 colId = variables.get('sortcolumn')
1980 if (colId is not None
1981 and not ('' == colId)
1982 and not ('null' == colId)):
1983 idd = self._columnIdMap.get(colId)
1984 self.setSortContainerPropertyId(idd, False)
1985 doSort = True
1986
1987 if 'sortascending' in variables:
1988 state = bool(variables.get('sortascending'))
1989 if state != self._sortAscending:
1990 self.setSortAscending(state, False)
1991 doSort = True
1992
1993 if doSort:
1994 self.sort()
1995 self.resetPageBuffer()
1996
1997
1998
1999 if self.isColumnCollapsingAllowed():
2000 if 'collapsedcolumns' in variables:
2001 try:
2002 ids = variables.get('collapsedcolumns')
2003 for col in self._visibleColumns:
2004 self.setColumnCollapsed(col, False)
2005
2006 for i in range(len(ids)):
2007 idd = self._columnIdMap.get( str(ids[i]) )
2008 self.setColumnCollapsed(idd, True)
2009 except Exception:
2010
2011 logger.info('Could not determine column collapsing state')
2012
2013 clientNeedsContentRefresh = True
2014
2015 if self.isColumnReorderingAllowed():
2016 if 'columnorder' in variables:
2017 try:
2018 ids = variables['columnorder']
2019
2020 idsTemp = [None] * len(ids)
2021 for i in range(len(ids)):
2022 idsTemp[i] = self._columnIdMap.get( str(ids[i]) )
2023
2024 self.setColumnOrder(idsTemp)
2025 if self.hasListeners( ColumnReorderEvent ):
2026 self.fireEvent( ColumnReorderEvent(self) )
2027 except Exception:
2028
2029 logger.info('Could not determine column reordering state')
2030
2031 clientNeedsContentRefresh = True
2032
2033 self.enableContentRefreshing(clientNeedsContentRefresh)
2034
2035
2036 if 'action' in variables:
2037 st = variables.get('action').split(',')
2038 if len(st) == 2:
2039 itemId = self.itemIdMapper.get(st[0].strip())
2040 action = self._actionMapper.get(st[1].strip())
2041 if (action is not None
2042 and (itemId is None)
2043 or self.containsId(itemId)
2044 and self._actionHandlers is not None):
2045 for ah in self._actionHandlers:
2046 ah.handleAction(action, self, itemId)
2047
2048
2050 """Handles click event.
2051 """
2052
2053 if 'clickEvent' in variables:
2054 key = variables.get('clickedKey')
2055 itemId = self.itemIdMapper.get(key)
2056 propertyId = None
2057 colkey = variables.get('clickedColKey')
2058
2059 if colkey is not None:
2060 propertyId = self._columnIdMap.get(colkey)
2061
2062 evt = MouseEventDetails.deSerialize(variables.get('clickEvent'))
2063 item = self.getItem(itemId)
2064 if item is not None:
2065 event = ItemClickEvent(self, item, itemId, propertyId, evt)
2066 self.fireEvent(event)
2067
2068
2069 elif 'headerClickEvent' in variables:
2070 details = MouseEventDetails.deSerialize(
2071 variables.get('headerClickEvent'))
2072 cid = variables.get('headerClickCID')
2073 propertyId = None
2074 if cid is not None:
2075 propertyId = self._columnIdMap.get(str(cid))
2076 self.fireEvent( HeaderClickEvent(self, propertyId, details) )
2077
2078
2079 elif 'footerClickEvent' in variables:
2080 details = MouseEventDetails.deSerialize(
2081 variables.get('footerClickEvent'))
2082 cid = variables.get('footerClickCID')
2083 propertyId = None
2084 if cid is not None:
2085 propertyId = self._columnIdMap.get(str(cid))
2086 self.fireEvent( FooterClickEvent(self, propertyId, details) )
2087
2088
2090 """Handles the column resize event sent by the client.
2091 """
2092 if 'columnResizeEventColumn' in variables:
2093 cid = variables.get('columnResizeEventColumn')
2094 propertyId = None
2095 if cid is not None:
2096 propertyId = self._columnIdMap.get( str(cid) )
2097
2098 prev = variables.get('columnResizeEventPrev')
2099 previousWidth = -1
2100 if prev is not None:
2101 previousWidth = int( str(prev) )
2102
2103 curr = variables.get('columnResizeEventCurr')
2104 currentWidth = -1
2105 if curr is not None:
2106 currentWidth = int( str(curr) )
2107
2108 self.fireColumnResizeEvent(propertyId, previousWidth,
2109 currentWidth)
2110
2111
2120
2121
2123 if 'columnWidthUpdates' in variables:
2124 events = variables.get('columnWidthUpdates')
2125 for string in events:
2126 if string:
2127 eventDetails = string.split(':')
2128 propertyId = self._columnIdMap.get( eventDetails[0] )
2129 if propertyId is None:
2130 propertyId = self._ROW_HEADER_FAKE_PROPERTY_ID
2131
2132 width = int( eventDetails[1] )
2133 self.setColumnWidth(propertyId, width)
2134
2135
2137 """Go to mode where content updates are not done. This is due we want
2138 to bypass expensive content for some reason (like when we know we may
2139 have other content changes on their way).
2140
2141 @return: true if content refresh flag was enabled prior this call
2142 """
2143 wasDisabled = self._isContentRefreshesEnabled
2144 self._isContentRefreshesEnabled = False
2145 return wasDisabled
2146
2147
2148 - def enableContentRefreshing(self, refreshContent):
2149 """Go to mode where content content refreshing has effect.
2150
2151 @param refreshContent:
2152 true if content refresh needs to be done
2153 """
2154 self._isContentRefreshesEnabled = True
2155 if refreshContent:
2156 self.refreshRenderedCells()
2157
2158 self.requestRepaint()
2159
2160
2161 - def paintContent(self, target):
2162
2163
2164 actionSet = self.findAndPaintBodyActions(target)
2165
2166 cells = self.getVisibleCells()
2167 rows = self.findNumRowsToPaint(target, cells)
2168
2169 total = self.size()
2170 if self.shouldHideNullSelectionItem():
2171 total -= 1
2172 rows -= 1
2173
2174
2175 self.paintTableAttributes(target, rows, total)
2176
2177 self.paintVisibleColumnOrder(target)
2178
2179
2180 if (self.isPartialRowUpdate() and self._painted
2181 and not target.isFullRepaint()):
2182 self.paintPartialRowUpdate(target, actionSet)
2183
2184
2185
2186
2187
2188
2189
2190 pageBufferLastIndex = (self._pageBufferFirstIndex
2191 + self._pageBuffer[self.CELL_ITEMID].length) - 1
2192 target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_FIRST,
2193 self._pageBufferFirstIndex)
2194 target.addAttribute(VScrollTable.ATTRIBUTE_PAGEBUFFER_LAST,
2195 pageBufferLastIndex)
2196 elif target.isFullRepaint() or self.isRowCacheInvalidated():
2197 self.paintRows(target, cells, actionSet)
2198 self.setRowCacheInvalidated(False)
2199
2200 self.paintSorting(target)
2201
2202 self.resetVariablesAndPageBuffer(target)
2203
2204
2205 self.paintActions(target, actionSet)
2206
2207 self.paintColumnOrder(target)
2208
2209
2210 self.paintAvailableColumns(target)
2211
2212 self.paintVisibleColumns(target)
2213
2214 if self._dropHandler is not None:
2215 self._dropHandler.getAcceptCriterion().paint(target)
2216
2217 self._painted = True
2218
2219
2221 self._rowCacheInvalidated = invalidated
2222
2223
2225 return self._rowCacheInvalidated
2226
2227
2231
2232
2256
2257
2259 iscomponent = self.findCellsWithComponents()
2260
2261 firstIx = self.getFirstAddedItemIndex()
2262 count = self.getAddedRowCount()
2263
2264 target.startTag('prows')
2265
2266 if not self.shouldHideAddedRows():
2267 logger.debug('Paint rows for add. Index: %d, count: %d.' %
2268 (firstIx, count))
2269
2270
2271 cells = self.getVisibleCellsInsertIntoCache(firstIx, count)
2272 if len(cells[0]) < count:
2273
2274
2275 target.addAttribute('delbelow', True)
2276 count = len(cells[0])
2277
2278 for indexInRowbuffer in range(count):
2279 itemId = cells[self.CELL_ITEMID][indexInRowbuffer]
2280 if self.shouldHideNullSelectionItem():
2281
2282
2283 continue
2284
2285 self.paintRow(target, cells, self.isEditable(), actionSet,
2286 iscomponent, indexInRowbuffer, itemId)
2287 else:
2288 logger.debug('Paint rows for remove. Index: %d, count: %d.' %
2289 (firstIx, count))
2290 self.removeRowsFromCacheAndFillBottom(firstIx, count)
2291 target.addAttribute('hide', True)
2292
2293 target.addAttribute('firstprowix', firstIx)
2294 target.addAttribute('numprows', count)
2295 target.endTag('prows')
2296
2297
2299 """Subclass and override this to enable partial row updates and
2300 additions, which bypass the normal caching mechanism. This is useful
2301 for e.g. TreeTable.
2302
2303 @return: true if this update is a partial row update, false if not.
2304 For plain Table it is always false.
2305 """
2306 return False
2307
2308
2310 """Subclass and override this to enable partial row additions,
2311 bypassing the normal caching mechanism. This is useful for e.g.
2312 TreeTable, where expanding a node should only fetch and add the
2313 items inside of that node.
2314
2315 @return: The index of the first added item. For plain Table it
2316 is always 0.
2317 """
2318 return 0
2319
2320
2322 """Subclass and override this to enable partial row additions,
2323 bypassing the normal caching mechanism. This is useful for e.g.
2324 TreeTable, where expanding a node should only fetch and add the
2325 items inside of that node.
2326
2327 @return: the number of rows to be added, starting at the index
2328 returned by L{getFirstAddedItemIndex}. For plain Table
2329 it is always 0.
2330 """
2331 return 0
2332
2333
2335 """Subclass and override this to enable removing of rows, bypassing
2336 the normal caching and lazy loading mechanism. This is useful for e.g.
2337 TreeTable, when you need to hide certain rows as a node is collapsed.
2338
2339 This should return true if the rows pointed to by
2340 L{getFirstAddedItemIndex} and L{getAddedRowCount} should be hidden
2341 instead of added.
2342
2343 @return: whether the rows to add (see L{getFirstAddedItemIndex}
2344 and L{getAddedRowCount}) should be added or hidden. For
2345 plain Table it is always false.
2346 """
2347 return False
2348
2349
2351 """Subclass and override this to enable partial row updates, bypassing
2352 the normal caching and lazy loading mechanism. This is useful for
2353 updating the state of certain rows, e.g. in the TreeTable the collapsed
2354 state of a single node is updated using this mechanism.
2355
2356 @return: the index of the first item to be updated. For plain Table it
2357 is always 0.
2358 """
2359 return 0
2360
2361
2363 """Subclass and override this to enable partial row updates, bypassing
2364 the normal caching and lazy loading mechanism. This is useful for
2365 updating the state of certain rows, e.g. in the TreeTable the collapsed
2366 state of a single node is updated using this mechanism.
2367
2368 @return: the number of rows to update, starting at the index returned
2369 by L{getFirstUpdatedItemIndex}. For plain table it is always
2370 0.
2371 """
2372 return 0
2373
2374
2376 self.paintTabIndex(target)
2377 self.paintDragMode(target)
2378 self.paintSelectMode(target)
2379
2380 if self._cacheRate != self._CACHE_RATE_DEFAULT:
2381 target.addAttribute('cr', self._cacheRate)
2382
2383 target.addAttribute('cols', len(self.getVisibleColumns()))
2384 target.addAttribute('rows', rows)
2385
2386 if self._reqFirstRowToPaint >= 0:
2387 target.addAttribute('firstrow', self._reqFirstRowToPaint)
2388 else:
2389 target.addAttribute('firstrow', self._firstToBeRenderedInClient)
2390
2391 target.addAttribute('totalrows', total)
2392
2393 if self.getPageLength() != 0:
2394 target.addAttribute('pagelength', self.getPageLength())
2395
2396 if self.areColumnHeadersEnabled():
2397 target.addAttribute('colheaders', True)
2398
2399 if self.rowHeadersAreEnabled():
2400 target.addAttribute('rowheaders', True)
2401
2402 target.addAttribute('colfooters', self._columnFootersVisible)
2403
2404
2405 if ((self.getCurrentPageFirstItemIndex() != 0)
2406 or (self.getPageLength() > 0)):
2407 target.addVariable(self, 'firstvisible',
2408 self.getCurrentPageFirstItemIndex())
2409
2410
2412 """Resets and paints "to be painted next" variables. Also reset
2413 pageBuffer"""
2414 self._reqFirstRowToPaint = -1
2415 self._reqRowsToPaint = -1
2416 self._containerChangeToBeRendered = False
2417 target.addVariable(self, 'reqrows', self._reqRowsToPaint)
2418 target.addVariable(self, 'reqfirstrow', self._reqFirstRowToPaint)
2419
2420
2423
2424
2426 target.startTag('visiblecolumns')
2427 if self.rowHeadersAreEnabled():
2428 target.startTag('column')
2429 target.addAttribute('cid', self._ROW_HEADER_COLUMN_KEY)
2430 self.paintColumnWidth(target, self._ROW_HEADER_FAKE_PROPERTY_ID)
2431 target.endTag('column')
2432
2433 sortables = self.getSortableContainerPropertyIds()
2434 for colId in self._visibleColumns:
2435 if colId is not None:
2436 target.startTag('column')
2437 target.addAttribute('cid', self._columnIdMap.key(colId))
2438
2439 head = self.getColumnHeader(colId)
2440 if head is not None:
2441 target.addAttribute('caption', head)
2442 else:
2443 target.addAttribute('caption', '')
2444
2445 foot = self.getColumnFooter(colId)
2446 if foot is not None:
2447 target.addAttribute('fcaption', foot)
2448 else:
2449 target.addAttribute('fcaption', '')
2450
2451 if self.isColumnCollapsed(colId):
2452 target.addAttribute('collapsed', True)
2453
2454 if self.areColumnHeadersEnabled():
2455 if self.getColumnIcon(colId) is not None:
2456 target.addAttribute('icon', self.getColumnIcon(colId))
2457 if colId in sortables:
2458 target.addAttribute('sortable', True)
2459
2460 if not (self.ALIGN_LEFT == self.getColumnAlignment(colId)):
2461 target.addAttribute('align', self.getColumnAlignment(colId))
2462
2463 self.paintColumnWidth(target, colId)
2464 target.endTag('column')
2465 target.endTag('visiblecolumns')
2466
2467
2469 if self._columnCollapsingAllowed:
2470 collapsedCols = set()
2471 for colId in self._visibleColumns:
2472 if self.isColumnCollapsed(colId):
2473 collapsedCols.add(colId)
2474
2475 collapsedKeys = [None] * len(collapsedCols)
2476 nextColumn = 0
2477 for colId in self._visibleColumns:
2478 if self.isColumnCollapsed(colId):
2479 collapsedKeys[nextColumn] = self._columnIdMap.key(colId)
2480 nextColumn += 1
2481 target.addVariable(self, 'collapsedcolumns', collapsedKeys)
2482
2483
2499
2500
2502 if self._columnReorderingAllowed:
2503 colorder = [None] * len(self._visibleColumns)
2504 i = 0
2505 for colId in self._visibleColumns:
2506 colorder[i] = self._columnIdMap.key(colId)
2507 i += 1
2508 target.addVariable(self, 'columnorder', colorder)
2509
2510
2517
2518
2519 - def paintRows(self, target, cells, actionSet):
2520 iscomponent = self.findCellsWithComponents()
2521
2522 target.startTag('rows')
2523
2524
2525 start = 0
2526 if (self._reqFirstRowToPaint != -1
2527 and self._firstToBeRenderedInClient != -1):
2528 start = self._reqFirstRowToPaint - self._firstToBeRenderedInClient
2529
2530 end = len(cells[0])
2531 if self._reqRowsToPaint != -1:
2532 end = start + self._reqRowsToPaint
2533
2534
2535 if (self._lastToBeRenderedInClient != -1
2536 and self._lastToBeRenderedInClient < end):
2537 end = self._lastToBeRenderedInClient + 1
2538
2539 if (start > len(cells[self.CELL_ITEMID])) or (start < 0):
2540 start = 0
2541
2542 for indexInRowbuffer in range(start, end):
2543 itemId = cells[self.CELL_ITEMID][indexInRowbuffer]
2544
2545 if self.shouldHideNullSelectionItem():
2546
2547 continue
2548
2549 self.paintRow(target, cells, self.isEditable(), actionSet,
2550 iscomponent, indexInRowbuffer, itemId)
2551
2552 target.endTag('rows')
2553
2554
2556 isComponent = [None] * len(self._visibleColumns)
2557 ix = 0
2558 for columnId in self._visibleColumns:
2559 if columnId in self._columnGenerators:
2560 isComponent[ix] = True
2561 ix += 1
2562 else:
2563 colType = self.getType(columnId)
2564 isComponent[ix] = (colType is not None
2565 and issubclass(colType, IComponent))
2566 ix += 1
2567 return isComponent
2568
2569
2571
2572 visibleColOrder = list()
2573 for columnId in self._visibleColumns:
2574 if not self.isColumnCollapsed(columnId):
2575 visibleColOrder.append(self._columnIdMap.key(columnId))
2576 target.addAttribute('vcolorder', list(visibleColOrder))
2577
2578
2579 - def findAndPaintBodyActions(self, target):
2580 actionSet = OrderedSet()
2581 if self._actionHandlers is not None:
2582 keys = list()
2583 for ah in self._actionHandlers:
2584
2585
2586 actions = ah.getActions(None, self)
2587 if actions is not None:
2588 for action in actions:
2589 actionSet.add(action)
2590 keys.append(self._actionMapper.key(action))
2591
2592 target.addAttribute('alb', list(keys))
2593
2594 return actionSet
2595
2596
2601
2602
2604 if self._reqRowsToPaint >= 0:
2605 rows = self._reqRowsToPaint
2606 else:
2607 rows = len(cells[0])
2608 if self.alwaysRecalculateColumnWidths:
2609
2610
2611
2612
2613 target.addAttribute('recalcWidths', True)
2614 return rows
2615
2616
2636
2637
2655
2656
2661
2662
2667
2668
2677
2678
2681
2682
2683 - def paintRow(self, target, cells, iseditable, actionSet, iscomponent,
2684 indexInRowbuffer, itemId):
2685 target.startTag('tr')
2686
2687 self.paintRowAttributes(target, cells, actionSet, indexInRowbuffer,
2688 itemId)
2689
2690
2691 currentColumn = 0
2692 for columnId in self._visibleColumns:
2693 if (columnId is None) or self.isColumnCollapsed(columnId):
2694 continue
2695
2696
2697
2698
2699 if self._cellStyleGenerator is not None:
2700 cellStyle = self._cellStyleGenerator.getStyle(itemId, columnId)
2701 if (cellStyle is not None) and (cellStyle != ''):
2702 target.addAttribute(('style-'
2703 + self._columnIdMap.key(columnId)), cellStyle)
2704
2705 if ((iscomponent[currentColumn] or iseditable
2706 or cells[self.CELL_GENERATED_ROW][indexInRowbuffer] != None)
2707 and isinstance(cells[(self.CELL_FIRSTCOL
2708 + currentColumn)][indexInRowbuffer], IComponent)):
2709 c = cells[self.CELL_FIRSTCOL + currentColumn][indexInRowbuffer]
2710 if c is None:
2711 target.addText('')
2712 self.paintCellTooltips(target, itemId, columnId)
2713 else:
2714 c.paint(target)
2715 else:
2716 target.addText(cells[(self.CELL_FIRSTCOL
2717 + currentColumn)][indexInRowbuffer])
2718 self.paintCellTooltips(target, itemId, columnId)
2719
2720 currentColumn += 1
2721
2722 target.endTag('tr')
2723
2724
2733
2734
2742
2743
2745 """A method where extended Table implementations may add their
2746 custom attributes for rows.
2747 """
2748
2749 nargs = len(args)
2750 if nargs == 2:
2751 target, itemId = args
2752 pass
2753 elif nargs == 5:
2754 target, cells, actionSet, indexInRowbuffer, itemId = args
2755
2756 self.paintRowIcon(target, cells, indexInRowbuffer)
2757 self.paintRowHeader(target, cells, indexInRowbuffer)
2758 self.paintGeneratedRowInfo(target, cells, indexInRowbuffer)
2759
2760 target.addAttribute('key',
2761 int( str(cells[self.CELL_KEY][indexInRowbuffer]) ))
2762
2763 if self.isSelected(itemId):
2764 target.addAttribute('selected', True)
2765
2766
2767 if self._actionHandlers is not None:
2768 keys = list()
2769 for ah in self._actionHandlers:
2770 aa = ah.getActions(itemId, self)
2771
2772 if aa is not None:
2773 for ai in range(len(aa)):
2774 key = self._actionMapper.key(aa[ai])
2775 actionSet.add(aa[ai])
2776 keys.append(key)
2777
2778 target.addAttribute('al', keys)
2779
2780
2781
2782
2783 if self._cellStyleGenerator is not None:
2784 rowStyle = self._cellStyleGenerator.getStyle(itemId, None)
2785 if (rowStyle is not None) and (rowStyle != ''):
2786 target.addAttribute('rowstyle', rowStyle)
2787
2788 self.paintRowTooltips(target, itemId)
2789
2790 self.paintRowAttributes(target, itemId)
2791 else:
2792 raise ValueError, 'invalid number of arguments'
2793
2794
2804
2805
2811
2812
2818
2819
2821 """Gets the cached visible table contents.
2822
2823 @return: the cached visible table contents.
2824 """
2825 if self._pageBuffer is None:
2826 self.refreshRenderedCells()
2827 return self._pageBuffer
2828
2829
2831 """Gets the value of property.
2832
2833 By default if the table is editable the fieldFactory is used to
2834 create editors for table cells. Otherwise formatPropertyValue is
2835 used to format the value representation.
2836
2837 @param rowId:
2838 the Id of the row (same as item Id).
2839 @param colId:
2840 the Id of the column.
2841 @param prop:
2842 the Property to be presented.
2843 @return: Object either formatted value or IComponent for field.
2844 @see: L{setTableFieldFactory}
2845 """
2846 if self.isEditable() and (self._fieldFactory is not None):
2847 f = self._fieldFactory.createField(self.getContainerDataSource(),
2848 rowId, colId, self)
2849 if f is not None:
2850
2851
2852 self._associatedProperties[f] = prop
2853 f.setPropertyDataSource(prop)
2854 return f
2855
2856 return self.formatPropertyValue(rowId, colId, prop)
2857
2858
2874
2875
2876
2878 """Registers a new action handler for this container
2879
2880 @see: L{action.container.addActionHandler}
2881 """
2882 if actionHandler is not None:
2883
2884 if self._actionHandlers is None:
2885 self._actionHandlers = list()
2886 self._actionMapper = KeyMapper()
2887
2888 if actionHandler not in self._actionHandlers:
2889 self._actionHandlers.append(actionHandler)
2890
2891
2892
2893 self.refreshRenderedCells()
2894
2895
2897 """Removes a previously registered action handler for the contents
2898 of this container.
2899
2900 @see: L{container.removeActionHandler}
2901 """
2902 if (self._actionHandlers is not None
2903 and actionHandler in self._actionHandlers):
2904 self._actionHandlers.remove(actionHandler)
2905 if len(self._actionHandlers) == 0:
2906 self._actionHandlers = None
2907 self._actionMapper = None
2908
2909
2910
2911 self.refreshRenderedCells()
2912
2913
2915 """Removes all action handlers"""
2916 self._actionHandlers = None
2917 self._actionMapper = None
2918
2919
2920
2921 self.refreshRenderedCells()
2922
2923
2924
2925
2927 """Notifies this listener that the Property's value has changed.
2928
2929 Also listens changes in rendered items to refresh content area.
2930
2931 @see: L{property.IValueChangeListener.valueChange}
2932 """
2933 if event.getProperty() == self:
2934 super(Table, self).valueChange(event)
2935
2936 else:
2937 self.resetPageBuffer()
2938 self.refreshRenderedCells()
2939 self._containerChangeToBeRendered = True
2940
2941 self.requestRepaint()
2942
2943
2944 - def resetPageBuffer(self):
2945 """Clears the current page buffer. Call this before
2946 L{refreshRenderedCells} to ensure that all content is
2947 updated from the properties.
2948 """
2949 self._firstToBeRenderedInClient = -1
2950 self._lastToBeRenderedInClient = -1
2951 self._reqFirstRowToPaint = -1
2952 self._reqRowsToPaint = -1
2953 self._pageBuffer = None
2954
2955
2957 """Notifies the component that it is connected to an application.
2958
2959 @see: L{IComponent.attach}
2960 """
2961 super(Table, self).attach()
2962
2963 self.refreshRenderedCells()
2964
2965 if self._visibleComponents is not None:
2966 for c in self._visibleComponents:
2967 c.attach()
2968
2969
2971 """Notifies the component that it is detached from the application
2972
2973 @see: L{IComponent.detach}
2974 """
2975 super(Table, self).detach()
2976
2977 if self._visibleComponents is not None:
2978 for c in self._visibleComponents:
2979 c.detach()
2980
2981
2983 """Removes all Items from the IContainer.
2984
2985 @see: L{IContainer.removeAllItems}
2986 """
2987 self._currentPageFirstItemId = None
2988 self._currentPageFirstItemIndex = 0
2989 return super(Table, self).removeAllItems()
2990
2991
2993 """Removes the Item identified by C{ItemId} from the
2994 IContainer.
2995
2996 @see: L{IContainer.removeItem}
2997 """
2998 nextItemId = self.nextItemId(itemId)
2999 ret = super(Table, self).removeItem(itemId)
3000 if (ret and itemId is not None
3001 and itemId == self._currentPageFirstItemId):
3002 self._currentPageFirstItemId = nextItemId
3003
3004 if not isinstance(self.items, container.IItemSetChangeNotifier):
3005 self.refreshRowCache()
3006
3007 return ret
3008
3009
3011 """Removes a Property specified by the given Property ID from the
3012 IContainer.
3013
3014 @see: L{IContainer.removeContainerProperty}
3015 """
3016
3017 self._visibleColumns.remove(propertyId)
3018 if propertyId in self._columnAlignments:
3019 del self._columnAlignments[propertyId]
3020 if propertyId in self._columnIcons:
3021 del self._columnIcons[propertyId]
3022 if propertyId in self._columnHeaders:
3023 del self._columnHeaders[propertyId]
3024 if propertyId in self._columnFooters:
3025 del self._columnFooters[propertyId]
3026 return super(Table, self).removeContainerProperty(propertyId)
3027
3028
3030 """Adds a new property to the table and show it as a visible column.
3031
3032 @param args: tuple of the form
3033 - (propertyId, type, defaultValue)
3034 1. the Id of the proprty.
3035 2. the class of the property.
3036 3. the default value given for all existing items.
3037 - (propertyId, type, defaultValue, columnHeader, columnIcon,
3038 columnAlignment)
3039 1. the Id of the proprty
3040 2. the class of the property
3041 3. the default value given for all existing items
3042 4. the Explicit header of the column. If explicit header is
3043 not needed, this should be set null.
3044 5. the Icon of the column. If icon is not needed, this should
3045 be set null.
3046 6. the Alignment of the column. Null implies align left.
3047 @raise NotImplementedError:
3048 if the operation is not supported.
3049 @see: L{IContainer.addContainerProperty}
3050 """
3051 args = args
3052 nargs = len(args)
3053 if nargs == 3:
3054 propertyId, typ, defaultValue = args
3055 visibleColAdded = False
3056 if propertyId not in self._visibleColumns:
3057 self._visibleColumns.append(propertyId)
3058 visibleColAdded = True
3059 if not super(Table, self).addContainerProperty(propertyId,
3060 typ, defaultValue):
3061 if visibleColAdded:
3062 self._visibleColumns.remove(propertyId)
3063 return False
3064 if not isinstance(self.items, container.IPropertySetChangeNotifier):
3065 self.refreshRowCache()
3066 return True
3067 elif nargs == 6:
3068 (propertyId, typ, defaultValue, columnHeader, columnIcon,
3069 columnAlignment) = args
3070 if not self.addContainerProperty(propertyId, typ, defaultValue):
3071 return False
3072 self.setColumnAlignment(propertyId, columnAlignment)
3073 self.setColumnHeader(propertyId, columnHeader)
3074 self.setColumnIcon(propertyId, columnIcon)
3075 return True
3076 else:
3077 raise ValueError, 'invalid number of arguments'
3078
3079
3081 """Adds a generated column to the Table.
3082
3083 A generated column is a column that exists only in the Table, not as a
3084 property in the underlying IContainer. It shows up just as a regular
3085 column.
3086
3087 A generated column will override a property with the same id, so that
3088 the generated column is shown instead of the column representing the
3089 property. Note that getContainerProperty() will still get the real
3090 property.
3091
3092 Table will not listen to value change events from properties overridden
3093 by generated columns. If the content of your generated column depends
3094 on properties that are not directly visible in the table, attach value
3095 change listener to update the content on all depended properties.
3096 Otherwise your UI might not get updated as expected.
3097
3098 Also note that getVisibleColumns() will return the generated columns,
3099 while getContainerPropertyIds() will not.
3100
3101 @param idd:
3102 the id of the column to be added
3103 @param generatedColumn:
3104 the L{IColumnGenerator} to use for this column
3105 """
3106 if generatedColumn is None:
3107 raise ValueError, 'Can not add null as a GeneratedColumn'
3108 if idd in self._columnGenerators:
3109 raise ValueError, ('Can not add the same GeneratedColumn '
3110 'twice, id:' + idd)
3111 else:
3112 self._columnGenerators[idd] = generatedColumn
3113
3114
3115 if idd not in self._visibleColumns:
3116 self._visibleColumns.append(idd)
3117
3118 self.refreshRowCache()
3119
3120
3122 """Returns the IColumnGenerator used to generate the given column.
3123
3124 @param columnId:
3125 The id of the generated column
3126 @return: The IColumnGenerator used for the given columnId or null.
3127 """
3128 return self._columnGenerators.get(columnId)
3129
3130
3132 """Removes a generated column previously added with addGeneratedColumn.
3133
3134 @param columnId:
3135 id of the generated column to remove
3136 @return: true if the column could be removed (existed in the Table)
3137 """
3138 if columnId in self._columnGenerators:
3139 del self._columnGenerators[columnId]
3140
3141
3142 if columnId not in self.items.getContainerPropertyIds():
3143 self._visibleColumns.remove(columnId)
3144 self.refreshRowCache()
3145 return True
3146 else:
3147 return False
3148
3149
3151 """Returns item identifiers of the items which are currently rendered
3152 on the client.
3153
3154 Note, that some due to historical reasons the name of the method is bit
3155 misleading. Some items may be partly or totally out of the viewport of
3156 the table's scrollable area. Actully detecting rows which can be
3157 actually seen by the end user may be problematic due to the client
3158 server architecture. Using L{getCurrentPageFirstItemId}
3159 combined with L{getPageLength} may produce good enough
3160 estimates in some situations.
3161
3162 @see: L{Select.getVisibleItemIds}
3163 """
3164 visible = list()
3165 cells = self.getVisibleCells()
3166
3167 for i in range(len(cells[self.CELL_ITEMID])):
3168 visible.append(cells[self.CELL_ITEMID][i])
3169
3170 return visible
3171
3172
3200
3201
3203 """IContainer datasource property set change. Table must flush its
3204 buffers on change.
3205
3206 @see: L{container.PropertySetChangeListener.containerPropertySetChange}
3207 """
3208 self.disableContentRefreshing()
3209 super(Table, self).containerPropertySetChange(event)
3210
3211
3212
3213 containerPropertyIds = \
3214 self.getContainerDataSource().getContainerPropertyIds()
3215 newVisibleColumns = list(self._visibleColumns)
3216
3217 for idd in newVisibleColumns:
3218 if (idd not in containerPropertyIds
3219 or (idd in self._columnGenerators)):
3220 newVisibleColumns.remove()
3221 self.setVisibleColumns(list(newVisibleColumns))
3222
3223
3224 for idd in self._collapsedColumns:
3225 if (idd not in containerPropertyIds
3226 or (idd in self._columnGenerators)):
3227 self._collapsedColumns.remove()
3228
3229 self.resetPageBuffer()
3230 self.enableContentRefreshing(True)
3231
3232
3234 """Adding new items is not supported.
3235
3236 @raise NotImplementedError:
3237 if set to true.
3238 @see: L{Select.setNewItemsAllowed}
3239 """
3240 if allowNewOptions:
3241 raise NotImplementedError
3242
3243
3245 """Gets the ID of the Item following the Item that corresponds to
3246 itemId.
3247
3248 @see: L{IOrdered.nextItemId}
3249 """
3250 return self.items.nextItemId(itemId)
3251
3252
3254 """Gets the ID of the Item preceding the Item that corresponds to
3255 the itemId.
3256
3257 @see: L{IOrdered.prevItemId}
3258 """
3259 return self.items.prevItemId(itemId)
3260
3261
3263 """Gets the ID of the first Item in the IContainer.
3264
3265 @see: L{IOrdered.firstItemId}
3266 """
3267 return self.items.firstItemId()
3268
3269
3271 """Gets the ID of the last Item in the IContainer.
3272
3273 @see: L{IOrdered.lastItemId}
3274 """
3275 return self.items.lastItemId()
3276
3277
3279 """Tests if the Item corresponding to the given Item ID is the first
3280 Item in the IContainer.
3281
3282 @see: L{IOrdered.isFirstId}
3283 """
3284 return self.items.isFirstId(itemId)
3285
3286
3288 """Tests if the Item corresponding to the given Item ID is the last
3289 Item in the IContainer.
3290
3291 @see: L{IOrdered.isLastId}
3292 """
3293 return self.items.isLastId(itemId)
3294
3295
3310
3311
3313 """Sets the TableFieldFactory that is used to create editor for
3314 table cells.
3315
3316 The TableFieldFactory is only used if the Table is editable. By
3317 default the DefaultFieldFactory is used.
3318
3319 @param fieldFactory:
3320 the field factory to set.
3321 @see: L{isEditable}
3322 @see: L{DefaultFieldFactory}
3323 """
3324 self._fieldFactory = fieldFactory
3325
3326
3328 """Gets the TableFieldFactory that is used to create editor for
3329 table cells.
3330
3331 The IFieldFactory is only used if the Table is editable.
3332
3333 @return: TableFieldFactory used to create the IField instances.
3334 @see: L{isEditable}
3335 """
3336 return self._fieldFactory
3337
3338
3340 """Gets the IFieldFactory that is used to create editor for
3341 table cells.
3342
3343 The IFieldFactory is only used if the Table is editable.
3344
3345 @return: IFieldFactory used to create the IField instances.
3346 @see: L{isEditable}
3347 @deprecated: use L{getTableFieldFactory} instead
3348 """
3349 if isinstance(self._fieldFactory, IFieldFactory):
3350 return self._fieldFactory
3351
3352 return None
3353
3354
3356 """Sets the IFieldFactory that is used to create editor for table
3357 cells.
3358
3359 The IFieldFactory is only used if the Table is editable. By default
3360 the BaseFieldFactory is used.
3361
3362 @param fieldFactory:
3363 the field factory to set.
3364 @see: L{isEditable}
3365 @see: L{BaseFieldFactory}
3366 @deprecated: use L{setTableFieldFactory()} instead
3367 """
3368 warn('use setTableFieldFactory() instead', DeprecationWarning)
3369
3370 self._fieldFactory = fieldFactory
3371
3372 self.refreshRowCache()
3373
3374
3376 """Is table editable.
3377
3378 If table is editable a editor of type IField is created for each table
3379 cell. The assigned IFieldFactory is used to create the instances.
3380
3381 To provide custom editors for table cells create a class implementing
3382 the IFieldFactory interface, and assign it to table, and set the
3383 editable property to true.
3384
3385 @return: true if table is editable, false otherwise.
3386 @see: L{IField}
3387 @see: L{IFieldFactory}
3388 """
3389 return self._editable
3390
3391
3393 """Sets the editable property.
3394
3395 If table is editable a editor of type IField is created for each table
3396 cell. The assigned IFieldFactory is used to create the instances.
3397
3398 To provide custom editors for table cells create a class implementins
3399 the IFieldFactory interface, and assign it to table, and set the
3400 editable property to true.
3401
3402 @param editable:
3403 true if table should be editable by user.
3404 @see: L{IField}
3405 @see: L{IFieldFactory}
3406 """
3407 self._editable = editable
3408
3409 self.refreshRowCache()
3410
3411
3412 - def sort(self, *args):
3413 """Sorts the table.
3414
3415 @raise NotImplementedError:
3416 if the container data source does not implement
3417 container.ISortable
3418 @see: L{ISortable.sort}
3419 """
3420 nargs = len(args)
3421 if nargs == 0:
3422 if self.getSortContainerPropertyId() is None:
3423 return
3424 self.sort([self._sortContainerPropertyId], [self._sortAscending])
3425 elif nargs == 2:
3426 propertyId, ascending = args
3427 c = self.getContainerDataSource()
3428 if isinstance(c, container.ISortable):
3429 pageIndex = self.getCurrentPageFirstItemIndex()
3430 c.sort(propertyId, ascending)
3431 self.setCurrentPageFirstItemIndex(pageIndex)
3432 self.refreshRowCache()
3433 elif c is not None:
3434 raise NotImplementedError, \
3435 'Underlying Data does not allow sorting'
3436 else:
3437 raise ValueError, 'invalid number of arguments'
3438
3439
3450
3451
3453 """Gets the currently sorted column property ID.
3454
3455 @return: the IContainer property id of the currently sorted column.
3456 """
3457 return self._sortContainerPropertyId
3458
3459
3461 """Sets the currently sorted column property id. With
3462 doSort flag actual sorting may be bypassed.
3463
3464 @param propertyId:
3465 the IContainer property id of the currently sorted column.
3466 @param doSort:
3467 """
3468 if ((self._sortContainerPropertyId is not None
3469 and not (self._sortContainerPropertyId == propertyId))
3470 or (self._sortContainerPropertyId is None
3471 and propertyId is not None)):
3472 self._sortContainerPropertyId = propertyId
3473 if doSort:
3474 self.sort()
3475
3476
3477 self.refreshRenderedCells()
3478
3479
3481 """Is the table currently sorted in ascending order.
3482
3483 @return: C{True} if ascending, C{False} if descending.
3484 """
3485 return self._sortAscending
3486
3487
3489 """Sets the table in ascending order. With doSort flag actual
3490 sort can be bypassed.
3491
3492 @param ascending:
3493 C{True} if ascending, C{False} if descending.
3494 @param doSort:
3495 """
3496 if self._sortAscending != ascending:
3497 self._sortAscending = ascending
3498 if doSort:
3499 self.sort()
3500
3501
3502
3503 self.refreshRenderedCells()
3504
3505
3507 """Is sorting disabled altogether.
3508
3509 True iff no sortable columns are given even in the case where
3510 data source would support this.
3511
3512 @return: True iff sorting is disabled.
3513 """
3514 return self._sortDisabled
3515
3516
3518 """Disables the sorting altogether.
3519
3520 To disable sorting altogether, set to true. In this case no
3521 sortable columns are given even in the case where datasource
3522 would support this.
3523
3524 @param sortDisabled:
3525 True iff sorting is disabled.
3526 """
3527 if self._sortDisabled != sortDisabled:
3528 self._sortDisabled = sortDisabled
3529 self.requestRepaint()
3530
3531
3533 """Table does not support lazy options loading mode. Setting this
3534 true will throw NotImplementedError.
3535
3536 @see: L{Select.setLazyLoading}
3537 """
3538 if useLazyLoading:
3539 raise NotImplementedError, \
3540 'Lazy options loading is not supported by Table.'
3541
3542
3544 """Set cell style generator for Table.
3545
3546 @param cellStyleGenerator:
3547 New cell style generator or null to remove generator.
3548 """
3549 self._cellStyleGenerator = cellStyleGenerator
3550
3551
3552 self.refreshRenderedCells()
3553
3554
3556 """Get the current cell style generator."""
3557 return self._cellStyleGenerator
3558
3559
3561 """Adds a header click/footer click/column resize/column reorder
3562 listener which handles the click events when the user clicks on a
3563 column header cell in the Table.
3564
3565 The listener will receive events which contain information about which
3566 column was clicked and some details about the mouse event.
3567
3568 @param listener:
3569 The handler which should handle the events.
3570 """
3571 if (isinstance(listener, IColumnReorderListener) and
3572 (iface is None or issubclass(iface, IColumnReorderListener))):
3573 self.registerListener(
3574 VScrollTable.COLUMN_REORDER_EVENT_ID,
3575 ColumnReorderEvent, listener,
3576 COLUMN_REORDER_METHOD)
3577
3578 if (isinstance(listener, IColumnResizeListener) and
3579 (iface is None or issubclass(iface, IColumnResizeListener))):
3580 self.registerListener(
3581 VScrollTable.COLUMN_RESIZE_EVENT_ID,
3582 ColumnResizeEvent, listener,
3583 COLUMN_RESIZE_METHOD)
3584
3585 if (isinstance(listener, IFooterClickListener) and
3586 (iface is None or issubclass(iface, IFooterClickListener))):
3587 self.registerListener(
3588 VScrollTable.FOOTER_CLICK_EVENT_ID,
3589 FooterClickEvent, listener,
3590 FOOTER_CLICK_METHOD)
3591
3592 if (isinstance(listener, IHeaderClickListener) and
3593 (iface is None or issubclass(iface, IHeaderClickListener))):
3594 self.registerListener(
3595 VScrollTable.HEADER_CLICK_EVENT_ID,
3596 HeaderClickEvent, listener,
3597 HEADER_CLICK_METHOD)
3598
3599 if (isinstance(listener, IItemClickListener) and
3600 (iface is None or issubclass(iface, IItemClickListener))):
3601 self.registerListener(
3602 VScrollTable.ITEM_CLICK_EVENT_ID,
3603 ItemClickEvent, listener,
3604 ITEM_CLICK_METHOD)
3605
3606 super(Table, self).addListener(listener, iface)
3607
3608
3609 - def addCallback(self, callback, eventType=None, *args):
3610 if eventType is None:
3611 eventType = callback._eventType
3612
3613 if issubclass(eventType, ColumnReorderEvent):
3614 self.registerCallback(ColumnReorderEvent, callback,
3615 VScrollTable.COLUMN_REORDER_EVENT_ID, *args)
3616
3617 elif issubclass(eventType, ColumnResizeEvent):
3618 self.registerCallback(ColumnResizeEvent, callback,
3619 VScrollTable.COLUMN_RESIZE_EVENT_ID, *args)
3620
3621 elif issubclass(eventType, FooterClickEvent):
3622 self.registerCallback(FooterClickEvent, callback,
3623 VScrollTable.FOOTER_CLICK_EVENT_ID, *args)
3624
3625 elif issubclass(eventType, HeaderClickEvent):
3626 self.registerCallback(HeaderClickEvent, callback,
3627 VScrollTable.HEADER_CLICK_EVENT_ID, *args)
3628
3629 elif issubclass(eventType, ItemClickEvent):
3630 self.registerCallback(ItemClickEvent, callback,
3631 VScrollTable.ITEM_CLICK_EVENT_ID, *args)
3632
3633 else:
3634 super(Table, self).addCallback(callback, eventType, *args)
3635
3636
3638 """Removes a listener from the Table.
3639
3640 @param listener:
3641 The listener to remove
3642 """
3643 if (isinstance(listener, IColumnReorderListener) and
3644 (iface is None or issubclass(iface, IColumnReorderListener))):
3645 self.withdrawListener(
3646 VScrollTable.COLUMN_REORDER_EVENT_ID,
3647 ColumnReorderEvent, listener)
3648
3649 if (isinstance(listener, IColumnResizeListener) and
3650 (iface is None or issubclass(iface, IColumnResizeListener))):
3651 self.withdrawListener(
3652 VScrollTable.COLUMN_RESIZE_EVENT_ID,
3653 ColumnResizeEvent, listener)
3654
3655 if (isinstance(listener, IFooterClickListener) and
3656 (iface is None or issubclass(iface, IFooterClickListener))):
3657 self.withdrawListener(
3658 VScrollTable.FOOTER_CLICK_EVENT_ID,
3659 FooterClickEvent, listener)
3660
3661 if (isinstance(listener, IHeaderClickListener) and
3662 (iface is None or issubclass(iface, IHeaderClickListener))):
3663 self.withdrawListener(
3664 VScrollTable.HEADER_CLICK_EVENT_ID,
3665 HeaderClickEvent, listener)
3666
3667 if (isinstance(listener, IItemClickListener) and
3668 (iface is None or issubclass(iface, IItemClickListener))):
3669 self.withdrawListener(
3670 VScrollTable.ITEM_CLICK_EVENT_ID,
3671 ItemClickEvent, listener)
3672
3673 super(Table, self).removeListener(listener, iface)
3674
3675
3677 if eventType is None:
3678 eventType = callback._eventType
3679
3680 if issubclass(eventType, ColumnReorderEvent):
3681 self.withdrawCallback(ColumnReorderEvent, callback,
3682 VScrollTable.COLUMN_REORDER_EVENT_ID)
3683
3684 elif issubclass(eventType, ColumnResizeEvent):
3685 self.withdrawCallback(ColumnResizeEvent, callback,
3686 VScrollTable.COLUMN_RESIZE_EVENT_ID)
3687
3688 elif issubclass(eventType, FooterClickEvent):
3689 self.withdrawCallback(FooterClickEvent, callback,
3690 VScrollTable.FOOTER_CLICK_EVENT_ID)
3691
3692 elif issubclass(eventType, HeaderClickEvent):
3693 self.withdrawCallback(HeaderClickEvent, callback,
3694 VScrollTable.HEADER_CLICK_EVENT_ID)
3695
3696 elif issubclass(eventType, ItemClickEvent):
3697 self.withdrawCallback(ItemClickEvent, callback,
3698 VScrollTable.ITEM_CLICK_EVENT_ID)
3699
3700 else:
3701 super(Table, self).removeCallback(callback, eventType)
3702
3703
3712
3713
3729
3730
3732 """Sets the drag start mode of the Table. Drag start mode controls
3733 how Table behaves as a drag source.
3734 """
3735 self._dragMode = newDragMode
3736 self.requestRepaint()
3737
3738
3740 """@return: the current start mode of the Table. Drag start mode
3741 controls how Table behaves as a drag source.
3742 """
3743 return self._dragMode
3744
3745
3749
3750
3752 return self._dropHandler
3753
3754
3756 self._dropHandler = dropHandler
3757
3758
3761
3762
3764 """Sets the behavior of how the multi-select mode should behave when
3765 the table is both selectable and in multi-select mode.
3766
3767 Note, that on some clients the mode may not be respected. E.g. on
3768 touch based devices CTRL/SHIFT base selection method is invalid, so
3769 touch based browsers always use the L{MultiSelectMode.SIMPLE}.
3770
3771 @param mode:
3772 The select mode of the table
3773 """
3774 self._multiSelectMode = mode
3775 self.requestRepaint()
3776
3777
3779 """Returns the select mode in which multi-select is used.
3780
3781 @return: The multi select mode
3782 """
3783 return self._multiSelectMode
3784
3785
3794
3795
3812
3825
3826
3828 """Set the item description generator which generates tooltips for
3829 cells and rows in the Table
3830
3831 @param generator:
3832 The generator to use or null to disable
3833 """
3834 if generator != self._itemDescriptionGenerator:
3835 self._itemDescriptionGenerator = generator
3836
3837
3838 self.refreshRenderedCells()
3839
3840
3842 """Get the item description generator which generates tooltips for
3843 cells and rows in the Table.
3844 """
3845 return self._itemDescriptionGenerator
3846
3847
3854
3855
3857 """Assigns a row generator to the table. The row generator will be
3858 able to replace rows in the table when it is rendered.
3859
3860 @param generator:
3861 the new row generator
3862 """
3863 self._rowGenerator = generator
3864 self.refreshRowCache()
3865
3866
3868 """@return the current row generator"""
3869 return self._rowGenerator
3870
3873 """Modes that Table support as drag sourse."""
3874
3875
3876
3877 NONE = 'NONE'
3878
3879
3880 ROW = 'ROW'
3881
3882
3883
3884
3885
3886 MULTIROW = 'MULTIROW'
3887
3888 _values = [NONE, ROW, MULTIROW]
3889
3890 @classmethod
3893
3894 @classmethod
3897
3900 """Used to create "generated columns"; columns that exist only in the
3901 Table, not in the underlying IContainer. Implement this interface and pass
3902 it to Table.addGeneratedColumn along with an id for the column to be
3903 generated.
3904 """
3905
3907 """Called by Table when a cell in a generated column needs to be
3908 generated.
3909
3910 @param source:
3911 the source Table
3912 @param itemId:
3913 the itemId (aka rowId) for the of the cell to be generated
3914 @param columnId:
3915 the id for the generated column (as specified in
3916 addGeneratedColumn)
3917 @return: A L{IComponent} that should be rendered in the cell or a
3918 string that should be displayed in the cell. Other return
3919 values are not supported.
3920 """
3921 raise NotImplementedError
3922
3925 """Allow to define specific style on cells (and rows) contents. Implements
3926 this interface and pass it to Table.setCellStyleGenerator. Row styles are
3927 generated when porpertyId is null. The CSS class name that will be added
3928 to the cell content is C{v-table-cell-content-[style name]}, and
3929 the row style will be C{v-table-row-[style name]}.
3930 """
3931
3932 - def getStyle(self, itemId, propertyId):
3933 """Called by Table when a cell (and row) is painted.
3934
3935 @param itemId:
3936 The itemId of the painted cell
3937 @param propertyId:
3938 The propertyId of the cell, null when getting row style
3939 @return: The style name to add to this cell or row. (the CSS class
3940 name will be v-table-cell-content-[style name], or
3941 v-table-row-[style name] for rows)
3942 """
3943 raise NotImplementedError
3944
3947 """Concrete implementation of L{DataBoundTransferable} for data
3948 transferred from a table.
3949
3950 @see: L{DataBoundTransferable}.
3951 """
3952
3953 - def __init__(self, rawVariables, table):
3962
3963
3966
3967
3969 return self.getData('propertyId')
3970
3971
3974
3977 """Lazy loading accept criterion for Table. Accepted target rows are
3978 loaded from server once per drag and drop operation. Developer must
3979 override one method that decides on which rows the currently dragged
3980 data can be dropped.
3981
3982 Initially pretty much no data is sent to client. On first required
3983 criterion check (per drag request) the client side data structure is
3984 initialized from server and no subsequent requests requests are needed
3985 during that drag and drop operation.
3986 """
3987
3993
3994
3997
3998
3999 - def accept(self, dragEvent):
4013
4014
4016
4017
4018 arry = list(self._allowedItemIds)
4019 for i in range(len(arry)):
4020 key = self._table.itemIdMapper.key( arry[i] )
4021 arry[i] = key
4022
4023 target.addAttribute('allowedIds', arry)
4024
4025
4027 """@param dragEvent:
4028 @param table:
4029 the table for which the allowed item identifiers are
4030 defined
4031 @param visibleItemIds:
4032 the list of currently rendered item identifiers, accepted
4033 item id's need to be detected only for these visible items
4034 @return: the set of identifiers for items on which the dragEvent will
4035 be accepted
4036 """
4037 pass
4038
4041 """Interface for the listener for column header mouse click events. The
4042 headerClick method is called when the user presses a header column cell.
4043 """
4044
4046 """Called when a user clicks a header column cell
4047
4048 @param event:
4049 The event which contains information about the column and
4050 the mouse click event
4051 """
4052 raise NotImplementedError
4053
4054
4055 HEADER_CLICK_METHOD = getattr(IHeaderClickListener, 'headerClick')
4059 """Click event fired when clicking on the Table headers. The event
4060 includes a reference the the Table the event originated from, the property
4061 id of the column which header was pressed and details about the mouse
4062 event itself.
4063 """
4064
4068
4069
4071 """Gets the property id of the column which header was pressed
4072
4073 @return: The column propety id
4074 """
4075 return self._columnPropertyId
4076
4091
4092
4093 FOOTER_CLICK_METHOD = getattr(IFooterClickListener, 'footerClick')
4123
4126 """Interface for listening to column resize events."""
4127
4129 """This method is triggered when the column has been resized
4130
4131 @param event:
4132 The event which contains the column property id, the
4133 previous width of the column and the current width of
4134 the column
4135 """
4136 raise NotImplementedError
4137
4138
4139 COLUMN_RESIZE_METHOD = getattr(IColumnResizeListener, 'columnResize')
4143 """This event is fired when a column is resized. The event contains the
4144 columns property id which was fired, the previous width of the column and
4145 the width of the column after the resize.
4146 """
4147
4148 - def __init__(self, source, propertyId, previous, current):
4149 """Constructor
4150
4151 @param source:
4152 The source of the event
4153 @param propertyId:
4154 The columns property id
4155 @param previous:
4156 The width in pixels of the column before the resize event
4157 @param current:
4158 The width in pixels of the column after the resize event
4159 """
4160 super(ColumnResizeEvent, self).__init__(source)
4161 self._previousWidth = previous
4162 self._currentWidth = current
4163 self._columnPropertyId = propertyId
4164
4165
4167 """Get the column property id of the column that was resized.
4168
4169 @return: The column property id
4170 """
4171 return self._columnPropertyId
4172
4173
4175 """Get the width in pixels of the column before the resize event
4176
4177 @return: Width in pixels
4178 """
4179 return self._previousWidth
4180
4181
4183 """Get the width in pixels of the column after the resize event
4184
4185 @return: Width in pixels
4186 """
4187 return self._currentWidth
4188
4191 """Interface for listening to column reorder events."""
4192
4194 """This method is triggered when the column has been reordered
4195 """
4196 raise NotImplementedError
4197
4198
4199 COLUMN_REORDER_METHOD = getattr(IColumnReorderListener, 'columnReorder')
4203 """This event is fired when a columns are reordered by the end user user."""
4204
4205
4207 """Constructor
4208
4209 @param source:
4210 The source of the event
4211 """
4212 super(ColumnReorderEvent, self).__init__(source)
4213
4216 """Row generators can be used to replace certain items in a table with a
4217 generated string. The generator is called each time the table is
4218 rendered, which means that new strings can be generated each time.
4219
4220 Row generators can be used for e.g. summary rows or grouping of items.
4221 """
4222
4224 """Called for every row that is painted in the Table. Returning a
4225 GeneratedRow object will cause the row to be painted based on the
4226 contents of the GeneratedRow. A generated row is by default styled
4227 similarly to a header or footer row.
4228
4229 The GeneratedRow data object contains the text that should be
4230 rendered in the row. The itemId in the container thus works only as a
4231 placeholder.
4232
4233 If GeneratedRow.setSpanColumns(true) is used, there will be one
4234 String spanning all columns (use setText("Spanning text")). Otherwise
4235 you can define one String per visible column.
4236
4237 If GeneratedRow.setRenderAsHtml(true) is used, the strings can
4238 contain HTML markup, otherwise all strings will be rendered as text
4239 (the default).
4240
4241 A "v-table-generated-row" CSS class is added to all generated rows.
4242 For custom styling of a generated row you can combine a RowGenerator
4243 with a CellStyleGenerator.
4244
4245 @param table:
4246 The Table that is being painted
4247 @param itemId:
4248 The itemId for the row
4249 @return: A GeneratedRow describing how the row should be painted or
4250 null to paint the row with the contents from the container
4251 """
4252 raise NotImplementedError
4253
4256
4258 """Creates a new generated row. If only one string is passed in,
4259 columns are automatically spanned.
4260
4261 @param text
4262 """
4263 self._htmlContentAllowed = False
4264 self._spanColumns = False
4265 self._text = None
4266
4267 self.setHtmlContentAllowed(False)
4268 self.setSpanColumns((text is None) or (len(text) == 1))
4269 self.setText(text)
4270
4271
4272 - def setText(self, *text):
4273 """Pass one string if spanColumns is used, one string for each visible
4274 column otherwise
4275 """
4276 if (text is None) or (len(text) == 1 and text[0] is None):
4277 text = ['']
4278 self._text = text
4279
4280
4281 - def getText(self):
4283
4284
4287
4288
4290 return self._htmlContentAllowed
4291
4292
4293 - def setHtmlContentAllowed(self, htmlContentAllowed):
4294 """If set to true, all strings passed to L{setText} will be rendered
4295 as HTML.
4296
4297 @param htmlContentAllowed
4298 """
4299 self._htmlContentAllowed = htmlContentAllowed
4300
4301
4303 return self._spanColumns
4304
4305
4307 """If set to true, only one string will be rendered, spanning the
4308 entire row.
4309 """
4310 self._spanColumns = spanColumns
4311