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

Source Code for Module muntjac.ui.grid_layout

   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  """Defines a container that consists of components with certain coordinates 
  17  on a grid.""" 
  18   
  19  from warnings import warn 
  20   
  21  try: 
  22      from cStringIO import StringIO 
  23  except ImportError, e: 
  24      from StringIO import StringIO 
  25   
  26  from muntjac.ui.alignment import Alignment 
  27  from muntjac.ui.abstract_layout import AbstractLayout 
  28  from muntjac.ui.layout import IAlignmentHandler, ISpacingHandler 
  29  from muntjac.ui.alignment_utils import AlignmentUtils 
  30  from muntjac.terminal.gwt.client.event_id import EventId 
  31   
  32  from muntjac.event.layout_events import \ 
  33      ILayoutClickNotifier, LayoutClickEvent, ILayoutClickListener 
  34   
  35  from muntjac.util import fullname 
  36   
  37   
38 -class GridLayout(AbstractLayout, IAlignmentHandler, ISpacingHandler, 39 ILayoutClickNotifier):
40 """A container that consists of components with certain coordinates (cell 41 position) on a grid. It also maintains cursor for adding component in 42 left to right, top to bottom order. 43 44 Each component in a C{GridLayout} uses a certain L{area<grid_layout.Area>} 45 (column1,row1,column2,row2) from the grid. One should not add components 46 that would overlap with the existing components because in such case an 47 L{OverlapsException} is thrown. Adding component with cursor automatically 48 extends the grid by increasing the grid height. 49 50 @author: Vaadin Ltd. 51 @author: Richard Lincoln 52 @version: 1.1.2 53 """ 54 55 CLIENT_WIDGET = None #ClientWidget(VGridLayout, LoadStyle.EAGER) 56 57 _CLICK_EVENT = EventId.LAYOUT_CLICK 58 _ALIGNMENT_DEFAULT = Alignment.TOP_LEFT 59
60 - def __init__(self, columns=1, rows=1):
61 """Constructor for grid of given size (number of cells). Note that 62 grid's final size depends on the items that are added into the grid. 63 Grid grows if you add components outside the grid's area. 64 65 @param columns: 66 Number of columns in the grid. 67 @param rows: 68 Number of rows in the grid. 69 """ 70 super(GridLayout, self).__init__() 71 72 #: Initial grid columns. 73 self._cols = 0 74 75 #: Initial grid rows. 76 self._rows = 0 77 78 #: Cursor X position: this is where the next component with 79 # unspecified x,y is inserted 80 self._cursorX = 0 81 82 #: Cursor Y position: this is where the next component with 83 # unspecified x,y is inserted 84 self._cursorY = 0 85 86 #: Contains all items that are placed on the grid. These are 87 # components with grid area definition. 88 self._areas = list() 89 90 #: Mapping from components to their respective areas. 91 self._components = list() 92 93 #: Mapping from components to alignments (horizontal + vertical). 94 self._componentToAlignment = dict() 95 96 #: Is spacing between contained components enabled. Defaults to 97 # false. 98 self._spacing = False 99 100 #: Has there been rows inserted or deleted in the middle of the 101 # layout since the last paint operation. 102 self._structuralChange = False 103 104 self._columnExpandRatio = dict() 105 self._rowExpandRatio = dict() 106 107 self.setColumns(columns) 108 self.setRows(rows)
109 110
111 - def addComponent(self, *args):
112 """Adds a component with a specified area to the grid. The area the 113 new component should take is defined by specifying the upper left 114 corner (column1, row1) and the lower right corner (column2, row2) of 115 the area. 116 117 If the new component overlaps with any of the existing components 118 already present in the grid the operation will fail and an 119 L{OverlapsException} is thrown. 120 121 Alternatively, adds the component into this container to cells 122 column1,row1 (NortWest corner of the area.) End coordinates (SouthEast 123 corner of the area) are the same as column1,row1. Component width and 124 height is 1. 125 126 Finally, adds the component into this container to the cursor position. 127 If the cursor position is already occupied, the cursor is moved 128 forwards to find free position. If the cursor goes out from the bottom 129 of the grid, the grid is automatically extended. 130 131 @param args: tuple of the form 132 - (c, column1, row1, column2, row2) 133 1. the component to be added. 134 2. the column of the upper left corner of the area 135 C{c} is supposed to occupy. 136 3. the row of the upper left corner of the area 137 C{c} is supposed to occupy. 138 4. the column of the lower right corner of the area 139 C{c} is supposed to occupy. 140 5. the row of the lower right corner of the area 141 C{c} is supposed to occupy. 142 - (c, column, row) 143 1. the component to be added. 144 2. the column index. 145 3. the row index. 146 - (c) 147 1. the component to be added. 148 @raise OverlapsException: 149 if the new component overlaps with any of the components 150 already in the grid. 151 @raise OutOfBoundsException: 152 if the cell is outside the grid area. 153 """ 154 nargs = len(args) 155 if nargs == 1: 156 component, = args 157 # Finds first available place from the grid 158 done = False 159 while not done: 160 try: 161 area = Area(component, self._cursorX, self._cursorY, 162 self._cursorX, self._cursorY) 163 self.checkExistingOverlaps(area) 164 done = True 165 except OverlapsException, e: 166 self.space() 167 168 # Extends the grid if needed 169 if self._cursorX >= self._cols: 170 self._cols = self._cursorX + 1 171 else: 172 self._cols = self._cols 173 174 if self._cursorY >= self._rows: 175 self._rows = self._cursorY + 1 176 else: 177 self._rows = self._rows 178 179 self.addComponent(component, self._cursorX, self._cursorY) 180 elif nargs == 3: 181 c, column, row = args 182 self.addComponent(c, column, row, column, row) 183 elif nargs == 5: 184 component, column1, row1, column2, row2 = args 185 186 if component is None: 187 raise ValueError, 'Component must not be null' 188 189 # Checks that the component does not already exist in the container 190 if component in self._components: 191 raise ValueError, 'Component is already in the container' 192 193 # Creates the area 194 area = Area(component, column1, row1, column2, row2) 195 196 # Checks the validity of the coordinates 197 if column2 < column1 or row2 < row1: 198 raise ValueError, 'Illegal coordinates for the component' 199 200 if (column1 < 0 or row1 < 0 or column2 >= self._cols 201 or row2 >= self._rows): 202 raise OutOfBoundsException(area) 203 204 # Checks that newItem does not overlap with existing items 205 self.checkExistingOverlaps(area) 206 207 # Inserts the component to right place at the list 208 # Respect top-down, left-right ordering 209 # component.setParent(this); 210 index = 0 211 done = False 212 while not done and (index < len(self._areas)): 213 existingArea = self._areas[index] 214 if ((existingArea._row1 >= row1 215 and existingArea._column1 > column1) 216 or (existingArea._row1 > row1)): 217 self._areas.insert(index, area) 218 self._components.insert(index, component) 219 done = True 220 index += 1 221 222 if not done: 223 self._areas.append(area) 224 self._components.append(component) 225 226 # Attempt to add to super 227 try: 228 super(GridLayout, self).addComponent(component) 229 except ValueError, e: 230 self._areas.remove(area) 231 self._components.remove(component) 232 raise e 233 234 # update cursor position, if it's within this area; use first 235 # position outside this area, even if it's occupied 236 if (self._cursorX >= column1 and self._cursorX <= column2 237 and self._cursorY >= row1 and self._cursorY <= row2): 238 # cursor within area 239 self._cursorX = column2 + 1 # one right of area 240 if self._cursorX >= self._cols: 241 # overflowed columns 242 self._cursorX = 0 243 # first col 244 # move one row down, or one row under the area 245 self._cursorY = (row2 if column1 == 0 else row1) + 1 246 else: 247 self._cursorY = row1 248 249 self.requestRepaint() 250 else: 251 raise ValueError, 'invalid number of arguments'
252 253
254 - def checkExistingOverlaps(self, area):
255 """Tests if the given area overlaps with any of the items already 256 on the grid. 257 258 @param area: 259 the Area to be checked for overlapping. 260 @raise OverlapsException: 261 if C{area} overlaps with any existing area. 262 """ 263 for existingArea in self._areas: 264 if existingArea.overlaps(area): 265 # Component not added, overlaps with existing component 266 raise OverlapsException(existingArea)
267 268
269 - def newLine(self):
270 """Force the next component to be added to the beginning of the next 271 line. By calling this function user can ensure that no more components 272 are added to the right of the previous component. 273 274 @see: L{space} 275 """ 276 self._cursorX = 0 277 self._cursorY += 1
278 279
280 - def space(self):
281 """Moves the cursor forwards by one. If the cursor goes out of the 282 right grid border, move it to next line. 283 284 @see: L{newLine} 285 """ 286 self._cursorX += 1 287 if self._cursorX >= self._cols: 288 self._cursorX = 0 289 self._cursorY += 1
290 291
292 - def removeComponent(self, *args):
293 """Removes the given component from this container or removes the 294 component specified with it's cell index. 295 296 @param args: tuple of the form 297 - (c) 298 1. the component to be removed. 299 - (column, row) 300 1. the component's column. 301 2. the component's row. 302 """ 303 # Check that the component is contained in the container 304 nargs = len(args) 305 if nargs == 1: 306 component, = args 307 if component is None or component not in self._components: 308 return 309 area = None 310 for a in self._areas: 311 if a.getComponent() == component: 312 area = a 313 314 self._components.remove(component) 315 if area is not None: 316 self._areas.remove(area) 317 318 if component in self._componentToAlignment: 319 del self._componentToAlignment[component] 320 321 super(GridLayout, self).removeComponent(component) 322 323 self.requestRepaint() 324 elif nargs == 2: 325 column, row = args 326 # Finds the area 327 for area in self._areas: 328 if area.getColumn1() == column and area.getRow1() == row: 329 self.removeComponent(area.getComponent()) 330 return 331 else: 332 raise ValueError, 'too many arguments'
333 334
335 - def getComponentIterator(self):
336 """Gets an iterator to the component container contents. Using the 337 Iterator it's possible to step through the contents of the container. 338 339 @return: the iterator of the components inside the container. 340 """ 341 return iter(self._components)
342 343
344 - def getComponentCount(self):
345 """Gets the number of contained components. Consistent with the 346 iterator returned by L{getComponentIterator}. 347 348 @return: the number of contained components 349 """ 350 return len(self._components)
351 352
353 - def paintContent(self, target):
354 """Paints the contents of this component. 355 356 @param target: the Paint Event. 357 @raise PaintException: if the paint operation failed. 358 """ 359 super(GridLayout, self).paintContent(target) 360 361 # TODO refactor attribute names in future release. 362 target.addAttribute('h', self._rows) 363 target.addAttribute('w', self._cols) 364 365 target.addAttribute('structuralChange', self._structuralChange) 366 self._structuralChange = False 367 368 if self._spacing: 369 target.addAttribute('spacing', self._spacing) 370 371 # Area iterator 372 areaiterator = iter(self._areas) 373 374 # Current item to be processed (fetch first item) 375 try: 376 area = areaiterator.next() 377 except StopIteration: 378 area = None 379 380 # Collects rowspan related information here 381 cellUsed = dict() 382 383 # Empty cell collector 384 emptyCells = 0 385 386 alignmentsArray = [None] * len(self._components) 387 columnExpandRatioArray = [None] * self._cols 388 rowExpandRatioArray = [None] * self._rows 389 390 realColExpandRatioSum = 0 391 colSum = self.getExpandRatioSum(self._columnExpandRatio) 392 if colSum == 0: 393 # no columns has been expanded, all cols have same 394 # expand rate 395 equalSize = 1 / float(self._cols) 396 myRatio = int( round(equalSize * 1000) ) 397 for i in range(self._cols): 398 columnExpandRatioArray[i] = myRatio 399 400 realColExpandRatioSum = myRatio * self._cols 401 else: 402 for i in range(self._cols): 403 myRatio = int( round((self.getColumnExpandRatio(i) / colSum) 404 * 1000) ) 405 columnExpandRatioArray[i] = myRatio 406 realColExpandRatioSum += myRatio 407 408 equallyDividedRows = False 409 realRowExpandRatioSum = 0 410 rowSum = self.getExpandRatioSum(self._rowExpandRatio) 411 if rowSum == 0: 412 # no rows have been expanded 413 equallyDividedRows = True 414 equalSize = 1 / float(self._rows) 415 myRatio = int( round(equalSize * 1000) ) 416 for i in range(self._rows): 417 rowExpandRatioArray[i] = myRatio 418 realRowExpandRatioSum = myRatio * self._rows 419 420 index = 0 421 # Iterates every applicable row 422 for cury in range(self._rows): 423 target.startTag('gr') 424 425 if not equallyDividedRows: 426 myRatio = int( round((self.getRowExpandRatio(cury) / rowSum) 427 * 1000) ) 428 rowExpandRatioArray[cury] = myRatio 429 realRowExpandRatioSum += myRatio 430 431 # Iterates every applicable column 432 for curx in range(self._cols): 433 434 # Checks if current item is located at curx,cury 435 if (area is not None and (area._row1 == cury) 436 and (area._column1 == curx)): 437 438 # First check if empty cell needs to be rendered 439 if emptyCells > 0: 440 target.startTag('gc') 441 target.addAttribute('x', curx - emptyCells) 442 target.addAttribute('y', cury) 443 if emptyCells > 1: 444 target.addAttribute('w', emptyCells) 445 446 target.endTag('gc') 447 emptyCells = 0 448 449 # Now proceed rendering current item 450 cols = (area._column2 - area._column1) + 1 451 rows = (area._row2 - area._row1) + 1 452 target.startTag('gc') 453 454 target.addAttribute('x', curx) 455 target.addAttribute('y', cury) 456 457 if cols > 1: 458 target.addAttribute('w', cols) 459 460 if rows > 1: 461 target.addAttribute('h', rows) 462 463 area.getComponent().paint(target) 464 465 ca = self.getComponentAlignment( area.getComponent() ) 466 alignmentsArray[index] = str(ca.getBitMask()) 467 index += 1 468 469 target.endTag('gc') 470 471 # Fetch next item 472 try: 473 area = areaiterator.next() 474 except StopIteration: 475 area = None 476 477 # Updates the cellUsed if rowspan needed 478 if rows > 1: 479 spannedx = curx 480 for _ in range(1, self._cols + 1): 481 cellUsed[int(spannedx)] = int((cury + rows) - 1) 482 spannedx += 1 483 484 # Skips the current item's spanned columns 485 if cols > 1: 486 curx += cols - 1 487 else: 488 # Checks against cellUsed, render space or ignore cell 489 if int(curx) in cellUsed: 490 491 # Current column contains already an item, 492 # check if rowspan affects at current x,y position 493 rowspanDepth = int( cellUsed.get(int(curx)) ) 494 495 if rowspanDepth >= cury: 496 497 # ignore cell 498 # Check if empty cell needs to be rendered 499 if emptyCells > 0: 500 target.startTag('gc') 501 target.addAttribute('x', curx - emptyCells) 502 target.addAttribute('y', cury) 503 if emptyCells > 1: 504 target.addAttribute('w', emptyCells) 505 506 target.endTag('gc') 507 508 emptyCells = 0 509 else: 510 511 # empty cell is needed 512 emptyCells += 1 513 514 # Removes the cellUsed key as it has become 515 # obsolete 516 del cellUsed[int(curx)] 517 else: 518 # empty cell is needed 519 emptyCells += 1 520 521 # Last column handled of current row 522 523 # Checks if empty cell needs to be rendered 524 if emptyCells > 0: 525 target.startTag('gc') 526 target.addAttribute('x', self._cols - emptyCells) 527 target.addAttribute('y', cury) 528 if emptyCells > 1: 529 target.addAttribute('w', emptyCells) 530 531 target.endTag('gc') 532 533 emptyCells = 0 534 535 target.endTag('gr') 536 537 # Last row handled 538 539 # correct possible rounding error 540 if len(rowExpandRatioArray) > 0: 541 rowExpandRatioArray[0] -= realRowExpandRatioSum - 1000 542 543 if len(columnExpandRatioArray) > 0: 544 columnExpandRatioArray[0] -= realColExpandRatioSum - 1000 545 546 target.addAttribute('colExpand', columnExpandRatioArray) 547 target.addAttribute('rowExpand', rowExpandRatioArray) 548 549 # Add child component alignment info to layout tag 550 target.addAttribute('alignments', alignmentsArray)
551 552
553 - def getExpandRatioSum(self, ratioMap):
554 summ = 0.0 555 for v in ratioMap.values(): 556 summ += v 557 return summ
558 559
560 - def getComponentAlignment(self, childComponent):
561 alignment = self._componentToAlignment.get(childComponent) 562 if alignment is None: 563 return self._ALIGNMENT_DEFAULT 564 else: 565 return alignment
566 567 568
569 - def setColumns(self, columns):
570 """Sets the number of columns in the grid. The column count can 571 not be reduced if there are any areas that would be outside of the 572 shrunk grid. 573 574 @param columns: the new number of columns in the grid. 575 """ 576 # The the param 577 if columns < 1: 578 raise ValueError, ('The number of columns and rows in the ' 579 'grid must be at least 1') 580 581 # In case of no change 582 if self._cols == columns: 583 return 584 585 # Checks for overlaps 586 if self._cols > columns: 587 for area in self._areas: 588 if area.column2 >= columns: 589 raise OutOfBoundsException(area) 590 591 self._cols = columns 592 593 self.requestRepaint()
594 595
596 - def getColumns(self):
597 """Get the number of columns in the grid. 598 599 @return: the number of columns in the grid. 600 """ 601 return self._cols
602 603
604 - def setRows(self, rows):
605 """Sets the number of rows in the grid. The number of rows can 606 not be reduced if there are any areas that would be outside of 607 the shrunk grid. 608 609 @param rows: the new number of rows in the grid. 610 """ 611 # The the param 612 if rows < 1: 613 raise ValueError, ('The number of columns and rows in the ' 614 'grid must be at least 1') 615 616 # In case of no change 617 if self._rows == rows: 618 return 619 620 # Checks for overlaps 621 if self._rows > rows: 622 for area in self._areas: 623 if area.row2 >= rows: 624 raise OutOfBoundsException(area) 625 626 self._rows = rows 627 628 self.requestRepaint()
629 630
631 - def getRows(self):
632 """Get the number of rows in the grid. 633 634 @return: the number of rows in the grid. 635 """ 636 return self._rows
637 638
639 - def getCursorX(self):
640 """Gets the current cursor x-position. The cursor position points 641 the position for the next component that is added without specifying 642 its coordinates (grid cell). When the cursor position is occupied, 643 the next component will be added to first free position after the 644 cursor. 645 646 @return: the grid column the Cursor is on. 647 """ 648 return self._cursorX
649 650
651 - def setCursorX(self, cursorX):
652 """Sets the current cursor x-position. This is usually handled 653 automatically by GridLayout. 654 """ 655 self._cursorX = cursorX
656 657
658 - def getCursorY(self):
659 """Gets the current cursor y-position. The cursor position points 660 the position for the next component that is added without specifying 661 its coordinates (grid cell). When the cursor position is occupied, 662 the next component will be added to first free position after the 663 cursor. 664 665 @return: the grid row the Cursor is on. 666 """ 667 return self._cursorY
668 669
670 - def setCursorY(self, cursorY):
671 """Sets the current cursor y-position. This is usually handled 672 automatically by GridLayout. 673 """ 674 self._cursorY = cursorY
675 676
677 - def replaceComponent(self, oldComponent, newComponent):
678 # Gets the locations 679 oldLocation = None 680 newLocation = None 681 682 for location in self._areas: 683 component = location.getComponent() 684 if component == oldComponent: 685 oldLocation = location 686 687 if component == newComponent: 688 newLocation = location 689 690 if oldLocation is None: 691 self.addComponent(newComponent) 692 elif newLocation is None: 693 self.removeComponent(oldComponent) 694 self.addComponent(newComponent, 695 oldLocation.getColumn1(), oldLocation.getRow1(), 696 oldLocation.getColumn2(), oldLocation.getRow2()) 697 else: 698 oldLocation.setComponent(newComponent) 699 newLocation.setComponent(oldComponent) 700 self.requestRepaint()
701 702
703 - def removeAllComponents(self):
704 # Removes all components from this container. 705 super(GridLayout, self).removeAllComponents() 706 self._componentToAlignment = dict() 707 self._cursorX = 0 708 self._cursorY = 0
709 710
711 - def setComponentAlignment(self, *args):
712 """Sets the component alignment using a short hand string notation. 713 714 @deprecated: Replaced by L{setComponentAlignment} 715 @param args: tuple of the form 716 - (component, alignment) 717 1. A child component in this layout 718 2. A short hand notation described in L{AlignmentUtils} 719 - (childComponent, horizontalAlignment, verticalAlignment) 720 """ 721 warn('replaced by setComponentAlignment', DeprecationWarning) 722 723 nargs = len(args) 724 if nargs == 2: 725 if isinstance(args[1], Alignment): 726 childComponent, alignment = args 727 self._componentToAlignment[childComponent] = alignment 728 self.requestRepaint() 729 else: 730 component, alignment = args 731 AlignmentUtils.setComponentAlignment(self, component, 732 alignment) 733 elif nargs == 3: 734 childComponent, horizontalAlignment, verticalAlignment = args 735 self._componentToAlignment[childComponent] = \ 736 Alignment(horizontalAlignment + verticalAlignment) 737 self.requestRepaint() 738 else: 739 raise ValueError, 'invalid number of arguments'
740 741
742 - def setSpacing(self, enabled):
743 self._spacing = enabled 744 self.requestRepaint()
745 746
747 - def isSpacingEnabled(self):
748 return self._spacing
749 750
751 - def isSpacing(self):
752 return self._spacing
753 754
755 - def insertRow(self, row):
756 """Inserts an empty row at the chosen position in the grid. 757 758 @param row: Number of the row the new row will be inserted before 759 """ 760 if row > self._rows: 761 raise ValueError, ('Cannot insert row at ' 762 + row + ' in a gridlayout with height ' + self._rows) 763 764 for existingArea in self._areas: 765 # Areas ending below the row needs to be moved down or stretched 766 if existingArea.row2 >= row: 767 existingArea.row2 += 1 768 769 if existingArea.row1 >= row: 770 existingArea.row1 += 1 771 772 if self._cursorY >= row: 773 self._cursorY += 1 774 775 self.setRows(self._rows + 1) 776 self._structuralChange = True 777 self.requestRepaint()
778 779
780 - def removeRow(self, row):
781 """Removes row and all components in the row. Components which span 782 over several rows are removed if the selected row is the component's 783 first row. 784 785 If the last row is removed then all remaining components will be 786 removed and the grid will be reduced to one row. The cursor will be 787 moved to the upper left cell of the grid. 788 789 @param row: The row number to remove 790 """ 791 if row >= self._rows: 792 raise ValueError, ('Cannot delete row ' 793 + row + ' from a gridlayout with height ' + self._rows) 794 795 # Remove all components in row 796 for col in range(self.getColumns()): 797 self.removeComponent(col, row) 798 799 # Shrink or remove areas in the selected row 800 for existingArea in self._areas: 801 if existingArea.row2 >= row: 802 existingArea.row2 -= 1 803 804 if existingArea.row1 > row: 805 existingArea.row1 -= 1 806 807 if self._rows == 1: 808 # Removing the last row means that the dimensions of the Grid 809 # layout will be truncated to 1 empty row and the cursor is moved 810 # to the first cell 811 self._cursorX = 0 812 self._cursorY = 0 813 else: 814 self.setRows(self._rows - 1) 815 if self._cursorY > row: 816 self._cursorY -= 1 817 818 self._structuralChange = True 819 self.requestRepaint()
820 821
822 - def setColumnExpandRatio(self, columnIndex, ratio):
823 """Sets the expand ratio of given column. Expand ratio defines how 824 excess space is distributed among columns. Excess space means the 825 space not consumed by non relatively sized components. 826 827 By default excess space is distributed evenly. 828 829 Note, that width needs to be defined for this method to have any 830 effect. 831 832 @see: L{setWidth} 833 """ 834 self._columnExpandRatio[columnIndex] = ratio 835 self.requestRepaint()
836 837
838 - def getColumnExpandRatio(self, columnIndex):
839 """Returns the expand ratio of given column 840 841 @see: L{setColumnExpandRatio} 842 @return: the expand ratio, 0.0 by default 843 """ 844 r = self._columnExpandRatio.get(columnIndex) 845 return 0 if r is None else float(r)
846 847
848 - def setRowExpandRatio(self, rowIndex, ratio):
849 """Sets the expand ratio of given row. Expand ratio defines how 850 excess space is distributed among rows. Excess space means the 851 space not consumed by non relatively sized components. 852 853 By default excess space is distributed evenly. 854 855 Note, that height needs to be defined for this method to have 856 any effect. 857 858 @see: L{setHeight} 859 """ 860 self._rowExpandRatio[rowIndex] = ratio 861 self.requestRepaint()
862 863
864 - def getRowExpandRatio(self, rowIndex):
865 """Returns the expand ratio of given row. 866 867 @see: L{setRowExpandRatio} 868 @return: the expand ratio, 0.0 by default 869 """ 870 r = self._rowExpandRatio.get(rowIndex) 871 return 0 if r is None else float(r)
872 873
874 - def getComponent(self, x, y):
875 """Gets the Component at given index. 876 877 @param x: 878 x-index 879 @param y: 880 y-index 881 @return: Component in given cell or null if empty 882 """ 883 for area in self._areas: 884 if (area.getColumn1() <= x and x <= area.getColumn2() 885 and area.getRow1() <= y and y <= area.getRow2()): 886 return area.getComponent() 887 888 return None
889 890
891 - def getComponentArea(self, component):
892 """Returns information about the area where given component is layed 893 in the GridLayout. 894 895 @param component: 896 the component whose area information is requested. 897 @return: an Area object that contains information how component is 898 layed in the grid 899 """ 900 for area in self._areas: 901 if area.getComponent() == component: 902 return area 903 return None
904 905
906 - def addListener(self, listener, iface=None):
907 if (isinstance(listener, ILayoutClickListener) and 908 (iface is None or issubclass(iface, ILayoutClickListener))): 909 self.registerListener(self._CLICK_EVENT, LayoutClickEvent, 910 listener, ILayoutClickListener.clickMethod) 911 912 super(GridLayout, self).addListener(listener, iface)
913 914
915 - def addCallback(self, callback, eventType=None, *args):
916 if eventType is None: 917 eventType = callback._eventType 918 919 if issubclass(eventType, LayoutClickEvent): 920 self.registerCallback(LayoutClickEvent, callback, 921 self._CLICK_EVENT, *args) 922 else: 923 super(GridLayout, self).addCallback(callback, eventType, *args)
924 925
926 - def removeListener(self, listener, iface=None):
927 if (isinstance(listener, ILayoutClickListener) and 928 (iface is None or issubclass(iface, ILayoutClickListener))): 929 self.withdrawListener(self._CLICK_EVENT, LayoutClickEvent, 930 listener) 931 932 super(GridLayout, self).removeListener(listener, iface)
933 934
935 - def removeCallback(self, callback, eventType=None):
936 if eventType is None: 937 eventType = callback._eventType 938 939 if issubclass(eventType, LayoutClickEvent): 940 self.withdrawCallback(LayoutClickEvent, callback, 941 self._CLICK_EVENT) 942 943 else: 944 super(GridLayout, self).removeCallback(callback, eventType)
945 946
947 -class Area(object):
948 """This class defines an area on a grid. An Area is defined by the cells 949 of its upper left corner (column1,row1) and lower right corner 950 (column2, row2). 951 952 @author: Vaadin Ltd. 953 @author: Richard Lincoln 954 @version: 1.1.2 955 """ 956
957 - def __init__(self, component, column1, row1, column2, row2):
958 """Construct a new area on a grid. 959 960 @param component: 961 the component connected to the area. 962 @param column1: 963 The column of the upper left corner cell of the area 964 C{c} is supposed to occupy. 965 @param row1: 966 The row of the upper left corner cell of the area 967 C{c} is supposed to occupy. 968 @param column2: 969 The column of the lower right corner cell of the area 970 C{c} is supposed to occupy. 971 @param row2: 972 The row of the lower right corner cell of the area 973 C{c} is supposed to occupy. 974 @raise OverlapsException: 975 if the new component overlaps with any of the components 976 already in the grid 977 """ 978 # The column of the upper left corner cell of the area. 979 self._column1 = column1 980 981 # The row of the upper left corner cell of the area. 982 self._row1 = row1 983 984 # The column of the lower right corner cell of the area. 985 self._column2 = column2 986 987 # The row of the lower right corner cell of the area. 988 self._row2 = row2 989 990 # Component painted on the area. 991 self._component = component
992 993
994 - def overlaps(self, other):
995 """Tests if the given Area overlaps with an another Area. 996 997 @param other: 998 the Another Area that's to be tested for overlap with this 999 area. 1000 @return: C{True} if C{other} overlaps with this 1001 area, C{False} if it doesn't. 1002 """ 1003 return (self._column1 <= other.getColumn2() 1004 and self._row1 <= other.getRow2() 1005 and self._column2 >= other.getColumn1() 1006 and self._row2 >= other.getRow1())
1007 1008
1009 - def getComponent(self):
1010 """Gets the component connected to the area. 1011 1012 @return: the Component. 1013 """ 1014 return self._component
1015 1016
1017 - def setComponent(self, newComponent):
1018 """Sets the component connected to the area. 1019 1020 This function only sets the value in the datastructure and does not 1021 send any events or set parents. 1022 1023 @param newComponent: 1024 the new connected overriding the existing one. 1025 """ 1026 self._component = newComponent
1027 1028
1029 - def getX1(self):
1030 """@deprecated: Use getColumn1() instead. 1031 1032 @see: L{GridLayout.getColumn1} 1033 """ 1034 warn('Use getColumn1() instead.', DeprecationWarning) 1035 return self.getColumn1()
1036 1037
1038 - def getColumn1(self):
1039 """Gets the column of the top-left corner cell. 1040 1041 @return: the column of the top-left corner cell. 1042 """ 1043 return self._column1
1044 1045
1046 - def getX2(self):
1047 """@deprecated: Use getColumn2() instead. 1048 1049 @see: L{GridLayout.getColumn2} 1050 """ 1051 warn('Use getColumn2() instead.', DeprecationWarning) 1052 return self.getColumn2()
1053 1054
1055 - def getColumn2(self):
1056 """Gets the column of the bottom-right corner cell. 1057 1058 @return: the column of the bottom-right corner cell. 1059 """ 1060 return self._column2
1061 1062
1063 - def getY1(self):
1064 """@deprecated: Use getRow1() instead. 1065 1066 @see: L{GridLayout.getRow1} 1067 """ 1068 warn('Use getRow1() instead.', DeprecationWarning) 1069 return self.getRow1()
1070 1071
1072 - def getRow1(self):
1073 """Gets the row of the top-left corner cell. 1074 1075 @return: the row of the top-left corner cell. 1076 """ 1077 return self._row1
1078 1079
1080 - def getY2(self):
1081 """@deprecated: Use getRow2() instead. 1082 1083 @see: L{GridLayout.getRow2} 1084 """ 1085 warn('Use getRow2() instead.', DeprecationWarning) 1086 return self.getRow2()
1087 1088
1089 - def getRow2(self):
1090 """Gets the row of the bottom-right corner cell. 1091 1092 @return: the row of the bottom-right corner cell. 1093 """ 1094 return self._row2
1095 1096
1097 -class OverlapsException(RuntimeError):
1098 """Gridlayout does not support laying components on top of each other. 1099 An C{OverlapsException} is thrown when a component already 1100 exists (even partly) at the same space on a grid with the new component. 1101 1102 @author: Vaadin Ltd. 1103 @author: Richard Lincoln 1104 @version: 1.1.2 1105 """ 1106
1107 - def __init__(self, existingArea):
1108 """Constructs an C{OverlapsException}. 1109 """ 1110 self._existingArea = existingArea
1111 1112
1113 - def getMessage(self):
1114 sb = StringIO() 1115 component = self._existingArea.getComponent() 1116 sb.write(component) 1117 sb.write('( type = ') 1118 sb.write(fullname(component)) 1119 if component.getCaption() is not None: 1120 sb.write(', caption = \"') 1121 sb.write(component.getCaption()) 1122 sb.write('\"') 1123 sb.write(')') 1124 sb.write(' is already added to ') 1125 sb.write(self._existingArea.column1) 1126 sb.write(',') 1127 sb.write(self._existingArea.column1) 1128 sb.write(',') 1129 sb.write(self._existingArea.row1) 1130 sb.write(',') 1131 sb.write(self._existingArea.row2) 1132 sb.write('(column1, column2, row1, row2).') 1133 result = sb.getvalue() 1134 sb.close() 1135 return result
1136 1137
1138 - def getArea(self):
1139 """Gets the area . 1140 1141 @return: the existing area. 1142 """ 1143 return self._existingArea
1144 1145
1146 -class OutOfBoundsException(RuntimeError):
1147 """An C{Exception} object which is thrown when an area 1148 exceeds the bounds of the grid. 1149 1150 @author: Vaadin Ltd. 1151 @author: Richard Lincoln 1152 @version: 1.1.2 1153 """ 1154
1155 - def __init__(self, areaOutOfBounds):
1156 """Constructs an C{OoutOfBoundsException} with the 1157 specified detail message. 1158 """ 1159 self._areaOutOfBounds = areaOutOfBounds
1160 1161
1162 - def getArea(self):
1163 """Gets the area that is out of bounds. 1164 1165 @return: the area out of Bound. 1166 """ 1167 return self._areaOutOfBounds
1168