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