1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2004 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing the TR Previewer main window. 8""" 9 10import os 11import contextlib 12 13from PyQt5.QtCore import ( 14 QDir, QTimer, QFileInfo, pyqtSignal, QEvent, QSize, QTranslator, QObject, 15 Qt, QCoreApplication 16) 17from PyQt5.QtGui import QKeySequence 18from PyQt5.QtWidgets import ( 19 QSizePolicy, QSpacerItem, QWidget, QHBoxLayout, QWhatsThis, QMdiArea, 20 QApplication, QComboBox, QVBoxLayout, QAction, QLabel 21) 22from PyQt5 import uic 23 24 25from E5Gui import E5MessageBox, E5FileDialog 26from E5Gui.E5MainWindow import E5MainWindow 27from E5Gui.E5Application import e5App 28 29import UI.PixmapCache 30import UI.Config 31 32import Preferences 33 34 35noTranslationName = QCoreApplication.translate( 36 "TRPreviewer", "<No translation>") 37 38 39class TRPreviewer(E5MainWindow): 40 """ 41 Class implementing the UI Previewer main window. 42 """ 43 def __init__(self, filenames=None, parent=None, name=None): 44 """ 45 Constructor 46 47 @param filenames filenames of form and/or translation files to load 48 @param parent parent widget of this window (QWidget) 49 @param name name of this window (string) 50 """ 51 self.mainWidget = None 52 self.currentFile = QDir.currentPath() 53 54 super().__init__(parent) 55 if not name: 56 self.setObjectName("TRPreviewer") 57 else: 58 self.setObjectName(name) 59 60 self.setStyle(Preferences.getUI("Style"), 61 Preferences.getUI("StyleSheet")) 62 63 self.resize(QSize(800, 600).expandedTo(self.minimumSizeHint())) 64 self.statusBar() 65 66 self.setWindowIcon(UI.PixmapCache.getIcon("eric")) 67 self.setWindowTitle(self.tr("Translations Previewer")) 68 69 self.cw = QWidget(self) 70 self.cw.setObjectName("qt_central_widget") 71 72 self.TRPreviewerLayout = QVBoxLayout(self.cw) 73 self.TRPreviewerLayout.setContentsMargins(6, 6, 6, 6) 74 self.TRPreviewerLayout.setSpacing(6) 75 self.TRPreviewerLayout.setObjectName("TRPreviewerLayout") 76 77 self.languageLayout = QHBoxLayout() 78 self.languageLayout.setContentsMargins(0, 0, 0, 0) 79 self.languageLayout.setSpacing(6) 80 self.languageLayout.setObjectName("languageLayout") 81 82 self.languageLabel = QLabel( 83 self.tr("Select language file"), self.cw) 84 self.languageLabel.setObjectName("languageLabel") 85 self.languageLayout.addWidget(self.languageLabel) 86 87 self.languageCombo = QComboBox(self.cw) 88 self.languageCombo.setObjectName("languageCombo") 89 self.languageCombo.setEditable(False) 90 self.languageCombo.setToolTip(self.tr("Select language file")) 91 self.languageCombo.setSizePolicy( 92 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) 93 self.languageLayout.addWidget(self.languageCombo) 94 95 languageSpacer = QSpacerItem( 96 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum) 97 self.languageLayout.addItem(languageSpacer) 98 self.TRPreviewerLayout.addLayout(self.languageLayout) 99 100 self.preview = WidgetArea(self.cw) 101 self.preview.setObjectName("preview") 102 self.TRPreviewerLayout.addWidget(self.preview) 103 self.preview.lastWidgetClosed.connect(self.__updateActions) 104 105 self.setCentralWidget(self.cw) 106 107 self.languageCombo.activated[int].connect(self.__setTranslation) 108 109 self.translations = TranslationsDict(self.languageCombo, self) 110 self.translations.translationChanged.connect( 111 self.preview.rebuildWidgets) 112 113 self.__initActions() 114 self.__initMenus() 115 self.__initToolbars() 116 117 self.__updateActions() 118 119 # fire up the single application server 120 from .TRSingleApplication import TRSingleApplicationServer 121 self.SAServer = TRSingleApplicationServer(self) 122 self.SAServer.loadForm.connect(self.preview.loadWidget) 123 self.SAServer.loadTranslation.connect(self.translations.add) 124 125 # defere loading of a UI file until we are shown 126 self.filesToLoad = [] if filenames is None else filenames[:] 127 128 def show(self): 129 """ 130 Public slot to show this dialog. 131 132 This overloaded slot loads a UI file to be previewed after 133 the main window has been shown. This way, previewing a dialog 134 doesn't interfere with showing the main window. 135 """ 136 super().show() 137 if self.filesToLoad: 138 filenames, self.filesToLoad = (self.filesToLoad[:], []) 139 first = True 140 for fn in filenames: 141 fi = QFileInfo(fn) 142 if fi.suffix().lower() == 'ui': 143 self.preview.loadWidget(fn) 144 elif fi.suffix().lower() == 'qm': 145 self.translations.add(fn, first) 146 first = False 147 148 self.__updateActions() 149 150 def closeEvent(self, event): 151 """ 152 Protected event handler for the close event. 153 154 @param event close event (QCloseEvent) 155 """ 156 if self.SAServer is not None: 157 self.SAServer.shutdown() 158 self.SAServer = None 159 event.accept() 160 161 def __initActions(self): 162 """ 163 Private method to define the user interface actions. 164 """ 165 self.openUIAct = QAction( 166 UI.PixmapCache.getIcon("openUI"), 167 self.tr('&Open UI Files...'), self) 168 self.openUIAct.setStatusTip(self.tr('Open UI files for display')) 169 self.openUIAct.setWhatsThis(self.tr( 170 """<b>Open UI Files</b>""" 171 """<p>This opens some UI files for display.</p>""" 172 )) 173 self.openUIAct.triggered.connect(self.__openWidget) 174 175 self.openQMAct = QAction( 176 UI.PixmapCache.getIcon("openQM"), 177 self.tr('Open &Translation Files...'), self) 178 self.openQMAct.setStatusTip(self.tr( 179 'Open Translation files for display')) 180 self.openQMAct.setWhatsThis(self.tr( 181 """<b>Open Translation Files</b>""" 182 """<p>This opens some translation files for display.</p>""" 183 )) 184 self.openQMAct.triggered.connect(self.__openTranslation) 185 186 self.reloadAct = QAction( 187 UI.PixmapCache.getIcon("reload"), 188 self.tr('&Reload Translations'), self) 189 self.reloadAct.setStatusTip(self.tr( 190 'Reload the loaded translations')) 191 self.reloadAct.setWhatsThis(self.tr( 192 """<b>Reload Translations</b>""" 193 """<p>This reloads the translations for the loaded""" 194 """ languages.</p>""" 195 )) 196 self.reloadAct.triggered.connect(self.translations.reload) 197 198 self.exitAct = QAction( 199 UI.PixmapCache.getIcon("exit"), self.tr('&Quit'), self) 200 self.exitAct.setShortcut(QKeySequence( 201 self.tr("Ctrl+Q", "File|Quit"))) 202 self.exitAct.setStatusTip(self.tr('Quit the application')) 203 self.exitAct.setWhatsThis(self.tr( 204 """<b>Quit</b>""" 205 """<p>Quit the application.</p>""" 206 )) 207 self.exitAct.triggered.connect(e5App().closeAllWindows) 208 209 self.whatsThisAct = QAction( 210 UI.PixmapCache.getIcon("whatsThis"), 211 self.tr('&What\'s This?'), self) 212 self.whatsThisAct.setShortcut(QKeySequence(self.tr("Shift+F1"))) 213 self.whatsThisAct.setStatusTip(self.tr('Context sensitive help')) 214 self.whatsThisAct.setWhatsThis(self.tr( 215 """<b>Display context sensitive help</b>""" 216 """<p>In What's This? mode, the mouse cursor shows an arrow""" 217 """ with a question mark, and you can click on the interface""" 218 """ elements to get a short description of what they do and""" 219 """ how to use them. In dialogs, this feature can be accessed""" 220 """ using the context help button in the titlebar.</p>""" 221 )) 222 self.whatsThisAct.triggered.connect(self.__whatsThis) 223 224 self.aboutAct = QAction(self.tr('&About'), self) 225 self.aboutAct.setStatusTip(self.tr( 226 'Display information about this software')) 227 self.aboutAct.setWhatsThis(self.tr( 228 """<b>About</b>""" 229 """<p>Display some information about this software.</p>""" 230 )) 231 self.aboutAct.triggered.connect(self.__about) 232 233 self.aboutQtAct = QAction(self.tr('About &Qt'), self) 234 self.aboutQtAct.setStatusTip( 235 self.tr('Display information about the Qt toolkit')) 236 self.aboutQtAct.setWhatsThis(self.tr( 237 """<b>About Qt</b>""" 238 """<p>Display some information about the Qt toolkit.</p>""" 239 )) 240 self.aboutQtAct.triggered.connect(self.__aboutQt) 241 242 self.tileAct = QAction(self.tr('&Tile'), self) 243 self.tileAct.setStatusTip(self.tr('Tile the windows')) 244 self.tileAct.setWhatsThis(self.tr( 245 """<b>Tile the windows</b>""" 246 """<p>Rearrange and resize the windows so that they are""" 247 """ tiled.</p>""" 248 )) 249 self.tileAct.triggered.connect(self.preview.tileSubWindows) 250 251 self.cascadeAct = QAction(self.tr('&Cascade'), self) 252 self.cascadeAct.setStatusTip(self.tr('Cascade the windows')) 253 self.cascadeAct.setWhatsThis(self.tr( 254 """<b>Cascade the windows</b>""" 255 """<p>Rearrange and resize the windows so that they are""" 256 """ cascaded.</p>""" 257 )) 258 self.cascadeAct.triggered.connect(self.preview.cascadeSubWindows) 259 260 self.closeAct = QAction( 261 UI.PixmapCache.getIcon("close"), self.tr('&Close'), self) 262 self.closeAct.setShortcut(QKeySequence(self.tr( 263 "Ctrl+W", "File|Close"))) 264 self.closeAct.setStatusTip(self.tr('Close the current window')) 265 self.closeAct.setWhatsThis(self.tr( 266 """<b>Close Window</b>""" 267 """<p>Close the current window.</p>""" 268 )) 269 self.closeAct.triggered.connect(self.preview.closeWidget) 270 271 self.closeAllAct = QAction(self.tr('Clos&e All'), self) 272 self.closeAllAct.setStatusTip(self.tr('Close all windows')) 273 self.closeAllAct.setWhatsThis(self.tr( 274 """<b>Close All Windows</b>""" 275 """<p>Close all windows.</p>""" 276 )) 277 self.closeAllAct.triggered.connect(self.preview.closeAllWidgets) 278 279 def __initMenus(self): 280 """ 281 Private method to create the menus. 282 """ 283 mb = self.menuBar() 284 285 menu = mb.addMenu(self.tr('&File')) 286 menu.setTearOffEnabled(True) 287 menu.addAction(self.openUIAct) 288 menu.addAction(self.openQMAct) 289 menu.addAction(self.reloadAct) 290 menu.addSeparator() 291 menu.addAction(self.closeAct) 292 menu.addAction(self.closeAllAct) 293 menu.addSeparator() 294 menu.addAction(self.exitAct) 295 296 self.windowMenu = mb.addMenu(self.tr('&Window')) 297 self.windowMenu.setTearOffEnabled(True) 298 self.windowMenu.aboutToShow.connect(self.__showWindowMenu) 299 self.windowMenu.triggered.connect(self.preview.toggleSelectedWidget) 300 301 mb.addSeparator() 302 303 menu = mb.addMenu(self.tr('&Help')) 304 menu.setTearOffEnabled(True) 305 menu.addAction(self.aboutAct) 306 menu.addAction(self.aboutQtAct) 307 menu.addSeparator() 308 menu.addAction(self.whatsThisAct) 309 310 def __initToolbars(self): 311 """ 312 Private method to create the toolbars. 313 """ 314 filetb = self.addToolBar(self.tr("File")) 315 filetb.setIconSize(UI.Config.ToolBarIconSize) 316 filetb.addAction(self.openUIAct) 317 filetb.addAction(self.openQMAct) 318 filetb.addAction(self.reloadAct) 319 filetb.addSeparator() 320 filetb.addAction(self.closeAct) 321 filetb.addSeparator() 322 filetb.addAction(self.exitAct) 323 324 helptb = self.addToolBar(self.tr("Help")) 325 helptb.setIconSize(UI.Config.ToolBarIconSize) 326 helptb.addAction(self.whatsThisAct) 327 328 def __whatsThis(self): 329 """ 330 Private slot called in to enter Whats This mode. 331 """ 332 QWhatsThis.enterWhatsThisMode() 333 334 def __updateActions(self): 335 """ 336 Private slot to update the actions state. 337 """ 338 if self.preview.hasWidgets(): 339 self.closeAct.setEnabled(True) 340 self.closeAllAct.setEnabled(True) 341 self.tileAct.setEnabled(True) 342 self.cascadeAct.setEnabled(True) 343 else: 344 self.closeAct.setEnabled(False) 345 self.closeAllAct.setEnabled(False) 346 self.tileAct.setEnabled(False) 347 self.cascadeAct.setEnabled(False) 348 349 if self.translations.hasTranslations(): 350 self.reloadAct.setEnabled(True) 351 else: 352 self.reloadAct.setEnabled(False) 353 354 def __about(self): 355 """ 356 Private slot to show the about information. 357 """ 358 E5MessageBox.about( 359 self, 360 self.tr("TR Previewer"), 361 self.tr( 362 """<h3> About TR Previewer </h3>""" 363 """<p>The TR Previewer loads and displays Qt User-Interface""" 364 """ files and translation files and shows dialogs for a""" 365 """ selected language.</p>""" 366 ) 367 ) 368 369 def __aboutQt(self): 370 """ 371 Private slot to show info about Qt. 372 """ 373 E5MessageBox.aboutQt(self, self.tr("TR Previewer")) 374 375 def __openWidget(self): 376 """ 377 Private slot to handle the Open Dialog action. 378 """ 379 fileNameList = E5FileDialog.getOpenFileNames( 380 None, 381 self.tr("Select UI files"), 382 "", 383 self.tr("Qt User-Interface Files (*.ui)")) 384 385 for fileName in fileNameList: 386 self.preview.loadWidget(fileName) 387 388 self.__updateActions() 389 390 def __openTranslation(self): 391 """ 392 Private slot to handle the Open Translation action. 393 """ 394 fileNameList = E5FileDialog.getOpenFileNames( 395 None, 396 self.tr("Select translation files"), 397 "", 398 self.tr("Qt Translation Files (*.qm)")) 399 400 first = True 401 for fileName in fileNameList: 402 self.translations.add(fileName, first) 403 first = False 404 405 self.__updateActions() 406 407 def __setTranslation(self, index): 408 """ 409 Private slot to activate a translation. 410 411 @param index index of the selected entry 412 @type int 413 """ 414 name = self.languageCombo.itemText(index) 415 self.translations.set(name) 416 417 def __showWindowMenu(self): 418 """ 419 Private slot to handle the aboutToShow signal of the window menu. 420 """ 421 self.windowMenu.clear() 422 self.windowMenu.addAction(self.tileAct) 423 self.windowMenu.addAction(self.cascadeAct) 424 self.windowMenu.addSeparator() 425 426 self.preview.showWindowMenu(self.windowMenu) 427 428 def reloadTranslations(self): 429 """ 430 Public slot to reload all translations. 431 """ 432 self.translations.reload() 433 434 435class Translation: 436 """ 437 Class to store the properties of a translation. 438 """ 439 def __init__(self): 440 """ 441 Constructor 442 """ 443 self.fileName = None 444 self.name = None 445 self.translator = None 446 447 448class TranslationsDict(QObject): 449 """ 450 Class to store all loaded translations. 451 452 @signal translationChanged() emit after a translator was set 453 """ 454 translationChanged = pyqtSignal() 455 456 def __init__(self, selector, parent): 457 """ 458 Constructor 459 460 @param selector reference to the QComboBox used to show the 461 available languages (QComboBox) 462 @param parent parent widget (QWidget) 463 """ 464 super().__init__(parent) 465 466 self.selector = selector 467 self.currentTranslator = None 468 self.selector.addItem(noTranslationName) 469 self.translations = [] # list of Translation objects 470 471 def add(self, fileName, setTranslation=True): 472 """ 473 Public method to add a translation to the list. 474 475 If the translation file (*.qm) has not been loaded yet, it will 476 be loaded automatically. 477 478 @param fileName name of the translation file to be added (string) 479 @param setTranslation flag indicating, if this should be set as 480 the active translation (boolean) 481 """ 482 if not self.__haveFileName(fileName): 483 ntr = Translation() 484 ntr.fileName = fileName 485 ntr.name = self.__uniqueName(fileName) 486 if ntr.name is None: 487 E5MessageBox.warning( 488 self.parent(), 489 self.tr("Set Translator"), 490 self.tr( 491 """<p>The translation filename <b>{0}</b>""" 492 """ is invalid.</p>""").format(fileName)) 493 return 494 495 ntr.translator = self.loadTransFile(fileName) 496 if ntr.translator is None: 497 return 498 499 self.selector.addItem(ntr.name) 500 self.translations.append(ntr) 501 502 if setTranslation: 503 tr = self.__findFileName(fileName) 504 self.set(tr.name) 505 506 def set(self, name): 507 """ 508 Public slot to set a translator by name. 509 510 @param name name (language) of the translator to set (string) 511 """ 512 nTranslator = None 513 514 if name != noTranslationName: 515 trans = self.__findName(name) 516 if trans is None: 517 E5MessageBox.warning( 518 self.parent(), 519 self.tr("Set Translator"), 520 self.tr( 521 """<p>The translator <b>{0}</b> is not known.</p>""") 522 .format(name)) 523 return 524 525 nTranslator = trans.translator 526 527 if nTranslator == self.currentTranslator: 528 return 529 530 if self.currentTranslator is not None: 531 QApplication.removeTranslator(self.currentTranslator) 532 if nTranslator is not None: 533 QApplication.installTranslator(nTranslator) 534 self.currentTranslator = nTranslator 535 536 self.selector.blockSignals(True) 537 self.selector.setCurrentIndex(self.selector.findText(name)) 538 self.selector.blockSignals(False) 539 540 self.translationChanged.emit() 541 542 def reload(self): 543 """ 544 Public method to reload all translators. 545 """ 546 cname = self.selector.currentText() 547 if self.currentTranslator is not None: 548 QApplication.removeTranslator(self.currentTranslator) 549 self.currentTranslator = None 550 551 fileNames = [] 552 for trans in self.translations: 553 trans.translator = None 554 fileNames.append(trans.fileName) 555 self.translations = [] 556 self.selector.clear() 557 558 self.selector.addItem(noTranslationName) 559 560 for fileName in fileNames: 561 self.add(fileName, False) 562 563 if self.__haveName(cname): 564 self.set(cname) 565 else: 566 self.set(noTranslationName) 567 568 def __findFileName(self, transFileName): 569 """ 570 Private method to find a translation by file name. 571 572 @param transFileName file name of the translation file (string) 573 @return reference to a translation object or None 574 """ 575 for trans in self.translations: 576 if trans.fileName == transFileName: 577 return trans 578 return None 579 580 def __findName(self, name): 581 """ 582 Private method to find a translation by name. 583 584 @param name name (language) of the translation (string) 585 @return reference to a translation object or None 586 """ 587 for trans in self.translations: 588 if trans.name == name: 589 return trans 590 return None 591 592 def __haveFileName(self, transFileName): 593 """ 594 Private method to check for the presence of a translation. 595 596 @param transFileName file name of the translation file (string) 597 @return flag indicating the presence of the translation (boolean) 598 """ 599 return self.__findFileName(transFileName) is not None 600 601 def __haveName(self, name): 602 """ 603 Private method to check for the presence of a named translation. 604 605 @param name name (language) of the translation (string) 606 @return flag indicating the presence of the translation (boolean) 607 """ 608 return self.__findName(name) is not None 609 610 def __uniqueName(self, transFileName): 611 """ 612 Private method to generate a unique name. 613 614 @param transFileName file name of the translation file (string) 615 @return unique name (string or None) 616 """ 617 name = os.path.basename(transFileName) 618 if not name: 619 return None 620 621 uname = name 622 cnt = 1 623 while self.__haveName(uname): 624 cnt += 1 625 uname = "{0} <{1}>".format(name, cnt) 626 627 return uname 628 629 def __del(self, name): 630 """ 631 Private method to delete a translator from the list of available 632 translators. 633 634 @param name name of the translator to delete (string) 635 """ 636 if name == noTranslationName: 637 return 638 639 trans = self.__findName(name) 640 if trans is None: 641 return 642 643 if self.selector().currentText() == name: 644 self.set(noTranslationName) 645 646 self.translations.remove(trans) 647 del trans 648 649 def loadTransFile(self, transFileName): 650 """ 651 Public slot to load a translation file. 652 653 @param transFileName file name of the translation file (string) 654 @return reference to the new translator object (QTranslator) 655 """ 656 tr = QTranslator() 657 if tr.load(transFileName): 658 return tr 659 660 E5MessageBox.warning( 661 self.parent(), 662 self.tr("Load Translator"), 663 self.tr("""<p>The translation file <b>{0}</b> could""" 664 """ not be loaded.</p>""").format(transFileName)) 665 return None 666 667 def hasTranslations(self): 668 """ 669 Public method to check for loaded translations. 670 671 @return flag signaling if any translation was loaded (boolean) 672 """ 673 return len(self.translations) > 0 674 675 676class WidgetView(QWidget): 677 """ 678 Class to show a dynamically loaded widget (or dialog). 679 """ 680 def __init__(self, uiFileName, parent=None, name=None): 681 """ 682 Constructor 683 684 @param uiFileName name of the UI file to load (string) 685 @param parent parent widget (QWidget) 686 @param name name of this widget (string) 687 """ 688 super().__init__(parent) 689 if name: 690 self.setObjectName(name) 691 self.setWindowTitle(name) 692 693 self.__widget = None 694 self.__uiFileName = uiFileName 695 self.__layout = QHBoxLayout(self) 696 self.__valid = False 697 self.__timer = QTimer(self) 698 self.__timer.setSingleShot(True) 699 self.__timer.timeout.connect(self.buildWidget) 700 701 def isValid(self): 702 """ 703 Public method to return the validity of this widget view. 704 705 @return flag indicating the validity (boolean) 706 """ 707 return self.__valid 708 709 def uiFileName(self): 710 """ 711 Public method to retrieve the name of the UI file. 712 713 @return filename of the loaded UI file (string) 714 """ 715 return self.__uiFileName 716 717 def buildWidget(self): 718 """ 719 Public slot to load a UI file. 720 """ 721 if self.__widget: 722 self.__widget.close() 723 self.__layout.removeWidget(self.__widget) 724 del self.__widget 725 self.__widget = None 726 727 with contextlib.suppress(Exception): 728 self.__widget = uic.loadUi(self.__uiFileName) 729 730 if not self.__widget: 731 E5MessageBox.warning( 732 self, 733 self.tr("Load UI File"), 734 self.tr( 735 """<p>The file <b>{0}</b> could not be loaded.</p>""") 736 .format(self.__uiFileName)) 737 self.__valid = False 738 return 739 740 self.__widget.setParent(self) 741 self.__layout.addWidget(self.__widget) 742 self.__widget.show() 743 self.__valid = True 744 self.adjustSize() 745 746 self.__timer.stop() 747 748 def __rebuildWidget(self): 749 """ 750 Private method to schedule a rebuild of the widget. 751 """ 752 self.__timer.start(0) 753 754 755class WidgetArea(QMdiArea): 756 """ 757 Specialized MDI area to show the loaded widgets. 758 759 @signal lastWidgetClosed() emitted after the last widget was closed 760 @signal rebuildWidgets() emitted to indicate a change of loaded widgets 761 """ 762 lastWidgetClosed = pyqtSignal() 763 rebuildWidgets = pyqtSignal() 764 765 def __init__(self, parent=None): 766 """ 767 Constructor 768 769 @param parent parent widget (QWidget) 770 """ 771 super().__init__(parent) 772 773 self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 774 self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) 775 776 self.widgets = [] 777 778 def loadWidget(self, uiFileName): 779 """ 780 Public slot to load a UI file. 781 782 @param uiFileName name of the UI file to load (string) 783 """ 784 wview = self.__findWidget(uiFileName) 785 if wview is None: 786 name = os.path.basename(uiFileName) 787 if not name: 788 E5MessageBox.warning( 789 self, 790 self.tr("Load UI File"), 791 self.tr( 792 """<p>The file <b>{0}</b> could not be loaded.</p>""") 793 .format(uiFileName)) 794 return 795 796 uname = name 797 cnt = 1 798 while self.findChild(WidgetView, uname) is not None: 799 cnt += 1 800 uname = "{0} <{1}>".format(name, cnt) 801 name = uname 802 803 wview = WidgetView(uiFileName, self, name) 804 wview.buildWidget() 805 if not wview.isValid(): 806 del wview 807 return 808 809 self.rebuildWidgets.connect(wview.buildWidget) 810 wview.installEventFilter(self) 811 812 win = self.addSubWindow(wview) 813 self.widgets.append(win) 814 815 wview.showNormal() 816 817 def eventFilter(self, obj, ev): 818 """ 819 Public method called to filter an event. 820 821 @param obj object, that generated the event (QObject) 822 @param ev the event, that was generated by object (QEvent) 823 @return flag indicating if event was filtered out 824 """ 825 if obj in self.widgets and ev.type() == QEvent.Type.Close: 826 with contextlib.suppress(ValueError): 827 self.widgets.remove(obj) 828 if len(self.widgets) == 0: 829 self.lastWidgetClosed.emit() 830 831 return QMdiArea.eventFilter(self, obj, ev) 832 833 def __findWidget(self, uiFileName): 834 """ 835 Private method to find a specific widget view. 836 837 @param uiFileName filename of the loaded UI file (string) 838 @return reference to the widget (WidgetView) or None 839 """ 840 wviewList = self.findChildren(WidgetView) 841 if wviewList is None: 842 return None 843 844 for wview in wviewList: 845 if wview.uiFileName() == uiFileName: 846 return wview 847 848 return None 849 850 def closeWidget(self): 851 """ 852 Public slot to close the active window. 853 """ 854 aw = self.activeSubWindow() 855 if aw is not None: 856 aw.close() 857 858 def closeAllWidgets(self): 859 """ 860 Public slot to close all windows. 861 """ 862 for w in self.widgets[:]: 863 w.close() 864 865 def showWindowMenu(self, windowMenu): 866 """ 867 Public method to set up the widgets part of the Window menu. 868 869 @param windowMenu reference to the window menu 870 """ 871 for idx, wid in enumerate(self.widgets): 872 act = windowMenu.addAction(wid.windowTitle()) 873 act.setData(idx) 874 act.setCheckable(True) 875 act.setChecked(not wid.isHidden()) 876 877 def toggleSelectedWidget(self, act): 878 """ 879 Public method to handle the toggle of a window. 880 881 @param act reference to the action that triggered (QAction) 882 """ 883 idx = act.data() 884 if idx is not None: 885 self.__toggleWidget(self.widgets[idx]) 886 887 def __toggleWidget(self, w): 888 """ 889 Private method to toggle a workspace window. 890 891 @param w window to be toggled 892 """ 893 if w.isHidden(): 894 w.show() 895 else: 896 w.hide() 897 898 def hasWidgets(self): 899 """ 900 Public method to check for loaded widgets. 901 902 @return flag signaling if any widget was loaded (boolean) 903 """ 904 return len(self.widgets) > 0 905