1############################################################################ 2## 3## Copyright (C) 2016 The Qt Company Ltd. 4## Contact: https://www.qt.io/licensing/ 5## 6## This file is part of the examples of Qt for Python. 7## 8## $QT_BEGIN_LICENSE:BSD$ 9## Commercial License Usage 10## Licensees holding valid commercial Qt licenses may use this file in 11## accordance with the commercial license agreement provided with the 12## Software or, alternatively, in accordance with the terms contained in 13## a written agreement between you and The Qt Company. For licensing terms 14## and conditions see https://www.qt.io/terms-conditions. For further 15## information use the contact form at https://www.qt.io/contact-us. 16## 17## BSD License Usage 18## Alternatively, you may use this file under the terms of the BSD license 19## as follows: 20## 21## "Redistribution and use in source and binary forms, with or without 22## modification, are permitted provided that the following conditions are 23## met: 24## * Redistributions of source code must retain the above copyright 25## notice, this list of conditions and the following disclaimer. 26## * Redistributions in binary form must reproduce the above copyright 27## notice, this list of conditions and the following disclaimer in 28## the documentation and/or other materials provided with the 29## distribution. 30## * Neither the name of The Qt Company Ltd nor the names of its 31## contributors may be used to endorse or promote products derived 32## from this software without specific prior written permission. 33## 34## 35## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46## 47## $QT_END_LICENSE$ 48## 49############################################################################ 50 51from PySide2.QtGui import * 52 53class QMdiSubWindow(QMainWindow): 54 def __init__(self, parent=None): 55 QMainWindow.__init__(self, parent) 56 57 mdiArea = QMdiArea() 58 mdiArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) 59 mdiArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) 60 setCentralWidget(mdiArea) 61 mdiArea.subWindowActivated[QMdiSubWindow].connect(updateMenus) 62 windowMapper = QSignalMapper(self) 63 windowMapper.mapped[QWidget].connect(setActiveSubWindow) 64 65 self.createActions() 66 self.createMenus() 67 self.createToolBars() 68 self.createStatusBar() 69 self.updateMenus() 70 self.readSettings() 71 self.setWindowTitle(tr("MDI")) 72 self.setUnifiedTitleAndToolBarOnMac(True) 73 74 75 def closeEvent(self, event): 76 mdiArea.closeAllSubWindows() 77 if self.activeMdiChild(): 78 event.ignore() 79 else: 80 self.writeSettings() 81 event.accept() 82 83 def File(self): 84 child = self.createMdiChild() 85 child.File() 86 child.show() 87 88 89 def open(self): 90 fileName = QFileDialog.getOpenFileName(self) 91 if not fileName.isEmpty(): 92 existing = self.findMdiChild(fileName) 93 if existing: 94 mdiArea.setActiveSubWindow(existing) 95 return 96 97 child = createMdiChild() 98 if child.loadFile(fileName): 99 statusBar().showMessage(tr("File loaded"), 2000) 100 child.show() 101 else: 102 child.close() 103 104 def save(self): 105 if self.activeMdiChild() and self.activeMdiChild().save(): 106 self.statusBar().showMessage(tr("File saved"), 2000) 107 108 def saveAs(self): 109 if self.activeMdiChild() and self.activeMdiChild().saveAs(): 110 self.statusBar().showMessage(tr("File saved"), 2000) 111 112 def cut(self): 113 if self.activeMdiChild(): 114 self.activeMdiChild().cut() 115 116 def copy(self): 117 if self.activeMdiChild(): 118 activeMdiChild().copy() 119 120 def paste(self): 121 if self.activeMdiChild(): 122 activeMdiChild().paste() 123 124 def about(self): 125 QMessageBox.about(self, tr("About MDI"), 126 tr("The <b>MDI</b> example demonstrates how to write multiple " 127 "document interface applications using Qt.")) 128 129 def updateMenus(self): 130 hasMdiChild = (activeMdiChild() != 0) 131 self.saveAct.setEnabled(hasMdiChild) 132 self.saveAsAct.setEnabled(hasMdiChild) 133 self.pasteAct.setEnabled(hasMdiChild) 134 self.closeAct.setEnabled(hasMdiChild) 135 self.closeAllAct.setEnabled(hasMdiChild) 136 self.tileAct.setEnabled(hasMdiChild) 137 self.cascadeAct.setEnabled(hasMdiChild) 138 self.nextAct.setEnabled(hasMdiChild) 139 self.previousAct.setEnabled(hasMdiChild) 140 self.separatorAct.setVisible(hasMdiChild) 141 142 hasSelection = (self.activeMdiChild() and 143 self.activeMdiChild().textCursor().hasSelection()) 144 self.cutAct.setEnabled(hasSelection) 145 self.copyAct.setEnabled(hasSelection) 146 147 def updateWindowMenu(self): 148 self.windowMenu.clear() 149 self.windowMenu.addAction(closeAct) 150 self.windowMenu.addAction(closeAllAct) 151 self.windowMenu.addSeparator() 152 self.windowMenu.addAction(tileAct) 153 self.windowMenu.addAction(cascadeAct) 154 self.windowMenu.addSeparator() 155 self.windowMenu.addAction(nextAct) 156 self.windowMenu.addAction(previousAct) 157 self.windowMenu.addAction(separatorAct) 158 159 windows = mdiArea.subWindowList() 160 separatorAct.setVisible(not windows.isEmpty()) 161 162 for i in range(0, windows.size()): 163 child = windows.at(i).widget() 164 165 text = "" 166 if i < 9: 167 text = "{} {}".format(i + 1, child.userFriendlyCurrentFile()) 168 else: 169 text = "{} {}".format(i + 1, child.userFriendlyCurrentFile()) 170 171 action = windowMenu.addAction(text) 172 action.setCheckable(True) 173 action.setChecked(child == activeMdiChild()) 174 action.triggered.connect(windowMapper.map) 175 windowMapper.setMapping(action, windows.at(i)) 176 177 createMdiChild = MdiChild() 178 179 child = MdiChild() 180 mdiArea.addSubWindow(child) 181 182 child.copyAvailable[bool].connect(cutAct.setEnabled) 183 child.copyAvailable[bool].connect(copyAct.setEnabled) 184 185 return child 186 187 188 def createActions(self): 189 190 Act = QAction(QIcon(":/images/new.png"), tr("&New"), self) 191 Act.setShortcuts(QKeySequence.New) 192 Act.setStatusTip(tr("Create a new file")) 193 Act.triggered.connect(self.newFile) 194 195 openAct = QAction(QIcon(":/images/open.png"), tr("&Open..."), self) 196 openAct.setShortcuts(QKeySequence.Open) 197 openAct.setStatusTip(tr("Open an existing file")) 198 openAct.triggered.connect(self.open) 199 200 saveAct = QAction(QIcon(":/images/save.png"), tr("&Save"), self) 201 saveAct.setShortcuts(QKeySequence.Save) 202 saveAct.setStatusTip(tr("Save the document to disk")) 203 saveAct.triggered.connect(self.save) 204 205 saveAsAct = QAction(tr("Save &As..."), self) 206 saveAsAct.setShortcuts(QKeySequence.SaveAs) 207 saveAsAct.setStatusTip(tr("Save the document under a name")) 208 saveAsAct.triggered.connect(self.saveAs) 209 210//! [0] 211 exitAct = QAction(tr("E&xit"), self) 212 exitAct.setShortcut(tr("Ctrl+Q")) 213 exitAct.setStatusTip(tr("Exit the application")) 214 exitAct.triggered.connect(qApp.closeAllWindows) 215//! [0] 216 217 cutAct = QAction(QIcon(":/images/cut.png"), tr("Cu&t"), self) 218 cutAct.setShortcuts(QKeySequence.Cut) 219 cutAct.setStatusTip(tr("Cut the current selection's contents to the " 220 "clipboard")) 221 cutAct.triggered.connect(self.cut) 222 223 copyAct = QAction(QIcon(":/images/copy.png"), tr("&Copy"), self) 224 copyAct.setShortcuts(QKeySequence.Copy) 225 copyAct.setStatusTip(tr("Copy the current selection's contents to the " 226 "clipboard")) 227 copyAct.triggered.connect(self.copy) 228 229 pasteAct = QAction(QIcon(":/images/paste.png"), tr("&Paste"), self) 230 pasteAct.setShortcuts(QKeySequence.Paste) 231 pasteAct.setStatusTip(tr("Paste the clipboard's contents into the current " 232 "selection")) 233 pasteAct.triggered.connect(self.paste) 234 235 closeAct = QAction(tr("Cl&ose"), self) 236 closeAct.setShortcut(tr("Ctrl+F4")) 237 closeAct.setStatusTip(tr("Close the active window")) 238 closeAct.triggered.connect(mdiArea.closeActiveSubWindow) 239 240 closeAllAct = QAction(tr("Close &All"), self) 241 closeAllAct.setStatusTip(tr("Close all the windows")) 242 closeAllAct.triggered.connect(mdiArea.closeAllSubWindows) 243 244 tileAct = QAction(tr("&Tile"), self) 245 tileAct.setStatusTip(tr("Tile the windows")) 246 tileAct.triggered.connect(mdiArea.tileSubWindows) 247 248 cascadeAct = QAction(tr("&Cascade"), self) 249 cascadeAct.setStatusTip(tr("Cascade the windows")) 250 cascadeAct.triggered.connect(mdiArea.cascadeSubWindows) 251 252 nextAct = QAction(tr("Ne&xt"), self) 253 nextAct.setShortcuts(QKeySequence.NextChild) 254 nextAct.setStatusTip(tr("Move the focus to the next window")) 255 nextAct.triggered.connect(mdiArea.activateNextSubWindow) 256 257 previousAct = QAction(tr("Pre&vious"), self) 258 previousAct.setShortcuts(QKeySequence.PreviousChild) 259 previousAct.setStatusTip(tr("Move the focus to the previous " 260 "window")) 261 previousAct.triggered.connect(mdiArea.activatePreviousSubWindow) 262 263 separatorAct = QAction(self) 264 separatorAct.setSeparator(True) 265 266 aboutAct = QAction(tr("&About"), self) 267 aboutAct.setStatusTip(tr("Show the application's About box")) 268 aboutAct.triggered.connect(self.about) 269 270 aboutQtAct = QAction(tr("About &Qt"), self) 271 aboutQtAct.setStatusTip(tr("Show the Qt library's About box")) 272 aboutQtAct.triggered.connect(qApp.aboutQt) 273 274 275 def createMenus(self): 276 277 fileMenu = menuBar().addMenu(tr("&File")) 278 fileMenu.addAction(Act) 279 fileMenu.addAction(openAct) 280 fileMenu.addAction(saveAct) 281 fileMenu.addAction(saveAsAct) 282 fileMenu.addSeparator() 283 action = fileMenu.addAction(tr("Switch layout direction")) 284 action.triggered.connect(self.switchLayoutDirection) 285 fileMenu.addAction(exitAct) 286 287 editMenu = menuBar().addMenu(tr("&Edit")) 288 editMenu.addAction(cutAct) 289 editMenu.addAction(copyAct) 290 editMenu.addAction(pasteAct) 291 292 windowMenu = menuBar().addMenu(tr("&Window")) 293 updateWindowMenu() 294 windowMenu.aboutToShow.connect(self.updateWindowMenu) 295 296 menuBar().addSeparator() 297 298 helpMenu = menuBar().addMenu(tr("&Help")) 299 helpMenu.addAction(aboutAct) 300 helpMenu.addAction(aboutQtAct) 301 302 303 def createToolBars(self): 304 fileToolBar = addToolBar(tr("File")) 305 fileToolBar.addAction(Act) 306 fileToolBar.addAction(openAct) 307 fileToolBar.addAction(saveAct) 308 309 editToolBar = addToolBar(tr("Edit")) 310 editToolBar.addAction(cutAct) 311 editToolBar.addAction(copyAct) 312 editToolBar.addAction(pasteAct) 313 314 315 def createStatusBar(self): 316 statusBar().showMessage(tr("Ready")) 317 318 319 def readSettings(self): 320 settings = QSettings("Trolltech", "MDI Example") 321 QPoint pos = settings.value("pos", QPoint(200, 200)").toPoint() 322 QSize size = settings.value("size", QSize(400, 400)").toSize() 323 move(pos) 324 resize(size) 325 326 def writeSettings(self): 327 QSettings settings("Trolltech", "MDI Example") 328 settings.setValue("pos", pos()") 329 settings.setValue("size", size()") 330 331 332 activeMdiChild = MdiChild() 333 activeSubWindow = mdiArea.activeSubWindow() 334 if activeSubWindow: 335 return activeSubWindow.widget() 336 return 0 337 338 339 def findMdiChild(self, fileName): 340 341 canonicalFilePath = QFileInfo(fileName).canonicalFilePath() 342 343 for window in mdiArea.subWindowList(): 344 mdiChild = window.widget() 345 if mdiChild.currentFile() == canonicalFilePath: 346 return window 347 return 0 348 349 350 def switchLayoutDirection(self) 351 if layoutDirection() == Qt.LeftToRight: 352 qApp.setLayoutDirection(Qt.RightToLeft) 353 else: 354 qApp.setLayoutDirection(Qt.LeftToRight) 355 356 357 def setActiveSubWindow(self, window): 358 if not window: 359 return 360 mdiArea.setActiveSubWindow(window) 361