class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("MDI") def closeEvent(self, event): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() else: self.writeSettings() event.accept() def newFile(self): child = self.createMdiChild() child.newFile() child.show() def open(self): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findMdiChild(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createMdiChild() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): if self.activeMdiChild() and self.activeMdiChild().save(): self.statusBar().showMessage("File saved", 2000) def saveAs(self): if self.activeMdiChild() and self.activeMdiChild().saveAs(): self.statusBar().showMessage("File saved", 2000) def cut(self): if self.activeMdiChild(): self.activeMdiChild().cut() def copy(self): if self.activeMdiChild(): self.activeMdiChild().copy() def paste(self): if self.activeMdiChild(): self.activeMdiChild().paste() def about(self): QMessageBox.about( self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) self.saveAct.setEnabled(hasMdiChild) self.saveAsAct.setEnabled(hasMdiChild) self.pasteAct.setEnabled(hasMdiChild) self.closeAct.setEnabled(hasMdiChild) self.closeAllAct.setEnabled(hasMdiChild) self.tileAct.setEnabled(hasMdiChild) self.cascadeAct.setEnabled(hasMdiChild) self.nextAct.setEnabled(hasMdiChild) self.previousAct.setEnabled(hasMdiChild) self.separatorAct.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cutAct.setEnabled(hasSelection) self.copyAct.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tileAct) self.windowMenu.addAction(self.cascadeAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createMdiChild(self): child = MdiChild() self.mdiArea.addSubWindow(child) child.copyAvailable.connect(self.cutAct.setEnabled) child.copyAvailable.connect(self.copyAct.setEnabled) return child def createActions(self): self.newAct = QAction(QIcon.fromTheme("document-new", QIcon(':/images/new.png')), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(QIcon.fromTheme("document-open", QIcon(':/images/open.png')), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(QIcon.fromTheme("document-save", QIcon(':/images/save.png')), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction( "Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.exitAct = QAction( "E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.cutAct = QAction( QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png')), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.copyAct = QAction( QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png')), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction( QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png')), "&Paste", self, shortcut=QKeySequence.Paste, statusTip= "Paste the clipboard's contents into the current selection", triggered=self.paste) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.tileAct = QAction("&Tile", self, statusTip="Tile the windows", triggered=self.mdiArea.tileSubWindows) self.cascadeAct = QAction("&Cascade", self, statusTip="Cascade the windows", triggered=self.mdiArea.cascadeSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction( "Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() action = self.fileMenu.addAction("Switch layout direction") action.triggered.connect(self.switchLayoutDirection) self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('Trolltech', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('Trolltech', 'MDI Example') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeMdiChild(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() return None def findMdiChild(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def switchLayoutDirection(self): if self.layoutDirection() == Qt.LeftToRight: QApplication.setLayoutDirection(Qt.RightToLeft) else: QApplication.setLayoutDirection(Qt.LeftToRight) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window)
class MainWindow(QMainWindow): """ Main Window service for the nexxT frameworks. Other services usually create dock windows, filters use the subplot functionality to create grid-layouted views. """ mdiSubWindowCreated = Signal( QMdiSubWindow ) # TODO: remove, is not necessary anymore with subplot feature aboutToClose = Signal(object) def __init__(self, config): super().__init__() self.config = config self.config.appActivated.connect(self._appActivated) self.mdi = QMdiArea(self) self.mdi.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdi.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdi) self.menu = self.menuBar().addMenu("&Windows") self.aboutMenu = QMenuBar(self) self.menuBar().setCornerWidget(self.aboutMenu) m = self.aboutMenu.addMenu("&About") self.aboutNexxT = QAction("About nexxT ...") self.aboutQt = QAction("About Qt ...") self.aboutPython = QAction("About Python ...") m.addActions([self.aboutNexxT, self.aboutQt, self.aboutPython]) self.aboutNexxT.triggered.connect(lambda: QMessageBox.about( self, "About nexxT", """\ This program uses <b>nexxT</b> %(version)s, a generic hybrid python/c++ framework for developing computer vision algorithms.<br><br> nexxT is available under the <a href='https://github.com/ifm/nexxT/blob/master/LICENSE'>Apache 2.0 License</a> together with the <a href='https://github.com/ifm/nexxT/blob/master/NOTICE'>notice</a>. """ % dict(version=nexxT.__version__))) self.aboutQt.triggered.connect(lambda: QMessageBox.aboutQt(self)) self.aboutPython.triggered.connect(self._aboutPython) self.toolbar = None self.managedMdiWindows = [] self.managedSubplots = {} self.windows = {} self.activeApp = None self._ignoreCloseEvent = False def closeEvent(self, closeEvent): """ Override from QMainWindow, saves the state. :param closeEvent: a QCloseEvent instance :return: """ self._ignoreCloseEvent = False self.aboutToClose.emit(self) if self._ignoreCloseEvent: logger.info("Ignoring event") closeEvent.ignore() return closeEvent.accept() self.saveState() self.saveMdiState() super().closeEvent(closeEvent) def ignoreCloseEvent(self): """ Can be called in slots connected to aboutToClose for requesting to ignore the event. Use case is the "There are unsaved changes" dialog. :return: """ self._ignoreCloseEvent = True def restoreState(self): """ restores the state of the main window including the dock windows of Services :return: """ logger.info("restoring main window's state") settings = QSettings() v = settings.value("MainWindowState") if v is not None: super().restoreState(v) v = settings.value("MainWindowGeometry") if v is not None: self.restoreGeometry(v) if self.toolbar is not None: # TODO: add toolbar to windows menu, so we don't need this self.toolbar.show() def saveState(self): """ saves the state of the main window including the dock windows of Services :return: """ logger.info("saving main window's state") settings = QSettings() settings.setValue("MainWindowState", super().saveState()) settings.setValue("MainWindowGeometry", self.saveGeometry()) def saveMdiState(self): """ saves the state of the individual MDI windows :return: """ for i in self.managedMdiWindows: window = i["window"] propColl = i["propColl"] prefix = i["prefix"] logger.debug("save window geometry %s: %s", prefix, window.geometry()) geom = str(window.saveGeometry().toBase64(), "ascii") visible = self.windows[shiboken2.getCppPointer(window) [0]].isChecked() # pylint: disable=no-member propColl.setProperty(prefix + "_geom", geom) logger.debug("%s is visible: %d", prefix, int(visible)) propColl.setProperty(prefix + "_visible", int(visible)) self.managedMdiWindows = [] def __del__(self): logging.getLogger(__name__).debug("deleting MainWindow") @Slot() def getToolBar(self): """ Get the main toolbar (adds seperators as appropriate). :return: """ if self.toolbar is None: self.toolbar = self.addToolBar("NexxT") self.toolbar.setObjectName("NexxT_main_toolbar") else: self.toolbar.addSeparator() return self.toolbar @Slot(str, QObject, int, int) def newDockWidget(self, name, parent, defaultArea, allowedArea=Qt.LeftDockWidgetArea | Qt.BottomDockWidgetArea, defaultLoc=None): """ This function is supposed to be called by services :param name: the name of the dock window :param parent: the parent (usually None) :param defaultArea: the default dock area :param allowedArea: the allowed dock areas :return: a new QDockWindow instance """ res = NexxTDockWidget(name, parent if parent is not None else self) res.setAllowedAreas(allowedArea) res.setAttribute(Qt.WA_DeleteOnClose, False) self.addDockWidget(defaultArea, res) self._registerWindow(res, res.objectNameChanged) res.setObjectName(name) if defaultLoc is not None: dl = self.findChild(QDockWidget, defaultLoc) if dl is not None: self.tabifyDockWidget(dl, res) return res @staticmethod def parseWindowId(windowId): """ convers a subplot window id into windowTitle, row and column :param windowId: the window id :return: title, row, column """ regexp = re.compile(r"([^\[]+)\[(\d+),\s*(\d+)\]") match = regexp.match(windowId) if not match is None: return match.group(1), int(match.group(2)), int(match.group(3)) return windowId, 0, 0 @Slot(str, QObject, QWidget) def subplot(self, windowId, theFilter, widget): """ Adds widget to the GridLayout specified by windowId. :param windowId: a string with the format "<windowTitle>[<row>,<col>]" where <windowTitle> is the caption of the MDI window (and it is used as identifier for saving/restoring window state) and <row>, <col> are the coordinates of the addressed subplots (starting at 0) :param theFilter: a Filter instance which is requesting the subplot :param widget: a QWidget which shall be placed into the grid layout. Note that this widget is reparented as a result of this operation and the parents can be used to get access to the MDI sub window. Use releaseSubplot to remove the window :return: None """ logger.internal("subplot '%s'", windowId) title, row, col = self.parseWindowId(windowId) if title == "": title = "(view)" if title in self.managedSubplots and ( row, col) in self.managedSubplots[title]["plots"]: logger.warning( "subplot %s[%d,%d] is already registered. Creating a new window for the plot.", title, row, col) i = 2 while "%s(%d)" % (title, i) in self.managedSubplots: i += 1 title = "%s(%d)" % (title, i) row = 0 col = 0 if title not in self.managedSubplots: subWindow = self._newMdiSubWindow(theFilter, title) swwidget = QWidget() subWindow.setWidget(swwidget) layout = QGridLayout(swwidget) swwidget.setLayout(layout) self.managedSubplots[title] = dict(mdiSubWindow=subWindow, layout=layout, swwidget=swwidget, plots={}) self.managedSubplots[title]["layout"].addWidget(widget, row, col) self.managedSubplots[title]["mdiSubWindow"].updateGeometry() widget.setParent(self.managedSubplots[title]["swwidget"]) QTimer.singleShot( 0, lambda: (self.managedSubplots[title]["mdiSubWindow"].adjustSize() if widget.parent().size().height() < widget.minimumSizeHint(). height() or widget.parent().size().height() < widget.minimumSize( ).height() else None)) self.managedSubplots[title]["plots"][row, col] = widget @Slot(QWidget) @Slot(str) def releaseSubplot(self, arg): """ This needs to be called to release the previously allocated subplot called windowId. The managed widget is deleted as a consequence of this function. :param arg: the widget as passed to subplot. Passing the windowId is also supported, but deprecated. :return: """ if isinstance(arg, str): windowId = arg logger.warning( "Using deprecated API to release a subplot. Please pass the widget instead of the windowId." ) logger.internal("releaseSubplot '%s'", windowId) title, row, col = self.parseWindowId(windowId) if title not in self.managedSubplots or ( row, col) not in self.managedSubplots[title]["plots"]: logger.warning("releasSubplot: cannot find %s", windowId) return widget = self.managedSubplots[title]["plots"][row, col] elif isinstance(arg, QWidget): widget = arg found = False for title in self.managedSubplots: for row, col in self.managedSubplots[title]["plots"]: if self.managedSubplots[title]["plots"][row, col] is widget: found = True break if found: break if not found: raise RuntimeError( "cannot find widget given for releaseSubplot.") else: raise RuntimeError( "arg of releaseSubplot must be either a string or a QWidget instance." ) self.managedSubplots[title]["layout"].removeWidget(widget) self.managedSubplots[title]["plots"][row, col].deleteLater() del self.managedSubplots[title]["plots"][row, col] if len(self.managedSubplots[title]["plots"]) == 0: self.managedSubplots[title]["layout"].deleteLater() self.managedSubplots[title]["swwidget"].deleteLater() self.managedSubplots[title]["mdiSubWindow"].deleteLater() del self.managedSubplots[title] @Slot(QObject) @Slot(QObject, str) def newMdiSubWindow(self, filterOrService, windowTitle=None): """ Deprectated (use subplot(...) instead): This function is supposed to be called by filters. :param filterOrService: a Filter instance :param windowTitle: the title of the window (might be None) :return: a new QMdiSubWindow instance """ logger.warning( "This function is deprecated. Use subplot function instead.") return self._newMdiSubWindow(filterOrService, windowTitle) def _newMdiSubWindow(self, filterOrService, windowTitle): res = NexxTMdiSubWindow(None) res.setAttribute(Qt.WA_DeleteOnClose, False) self.mdi.addSubWindow(res) self._registerWindow(res, res.windowTitleChanged) if isinstance(filterOrService, Filter): propColl = filterOrService.guiState() res.setWindowTitle( propColl.objectName() if windowTitle is None else windowTitle) else: app = Application.activeApplication.getApplication() propColl = app.guiState("services/MainWindow") res.setWindowTitle( "<unnamed>" if windowTitle is None else windowTitle) prefix = re.sub(r'[^A-Za-z_0-9]', '_', "MainWindow_MDI_" + res.windowTitle()) i = dict(window=res, propColl=propColl, prefix=prefix) self.managedMdiWindows.append(i) window = i["window"] propColl = i["propColl"] prefix = i["prefix"] propColl.defineProperty(prefix + "_geom", "", "Geometry of MDI window") b = QByteArray.fromBase64( bytes(propColl.getProperty(prefix + "_geom"), "ascii")) window.restoreGeometry(b) logger.debug("restored geometry %s:%s (%s)", prefix, window.geometry(), b) propColl.defineProperty(prefix + "_visible", 1, "Visibility of MDI window") if propColl.getProperty(prefix + "_visible"): window.show() else: window.hide() self.mdiSubWindowCreated.emit(res) return res def _registerWindow(self, window, nameChangedSignal): act = QAction("<unnamed>", self) act.setCheckable(True) act.toggled.connect(window.setVisible) window.visibleChanged.connect(act.setChecked) nameChangedSignal.connect(act.setText) self.windows[shiboken2.getCppPointer(window)[0]] = act # pylint: disable=no-member self.menu.addAction(act) logger.debug("Registering window %s, new len=%d", shiboken2.getCppPointer(window), len(self.windows)) # pylint: disable=no-member window.destroyed.connect(self._windowDestroyed) def _windowDestroyed(self, obj): logger.internal("_windowDestroyed") ptr = shiboken2.getCppPointer(obj) # pylint: disable=no-member try: ptr = ptr[0] except TypeError: pass logger.debug("Deregistering window %s, old len=%d", ptr, len(self.windows)) self.windows[ptr].deleteLater() del self.windows[ptr] logger.debug("Deregistering window %s done", ptr) def _appActivated(self, name, app): if app is not None: self.activeApp = name app.aboutToClose.connect(self.saveMdiState, Qt.UniqueConnection) else: self.activeApp = None def _aboutPython(self): piplic = subprocess.check_output( [sys.executable, "-m", "piplicenses", "--format=plain"], encoding="utf-8").replace("\n", "<br>").replace(" ", " ") QMessageBox.about( self, "About python", """\ This program uses <b>python</b> %(version)s and the following installed python packages.<br><br> <pre> %(table)s </pre> """ % dict(version=sys.version, table=piplic))
class Window(QMainWindow, EditActions.Mixin, FileActions.Mixin, HelpActions.Mixin, ItemsTreeView.Mixin, OptionsActions.Mixin, PragmaView.Mixin, ViewActions.Mixin, SaveRestoreUi.Mixin): def __init__(self, filename): super().__init__() self.setWindowTitle(f'{APPNAME} {VERSION}') self.make_variables() self.make_widgets() self.make_actions() self.make_connections() qApp.commitDataRequest.connect(self.close) options = self.load_options(filename) self.update_ui() QTimer.singleShot(0, lambda: self.initalize_toggle_actions(options)) def closeEvent(self, event): self.closing = True options = Config.MainWindowOptions( state=self.saveState(), geometry=self.saveGeometry(), last_filename=str(self.db.filename or ''), recent_files=list(self.recent_files), show_items_tree=self.itemsTreeDock.isVisible(), show_pragmas=self.pragmasDock.isVisible(), show_as_tabs=self.mdiArea.viewMode() == QMdiArea.TabbedView) Config.write_main_window_options(options) self.clear() event.accept() def make_variables(self): self.db = Db.Db() self.path = self.export_path = QStandardPaths.writableLocation( QStandardPaths.DocumentsLocation) self.recent_files = RecentFiles.get(RECENT_FILES_MAX) self.closing = False def make_widgets(self): self.mdiArea = QMdiArea() self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setTabsClosable(True) self.mdiArea.setTabsMovable(True) self.setCentralWidget(self.mdiArea) allowedAreas = Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea view = ItemsTreeView.View(self.db) self.itemsTreeDock = make_dock_widget(self, 'Items Tree', view, Qt.LeftDockWidgetArea, allowedAreas) view = PragmaView.View(self.db) self.pragmasDock = make_dock_widget(self, 'Pragmas', view, Qt.RightDockWidgetArea, allowedAreas) # TODO Calendar dock widget def make_actions(self): self.make_file_actions() self.file_menu = self.menuBar().addMenu('&File') add_actions(self.file_menu, self.file_actions_for_menu) self.file_toolbar = self.addToolBar('File') self.file_toolbar.setObjectName('File') add_actions(self.file_toolbar, self.file_actions_for_toolbar) self.make_edit_actions() self.edit_menu = self.menuBar().addMenu('&Edit') add_actions(self.edit_menu, self.edit_actions_for_menu) self.edit_toolbar = self.addToolBar('Edit') self.edit_toolbar.setObjectName('Edit') add_actions(self.edit_toolbar, self.edit_actions_for_toolbar) self.make_view_actions() self.view_menu = self.menuBar().addMenu('&View') add_actions(self.view_menu, self.view_actions_for_menu) self.view_toolbar = self.addToolBar('View') self.view_toolbar.setObjectName('View') add_actions(self.view_toolbar, self.view_actions_for_toolbar) # TODO record actions & database actions & (sdi) window actions + # update OptionsActions options_restore_toolbars() self.make_options_actions() self.options_menu = self.menuBar().addMenu('&Options') add_actions(self.options_menu, self.options_actions_for_menu) # self.options_toolbar = self.addToolBar('Options') # self.options_toolbar.setObjectName('Options') # add_actions(self.options_toolbar, self.options_actions_for_toolbar) self.make_help_actions() self.help_menu = self.menuBar().addMenu('&Help') add_actions(self.help_menu, self.help_actions_for_menu) def make_connections(self): widget = self.itemsTreeDock.widget() widget.itemDoubleClicked.connect(self.maybe_show_item) widget.itemSelectionChanged.connect(self.view_update_ui) # TODO def load_options(self, filename): options = Config.read_main_window_options() if options.state is not None: self.restoreState(options.state) if options.geometry is not None: self.restoreGeometry(options.geometry) self.recent_files.load(options.recent_files) if (not filename and options.last_filename and pathlib.Path(options.last_filename).exists()): filename = options.last_filename if filename and not pathlib.Path(filename).exists(): filename = None if filename: if options.last_filename: self.recent_files.add(options.last_filename) self.file_load(filename) else: self.statusBar().showMessage( 'Click File→New or File→Open to open or create a database', TIMEOUT_LONG) return options def initalize_toggle_actions(self, options): self.itemsTreeDock.setVisible(options.show_items_tree) self.view_update_toggle_action(options.show_items_tree) show_pragmas = bool(self.db) and options.show_pragmas self.pragmasDock.setVisible(show_pragmas) self.view_pragmas_update_toggle_action(show_pragmas) self.mdiArea.setViewMode( QMdiArea.SubWindowView if options.show_as_tabs else QMdiArea.TabbedView) # Start with the opposite self.view_items_tree_toggle_tabs() # Toggle to correct & set action def update_ui(self): self.file_update_ui() self.edit_update_ui() self.view_update_ui() # TODO record & database & (sdi) window actions self.options_update_ui() def clear(self): self.maybe_save_ui() widget = self.pragmasDock.widget() widget.save(closing=self.closing) widget.clear() for widget in self.mdiArea.subWindowList(): widget.close() # Will save if dirty def findSubWindow(self, name): for widget in self.mdiArea.subWindowList(): if widget.windowTitle() == name: return widget
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.mdiArea = QMdiArea() self.mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.setCentralWidget(self.mdiArea) self.mdiArea.subWindowActivated.connect(self.updateMenus) self.windowMapper = QSignalMapper(self) self.windowMapper.mapped[QWidget].connect(self.setActiveSubWindow) self.createActions() self.createMenus() self.createToolBars() self.createStatusBar() self.updateMenus() self.readSettings() self.setWindowTitle("MDI") def closeEvent(self, event): self.mdiArea.closeAllSubWindows() if self.mdiArea.currentSubWindow(): event.ignore() else: self.writeSettings() event.accept() def newFile(self): child = self.createMdiChild() child.newFile() child.show() def open(self): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: existing = self.findMdiChild(fileName) if existing: self.mdiArea.setActiveSubWindow(existing) return child = self.createMdiChild() if child.loadFile(fileName): self.statusBar().showMessage("File loaded", 2000) child.show() else: child.close() def save(self): if self.activeMdiChild() and self.activeMdiChild().save(): self.statusBar().showMessage("File saved", 2000) def saveAs(self): if self.activeMdiChild() and self.activeMdiChild().saveAs(): self.statusBar().showMessage("File saved", 2000) def cut(self): if self.activeMdiChild(): self.activeMdiChild().cut() def copy(self): if self.activeMdiChild(): self.activeMdiChild().copy() def paste(self): if self.activeMdiChild(): self.activeMdiChild().paste() def about(self): QMessageBox.about(self, "About MDI", "The <b>MDI</b> example demonstrates how to write multiple " "document interface applications using Qt.") def updateMenus(self): hasMdiChild = (self.activeMdiChild() is not None) self.saveAct.setEnabled(hasMdiChild) self.saveAsAct.setEnabled(hasMdiChild) self.pasteAct.setEnabled(hasMdiChild) self.closeAct.setEnabled(hasMdiChild) self.closeAllAct.setEnabled(hasMdiChild) self.tileAct.setEnabled(hasMdiChild) self.cascadeAct.setEnabled(hasMdiChild) self.nextAct.setEnabled(hasMdiChild) self.previousAct.setEnabled(hasMdiChild) self.separatorAct.setVisible(hasMdiChild) hasSelection = (self.activeMdiChild() is not None and self.activeMdiChild().textCursor().hasSelection()) self.cutAct.setEnabled(hasSelection) self.copyAct.setEnabled(hasSelection) def updateWindowMenu(self): self.windowMenu.clear() self.windowMenu.addAction(self.closeAct) self.windowMenu.addAction(self.closeAllAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.tileAct) self.windowMenu.addAction(self.cascadeAct) self.windowMenu.addSeparator() self.windowMenu.addAction(self.nextAct) self.windowMenu.addAction(self.previousAct) self.windowMenu.addAction(self.separatorAct) windows = self.mdiArea.subWindowList() self.separatorAct.setVisible(len(windows) != 0) for i, window in enumerate(windows): child = window.widget() text = "%d %s" % (i + 1, child.userFriendlyCurrentFile()) if i < 9: text = '&' + text action = self.windowMenu.addAction(text) action.setCheckable(True) action.setChecked(child is self.activeMdiChild()) action.triggered.connect(self.windowMapper.map) self.windowMapper.setMapping(action, window) def createMdiChild(self): child = MdiChild() self.mdiArea.addSubWindow(child) child.copyAvailable.connect(self.cutAct.setEnabled) child.copyAvailable.connect(self.copyAct.setEnabled) return child def createActions(self): self.newAct = QAction(QIcon.fromTheme("document-new", QIcon(':/images/new.png')), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(QIcon.fromTheme("document-open", QIcon(':/images/open.png')), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(QIcon.fromTheme("document-save", QIcon(':/images/save.png')), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.exitAct = QAction("E&xit", self, shortcut=QKeySequence.Quit, statusTip="Exit the application", triggered=QApplication.instance().closeAllWindows) self.cutAct = QAction(QIcon.fromTheme("edit-cut", QIcon(':/images/cut.png')), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.cut) self.copyAct = QAction(QIcon.fromTheme("edit-copy", QIcon(':/images/copy.png')), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.copy) self.pasteAct = QAction(QIcon.fromTheme("edit-paste", QIcon(':/images/paste.png')), "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.paste) self.closeAct = QAction("Cl&ose", self, statusTip="Close the active window", triggered=self.mdiArea.closeActiveSubWindow) self.closeAllAct = QAction("Close &All", self, statusTip="Close all the windows", triggered=self.mdiArea.closeAllSubWindows) self.tileAct = QAction("&Tile", self, statusTip="Tile the windows", triggered=self.mdiArea.tileSubWindows) self.cascadeAct = QAction("&Cascade", self, statusTip="Cascade the windows", triggered=self.mdiArea.cascadeSubWindows) self.nextAct = QAction("Ne&xt", self, shortcut=QKeySequence.NextChild, statusTip="Move the focus to the next window", triggered=self.mdiArea.activateNextSubWindow) self.previousAct = QAction("Pre&vious", self, shortcut=QKeySequence.PreviousChild, statusTip="Move the focus to the previous window", triggered=self.mdiArea.activatePreviousSubWindow) self.separatorAct = QAction(self) self.separatorAct.setSeparator(True) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addSeparator() action = self.fileMenu.addAction("Switch layout direction") action.triggered.connect(self.switchLayoutDirection) self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.windowMenu = self.menuBar().addMenu("&Window") self.updateWindowMenu() self.windowMenu.aboutToShow.connect(self.updateWindowMenu) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings('Trolltech', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size) def writeSettings(self): settings = QSettings('Trolltech', 'MDI Example') settings.setValue('pos', self.pos()) settings.setValue('size', self.size()) def activeMdiChild(self): activeSubWindow = self.mdiArea.activeSubWindow() if activeSubWindow: return activeSubWindow.widget() return None def findMdiChild(self, fileName): canonicalFilePath = QFileInfo(fileName).canonicalFilePath() for window in self.mdiArea.subWindowList(): if window.widget().currentFile() == canonicalFilePath: return window return None def switchLayoutDirection(self): if self.layoutDirection() == Qt.LeftToRight: QApplication.setLayoutDirection(Qt.RightToLeft) else: QApplication.setLayoutDirection(Qt.LeftToRight) def setActiveSubWindow(self, window): if window: self.mdiArea.setActiveSubWindow(window)