1############################################################################# 2## 3## Copyright (C) 2019 Riverbank Computing Limited. 4## Copyright (C) 2006 Thorsten Marek. 5## All right reserved. 6## 7## This file is part of PyQt. 8## 9## You may use this file under the terms of the GPL v2 or the revised BSD 10## license as follows: 11## 12## "Redistribution and use in source and binary forms, with or without 13## modification, are permitted provided that the following conditions are 14## met: 15## * Redistributions of source code must retain the above copyright 16## notice, this list of conditions and the following disclaimer. 17## * Redistributions in binary form must reproduce the above copyright 18## notice, this list of conditions and the following disclaimer in 19## the documentation and/or other materials provided with the 20## distribution. 21## * Neither the name of the Riverbank Computing Limited nor the names 22## of its contributors may be used to endorse or promote products 23## derived from this software without specific prior written 24## permission. 25## 26## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 37## 38############################################################################# 39 40 41import sys 42import logging 43import os 44import re 45from xml.etree.ElementTree import parse, SubElement 46 47from .objcreator import QObjectCreator 48from .properties import Properties 49 50 51logger = logging.getLogger(__name__) 52DEBUG = logger.debug 53 54QtCore = None 55QtWidgets = None 56 57 58def _parse_alignment(alignment): 59 """ Convert a C++ alignment to the corresponding flags. """ 60 61 align_flags = None 62 for qt_align in alignment.split('|'): 63 _, qt_align = qt_align.split('::') 64 align = getattr(QtCore.Qt, qt_align) 65 66 if align_flags is None: 67 align_flags = align 68 else: 69 align_flags |= align 70 71 return align_flags 72 73 74def _layout_position(elem): 75 """ Return either (), (0, alignment), (row, column, rowspan, colspan) or 76 (row, column, rowspan, colspan, alignment) depending on the type of layout 77 and its configuration. The result will be suitable to use as arguments to 78 the layout. 79 """ 80 81 row = elem.attrib.get('row') 82 column = elem.attrib.get('column') 83 alignment = elem.attrib.get('alignment') 84 85 # See if it is a box layout. 86 if row is None or column is None: 87 if alignment is None: 88 return () 89 90 return (0, _parse_alignment(alignment)) 91 92 # It must be a grid or a form layout. 93 row = int(row) 94 column = int(column) 95 96 rowspan = int(elem.attrib.get('rowspan', 1)) 97 colspan = int(elem.attrib.get('colspan', 1)) 98 99 if alignment is None: 100 return (row, column, rowspan, colspan) 101 102 return (row, column, rowspan, colspan, _parse_alignment(alignment)) 103 104 105class WidgetStack(list): 106 topwidget = None 107 def push(self, item): 108 DEBUG("push %s %s" % (item.metaObject().className(), 109 item.objectName())) 110 self.append(item) 111 if isinstance(item, QtWidgets.QWidget): 112 self.topwidget = item 113 114 def popLayout(self): 115 layout = list.pop(self) 116 DEBUG("pop layout %s %s" % (layout.metaObject().className(), 117 layout.objectName())) 118 return layout 119 120 def popWidget(self): 121 widget = list.pop(self) 122 DEBUG("pop widget %s %s" % (widget.metaObject().className(), 123 widget.objectName())) 124 for item in reversed(self): 125 if isinstance(item, QtWidgets.QWidget): 126 self.topwidget = item 127 break 128 else: 129 self.topwidget = None 130 DEBUG("new topwidget %s" % (self.topwidget,)) 131 return widget 132 133 def peek(self): 134 return self[-1] 135 136 def topIsLayout(self): 137 return isinstance(self[-1], QtWidgets.QLayout) 138 139 def topIsLayoutWidget(self): 140 # A plain QWidget is a layout widget unless it's parent is a 141 # QMainWindow or a container widget. Note that the corresponding uic 142 # test is a little more complicated as it involves features not 143 # supported by pyuic. 144 145 if type(self[-1]) is not QtWidgets.QWidget: 146 return False 147 148 if len(self) < 2: 149 return False 150 151 parent = self[-2] 152 153 return isinstance(parent, QtWidgets.QWidget) and type(parent) not in ( 154 QtWidgets.QMainWindow, 155 QtWidgets.QStackedWidget, 156 QtWidgets.QToolBox, 157 QtWidgets.QTabWidget, 158 QtWidgets.QScrollArea, 159 QtWidgets.QMdiArea, 160 QtWidgets.QWizard, 161 QtWidgets.QDockWidget) 162 163 164class ButtonGroup(object): 165 """ Encapsulate the configuration of a button group and its implementation. 166 """ 167 168 def __init__(self): 169 """ Initialise the button group. """ 170 171 self.exclusive = True 172 self.object = None 173 174 175class UIParser(object): 176 def __init__(self, qtcore_module, qtgui_module, qtwidgets_module, creatorPolicy): 177 self.factory = QObjectCreator(creatorPolicy) 178 self.wprops = Properties(self.factory, qtcore_module, qtgui_module, 179 qtwidgets_module) 180 181 global QtCore, QtWidgets 182 QtCore = qtcore_module 183 QtWidgets = qtwidgets_module 184 185 self.reset() 186 187 def uniqueName(self, name): 188 """UIParser.uniqueName(string) -> string 189 190 Create a unique name from a string. 191 >>> p = UIParser(QtCore, QtGui, QtWidgets) 192 >>> p.uniqueName("foo") 193 'foo' 194 >>> p.uniqueName("foo") 195 'foo1' 196 """ 197 try: 198 suffix = self.name_suffixes[name] 199 except KeyError: 200 self.name_suffixes[name] = 0 201 return name 202 203 suffix += 1 204 self.name_suffixes[name] = suffix 205 206 return "%s%i" % (name, suffix) 207 208 def reset(self): 209 try: self.wprops.reset() 210 except AttributeError: pass 211 self.toplevelWidget = None 212 self.stack = WidgetStack() 213 self.name_suffixes = {} 214 self.defaults = {'spacing': -1, 'margin': -1} 215 self.actions = [] 216 self.currentActionGroup = None 217 self.resources = [] 218 self.button_groups = {} 219 220 def setupObject(self, clsname, parent, branch, is_attribute=True): 221 name = self.uniqueName(branch.attrib.get('name') or clsname[1:].lower()) 222 223 if parent is None: 224 args = () 225 else: 226 args = (parent, ) 227 228 obj = self.factory.createQObject(clsname, name, args, is_attribute) 229 230 self.wprops.setProperties(obj, branch) 231 obj.setObjectName(name) 232 233 if is_attribute: 234 setattr(self.toplevelWidget, name, obj) 235 236 return obj 237 238 def getProperty(self, elem, name): 239 for prop in elem.findall('property'): 240 if prop.attrib['name'] == name: 241 return prop 242 243 return None 244 245 def createWidget(self, elem): 246 self.column_counter = 0 247 self.row_counter = 0 248 self.item_nr = 0 249 self.itemstack = [] 250 self.sorting_enabled = None 251 252 widget_class = elem.attrib['class'].replace('::', '.') 253 if widget_class == 'Line': 254 widget_class = 'QFrame' 255 256 # Ignore the parent if it is a container. 257 parent = self.stack.topwidget 258 if isinstance(parent, (QtWidgets.QDockWidget, QtWidgets.QMdiArea, 259 QtWidgets.QScrollArea, QtWidgets.QStackedWidget, 260 QtWidgets.QToolBox, QtWidgets.QTabWidget, 261 QtWidgets.QWizard)): 262 parent = None 263 264 self.stack.push(self.setupObject(widget_class, parent, elem)) 265 266 if isinstance(self.stack.topwidget, QtWidgets.QTableWidget): 267 if self.getProperty(elem, 'columnCount') is None: 268 self.stack.topwidget.setColumnCount(len(elem.findall("column"))) 269 270 if self.getProperty(elem, 'rowCount') is None: 271 self.stack.topwidget.setRowCount(len(elem.findall("row"))) 272 273 self.traverseWidgetTree(elem) 274 widget = self.stack.popWidget() 275 276 if isinstance(widget, QtWidgets.QTreeView): 277 self.handleHeaderView(elem, "header", widget.header()) 278 279 elif isinstance(widget, QtWidgets.QTableView): 280 self.handleHeaderView(elem, "horizontalHeader", 281 widget.horizontalHeader()) 282 self.handleHeaderView(elem, "verticalHeader", 283 widget.verticalHeader()) 284 285 elif isinstance(widget, QtWidgets.QAbstractButton): 286 bg_i18n = self.wprops.getAttribute(elem, "buttonGroup") 287 if bg_i18n is not None: 288 # This should be handled properly in case the problem arises 289 # elsewhere as well. 290 try: 291 # We are compiling the .ui file. 292 bg_name = bg_i18n.string 293 except AttributeError: 294 # We are loading the .ui file. 295 bg_name = bg_i18n 296 297 # Designer allows the creation of .ui files without explicit 298 # button groups, even though uic then issues warnings. We 299 # handle it in two stages by first making sure it has a name 300 # and then making sure one exists with that name. 301 if not bg_name: 302 bg_name = 'buttonGroup' 303 304 try: 305 bg = self.button_groups[bg_name] 306 except KeyError: 307 bg = self.button_groups[bg_name] = ButtonGroup() 308 309 if bg.object is None: 310 bg.object = self.factory.createQObject("QButtonGroup", 311 bg_name, (self.toplevelWidget, )) 312 setattr(self.toplevelWidget, bg_name, bg.object) 313 314 bg.object.setObjectName(bg_name) 315 316 if not bg.exclusive: 317 bg.object.setExclusive(False) 318 319 bg.object.addButton(widget) 320 321 if self.sorting_enabled is not None: 322 widget.setSortingEnabled(self.sorting_enabled) 323 self.sorting_enabled = None 324 325 if self.stack.topIsLayout(): 326 lay = self.stack.peek() 327 lp = elem.attrib['layout-position'] 328 329 if isinstance(lay, QtWidgets.QFormLayout): 330 lay.setWidget(lp[0], self._form_layout_role(lp), widget) 331 else: 332 lay.addWidget(widget, *lp) 333 334 topwidget = self.stack.topwidget 335 336 if isinstance(topwidget, QtWidgets.QToolBox): 337 icon = self.wprops.getAttribute(elem, "icon") 338 if icon is not None: 339 topwidget.addItem(widget, icon, self.wprops.getAttribute(elem, "label")) 340 else: 341 topwidget.addItem(widget, self.wprops.getAttribute(elem, "label")) 342 343 tooltip = self.wprops.getAttribute(elem, "toolTip") 344 if tooltip is not None: 345 topwidget.setItemToolTip(topwidget.indexOf(widget), tooltip) 346 347 elif isinstance(topwidget, QtWidgets.QTabWidget): 348 icon = self.wprops.getAttribute(elem, "icon") 349 if icon is not None: 350 topwidget.addTab(widget, icon, self.wprops.getAttribute(elem, "title")) 351 else: 352 topwidget.addTab(widget, self.wprops.getAttribute(elem, "title")) 353 354 tooltip = self.wprops.getAttribute(elem, "toolTip") 355 if tooltip is not None: 356 topwidget.setTabToolTip(topwidget.indexOf(widget), tooltip) 357 358 elif isinstance(topwidget, QtWidgets.QWizard): 359 topwidget.addPage(widget) 360 361 elif isinstance(topwidget, QtWidgets.QStackedWidget): 362 topwidget.addWidget(widget) 363 364 elif isinstance(topwidget, (QtWidgets.QDockWidget, QtWidgets.QScrollArea)): 365 topwidget.setWidget(widget) 366 367 elif isinstance(topwidget, QtWidgets.QMainWindow): 368 if type(widget) == QtWidgets.QWidget: 369 topwidget.setCentralWidget(widget) 370 elif isinstance(widget, QtWidgets.QToolBar): 371 tbArea = self.wprops.getAttribute(elem, "toolBarArea") 372 373 if tbArea is None: 374 topwidget.addToolBar(widget) 375 else: 376 topwidget.addToolBar(tbArea, widget) 377 378 tbBreak = self.wprops.getAttribute(elem, "toolBarBreak") 379 380 if tbBreak: 381 topwidget.insertToolBarBreak(widget) 382 383 elif isinstance(widget, QtWidgets.QMenuBar): 384 topwidget.setMenuBar(widget) 385 elif isinstance(widget, QtWidgets.QStatusBar): 386 topwidget.setStatusBar(widget) 387 elif isinstance(widget, QtWidgets.QDockWidget): 388 dwArea = self.wprops.getAttribute(elem, "dockWidgetArea") 389 topwidget.addDockWidget(QtCore.Qt.DockWidgetArea(dwArea), 390 widget) 391 392 def handleHeaderView(self, elem, name, header): 393 value = self.wprops.getAttribute(elem, name + "Visible") 394 if value is not None: 395 header.setVisible(value) 396 397 value = self.wprops.getAttribute(elem, name + "CascadingSectionResizes") 398 if value is not None: 399 header.setCascadingSectionResizes(value) 400 401 value = self.wprops.getAttribute(elem, name + "DefaultSectionSize") 402 if value is not None: 403 header.setDefaultSectionSize(value) 404 405 value = self.wprops.getAttribute(elem, name + "HighlightSections") 406 if value is not None: 407 header.setHighlightSections(value) 408 409 value = self.wprops.getAttribute(elem, name + "MinimumSectionSize") 410 if value is not None: 411 header.setMinimumSectionSize(value) 412 413 value = self.wprops.getAttribute(elem, name + "ShowSortIndicator") 414 if value is not None: 415 header.setSortIndicatorShown(value) 416 417 value = self.wprops.getAttribute(elem, name + "StretchLastSection") 418 if value is not None: 419 header.setStretchLastSection(value) 420 421 def createSpacer(self, elem): 422 width = elem.findtext("property/size/width") 423 height = elem.findtext("property/size/height") 424 425 if width is None or height is None: 426 size_args = () 427 else: 428 size_args = (int(width), int(height)) 429 430 sizeType = self.wprops.getProperty(elem, "sizeType", 431 QtWidgets.QSizePolicy.Expanding) 432 433 policy = (QtWidgets.QSizePolicy.Minimum, sizeType) 434 435 if self.wprops.getProperty(elem, "orientation") == QtCore.Qt.Horizontal: 436 policy = policy[1], policy[0] 437 438 spacer = self.factory.createQObject("QSpacerItem", 439 self.uniqueName("spacerItem"), size_args + policy, 440 is_attribute=False) 441 442 if self.stack.topIsLayout(): 443 lay = self.stack.peek() 444 lp = elem.attrib['layout-position'] 445 446 if isinstance(lay, QtWidgets.QFormLayout): 447 lay.setItem(lp[0], self._form_layout_role(lp), spacer) 448 else: 449 lay.addItem(spacer, *lp) 450 451 def createLayout(self, elem): 452 # We use an internal property to handle margins which will use separate 453 # left, top, right and bottom margins if they are found to be 454 # different. The following will select, in order of preference, 455 # separate margins, the same margin in all directions, and the default 456 # margin. 457 margin = -1 if self.stack.topIsLayout() else self.defaults['margin'] 458 margin = self.wprops.getProperty(elem, 'margin', margin) 459 left = self.wprops.getProperty(elem, 'leftMargin', margin) 460 top = self.wprops.getProperty(elem, 'topMargin', margin) 461 right = self.wprops.getProperty(elem, 'rightMargin', margin) 462 bottom = self.wprops.getProperty(elem, 'bottomMargin', margin) 463 464 # A layout widget should, by default, have no margins. 465 if self.stack.topIsLayoutWidget(): 466 if left < 0: left = 0 467 if top < 0: top = 0 468 if right < 0: right = 0 469 if bottom < 0: bottom = 0 470 471 if left >= 0 or top >= 0 or right >= 0 or bottom >= 0: 472 # We inject the new internal property. 473 cme = SubElement(elem, 'property', name='pyuicMargins') 474 SubElement(cme, 'number').text = str(left) 475 SubElement(cme, 'number').text = str(top) 476 SubElement(cme, 'number').text = str(right) 477 SubElement(cme, 'number').text = str(bottom) 478 479 # We use an internal property to handle spacing which will use separate 480 # horizontal and vertical spacing if they are found to be different. 481 # The following will select, in order of preference, separate 482 # horizontal and vertical spacing, the same spacing in both directions, 483 # and the default spacing. 484 spacing = self.wprops.getProperty(elem, 'spacing', 485 self.defaults['spacing']) 486 horiz = self.wprops.getProperty(elem, 'horizontalSpacing', spacing) 487 vert = self.wprops.getProperty(elem, 'verticalSpacing', spacing) 488 489 if horiz >= 0 or vert >= 0: 490 # We inject the new internal property. 491 cme = SubElement(elem, 'property', name='pyuicSpacing') 492 SubElement(cme, 'number').text = str(horiz) 493 SubElement(cme, 'number').text = str(vert) 494 495 classname = elem.attrib["class"] 496 if self.stack.topIsLayout(): 497 parent = None 498 else: 499 parent = self.stack.topwidget 500 if "name" not in elem.attrib: 501 elem.attrib["name"] = classname[1:].lower() 502 self.stack.push(self.setupObject(classname, parent, elem)) 503 self.traverseWidgetTree(elem) 504 505 layout = self.stack.popLayout() 506 self.configureLayout(elem, layout) 507 508 if self.stack.topIsLayout(): 509 top_layout = self.stack.peek() 510 lp = elem.attrib['layout-position'] 511 512 if isinstance(top_layout, QtWidgets.QFormLayout): 513 top_layout.setLayout(lp[0], self._form_layout_role(lp), layout) 514 else: 515 top_layout.addLayout(layout, *lp) 516 517 def configureLayout(self, elem, layout): 518 if isinstance(layout, QtWidgets.QGridLayout): 519 self.setArray(elem, 'columnminimumwidth', 520 layout.setColumnMinimumWidth) 521 self.setArray(elem, 'rowminimumheight', 522 layout.setRowMinimumHeight) 523 self.setArray(elem, 'columnstretch', layout.setColumnStretch) 524 self.setArray(elem, 'rowstretch', layout.setRowStretch) 525 526 elif isinstance(layout, QtWidgets.QBoxLayout): 527 self.setArray(elem, 'stretch', layout.setStretch) 528 529 def setArray(self, elem, name, setter): 530 array = elem.attrib.get(name) 531 if array: 532 for idx, value in enumerate(array.split(',')): 533 value = int(value) 534 if value > 0: 535 setter(idx, value) 536 537 def disableSorting(self, w): 538 if self.item_nr == 0: 539 self.sorting_enabled = self.factory.invoke("__sortingEnabled", 540 w.isSortingEnabled) 541 w.setSortingEnabled(False) 542 543 def handleItem(self, elem): 544 if self.stack.topIsLayout(): 545 elem[0].attrib['layout-position'] = _layout_position(elem) 546 self.traverseWidgetTree(elem) 547 else: 548 w = self.stack.topwidget 549 550 if isinstance(w, QtWidgets.QComboBox): 551 text = self.wprops.getProperty(elem, "text") 552 icon = self.wprops.getProperty(elem, "icon") 553 554 if icon: 555 w.addItem(icon, '') 556 else: 557 w.addItem('') 558 559 w.setItemText(self.item_nr, text) 560 561 elif isinstance(w, QtWidgets.QListWidget): 562 self.disableSorting(w) 563 item = self.createWidgetItem('QListWidgetItem', elem, w.item, 564 self.item_nr) 565 w.addItem(item) 566 567 elif isinstance(w, QtWidgets.QTreeWidget): 568 if self.itemstack: 569 parent, _ = self.itemstack[-1] 570 _, nr_in_root = self.itemstack[0] 571 else: 572 parent = w 573 nr_in_root = self.item_nr 574 575 item = self.factory.createQObject("QTreeWidgetItem", 576 "item_%d" % len(self.itemstack), (parent, ), False) 577 578 if self.item_nr == 0 and not self.itemstack: 579 self.sorting_enabled = self.factory.invoke("__sortingEnabled", w.isSortingEnabled) 580 w.setSortingEnabled(False) 581 582 self.itemstack.append((item, self.item_nr)) 583 self.item_nr = 0 584 585 # We have to access the item via the tree when setting the 586 # text. 587 titm = w.topLevelItem(nr_in_root) 588 for child, nr_in_parent in self.itemstack[1:]: 589 titm = titm.child(nr_in_parent) 590 591 column = -1 592 for prop in elem.findall('property'): 593 c_prop = self.wprops.convert(prop) 594 c_prop_name = prop.attrib['name'] 595 596 if c_prop_name == 'text': 597 column += 1 598 if c_prop: 599 titm.setText(column, c_prop) 600 elif c_prop_name == 'statusTip': 601 item.setStatusTip(column, c_prop) 602 elif c_prop_name == 'toolTip': 603 item.setToolTip(column, c_prop) 604 elif c_prop_name == 'whatsThis': 605 item.setWhatsThis(column, c_prop) 606 elif c_prop_name == 'font': 607 item.setFont(column, c_prop) 608 elif c_prop_name == 'icon': 609 item.setIcon(column, c_prop) 610 elif c_prop_name == 'background': 611 item.setBackground(column, c_prop) 612 elif c_prop_name == 'foreground': 613 item.setForeground(column, c_prop) 614 elif c_prop_name == 'flags': 615 item.setFlags(c_prop) 616 elif c_prop_name == 'checkState': 617 item.setCheckState(column, c_prop) 618 619 self.traverseWidgetTree(elem) 620 _, self.item_nr = self.itemstack.pop() 621 622 elif isinstance(w, QtWidgets.QTableWidget): 623 row = int(elem.attrib['row']) 624 col = int(elem.attrib['column']) 625 626 self.disableSorting(w) 627 item = self.createWidgetItem('QTableWidgetItem', elem, w.item, 628 row, col) 629 w.setItem(row, col, item) 630 631 self.item_nr += 1 632 633 def addAction(self, elem): 634 self.actions.append((self.stack.topwidget, elem.attrib["name"])) 635 636 @staticmethod 637 def any_i18n(*args): 638 """ Return True if any argument appears to be an i18n string. """ 639 640 for a in args: 641 if a is not None and not isinstance(a, str): 642 return True 643 644 return False 645 646 def createWidgetItem(self, item_type, elem, getter, *getter_args): 647 """ Create a specific type of widget item. """ 648 649 item = self.factory.createQObject(item_type, "item", (), False) 650 props = self.wprops 651 652 # Note that not all types of widget items support the full set of 653 # properties. 654 655 text = props.getProperty(elem, 'text') 656 status_tip = props.getProperty(elem, 'statusTip') 657 tool_tip = props.getProperty(elem, 'toolTip') 658 whats_this = props.getProperty(elem, 'whatsThis') 659 660 if self.any_i18n(text, status_tip, tool_tip, whats_this): 661 self.factory.invoke("item", getter, getter_args) 662 663 if text: 664 item.setText(text) 665 666 if status_tip: 667 item.setStatusTip(status_tip) 668 669 if tool_tip: 670 item.setToolTip(tool_tip) 671 672 if whats_this: 673 item.setWhatsThis(whats_this) 674 675 text_alignment = props.getProperty(elem, 'textAlignment') 676 if text_alignment: 677 item.setTextAlignment(text_alignment) 678 679 font = props.getProperty(elem, 'font') 680 if font: 681 item.setFont(font) 682 683 icon = props.getProperty(elem, 'icon') 684 if icon: 685 item.setIcon(icon) 686 687 background = props.getProperty(elem, 'background') 688 if background: 689 item.setBackground(background) 690 691 foreground = props.getProperty(elem, 'foreground') 692 if foreground: 693 item.setForeground(foreground) 694 695 flags = props.getProperty(elem, 'flags') 696 if flags: 697 item.setFlags(flags) 698 699 check_state = props.getProperty(elem, 'checkState') 700 if check_state: 701 item.setCheckState(check_state) 702 703 return item 704 705 def addHeader(self, elem): 706 w = self.stack.topwidget 707 708 if isinstance(w, QtWidgets.QTreeWidget): 709 props = self.wprops 710 col = self.column_counter 711 712 text = props.getProperty(elem, 'text') 713 if text: 714 w.headerItem().setText(col, text) 715 716 status_tip = props.getProperty(elem, 'statusTip') 717 if status_tip: 718 w.headerItem().setStatusTip(col, status_tip) 719 720 tool_tip = props.getProperty(elem, 'toolTip') 721 if tool_tip: 722 w.headerItem().setToolTip(col, tool_tip) 723 724 whats_this = props.getProperty(elem, 'whatsThis') 725 if whats_this: 726 w.headerItem().setWhatsThis(col, whats_this) 727 728 text_alignment = props.getProperty(elem, 'textAlignment') 729 if text_alignment: 730 w.headerItem().setTextAlignment(col, text_alignment) 731 732 font = props.getProperty(elem, 'font') 733 if font: 734 w.headerItem().setFont(col, font) 735 736 icon = props.getProperty(elem, 'icon') 737 if icon: 738 w.headerItem().setIcon(col, icon) 739 740 background = props.getProperty(elem, 'background') 741 if background: 742 w.headerItem().setBackground(col, background) 743 744 foreground = props.getProperty(elem, 'foreground') 745 if foreground: 746 w.headerItem().setForeground(col, foreground) 747 748 self.column_counter += 1 749 750 elif isinstance(w, QtWidgets.QTableWidget): 751 if len(elem) != 0: 752 if elem.tag == 'column': 753 item = self.createWidgetItem('QTableWidgetItem', elem, 754 w.horizontalHeaderItem, self.column_counter) 755 w.setHorizontalHeaderItem(self.column_counter, item) 756 self.column_counter += 1 757 elif elem.tag == 'row': 758 item = self.createWidgetItem('QTableWidgetItem', elem, 759 w.verticalHeaderItem, self.row_counter) 760 w.setVerticalHeaderItem(self.row_counter, item) 761 self.row_counter += 1 762 763 def setZOrder(self, elem): 764 # Designer can generate empty zorder elements. 765 if elem.text is None: 766 return 767 768 # Designer allows the z-order of spacer items to be specified even 769 # though they can't be raised, so ignore any missing raise_() method. 770 try: 771 getattr(self.toplevelWidget, elem.text).raise_() 772 except AttributeError: 773 # Note that uic issues a warning message. 774 pass 775 776 def createAction(self, elem): 777 self.setupObject("QAction", self.currentActionGroup or self.toplevelWidget, 778 elem) 779 780 def createActionGroup(self, elem): 781 action_group = self.setupObject("QActionGroup", self.toplevelWidget, elem) 782 self.currentActionGroup = action_group 783 self.traverseWidgetTree(elem) 784 self.currentActionGroup = None 785 786 widgetTreeItemHandlers = { 787 "widget" : createWidget, 788 "addaction" : addAction, 789 "layout" : createLayout, 790 "spacer" : createSpacer, 791 "item" : handleItem, 792 "action" : createAction, 793 "actiongroup": createActionGroup, 794 "column" : addHeader, 795 "row" : addHeader, 796 "zorder" : setZOrder, 797 } 798 799 def traverseWidgetTree(self, elem): 800 for child in iter(elem): 801 try: 802 handler = self.widgetTreeItemHandlers[child.tag] 803 except KeyError: 804 continue 805 806 handler(self, child) 807 808 def createUserInterface(self, elem): 809 # Get the names of the class and widget. 810 cname = elem.attrib["class"] 811 wname = elem.attrib["name"] 812 813 # If there was no widget name then derive it from the class name. 814 if not wname: 815 wname = cname 816 817 if wname.startswith("Q"): 818 wname = wname[1:] 819 820 wname = wname[0].lower() + wname[1:] 821 822 self.toplevelWidget = self.createToplevelWidget(cname, wname) 823 self.toplevelWidget.setObjectName(wname) 824 DEBUG("toplevel widget is %s", 825 self.toplevelWidget.metaObject().className()) 826 self.wprops.setProperties(self.toplevelWidget, elem) 827 self.stack.push(self.toplevelWidget) 828 self.traverseWidgetTree(elem) 829 self.stack.popWidget() 830 self.addActions() 831 self.setBuddies() 832 self.setDelayedProps() 833 834 def addActions(self): 835 for widget, action_name in self.actions: 836 if action_name == "separator": 837 widget.addSeparator() 838 else: 839 DEBUG("add action %s to %s", action_name, widget.objectName()) 840 action_obj = getattr(self.toplevelWidget, action_name) 841 if isinstance(action_obj, QtWidgets.QMenu): 842 widget.addAction(action_obj.menuAction()) 843 elif not isinstance(action_obj, QtWidgets.QActionGroup): 844 widget.addAction(action_obj) 845 846 def setDelayedProps(self): 847 for widget, layout, setter, args in self.wprops.delayed_props: 848 if layout: 849 widget = widget.layout() 850 851 setter = getattr(widget, setter) 852 setter(args) 853 854 def setBuddies(self): 855 for widget, buddy in self.wprops.buddies: 856 DEBUG("%s is buddy of %s", buddy, widget.objectName()) 857 try: 858 widget.setBuddy(getattr(self.toplevelWidget, buddy)) 859 except AttributeError: 860 DEBUG("ERROR in ui spec: %s (buddy of %s) does not exist", 861 buddy, widget.objectName()) 862 863 def classname(self, elem): 864 DEBUG("uiname is %s", elem.text) 865 name = elem.text 866 867 if name is None: 868 name = "" 869 870 self.uiname = name 871 self.wprops.uiname = name 872 self.setContext(name) 873 874 def setContext(self, context): 875 """ 876 Reimplemented by a sub-class if it needs to know the translation 877 context. 878 """ 879 pass 880 881 def readDefaults(self, elem): 882 self.defaults['margin'] = int(elem.attrib['margin']) 883 self.defaults['spacing'] = int(elem.attrib['spacing']) 884 885 def setTaborder(self, elem): 886 lastwidget = None 887 for widget_elem in elem: 888 widget = getattr(self.toplevelWidget, widget_elem.text) 889 890 if lastwidget is not None: 891 self.toplevelWidget.setTabOrder(lastwidget, widget) 892 893 lastwidget = widget 894 895 def readResources(self, elem): 896 """ 897 Read a "resources" tag and add the module to import to the parser's 898 list of them. 899 """ 900 try: 901 iterator = getattr(elem, 'iter') 902 except AttributeError: 903 iterator = getattr(elem, 'getiterator') 904 905 for include in iterator("include"): 906 loc = include.attrib.get("location") 907 908 # Apply the convention for naming the Python files generated by 909 # pyrcc5. 910 if loc and loc.endswith('.qrc'): 911 mname = os.path.basename(loc[:-4] + self._resource_suffix) 912 if mname not in self.resources: 913 self.resources.append(mname) 914 915 def createConnections(self, elem): 916 def name2object(obj): 917 if obj == self.uiname: 918 return self.toplevelWidget 919 else: 920 return getattr(self.toplevelWidget, obj) 921 922 for conn in iter(elem): 923 signal = conn.findtext('signal') 924 signal_name, signal_args = signal.split('(') 925 signal_args = signal_args[:-1].replace(' ', '') 926 sender = name2object(conn.findtext('sender')) 927 bound_signal = getattr(sender, signal_name) 928 929 slot = self.factory.getSlot(name2object(conn.findtext('receiver')), 930 conn.findtext('slot').split('(')[0]) 931 932 if signal_args == '': 933 bound_signal.connect(slot) 934 else: 935 signal_args = signal_args.split(',') 936 937 if len(signal_args) == 1: 938 bound_signal[signal_args[0]].connect(slot) 939 else: 940 bound_signal[tuple(signal_args)].connect(slot) 941 942 QtCore.QMetaObject.connectSlotsByName(self.toplevelWidget) 943 944 def customWidgets(self, elem): 945 def header2module(header): 946 """header2module(header) -> string 947 948 Convert paths to C++ header files to according Python modules 949 >>> header2module("foo/bar/baz.h") 950 'foo.bar.baz' 951 """ 952 if header.endswith(".h"): 953 header = header[:-2] 954 955 mpath = [] 956 for part in header.split('/'): 957 # Ignore any empty parts or those that refer to the current 958 # directory. 959 if part not in ('', '.'): 960 if part == '..': 961 # We should allow this for Python3. 962 raise SyntaxError("custom widget header file name may not contain '..'.") 963 964 mpath.append(part) 965 966 return '.'.join(mpath) 967 968 for custom_widget in iter(elem): 969 classname = custom_widget.findtext("class") 970 self.factory.addCustomWidget(classname, 971 custom_widget.findtext("extends") or "QWidget", 972 header2module(custom_widget.findtext("header"))) 973 974 def createToplevelWidget(self, classname, widgetname): 975 raise NotImplementedError 976 977 def buttonGroups(self, elem): 978 for button_group in iter(elem): 979 if button_group.tag == 'buttongroup': 980 bg_name = button_group.attrib['name'] 981 bg = ButtonGroup() 982 self.button_groups[bg_name] = bg 983 984 prop = self.getProperty(button_group, 'exclusive') 985 if prop is not None: 986 if prop.findtext('bool') == 'false': 987 bg.exclusive = False 988 989 # finalize will be called after the whole tree has been parsed and can be 990 # overridden. 991 def finalize(self): 992 pass 993 994 def parse(self, filename, resource_suffix): 995 if hasattr(filename, 'read'): 996 base_dir = '' 997 else: 998 # Allow the filename to be a QString. 999 filename = str(filename) 1000 base_dir = os.path.dirname(filename) 1001 1002 self.wprops.set_base_dir(base_dir) 1003 1004 self._resource_suffix = resource_suffix 1005 1006 # The order in which the different branches are handled is important. 1007 # The widget tree handler relies on all custom widgets being known, and 1008 # in order to create the connections, all widgets have to be populated. 1009 branchHandlers = ( 1010 ("layoutdefault", self.readDefaults), 1011 ("class", self.classname), 1012 ("buttongroups", self.buttonGroups), 1013 ("customwidgets", self.customWidgets), 1014 ("widget", self.createUserInterface), 1015 ("connections", self.createConnections), 1016 ("tabstops", self.setTaborder), 1017 ("resources", self.readResources), 1018 ) 1019 1020 document = parse(filename) 1021 root = document.getroot() 1022 1023 if root.tag != 'ui': 1024 raise SyntaxError("not created by Qt Designer") 1025 1026 version = root.attrib.get('version') 1027 if version is None: 1028 raise SyntaxError("missing version number") 1029 1030 # Right now, only version 4.0 is supported. 1031 if version != '4.0': 1032 raise SyntaxError("only Qt Designer files v4.0 are supported") 1033 1034 for tagname, actor in branchHandlers: 1035 elem = document.find(tagname) 1036 if elem is not None: 1037 actor(elem) 1038 self.finalize() 1039 w = self.toplevelWidget 1040 self.reset() 1041 return w 1042 1043 @staticmethod 1044 def _form_layout_role(layout_position): 1045 if layout_position[3] > 1: 1046 role = QtWidgets.QFormLayout.SpanningRole 1047 elif layout_position[1] == 1: 1048 role = QtWidgets.QFormLayout.FieldRole 1049 else: 1050 role = QtWidgets.QFormLayout.LabelRole 1051 1052 return role 1053