Package muntjac :: Package terminal :: Package gwt :: Package server :: Module component_size_validator
[hide private]
[frames] | no frames]

Source Code for Module muntjac.terminal.gwt.server.component_size_validator

  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  import logging 
 17  import traceback 
 18   
 19  from collections import deque 
 20   
 21  try: 
 22      from cStringIO import StringIO 
 23  except ImportError, e: 
 24      from StringIO import StringIO 
 25   
 26  from muntjac.ui.ordered_layout import OrderedLayout 
 27  from muntjac.terminal.sizeable import ISizeable 
 28  from muntjac.ui.custom_component import CustomComponent 
 29  from muntjac.ui.panel import Panel 
 30  from muntjac.ui.component_container import IComponentContainer 
 31  from muntjac.ui.form import Form 
 32  from muntjac.ui.window import Window 
 33  from muntjac.ui.abstract_ordered_layout import AbstractOrderedLayout 
 34  from muntjac.ui.vertical_layout import VerticalLayout 
 35  from muntjac.ui.grid_layout import GridLayout 
 36  from muntjac.ui.split_panel import SplitPanel 
 37  from muntjac.ui.tab_sheet import TabSheet 
 38   
 39   
 40  logger = logging.getLogger(__name__) 
41 42 43 -class ComponentSizeValidator(object):
44 45 _LAYERS_SHOWN = 4 46 47 _creationLocations = dict() 48 _widthLocations = dict() 49 _heightLocations = dict() 50 51 @classmethod
52 - def validateComponentRelativeSizes(cls, component, errors, parent):
53 """Recursively checks given component and its subtree for invalid 54 layout setups. Prints errors to std err stream. 55 56 @param component: 57 component to check 58 @return: set of first level errors found 59 """ 60 61 invalidHeight = not cls.checkHeights(component) 62 invalidWidth = not cls.checkWidths(component) 63 64 if invalidHeight or invalidWidth: 65 error = InvalidLayout(component, invalidHeight, invalidWidth) 66 if parent is not None: 67 parent.addError(error) 68 else: 69 if errors is None: 70 errors = list() 71 errors.append(error) 72 parent = error 73 74 if isinstance(component, Panel): 75 panel = component 76 errors = cls.validateComponentRelativeSizes(panel.getContent(), 77 errors, parent) 78 79 elif isinstance(component, IComponentContainer): 80 lo = component 81 for c in lo.getComponentIterator(): 82 errors = cls.validateComponentRelativeSizes(c, errors, parent) 83 84 elif isinstance(component, Form): 85 form = component 86 if form.getLayout() is not None: 87 errors = cls.validateComponentRelativeSizes(form.getLayout(), 88 errors, parent) 89 if form.getFooter() is not None: 90 errors = cls.validateComponentRelativeSizes(form.getFooter(), 91 errors, parent) 92 93 return errors
94 95 96 @classmethod
97 - def printServerError(cls, msg, attributes, widthError, errorStream):
98 err = StringIO() 99 err.write('Muntjac DEBUG\n') 100 101 indent = str() 102 if attributes is not None: 103 while len(attributes) > cls._LAYERS_SHOWN: 104 attributes.pop() 105 while len(attributes) > 0: 106 ci = attributes.pop() 107 cls.showComponent(ci.component, ci.info, err, indent, 108 widthError) 109 110 err.write('Layout problem detected: ') 111 err.write(msg) 112 err.write('\n') 113 err.write('Relative sizes were replaced by undefined sizes, ' 114 'components may not render as expected.\n') 115 errorStream.write(err.getvalue()) 116 err.close()
117 118 119 @classmethod
120 - def checkHeights(cls, component):
121 try: 122 if not cls.hasRelativeHeight(component): 123 return True 124 125 if isinstance(component, Window): 126 return True 127 128 if component.getParent() is None: 129 return True 130 131 return cls.parentCanDefineHeight(component) 132 except Exception: 133 logger.info('An exception occurred while validating sizes.') 134 return True
135 136 137 @classmethod
138 - def checkWidths(cls, component):
139 try: 140 if not cls.hasRelativeWidth(component): 141 return True 142 143 if isinstance(component, Window): 144 return True 145 146 if component.getParent() is None: 147 return True 148 149 return cls.parentCanDefineWidth(component) 150 except Exception: 151 logger.info('An exception occurred while validating sizes.') 152 return True
153 154 155 @classmethod
156 - def getHeightAttributes(cls, component):
157 attributes = deque() 158 info = ComponentInfo(component, cls.getHeightString(component)) 159 attributes.append(info) 160 parent = component.getParent() 161 info = ComponentInfo(parent, cls.getHeightString(parent)) 162 attributes.append(info) 163 164 while parent is not None: 165 info = ComponentInfo(parent, cls.getHeightString(parent)) 166 attributes.append(info) 167 parent = parent.getParent() 168 169 return attributes
170 171 172 @classmethod
173 - def getWidthAttributes(cls, component):
174 attributes = deque() 175 info = ComponentInfo(component, cls.getWidthString(component)) 176 attributes.append(info) 177 parent = component.getParent() 178 info = ComponentInfo(parent, cls.getWidthString(parent)) 179 attributes.append(info) 180 181 while parent is not None: 182 info = ComponentInfo(parent, cls.getWidthString(parent)) 183 attributes.append(info) 184 parent = parent.getParent() 185 186 return attributes
187 188 189 @classmethod
190 - def getWidthString(cls, component):
191 width = 'width: ' 192 if cls.hasRelativeWidth(component): 193 width += 'RELATIVE, ' + component.getWidth() + ' %' 194 elif isinstance(component, Window) and component.getParent() is None: 195 width += 'MAIN WINDOW' 196 elif component.getWidth() >= 0: 197 width += 'ABSOLUTE, ' + component.getWidth() + ' ' \ 198 + ISizeable.UNIT_SYMBOLS[ component.getWidthUnits() ] 199 else: 200 width += 'UNDEFINED' 201 202 return width
203 204 205 @classmethod
206 - def getHeightString(cls, component):
207 height = 'height: ' 208 if cls.hasRelativeHeight(component): 209 height += 'RELATIVE, ' + component.getHeight() + ' %' 210 elif isinstance(component, Window) and component.getParent() is None: 211 height += 'MAIN WINDOW' 212 elif component.getHeight() > 0: 213 height += 'ABSOLUTE, ' + component.getHeight() + ' ' \ 214 + ISizeable.UNIT_SYMBOLS[ component.getHeightUnits() ] 215 else: 216 height += 'UNDEFINED' 217 218 return height
219 220 221 @classmethod
222 - def showComponent(cls, component, attribute, err, indent, widthError):
223 224 createLoc = cls._creationLocations[component] 225 226 if widthError: 227 sizeLoc = cls._widthLocations[component] 228 else: 229 sizeLoc = cls._heightLocations[component] 230 231 err.write(indent) 232 indent.write(' ') 233 err.write('- ') 234 err.write(component.__class__.__name__) 235 err.write('/') 236 err.write(hex( hash(component) )) 237 238 if component.getCaption() is not None: 239 err.write(' \"') 240 err.write(component.getCaption()) 241 err.write('\"') 242 243 if component.getDebugId() is not None: 244 err.write(' debugId: ') 245 err.write(component.getDebugId()) 246 247 if createLoc is not None: 248 err.write(', created at (' + createLoc.file \ 249 + ':' + createLoc.lineNumber + ')') 250 251 if attribute is not None: 252 err.write(' (') 253 err.write(attribute) 254 if sizeLoc is not None: 255 err.write(', set at (' + sizeLoc.file 256 + ':' + sizeLoc.lineNumber + ')') 257 err.write(')') 258 259 err.write('\n')
260 261 262 @classmethod
263 - def hasNonRelativeHeightComponent(cls, ol):
264 for c in ol.getComponentIterator(): 265 if not cls.hasRelativeHeight(c): 266 return True 267 return False
268 269 270 @classmethod
271 - def parentCanDefineHeight(cls, component):
272 parent = component.getParent() 273 if parent is None: 274 # main window, valid situation 275 return True 276 277 if parent.getHeight() < 0: 278 # Undefined height 279 if isinstance(parent, Window): 280 w = parent 281 if w.getParent() is None: 282 # main window is considered to have size 283 return True 284 285 if isinstance(parent, AbstractOrderedLayout): 286 horizontal = True 287 if isinstance(parent, OrderedLayout): 288 horizontal = (parent.getOrientation() == 289 OrderedLayout.ORIENTATION_HORIZONTAL) 290 elif isinstance(parent, VerticalLayout): 291 horizontal = False 292 293 if horizontal and cls.hasNonRelativeHeightComponent(parent): 294 return True 295 else: 296 return False 297 298 elif isinstance(parent, GridLayout): 299 gl = parent 300 componentArea = gl.getComponentArea(component) 301 rowHasHeight = False 302 303 row = componentArea.getRow1() 304 while not rowHasHeight and row <= componentArea.getRow2(): 305 row += 1 306 column = 0 307 while not rowHasHeight and column < gl.getColumns(): 308 column += 1 309 c = gl.getComponent(column, row) 310 if c is not None: 311 rowHasHeight = not cls.hasRelativeHeight(c) 312 313 if not rowHasHeight: 314 return False 315 else: 316 # Other components define row height 317 return True 318 319 if isinstance(parent, Panel) \ 320 or isinstance(parent, SplitPanel) \ 321 or isinstance(parent, TabSheet) \ 322 or isinstance(parent, CustomComponent): 323 # height undefined, we know how how component works and no 324 # exceptions 325 # TODO horiz SplitPanel ?? 326 return False 327 else: 328 # We cannot generally know if undefined component can serve 329 # space for children (like CustomLayout or component built 330 # by third party) so we assume they can 331 return True 332 333 elif cls.hasRelativeHeight(parent): 334 # Relative height 335 if parent.getParent() is not None: 336 return cls.parentCanDefineHeight(parent) 337 else: 338 return True 339 else: 340 # Absolute height 341 return True
342 343 344 @classmethod
345 - def hasRelativeHeight(cls, component):
348 349 350 @classmethod
351 - def hasNonRelativeWidthComponent(cls, arg):
352 if isinstance(arg, AbstractOrderedLayout): 353 ol = arg 354 for c in ol.getComponentIterator(): 355 if not cls.hasRelativeWidth(c): 356 return True 357 return False 358 else: 359 form = arg 360 layout = form.getLayout() 361 footer = form.getFooter() 362 363 if layout is not None and not cls.hasRelativeWidth(layout): 364 return True 365 366 if footer is not None and not cls.hasRelativeWidth(footer): 367 return True 368 369 return False
370 371 372 @classmethod
373 - def hasRelativeWidth(cls, paintable):
376 377 378 @classmethod
379 - def parentCanDefineWidth(cls, component):
380 parent = component.getParent() 381 if parent is None: 382 # main window, valid situation 383 return True 384 385 if isinstance(parent, Window): 386 w = parent 387 if w.getParent() is None: 388 # main window is considered to have size 389 return True 390 391 if parent.getWidth() < 0: 392 # Undefined width 393 394 if isinstance(parent, AbstractOrderedLayout): 395 ol = parent 396 horizontal = True 397 if isinstance(ol, OrderedLayout): 398 if (ol.getOrientation() 399 == OrderedLayout.ORIENTATION_VERTICAL): 400 horizontal = False 401 elif isinstance(ol, VerticalLayout): 402 horizontal = False 403 if (not horizontal) and cls.hasNonRelativeWidthComponent(ol): 404 # valid situation, other components defined width 405 return True 406 else: 407 return False 408 elif isinstance(parent, GridLayout): 409 gl = parent 410 componentArea = gl.getComponentArea(component) 411 columnHasWidth = False 412 col = componentArea.getColumn1() 413 while ((not columnHasWidth) 414 and col <= componentArea.getColumn2()): 415 col += 1 416 row = 0 417 while (not columnHasWidth) and row < gl.getRows(): 418 row += 1 419 c = gl.getComponent(col, row) 420 if c is not None: 421 columnHasWidth = not cls.hasRelativeWidth(c) 422 423 if not columnHasWidth: 424 return False 425 else: 426 # Other components define column width 427 return True 428 elif isinstance(parent, Form): 429 # If some other part of the form is not relative it 430 # determines the component width 431 return cls.hasNonRelativeWidthComponent(parent) 432 elif (isinstance(parent, SplitPanel) 433 or isinstance(parent, TabSheet) 434 or isinstance(parent, CustomComponent)): 435 # FIXME: Could we use com.vaadin package name here and 436 # fail for all component containers? 437 # FIXME: Actually this should be moved to containers so 438 # it can be implemented for custom containers 439 # TODO vertical splitpanel with another non relative 440 # component? 441 return False 442 elif isinstance(parent, Window): 443 # Sub window can define width based on caption 444 if (parent.getCaption() is not None 445 and not (parent.getCaption() == '')): 446 return True 447 else: 448 return False 449 elif isinstance(parent, Panel): 450 # TODO Panel should be able to define width based on caption 451 return False 452 else: 453 return True 454 455 elif cls.hasRelativeWidth(parent): 456 # Relative width 457 if parent.getParent() is None: 458 return True 459 return cls.parentCanDefineWidth(parent) 460 else: 461 return True
462 463 464 @classmethod
465 - def setCreationLocation(cls, obj):
466 cls.setLocation(cls._creationLocations, obj)
467 468 469 @classmethod
470 - def setWidthLocation(cls, obj):
471 cls.setLocation(cls._widthLocations, obj)
472 473 474 @classmethod
475 - def setHeightLocation(cls, obj):
476 cls.setLocation(cls._heightLocations, obj)
477 478 479 @classmethod
480 - def setLocation(cls, mapp, obj):
481 traceLines = traceback.extract_stack() 482 for traceElement in traceLines: 483 try: 484 # FIXME: reduce map sizes 485 486 # clsName = traceElement.getClassName() 487 # if clsName.startswith('java.') or clsName.startswith('sun.'): 488 # continue 489 # 490 # clss = loadClass(clsName) 491 # if (clss == ComponentSizeValidator) or (clss == Thread): 492 # continue 493 # 494 # if (Component in clss.mro() 495 # and not CustomComponent in clss.mro()): 496 # continue 497 498 cl = FileLocation(traceElement) 499 map[object] = cl 500 return 501 except Exception: 502 # TODO Auto-generated catch block 503 logger.info('An exception occurred while validating sizes.')
504
505 506 -class FileLocation(object):
507
508 - def __init__(self, traceElement):
509 # filename, line number, function name, text 510 self.file = traceElement[0] 511 self.className = traceElement[2] 512 self.classNameSimple = traceElement[2] 513 self.lineNumber = traceElement[1] 514 self.method = traceElement[2]
515
516 517 -class InvalidLayout(object):
518
519 - def __init__(self, component, height, width):
520 self._component = component 521 self._invalidHeight = height 522 self._invalidWidth = width 523 self._subErrors = list()
524 525
526 - def addError(self, error):
527 self._subErrors.append(error)
528 529
530 - def reportErrors(self, clientJSON, communicationManager, 531 serverErrorStream):
532 clientJSON.write('{') 533 534 parent = self._component.getParent() 535 paintableId = communicationManager.getPaintableId(self._component) 536 537 clientJSON.write('id:\"' + paintableId + '\"') 538 539 if self._invalidHeight: 540 attributes = None 541 msg = '' 542 # set proper error messages 543 if isinstance(parent, AbstractOrderedLayout): 544 ol = parent 545 vertical = False 546 547 if isinstance(ol, OrderedLayout): 548 if (ol.getOrientation() 549 == OrderedLayout.ORIENTATION_VERTICAL): 550 vertical = True 551 elif isinstance(ol, VerticalLayout): 552 vertical = True 553 554 if vertical: 555 msg = ('Component with relative height inside a ' 556 'VerticalLayout with no height defined.') 557 attributes = ComponentSizeValidator.getHeightAttributes( 558 self._component) 559 else: 560 msg = ('At least one of a HorizontalLayout\'s components ' 561 'must have non relative height if the height of ' 562 'the layout is not defined') 563 attributes = ComponentSizeValidator.getHeightAttributes( 564 self._component) 565 elif isinstance(parent, GridLayout): 566 msg = ('At least one of the GridLayout\'s components in ' 567 'each row should have non relative height if the ' 568 'height of the layout is not defined.') 569 attributes = ComponentSizeValidator.getHeightAttributes( 570 self._component) 571 else: 572 # default error for non sized parent issue 573 msg = ('A component with relative height needs a parent ' 574 'with defined height.') 575 attributes = ComponentSizeValidator.getHeightAttributes( 576 self._component) 577 578 self.printServerError(msg, attributes, False, serverErrorStream) 579 clientJSON.write(',\"heightMsg\":\"' + msg + '\"') 580 581 if self._invalidWidth: 582 attributes = None 583 msg = '' 584 if isinstance(parent, AbstractOrderedLayout): 585 ol = parent 586 horizontal = True 587 588 if isinstance(ol, OrderedLayout): 589 if (ol.getOrientation() 590 == OrderedLayout.ORIENTATION_VERTICAL): 591 horizontal = False 592 elif isinstance(ol, VerticalLayout): 593 horizontal = False 594 595 if horizontal: 596 msg = ('Component with relative width inside a ' 597 'HorizontalLayout with no width defined') 598 attributes = ComponentSizeValidator.getWidthAttributes( 599 self._component) 600 else: 601 msg = ('At least one of a VerticalLayout\'s components ' 602 'must have non relative width if the width of ' 603 'the layout is not defined') 604 attributes = ComponentSizeValidator.getWidthAttributes( 605 self._component) 606 607 elif isinstance(parent, GridLayout): 608 msg = ('At least one of the GridLayout\'s components in each ' 609 'column should have non relative width if the width ' 610 'of the layout is not defined.') 611 attributes = ComponentSizeValidator.getWidthAttributes( 612 self._component) 613 614 else: 615 # default error for non sized parent issue 616 msg = ('A component with relative width needs a parent ' 617 'with defined width.') 618 attributes = self.getWidthAttributes(self._component) 619 620 clientJSON.write(',\"widthMsg\":\"' + msg + '\"') 621 self.printServerError(msg, attributes, True, serverErrorStream) 622 623 if len(self._subErrors) > 0: 624 serverErrorStream.write('Sub errors >>') 625 clientJSON.write(', \"subErrors\" : [') 626 first = True 627 for subError in self._subErrors: 628 if not first: 629 clientJSON.write(',') 630 else: 631 first = False 632 subError.reportErrors(clientJSON, communicationManager, 633 serverErrorStream) 634 clientJSON.write(']') 635 serverErrorStream.write('<< Sub erros\n') 636 637 clientJSON.write('}')
638
639 640 -class ComponentInfo(object):
641
642 - def __init__(self, component, info):
643 self._component = component 644 self._info = info
645