Package muntjac :: Package ui :: Module table
[hide private]
[frames] | no frames]

Source Code for Module muntjac.ui.table

   1  # Copyright (C) 2012 Vaadin Ltd.  
   2  # Copyright (C) 2012 Richard Lincoln 
   3  #  
   4  # Licensed under the Apache License, Version 2.0 (the "License");  
   5  # you may not use this file except in compliance with the License.  
   6  # You may obtain a copy of the License at  
   7  #  
   8  #     http://www.apache.org/licenses/LICENSE-2.0  
   9  #  
  10  # Unless required by applicable law or agreed to in writing, software  
  11  # distributed under the License is distributed on an "AS IS" BASIS,  
  12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
  13  # See the License for the specific language governing permissions and  
  14  # limitations under the License. 
  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, #container.IOrdered, action.IContainer, 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 #ClientWidget(VScrollTable, LoadStyle.EAGER) 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 #: Left column alignment. <b>This is the default behaviour.</b> 96 ALIGN_LEFT = 'b' 97 98 #: Center column alignment. 99 ALIGN_CENTER = 'c' 100 101 #: Right column alignment. 102 ALIGN_RIGHT = 'e' 103 104 #: Column header mode: Column headers are hidden. 105 COLUMN_HEADER_MODE_HIDDEN = -1 106 107 #: Column header mode: Property ID:s are used as column headers. 108 COLUMN_HEADER_MODE_ID = 0 109 110 #: Column header mode: Column headers are explicitly specified with 111 # L{setColumnHeaders}. 112 COLUMN_HEADER_MODE_EXPLICIT = 1 113 114 #: Column header mode: Column headers are explicitly specified with 115 # L{setColumnHeaders}. If a header is not specified 116 # for a given property, its property id is used instead. 117 # 118 # B{This is the default behavior.} 119 COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID = 2 120 121 #: Row caption mode: The row headers are hidden. 122 # B{This is the default mode.} 123 ROW_HEADER_MODE_HIDDEN = -1 124 125 #: Row caption mode: Items Id-objects toString is used as row caption. 126 ROW_HEADER_MODE_ID = AbstractSelect.ITEM_CAPTION_MODE_ID 127 128 #: Row caption mode: Item-objects toString is used as row caption. 129 ROW_HEADER_MODE_ITEM = AbstractSelect.ITEM_CAPTION_MODE_ITEM 130 131 #: Row caption mode: Index of the item is used as item caption. The 132 # index mode can only be used with the containers implementing 133 # container.IIndexed interface. 134 ROW_HEADER_MODE_INDEX = AbstractSelect.ITEM_CAPTION_MODE_INDEX 135 136 #: Row caption mode: Item captions are explicitly specified. 137 ROW_HEADER_MODE_EXPLICIT = AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT 138 139 #: Row caption mode: Item captions are read from property specified 140 # with L{#setItemCaptionPropertyId(Object)}. 141 ROW_HEADER_MODE_PROPERTY = AbstractSelect.ITEM_CAPTION_MODE_PROPERTY 142 143 #: Row caption mode: Only icons are shown, the captions are hidden. 144 ROW_HEADER_MODE_ICON_ONLY = AbstractSelect.ITEM_CAPTION_MODE_ICON_ONLY 145 146 #: Row caption mode: Item captions are explicitly specified, but if 147 # the caption is missing, the item id objects C{toString()} 148 # is used instead. 149 ROW_HEADER_MODE_EXPLICIT_DEFAULTS_ID = \ 150 AbstractSelect.ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID 151 152 #: The default rate that table caches rows for smooth scrolling. 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 #: True if column collapsing is allowed. 164 self._columnCollapsingAllowed = False 165 166 #: True if reordering of columns is allowed on the client side. 167 self._columnReorderingAllowed = False 168 169 #: Keymapper for column ids. 170 self._columnIdMap = KeyMapper() 171 172 #: Holds visible column propertyIds - in order. 173 self._visibleColumns = list() 174 175 #: Holds propertyIds of currently collapsed columns. 176 self._collapsedColumns = set() 177 178 #: Holds headers for visible columns (by propertyId). 179 self._columnHeaders = dict() 180 181 #: Holds footers for visible columns (by propertyId). 182 self._columnFooters = dict() 183 184 #: Holds icons for visible columns (by propertyId). 185 self._columnIcons = dict() 186 187 #: Holds alignments for visible columns (by propertyId). 188 self._columnAlignments = dict() 189 190 #: Holds column widths in pixels (Integer) or expand ratios 191 # (Float) for visible columns (by propertyId). 192 self._columnWidths = dict() 193 194 #: Holds column generators 195 self._columnGenerators = OrderedDict() 196 197 #: Holds value of property pageLength. 0 disables paging. 198 self._pageLength = 15 199 200 #: Id the first item on the current page. 201 self._currentPageFirstItemId = None 202 203 #: Index of the first item on the current page. 204 self._currentPageFirstItemIndex = 0 205 206 #: Holds value of property selectable. 207 self._selectable = False 208 209 #: Holds value of property columnHeaderMode. 210 self._columnHeaderMode = self.COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID 211 212 #: Should the Table footer be visible? 213 self._columnFootersVisible = False 214 215 #: True iff the row captions are hidden. 216 self._rowCaptionsAreHidden = True 217 218 #: Page contents buffer used in buffered mode. 219 self._pageBuffer = None 220 221 #: Set of properties listened - the list is kept to release 222 # the listeners later. 223 self._listenedProperties = None 224 225 #: Set of visible components - the is used for needsRepaint 226 # calculation. 227 self._visibleComponents = None 228 229 #: List of action handlers. 230 self._actionHandlers = None 231 232 #: Action mapper. 233 self._actionMapper = None 234 235 #: Table cell editor factory. 236 self._fieldFactory = DefaultFieldFactory.get() 237 238 #: Is table editable. 239 self._editable = False 240 241 #: Current sorting direction. 242 self._sortAscending = True 243 244 #: Currently table is sorted on this propertyId. 245 self._sortContainerPropertyId = None 246 247 #: Is table sorting disabled alltogether; even if some of 248 # the properties would be sortable. 249 self._sortDisabled = False 250 251 #: Number of rows explicitly requested by the client to be painted 252 # on next paint. This is -1 if no request by the client is made. 253 # Painting the component will automatically reset this to -1. 254 self._reqRowsToPaint = -1 255 256 #: Index of the first rows explicitly requested by the client to be 257 # painted. This is -1 if no request by the client is made. Painting 258 # the component will automatically reset this to -1. 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 #: Table cell specific style generator 272 self._cellStyleGenerator = None 273 274 #: Table cell specific tooltip generator 275 self._itemDescriptionGenerator = None 276 277 #: EXPERIMENTAL feature: will tell the client to re-calculate column 278 # widths if set to true. Currently no setter: extend to enable. 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
309 - def getVisibleColumns(self):
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
323 - def setVisibleColumns(self, visibleColumns):
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 # Visible columns must exist 332 if visibleColumns is None: 333 raise ValueError, 'Can not set visible columns to null value' 334 335 # TODO: add error check that no duplicate identifiers exist 336 337 # Checks that the new visible columns contains no nulls and 338 # properties exist 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 # If this is called before the constructor is finished, it might be 348 # uninitialized 349 newVC = list() 350 for vc in visibleColumns: 351 newVC.append(vc) 352 353 # Removes alignments, icons and headers from hidden columns 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 # Assures visual refresh 369 self.refreshRowCache()
370 371
372 - def getColumnHeaders(self):
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
394 - def setColumnHeaders(self, columnHeaders):
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
425 - def getColumnIcons(self):
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
446 - def setColumnIcons(self, columnIcons):
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 # FIXME: realloc array 465 466 for i, col in enumerate(self._visibleColumns): 467 self._columnIcons[col] = columnIcons[i] 468 469 # Assure visual refresh 470 self.requestRepaint()
471 472
473 - def getColumnAlignments(self):
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
500 - def setColumnAlignments(self, columnAlignments):
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 # Checks all alignments 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 # Resets the alignments 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 # Assures the visual refresh. No need to reset the page buffer before 544 # as the content has not changed, only the alignments. 545 self.refreshRenderedCells()
546 547
548 - def setColumnWidth(self, propertyId, width):
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 # Since propertyId is null, this is the row header. Use the 563 # magic id to store the width of the row header. 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
575 - def setColumnExpandRatio(self, propertyId, expandRatio):
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
612 - def getColumnExpandRatio(self, propertyId):
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
620 - def getColumnWidth(self, propertyId):
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 # Since propertyId is null, this is the row header. Use the 627 # magic id to retrieve the width of the row header. 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 # Assures the visual refresh 661 self.refreshRowCache()
662 663
664 - def setCacheRate(self, cacheRate):
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
690 - def getCacheRate(self):
691 """@see: L{setCacheRate} 692 693 @return: the current cache rate value 694 """ 695 return self._cacheRate
696 697
698 - def getCurrentPageFirstItemId(self):
699 """Getter for property currentPageFirstItem. 700 701 @return: the Value of property currentPageFirstItem. 702 """ 703 # Priorise index over id if indexes are supported 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 # If there is no item id at all, use the first one 714 if self._currentPageFirstItemId is None: 715 self._currentPageFirstItemId = self.firstItemId() 716 717 return self._currentPageFirstItemId
718 719
720 - def getIdByIndex(self, index):
721 return self.items.getIdByIndex(index)
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 # Gets the corresponding index 731 index = -1 732 if isinstance(self.items, container.IIndexed): 733 index = self.indexOfId(currentPageFirstItemId) 734 else: 735 # If the table item container does not have index, we have to 736 # calculates the index by hand 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 # If the search for item index was successful 745 if index >= 0: 746 # The table is not capable of displaying an item in the container 747 # as the first if there are not enough items following the selected 748 # item so the whole table (pagelength) is filled. 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 # Assures the visual refresh 761 self.refreshRowCache()
762 763
764 - def indexOfId(self, itemId):
765 return self.items.indexOfId(itemId)
766 767
768 - def getColumnIcon(self, propertyId):
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
779 - def setColumnIcon(self, propertyId, icon):
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 # Assures the visual refresh 797 self.requestRepaint()
798 799
800 - def getColumnHeader(self, propertyId):
801 """Gets the header for the specified column. 802 803 @param propertyId: 804 the propertyId identifying the column. 805 @return: the header for the specified column if it has one. 806 """ 807 if self.getColumnHeaderMode() == self.COLUMN_HEADER_MODE_HIDDEN: 808 return None 809 810 header = self._columnHeaders.get(propertyId) 811 if ((header is None 812 and self.getColumnHeaderMode() == 813 self.COLUMN_HEADER_MODE_EXPLICIT_DEFAULTS_ID) 814 or (self.getColumnHeaderMode() == self.COLUMN_HEADER_MODE_ID)): 815 header = str(propertyId) 816 817 return header
818 819
820 - def setColumnHeader(self, propertyId, header):
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
837 - def getColumnAlignment(self, propertyId):
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
848 - def setColumnAlignment(self, propertyId, alignment):
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 # Checks for valid alignments 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 # Assures the visual refresh. No need to reset the page buffer before 874 # as the content has not changed, only the alignments. 875 self.refreshRenderedCells()
876 877
878 - def isColumnCollapsed(self, propertyId):
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
889 - def setColumnCollapsed(self, propertyId, collapsed):
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 # Assures the visual refresh 909 self.refreshRowCache()
910 911
912 - def isColumnCollapsingAllowed(self):
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
920 - def setColumnCollapsingAllowed(self, collapsingAllowed):
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 # Assures the visual refresh. No need to reset the page buffer before 932 # as the content has not changed, only the alignments. 933 self.refreshRenderedCells()
934 935
936 - def isColumnReorderingAllowed(self):
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
944 - def setColumnReorderingAllowed(self, columnReorderingAllowed):
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
955 - def setColumnOrder(self, columnOrder):
956 # Arranges visible columns according to given columnOrder. Silently 957 # ignores colimnId:s that are not visible columns, and keeps the 958 # internal order of visible columns left out of the ordering 959 # (trailing). Silently does nothing if columnReordering is not 960 # allowed. 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 # Assure visual refresh 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 # minimize IContainer.size() calls which may be expensive. For 999 # example it may cause sql query. 1000 size = self.size() 1001 1002 # The table is not capable of displaying an item in the container as 1003 # the first if there are not enough items following the selected item 1004 # so the whole table (pagelength) is filled. 1005 maxIndex = size - self._pageLength 1006 if maxIndex < 0: 1007 maxIndex = 0 1008 1009 # Ensures that the new value is valid 1010 if newIndex > maxIndex: 1011 newIndex = maxIndex 1012 1013 # Refresh first item id 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 # For containers not supporting indexes, we must iterate the 1023 # container forwards / backwards 1024 # next available item forward or backward 1025 1026 self._currentPageFirstItemId = self.firstItemId() 1027 1028 # Go forwards in the middle of the list (respect borders) 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 # If we did hit the border 1036 if self.isLastId(self._currentPageFirstItemId): 1037 self._currentPageFirstItemIndex = size - 1 1038 1039 # Go backwards in the middle of the list (respect borders) 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 # If we did hit the border 1047 if self.isFirstId(self._currentPageFirstItemId): 1048 self._currentPageFirstItemIndex = 0 1049 1050 # Go forwards once more 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 # If for some reason we do hit border again, override 1058 # the user index request 1059 if self.isLastId(self._currentPageFirstItemId): 1060 newIndex = self._currentPageFirstItemIndex = size - 1 1061 1062 if needsPageBufferReset: 1063 # Assures the visual refresh 1064 self.refreshRowCache()
1065 1066
1067 - def isPageBufferingEnabled(self):
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
1089 - def isSelectable(self):
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
1099 - def setSelectable(self, selectable):
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
1112 - def getColumnHeaderMode(self):
1113 """Getter for property columnHeaderMode. 1114 1115 @return: the value of property columnHeaderMode. 1116 """ 1117 return self._columnHeaderMode
1118 1119
1120 - def setColumnHeaderMode(self, columnHeaderMode):
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
1134 - def refreshRenderedCells(self):
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 # Collects the basic facts about the table page 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 # If "to be painted next" variables are set, use them 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 # initial load 1168 self._firstToBeRenderedInClient = firstIndex 1169 1170 if totalRows > 0: 1171 if (rows + firstIndex) > totalRows: 1172 rows = totalRows - firstIndex 1173 else: 1174 rows = 0 1175 # Saves the results to internal buffer 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
1183 - def requestRepaint(self):
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 # Overridden only for doc-string 1191 super(Table, self).requestRepaint()
1192 1193
1194 - def removeRowsFromCacheAndFillBottom(self, firstIndex, rows):
1195 totalCachedRows = len(self._pageBuffer[self.CELL_ITEMID]) 1196 totalRows = self.size() 1197 firstIndexInPageBuffer = firstIndex - self._pageBufferFirstIndex 1198 1199 # firstIndexInPageBuffer is the first row to be removed. "rows" rows 1200 # after that should be removed. If the page buffer does not contain 1201 # that many rows, we only remove the rows that actually are in the page 1202 # buffer. 1203 if firstIndexInPageBuffer + rows > totalCachedRows: 1204 rows = totalCachedRows - firstIndexInPageBuffer 1205 1206 # Unregister components that will no longer be in the page buffer to 1207 # make sure that no components leak. 1208 self.unregisterComponentsAndPropertiesInRows(firstIndex, rows) 1209 1210 # The number of rows that should be in the cache after this operation 1211 # is done (pageBuffer currently contains the expanded items). 1212 newCachedRowCount = totalCachedRows 1213 if newCachedRowCount + self._pageBufferFirstIndex > totalRows: 1214 newCachedRowCount = totalRows - self._pageBufferFirstIndex 1215 1216 # The index at which we should render the first row that does not come 1217 # from the previous page buffer. 1218 firstAppendedRowInPageBuffer = totalCachedRows - rows 1219 firstAppendedRow = \ 1220 firstAppendedRowInPageBuffer + self._pageBufferFirstIndex 1221 1222 # Calculate the maximum number of new rows that we can add to the page 1223 # buffer. Less than the rows we removed if the container does not 1224 # contain that many items afterwards. 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 # Create the new cache buffer by copying the first rows from the old 1236 # buffer, moving the following rows upwards and appending more rows if 1237 # applicable. 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 # Copy the first rows 1243 newPageBuffer[i][row] = self._pageBuffer[i][row] 1244 1245 for row in range(firstIndexInPageBuffer, 1246 firstAppendedRowInPageBuffer): 1247 # Move the rows that were after the expanded rows 1248 newPageBuffer[i][row] = self._pageBuffer[i][row + rows] 1249 1250 for row in range(firstAppendedRowInPageBuffer, newCachedRowCount): 1251 # Add the newly rendered rows. Only used if rowsToAdd > 0 1252 # (cells != null) 1253 newPageBuffer[i][row] = cells[i][row - firstAppendedRowInPageBuffer] 1254 1255 self._pageBuffer = newPageBuffer
1256 1257
1258 - def getVisibleCellsUpdateCacheRows(self, firstIndex, rows):
1259 cells = self.getVisibleCellsNoCache(firstIndex, rows, False) 1260 cacheIx = firstIndex - self._pageBufferFirstIndex 1261 # update the new rows in the cache. 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
1270 - def getVisibleCellsInsertIntoCache(self, firstIndex, rows):
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 # Page buffer must not become larger than pageLength*cacheRate before 1283 # or after the current page 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 # If pageLength == 0 then all rows should be rendered 1295 maxBufferSize = len(self._pageBuffer[0]) + rows 1296 1297 # Number of rows that were previously cached. This is not necessarily 1298 # the same as pageLength if we do not have enough rows in the 1299 # container. 1300 currentlyCachedRowCount = len(self._pageBuffer[self.CELL_ITEMID]) 1301 1302 # firstIndexInPageBuffer is the offset in pageBuffer where the new rows 1303 # will be inserted (firstIndex is the index in the whole table). 1304 # 1305 # E.g. scrolled down to row 1000: firstIndex==1010, 1306 # pageBufferFirstIndex==1000 -> cacheIx==10 1307 firstIndexInPageBuffer = firstIndex - self._pageBufferFirstIndex 1308 1309 # If rows > size available in page buffer 1310 if firstIndexInPageBuffer + rows > maxBufferSize: 1311 rows = maxBufferSize - firstIndexInPageBuffer 1312 1313 # "rows" rows will be inserted at firstIndex. Find out how many old 1314 # rows fall outside the new buffer so we can unregister components in 1315 # the cache. 1316 1317 # All rows until the insertion point remain, always. 1318 firstCacheRowToRemoveInPageBuffer = firstIndexInPageBuffer 1319 1320 # IF there is space remaining in the buffer after the rows have been 1321 # inserted, we can keep more rows. 1322 1323 numberOfOldRowsAfterInsertedRows = (maxBufferSize 1324 - firstIndexInPageBuffer - rows) 1325 if numberOfOldRowsAfterInsertedRows > 0: 1326 firstCacheRowToRemoveInPageBuffer += \ 1327 numberOfOldRowsAfterInsertedRows 1328 1329 if firstCacheRowToRemoveInPageBuffer <= currentlyCachedRowCount: 1330 # Unregister all components that fall beyond the cache limits after 1331 # inserting the new rows. 1332 self.unregisterComponentsAndPropertiesInRows( 1333 firstCacheRowToRemoveInPageBuffer 1334 + self._pageBufferFirstIndex, 1335 (currentlyCachedRowCount 1336 - firstCacheRowToRemoveInPageBuffer) 1337 + self._pageBufferFirstIndex) 1338 1339 # Calculate the new cache size 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 # Paint the new rows into a separate buffer 1347 cells = self.getVisibleCellsNoCache(firstIndex, rows, False) 1348 1349 # Create the new cache buffer and fill it with the data from the old 1350 # buffer as well as the inserted rows. 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 # Copy the first rows 1358 newPageBuffer[i][row] = self._pageBuffer[i][row] 1359 1360 for row in range(firstIndexInPageBuffer, 1361 firstIndexInPageBuffer + rows): 1362 # Copy the newly created rows 1363 newPageBuffer[i][row] = cells[i][row - firstIndexInPageBuffer] 1364 1365 for row in range(firstIndexInPageBuffer + rows, newCachedRowCount): 1366 # Move the old rows down below the newly inserted rows 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
1380 - def getVisibleCellsNoCache(self, firstIndex, rows, replaceListeners):
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 # initialize the listener collections, this should only be done if 1396 # the entire cache is refreshed (through refreshRenderedCells) 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 # Gets the first item id 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 # Creates the page contents 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 # check in current pageBuffer already has row 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 # we already have data in our cache, 1479 # recycle it instead of fetching it via 1480 # getValue/getPropertyValue 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 # Avoid errors if a generator returns 1492 # something other than a Component or a string 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 # If returned value is Component (via 1500 # fieldfactory or overridden getPropertyValue) 1501 # we excpect it to listen property value 1502 # changes. Otherwise if property emits value 1503 # change events, table will start to listen 1504 # them and refresh content when needed. 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 # Gets the next item id 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 # Assures that all the rows of the cell-buffer are valid 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
1542 - def registerComponent(self, component):
1543 #logger.debug('Registered %s: %s' % (component.__class__.__name__, 1544 # component.getCaption())) 1545 if component.getParent() is not self: 1546 component.setParent(self) 1547 self._visibleComponents.add(component)
1548 1549
1550 - def listenProperty(self, p, oldListenedProperties):
1551 if isinstance(p, IValueChangeNotifier): 1552 if ((oldListenedProperties is None) 1553 or (p not in oldListenedProperties)): 1554 p.addListener(self, IValueChangeListener) 1555 1556 # register listened properties, so we can do proper cleanup to 1557 # free memory. Essential if table has loads of data and it is 1558 # used for a long time. 1559 self._listenedProperties.add(p)
1560 1561
1562 - def unregisterComponentsAndPropertiesInRows(self, firstIx, count):
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
1595 - def unregisterPropertiesAndComponents(self, oldListenedProperties, 1596 oldVisibleComponents):
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
1617 - def unregisterComponent(self, component):
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 #logger.debug("Unregistered %s: %s" % (component.__class__.__name__, 1633 # component.getCaption())) 1634 component.setParent(None) 1635 # Also remove property data sources to unregister listeners keeping 1636 # the fields in memory. 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 # Remove the property data source only if it's the one we 1644 # added in getPropertyValue 1645 field.setPropertyDataSource(None)
1646 1647
1648 - def refreshCurrentPage(self):
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
1656 - def setRowHeaderMode(self, mode):
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 # Assures the visual refresh. No need to reset the page buffer before 1689 # as the content has not changed, only the alignments. 1690 self.refreshRenderedCells()
1691 1692
1693 - def getRowHeaderMode(self):
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
1705 - def addItem(self, *args):
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 # remove generated columns from the list of columns being assigned 1726 availableCols = list() 1727 for idd in self._visibleColumns: 1728 if idd not in self._columnGenerators: 1729 availableCols.append(idd) 1730 1731 # Checks that a correct number of cells are given 1732 if len(cells) != len(availableCols): 1733 return None 1734 1735 # Creates new item 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 # Fills the item properties 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
1759 - def refreshRowCache(self):
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
1779 - def setContainerDataSource(self, newDataSource):
1780 1781 self.disableContentRefreshing() 1782 1783 if newDataSource is None: 1784 newDataSource = IndexedContainer() 1785 1786 # Assures that the data source is ordered by making unordered 1787 # containers ordered by wrapping them 1788 if isinstance(newDataSource, container.IOrdered): 1789 super(Table, self).setContainerDataSource(newDataSource) 1790 else: 1791 raise NotImplementedError 1792 #super(Table, self).setContainerDataSource( 1793 # ContainerOrderedWrapper(newDataSource) ) 1794 1795 # Resets page position 1796 self._currentPageFirstItemId = None 1797 self._currentPageFirstItemIndex = 0 1798 1799 # Resets column properties 1800 if self._collapsedColumns is not None: 1801 self._collapsedColumns.clear() 1802 1803 # columnGenerators 'override' properties, don't add 1804 # the same id twice 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 # generators added last 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 # Assure visual refresh 1819 self.resetPageBuffer() 1820 1821 self.enableContentRefreshing(True)
1822 1823
1824 - def getItemIdsInRange(self, itemId, length):
1825 """Gets items ids from a range of key values 1826 """ 1827 ids = set() 1828 for _ in range(length): 1829 # should not be null unless client-server 1830 # are out of sync 1831 assert itemId is not None 1832 ids.add(itemId) 1833 itemId = self.nextItemId(itemId) 1834 return ids
1835 1836
1837 - def handleSelectedItems(self, variables):
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 # the client side has instructed to swipe all previous selections 1852 newValue.clear() 1853 else: 1854 # first clear all selections that are currently rendered rows (the 1855 # ones that the client side counterpart is aware of) 1856 newValue = newValue.difference(renderedItemIds) 1857 1858 # Then add (possibly some of them back) rows that are currently 1859 # selected on the client side (the ones that the client side is aware 1860 # of). 1861 for i in range(len(ka)): 1862 # key to id 1863 idd = self.itemIdMapper.get( ka[i] ) 1864 if (not self.isNullSelectionAllowed() 1865 and (idd is None) 1866 or (idd == self.getNullSelectionItemId())): 1867 # skip empty selection if nullselection is not allowed 1868 self.requestRepaint() 1869 elif idd is not None and self.containsId(idd): 1870 newValue.add(idd) 1871 1872 # Add range items aka shift clicked multiselection areas 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 # empty selection not allowed, keep old value 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 # IComponent basics 1898
1899 - def changeVariables(self, source, variables):
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 # Not-selectable is a special case, AbstractSelect does not 1916 # support. TODO could be optimized. 1917 variables = dict(variables) 1918 del variables['selected'] 1919 1920 # The AbstractSelect cannot handle the multiselection properly, 1921 # instead we handle it ourself 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 # Client might update the pagelength if Table height is fixed 1933 if 'pagelength' in variables: 1934 # Sets pageLength directly to avoid repaint that setter causes 1935 self._pageLength = variables.get('pagelength') 1936 1937 # Page start index 1938 if 'firstvisible' in variables: 1939 value = variables.get('firstvisible') 1940 if value is not None: 1941 self.setCurrentPageFirstItemIndex(int(value), False) 1942 1943 # Sets requested firstrow and rows for the next paint 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 # FIXME: Handle exception 1953 logger.info('Could not parse the first and/or last rows.') 1954 1955 # respect suggested rows only if table is not otherwise 1956 # updated (row caches emptied by other event) 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 # sanity check 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 # Sorting 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 # Dynamic column hide/show and order 1998 # Update visible columns 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 # FIXME: Handle exception 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 # need a real Object[], ids can be a String[] 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 # FIXME: Handle exception 2029 logger.info('Could not determine column reordering state') 2030 2031 clientNeedsContentRefresh = True 2032 2033 self.enableContentRefreshing(clientNeedsContentRefresh) 2034 2035 # Actions 2036 if 'action' in variables: 2037 st = variables.get('action').split(',') # FIXME: StringTokenizer 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
2049 - def handleClickEvent(self, variables):
2050 """Handles click event. 2051 """ 2052 # Item click event 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 # click is not necessary on a property 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 # Header click event 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 # Footer click event 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
2089 - def handleColumnResizeEvent(self, variables):
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
2112 - def fireColumnResizeEvent(self, propertyId, previousWidth, currentWidth):
2113 # Update the sizes on the server side. If a column previously had a 2114 # expand ratio and the user resized the column then the expand ratio 2115 # will be turned into a static pixel size. 2116 self.setColumnWidth(propertyId, currentWidth) 2117 2118 evt = ColumnResizeEvent(self, propertyId, previousWidth, currentWidth) 2119 self.fireEvent(evt)
2120 2121
2122 - def handleColumnWidthUpdates(self, variables):
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
2136 - def disableContentRefreshing(self):
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 # Ensure that client gets a response 2158 self.requestRepaint()
2159 2160
2161 - def paintContent(self, target):
2162 # Body actions - Actions which has the target null and can be invoked 2163 # by right clicking on the table body. 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 # Table attributes 2175 self.paintTableAttributes(target, rows, total) 2176 2177 self.paintVisibleColumnOrder(target) 2178 2179 # Rows 2180 if (self.isPartialRowUpdate() and self._painted 2181 and not target.isFullRepaint()): 2182 self.paintPartialRowUpdate(target, actionSet) 2183 # Send the page buffer indexes to ensure that the client side stays 2184 # in sync. Otherwise we _might_ have the situation where the client 2185 # side discards too few or too many rows, causing out of sync 2186 # issues. 2187 # 2188 # This could probably be done for full repaints also to simplify 2189 # the client side. 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 # Actions 2205 self.paintActions(target, actionSet) 2206 2207 self.paintColumnOrder(target) 2208 2209 # Available columns 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
2220 - def setRowCacheInvalidated(self, invalidated):
2221 self._rowCacheInvalidated = invalidated
2222 2223
2224 - def isRowCacheInvalidated(self):
2225 return self._rowCacheInvalidated
2226 2227
2228 - def paintPartialRowUpdate(self, target, actionSet):
2229 self.paintPartialRowUpdates(target, actionSet) 2230 self.paintPartialRowAdditions(target, actionSet)
2231 2232
2233 - def paintPartialRowUpdates(self, target, actionSet):
2234 iscomponent = self.findCellsWithComponents() 2235 2236 firstIx = self.getFirstUpdatedItemIndex() 2237 count = self.getUpdatedRowCount() 2238 2239 target.startTag('urows') 2240 target.addAttribute('firsturowix', firstIx) 2241 target.addAttribute('numurows', count) 2242 2243 # Partial row updates bypass the normal caching mechanism. 2244 cells = self.getVisibleCellsUpdateCacheRows(firstIx, count) 2245 for indexInRowbuffer in range(count): 2246 itemId = cells[self.CELL_ITEMID][indexInRowbuffer] 2247 2248 if self.shouldHideNullSelectionItem(): 2249 # Remove null selection item if null selection is not allowed 2250 continue 2251 2252 self.paintRow(target, cells, self.isEditable(), actionSet, 2253 iscomponent, indexInRowbuffer, itemId) 2254 2255 target.endTag('urows')
2256 2257
2258 - def paintPartialRowAdditions(self, target, actionSet):
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 # Partial row additions bypass the normal caching mechanism. 2271 cells = self.getVisibleCellsInsertIntoCache(firstIx, count) 2272 if len(cells[0]) < count: 2273 # delete the rows below, since they will fall beyond the cache 2274 # page. 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 # Remove null selection item if null selection is not 2282 # allowed 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
2298 - def isPartialRowUpdate(self):
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
2309 - def getFirstAddedItemIndex(self):
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
2321 - def getAddedRowCount(self):
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
2334 - def shouldHideAddedRows(self):
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
2350 - def getFirstUpdatedItemIndex(self):
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
2362 - def getUpdatedRowCount(self):
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
2375 - def paintTableAttributes(self, target, rows, total):
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 # The cursors are only shown on pageable table 2405 if ((self.getCurrentPageFirstItemIndex() != 0) 2406 or (self.getPageLength() > 0)): 2407 target.addVariable(self, 'firstvisible', 2408 self.getCurrentPageFirstItemIndex())
2409 2410
2411 - def resetVariablesAndPageBuffer(self, target):
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
2421 - def areColumnHeadersEnabled(self):
2423 2424
2425 - def paintVisibleColumns(self, target):
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
2468 - def paintAvailableColumns(self, target):
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
2484 - def paintActions(self, target, actionSet):
2485 if len(actionSet) > 0: 2486 target.addVariable(self, 'action', '') 2487 target.startTag('actions') 2488 for a in actionSet: 2489 target.startTag('action') 2490 if a.getCaption() is not None: 2491 target.addAttribute('caption', a.getCaption()) 2492 2493 if a.getIcon() is not None: 2494 target.addAttribute('icon', a.getIcon()) 2495 2496 target.addAttribute('key', self._actionMapper.key(a)) 2497 target.endTag('action') 2498 target.endTag('actions')
2499 2500
2501 - def paintColumnOrder(self, target):
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
2511 - def paintSorting(self, target):
2512 # Sorting 2513 if isinstance(self.getContainerDataSource(), container.ISortable): 2514 target.addVariable(self, 'sortcolumn', 2515 self._columnIdMap.key(self._sortContainerPropertyId)) 2516 target.addVariable(self, 'sortascending', self._sortAscending)
2517 2518
2519 - def paintRows(self, target, cells, actionSet):
2520 iscomponent = self.findCellsWithComponents() 2521 2522 target.startTag('rows') 2523 # cells array contains all that are supposed to be visible on client, 2524 # but we'll start from the one requested by client 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 # sanity check 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 # Remove null selection item if null selection is not allowed 2547 continue 2548 2549 self.paintRow(target, cells, self.isEditable(), actionSet, 2550 iscomponent, indexInRowbuffer, itemId) 2551 2552 target.endTag('rows')
2553 2554
2555 - def findCellsWithComponents(self):
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
2570 - def paintVisibleColumnOrder(self, target):
2571 # Visible column order 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 # Getting actions for the null item, which in this case means 2585 # the body item 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
2598 return (not self.isNullSelectionAllowed() 2599 and (self.getNullSelectionItemId() is not None) 2600 and self.containsId(self.getNullSelectionItemId()))
2601 2602
2603 - def findNumRowsToPaint(self, target, cells):
2604 if self._reqRowsToPaint >= 0: 2605 rows = self._reqRowsToPaint 2606 else: 2607 rows = len(cells[0]) 2608 if self.alwaysRecalculateColumnWidths: 2609 # TODO experimental feature for now: tell the client to 2610 # recalculate column widths. 2611 # We'll only do this for paints that do not originate from 2612 # table scroll/cache requests (i.e when reqRowsToPaint<0) 2613 target.addAttribute('recalcWidths', True) 2614 return rows
2615 2616
2617 - def paintSelectMode(self, target):
2618 if self._multiSelectMode != MultiSelectMode.DEFAULT: 2619 target.addAttribute('multiselectmode', 2620 MultiSelectMode.ordinal(self._multiSelectMode)) 2621 if self.isSelectable(): 2622 if self.isMultiSelect(): 2623 target.addAttribute('selectmode', 'multi') 2624 else: 2625 target.addAttribute('selectmode', 'single') 2626 else: 2627 target.addAttribute('selectmode', 'none') 2628 2629 if not self.isNullSelectionAllowed(): 2630 target.addAttribute('nsa', False) 2631 2632 # selection support 2633 # The select variable is only enabled if selectable 2634 if self.isSelectable(): 2635 target.addVariable(self, 'selected', self.findSelectedKeys())
2636 2637
2638 - def findSelectedKeys(self):
2639 selectedKeys = list() 2640 if self.isMultiSelect(): 2641 sel = set(self.getValue()) 2642 vids = self.getVisibleItemIds() 2643 for idd in vids: 2644 if idd in sel: 2645 selectedKeys.append(self.itemIdMapper.key(idd)) 2646 else: 2647 value = self.getValue() 2648 if value is None: 2649 value = self.getNullSelectionItemId() 2650 2651 if value is not None: 2652 selectedKeys.append(self.itemIdMapper.key(value)) 2653 2654 return selectedKeys
2655 2656
2657 - def paintDragMode(self, target):
2658 if self._dragMode != TableDragMode.NONE: 2659 target.addAttribute('dragmode', 2660 TableDragMode.ordinal(self._dragMode))
2661 2662
2663 - def paintTabIndex(self, target):
2664 # The tab ordering number 2665 if self.getTabIndex() > 0: 2666 target.addAttribute('tabindex', self.getTabIndex())
2667 2668
2669 - def paintColumnWidth(self, target, columnId):
2670 if columnId in self._columnWidths: 2671 if self.getColumnWidth(columnId) > -1: 2672 target.addAttribute('width', 2673 str(self.getColumnWidth(columnId))) 2674 else: 2675 target.addAttribute('er', 2676 self.getColumnExpandRatio(columnId))
2677 2678
2679 - def rowHeadersAreEnabled(self):
2680 return self.getRowHeaderMode() != self.ROW_HEADER_MODE_HIDDEN
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 # cells 2691 currentColumn = 0 2692 for columnId in self._visibleColumns: 2693 if (columnId is None) or self.isColumnCollapsed(columnId): 2694 continue 2695 2696 # For each cell, if a cellStyleGenerator is specified, get the 2697 # specific style for the cell. If there is any, add it to the 2698 # target. 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
2725 - def paintCellTooltips(self, target, itemId, columnId):
2726 if self._itemDescriptionGenerator is not None: 2727 itemDescription = \ 2728 self._itemDescriptionGenerator.generateDescription(self, 2729 itemId, columnId) 2730 if itemDescription is not None and itemDescription != '': 2731 target.addAttribute('descr-' + self._columnIdMap.key(columnId), 2732 itemDescription)
2733 2734
2735 - def paintRowTooltips(self, target, itemId):
2736 if self._itemDescriptionGenerator is not None: 2737 rowDescription = \ 2738 self._itemDescriptionGenerator.generateDescription(self, 2739 itemId, None) 2740 if rowDescription is not None and rowDescription != '': 2741 target.addAttribute('rowdescr', rowDescription)
2742 2743
2744 - def paintRowAttributes(self, *args):
2745 """A method where extended Table implementations may add their 2746 custom attributes for rows. 2747 """ 2748 # tr attributes 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 # Actions 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 # For each row, if a cellStyleGenerator is specified, get the 2781 # specific style for the cell, using null as propertyId. If there 2782 # is any, add it to the target. 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
2795 - def paintGeneratedRowInfo(self, target, cells, indexInRowBuffer):
2796 generatedRow = cells[self.CELL_GENERATED_ROW][indexInRowBuffer] 2797 if generatedRow is not None: 2798 target.addAttribute('gen_html', 2799 generatedRow.isHtmlContentAllowed()) 2800 target.addAttribute('gen_span', 2801 generatedRow.isSpanColumns()) 2802 target.addAttribute('gen_widget', 2803 isinstance(generatedRow.getValue(), IComponent))
2804 2805
2806 - def paintRowHeader(self, target, cells, indexInRowbuffer):
2807 if self.rowHeadersAreEnabled(): 2808 if cells[self.CELL_HEADER][indexInRowbuffer] is not None: 2809 target.addAttribute('caption', 2810 cells[self.CELL_HEADER][indexInRowbuffer])
2811 2812
2813 - def paintRowIcon(self, target, cells, indexInRowbuffer):
2814 if (self.rowHeadersAreEnabled() 2815 and cells[self.CELL_ICON][indexInRowbuffer] is not None): 2816 target.addAttribute('icon', 2817 cells[self.CELL_ICON][indexInRowbuffer])
2818 2819
2820 - def getVisibleCells(self):
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
2830 - def getPropertyValue(self, rowId, colId, prop):
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 # Remember that we have made this association so we can remove 2851 # it when the component is removed 2852 self._associatedProperties[f] = prop 2853 f.setPropertyDataSource(prop) 2854 return f 2855 2856 return self.formatPropertyValue(rowId, colId, prop)
2857 2858
2859 - def formatPropertyValue(self, rowId, colId, prop):
2860 """Formats table cell property values. By default the 2861 property.__str__ and return a empty string for null properties. 2862 2863 @param rowId: 2864 the Id of the row (same as item Id). 2865 @param colId: 2866 the Id of the column. 2867 @param prop: 2868 the Property to be formatted. 2869 @return: the string representation of property and its value. 2870 """ 2871 if prop is None: 2872 return '' 2873 return str(prop)
2874 2875 # Action container 2876
2877 - def addActionHandler(self, actionHandler):
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 # Assures the visual refresh. No need to reset the page buffer 2891 # before as the content has not changed, only the action 2892 # handlers. 2893 self.refreshRenderedCells()
2894 2895
2896 - def removeActionHandler(self, actionHandler):
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 # Assures the visual refresh. No need to reset the page buffer 2909 # before as the content has not changed, only the action 2910 # handlers. 2911 self.refreshRenderedCells()
2912 2913
2914 - def removeAllActionHandlers(self):
2915 """Removes all action handlers""" 2916 self._actionHandlers = None 2917 self._actionMapper = None 2918 # Assures the visual refresh. No need to reset the page buffer 2919 # before as the content has not changed, only the action 2920 # handlers. 2921 self.refreshRenderedCells()
2922 2923 2924 # Property value change listening support 2925
2926 - def valueChange(self, event):
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
2956 - def attach(self):
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
2970 - def detach(self):
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
2982 - def removeAllItems(self):
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
2992 - def removeItem(self, itemId):
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
3010 - def removeContainerProperty(self, propertyId):
3011 """Removes a Property specified by the given Property ID from the 3012 IContainer. 3013 3014 @see: L{IContainer.removeContainerProperty} 3015 """ 3016 # If a visible property is removed, remove the corresponding column 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
3029 - def addContainerProperty(self, *args):
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
3080 - def addGeneratedColumn(self, idd, generatedColumn):
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 # add to visible column list unless already there (overriding 3114 # column from DS) 3115 if idd not in self._visibleColumns: 3116 self._visibleColumns.append(idd) 3117 3118 self.refreshRowCache()
3119 3120
3121 - def getColumnGenerator(self, columnId):
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
3131 - def removeGeneratedColumn(self, columnId):
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 # remove column from visibleColumns list unless it exists in 3141 # container (generator previously overrode this column) 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
3150 - def getVisibleItemIds(self):
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
3173 - def containerItemSetChange(self, event):
3174 """IContainer datasource item set change. Table must flush its buffers 3175 on change. 3176 3177 @see: L{container.ItemSetChangeListener.containerItemSetChange} 3178 """ 3179 super(Table, self).containerItemSetChange(event) 3180 3181 if isinstance(event, ItemSetChangeEvent): 3182 evt = event 3183 # if the event is not a global one and the added item is outside 3184 # the visible/buffered area, no need to do anything 3185 if (evt.getAddedItemIndex() != -1 3186 and self._firstToBeRenderedInClient >= 0 3187 and self._lastToBeRenderedInClient >= 0 3188 and (self._firstToBeRenderedInClient 3189 > evt.getAddedItemIndex()) 3190 or (self._lastToBeRenderedInClient 3191 < evt.getAddedItemIndex())): 3192 return 3193 3194 # ensure that page still has first item in page, ignore buffer refresh 3195 # (forced in this method) 3196 self.setCurrentPageFirstItemIndex(self.getCurrentPageFirstItemIndex(), 3197 False) 3198 3199 self.refreshRowCache()
3200 3201
3202 - def containerPropertySetChange(self, event):
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 # sanitetize visibleColumns. note that we are not adding previously 3212 # non-existing properties as columns 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 # same for collapsed columns 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
3233 - def setNewItemsAllowed(self, allowNewOptions):
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
3244 - def nextItemId(self, itemId):
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
3253 - def prevItemId(self, itemId):
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
3262 - def firstItemId(self):
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
3270 - def lastItemId(self):
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
3278 - def isFirstId(self, itemId):
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
3287 - def isLastId(self, itemId):
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
3296 - def addItemAfter(self, previousItemId, newItemId=None):
3297 """Adds new item after the given item. 3298 3299 @see: L{IOrdered.addItemAfter} 3300 """ 3301 if newItemId is None: 3302 item = self.items.addItemAfter(previousItemId) 3303 else: 3304 item = self.items.addItemAfter(previousItemId, newItemId) 3305 3306 if not isinstance(self.items, container.IItemSetChangeNotifier): 3307 self.refreshRowCache() 3308 3309 return item
3310 3311
3312 - def setTableFieldFactory(self, fieldFactory):
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
3327 - def getTableFieldFactory(self):
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
3339 - def getFieldFactory(self):
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
3355 - def setFieldFactory(self, fieldFactory):
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 # Assure visual refresh 3372 self.refreshRowCache()
3373 3374
3375 - def isEditable(self):
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
3392 - def setEditable(self, editable):
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 # Assure visual refresh 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
3441 """Gets the container property IDs, which can be used to sort the item. 3442 3443 @see: L{ISortable.getSortableContainerPropertyIds} 3444 """ 3445 c = self.getContainerDataSource() 3446 if isinstance(c, container.ISortable) and not self.isSortDisabled(): 3447 return c.getSortableContainerPropertyIds() 3448 else: 3449 return list()
3450 3451
3452 - def getSortContainerPropertyId(self):
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
3460 - def setSortContainerPropertyId(self, propertyId, doSort=True):
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 # Assures the visual refresh. This should not be necessary as 3476 # sort() calls refreshRowCache. 3477 self.refreshRenderedCells()
3478 3479
3480 - def isSortAscending(self):
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
3488 - def setSortAscending(self, ascending, doSort=True):
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 # Assures the visual refresh. This should not be necessary as 3502 # sort() calls refreshRowCache 3503 self.refreshRenderedCells()
3504 3505
3506 - def isSortDisabled(self):
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
3517 - def setSortDisabled(self, sortDisabled):
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
3532 - def setLazyLoading(self, useLazyLoading):
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
3543 - def setCellStyleGenerator(self, cellStyleGenerator):
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 # Assures the visual refresh. No need to reset the page buffer 3551 # before as the content has not changed, only the style generators 3552 self.refreshRenderedCells()
3553 3554
3555 - def getCellStyleGenerator(self):
3556 """Get the current cell style generator.""" 3557 return self._cellStyleGenerator
3558 3559
3560 - def addListener(self, listener, iface=None):
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
3637 - def removeListener(self, listener, iface=None):
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
3676 - def removeCallback(self, callback, eventType=None):
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
3704 - def setEnabled(self, enabled):
3705 # Virtually identical to AbstractCompoenentContainer.setEnabled(); 3706 super(Table, self).setEnabled(enabled) 3707 if self.getParent() is not None and not self.getParent().isEnabled(): 3708 # some ancestor still disabled, don't update children 3709 return 3710 else: 3711 self.requestRepaintAll()
3712 3713
3714 - def requestRepaintAll(self):
3715 self.requestRepaint() 3716 if self._visibleComponents is not None: 3717 for c in self._visibleComponents: 3718 if isinstance(c, Form): 3719 # Form has children in layout, but is 3720 # not IComponentContainer 3721 c.requestRepaint() 3722 c.getLayout().requestRepaintAll() 3723 elif isinstance(c, Table): 3724 c.requestRepaintAll() 3725 elif isinstance(c, IComponentContainer): 3726 c.requestRepaintAll() 3727 else: 3728 c.requestRepaint()
3729 3730
3731 - def setDragMode(self, newDragMode):
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
3739 - def getDragMode(self):
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
3746 - def getTransferable(self, rawVariables):
3747 transferable = TableTransferable(rawVariables, self) 3748 return transferable
3749 3750
3751 - def getDropHandler(self):
3752 return self._dropHandler
3753 3754
3755 - def setDropHandler(self, dropHandler):
3756 self._dropHandler = dropHandler
3757 3758
3759 - def translateDropTargetDetails(self, clientVariables):
3760 return AbstractSelectTargetDetails(clientVariables, self)
3761 3762
3763 - def setMultiSelectMode(self, mode):
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
3778 - def getMultiSelectMode(self):
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
3786 - def getColumnFooter(self, propertyId):
3787 """Gets the footer caption beneath the rows 3788 3789 @param propertyId: 3790 The propertyId of the column * 3791 @return: The caption of the footer or NULL if not set 3792 """ 3793 return self._columnFooters.get(propertyId)
3794 3795
3796 - def setColumnFooter(self, propertyId, footer):
3797 """Sets the column footer caption. The column footer caption is the 3798 text displayed beneath the column if footers have been set visible. 3799 3800 @param propertyId: 3801 The properyId of the column 3802 @param footer: 3803 The caption of the footer 3804 """ 3805 if footer is None: 3806 if propertyId in self._columnFooters: 3807 del self._columnFooters[propertyId] 3808 else: 3809 self._columnFooters[propertyId] = footer 3810 3811 self.requestRepaint()
3812
3813 - def setFooterVisible(self, visible):
3814 """Sets the footer visible in the bottom of the table. 3815 3816 The footer can be used to add column related data like sums to the 3817 bottom of the Table using setColumnFooter. 3818 3819 @param visible: 3820 Should the footer be visible 3821 """ 3822 if visible != self._columnFootersVisible: 3823 self._columnFootersVisible = visible 3824 self.requestRepaint()
3825 3826
3827 - def setItemDescriptionGenerator(self, generator):
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 # Assures the visual refresh. No need to reset the page buffer 3837 # before as the content has not changed, only the descriptions 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
3848 - def isFooterVisible(self):
3849 """Is the footer currently visible? 3850 3851 @return: Returns true if visible else false 3852 """ 3853 return self._columnFootersVisible
3854 3855
3856 - def setRowGenerator(self, generator):
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
3867 - def getRowGenerator(self):
3868 """@return the current row generator""" 3869 return self._rowGenerator
3870
3871 3872 -class TableDragMode(object):
3873 """Modes that Table support as drag sourse.""" 3874 3875 #: Table does not start drag and drop events. HTM5 style events started 3876 # by browser may still happen. 3877 NONE = 'NONE' 3878 3879 #: Table starts drag with a one row only. 3880 ROW = 'ROW' 3881 3882 #: Table drags selected rows, if drag starts on a selected rows. Else it 3883 # starts like in ROW mode. Note, that in Transferable there will still 3884 # be only the row on which the drag started, other dragged rows need to 3885 # be checked from the source Table. 3886 MULTIROW = 'MULTIROW' 3887 3888 _values = [NONE, ROW, MULTIROW] 3889 3890 @classmethod
3891 - def values(cls):
3892 return cls._values[:]
3893 3894 @classmethod
3895 - def ordinal(cls, val):
3896 return cls._values.index(val)
3897
3898 3899 -class IColumnGenerator(object):
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
3906 - def generateCell(self, source, itemId, columnId):
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
3923 3924 -class ICellStyleGenerator(object):
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
3945 3946 -class TableTransferable(DataBoundTransferable):
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):
3954 super(TableTransferable, self).__init__(table, rawVariables) 3955 obj = rawVariables.get('itemId') 3956 if obj is not None: 3957 self.setData('itemId', table.itemIdMapper.get(object)) 3958 3959 obj = rawVariables.get('propertyId') 3960 if obj is not None: 3961 self.setData('propertyId', table._columnIdMap.get(object))
3962 3963
3964 - def getItemId(self):
3965 return self.getData('itemId')
3966 3967
3968 - def getPropertyId(self):
3969 return self.getData('propertyId')
3970 3971
3972 - def getSourceComponent(self):
3973 return super(TableTransferable, self).getSourceComponent()
3974
3975 3976 -class TableDropCriterion(ServerSideCriterion):
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
3988 - def __init__(self):
3989 super(TableDropCriterion, self).__init__() 3990 3991 self._table = None 3992 self._allowedItemIds = None
3993 3994
3995 - def getIdentifier(self):
3997 3998
3999 - def accept(self, dragEvent):
4000 dropTargetData = dragEvent.getTargetDetails() 4001 self._table = dragEvent.getTargetDetails().getTarget() 4002 visibleItemIds = list(self._table.getPageLength()) 4003 len(visibleItemIds) 4004 idd = self._table.getCurrentPageFirstItemId() 4005 i = 0 4006 while i < self._table.getPageLength() and idd is not None: 4007 visibleItemIds.append(idd) 4008 idd = self._table.nextItemId(idd) 4009 i += 1 4010 self._allowedItemIds = self.getAllowedItemIds(dragEvent, 4011 self._table, visibleItemIds) 4012 return dropTargetData.getItemIdOver() in self._allowedItemIds
4013 4014
4015 - def paintResponse(self, target):
4016 # send allowed nodes to client so subsequent requests 4017 # can be avoided 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
4026 - def getAllowedItemIds(self, dragEvent, table, visibleItemIds):
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
4039 4040 -class IHeaderClickListener(object):
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
4045 - def headerClick(self, event):
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')
4056 4057 4058 -class HeaderClickEvent(ClickEvent):
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
4065 - def __init__(self, source, propertyId, details):
4066 super(HeaderClickEvent, self).__init__(source, details) 4067 self._columnPropertyId = propertyId
4068 4069
4070 - def getPropertyId(self):
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
4077 4078 -class IFooterClickListener(object):
4079 """Interface for the listener for column footer mouse click events. The 4080 footerClick method is called when the user presses a footer column cell. 4081 """ 4082
4083 - def footerClick(self, event):
4084 """Called when a user clicks a footer column cell 4085 4086 @param event: 4087 The event which contains information about the column and 4088 the mouse click event 4089 """ 4090 raise NotImplementedError
4091 4092 4093 FOOTER_CLICK_METHOD = getattr(IFooterClickListener, 'footerClick')
4094 4095 4096 -class FooterClickEvent(ClickEvent):
4097 """Click event fired when clicking on the Table footers. The event 4098 includes a reference the the Table the event originated from, the property 4099 id of the column which header was pressed and details about the mouse 4100 event itself. 4101 """ 4102
4103 - def __init__(self, source, propertyId, details):
4104 """Constructor. 4105 4106 @param source: 4107 The source of the component 4108 @param propertyId: 4109 The propertyId of the column 4110 @param details: 4111 The mouse details of the click 4112 """ 4113 super(FooterClickEvent, self).__init__(source, details) 4114 self._columnPropertyId = propertyId
4115 4116
4117 - def getPropertyId(self):
4118 """Gets the property id of the column which header was pressed 4119 4120 @return: The column propety id 4121 """ 4122 return self._columnPropertyId
4123
4124 4125 -class IColumnResizeListener(object):
4126 """Interface for listening to column resize events.""" 4127
4128 - def columnResize(self, event):
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')
4140 4141 4142 -class ColumnResizeEvent(ComponentEvent):
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
4166 - def getPropertyId(self):
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
4174 - def getPreviousWidth(self):
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
4182 - def getCurrentWidth(self):
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
4189 4190 -class IColumnReorderListener(object):
4191 """Interface for listening to column reorder events.""" 4192
4193 - def columnReorder(self, event):
4194 """This method is triggered when the column has been reordered 4195 """ 4196 raise NotImplementedError
4197 4198 4199 COLUMN_REORDER_METHOD = getattr(IColumnReorderListener, 'columnReorder')
4200 4201 4202 -class ColumnReorderEvent(ComponentEvent):
4203 """This event is fired when a columns are reordered by the end user user.""" 4204 4205
4206 - def __init__(self, source):
4207 """Constructor 4208 4209 @param source: 4210 The source of the event 4211 """ 4212 super(ColumnReorderEvent, self).__init__(source)
4213
4214 4215 -class IRowGenerator(object):
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
4223 - def generateRow(self, table, itemId):
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
4254 4255 -class GeneratedRow(object):
4256
4257 - def __init__(self, *text):
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):
4282 return self._text
4283 4284
4285 - def getValue(self):
4286 return self.getText()
4287 4288
4289 - def isHtmlContentAllowed(self):
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
4302 - def isSpanColumns(self):
4303 return self._spanColumns
4304 4305
4306 - def setSpanColumns(self, spanColumns):
4307 """If set to true, only one string will be rendered, spanning the 4308 entire row. 4309 """ 4310 self._spanColumns = spanColumns
4311