1#!/usr/bin/env python
2
3
4#############################################################################
5##
6## Copyright (C) 2013 Riverbank Computing Limited.
7## Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
8## All rights reserved.
9##
10## This file is part of the examples of PyQt.
11##
12## $QT_BEGIN_LICENSE:BSD$
13## You may use this file under the terms of the BSD license as follows:
14##
15## "Redistribution and use in source and binary forms, with or without
16## modification, are permitted provided that the following conditions are
17## met:
18##   * Redistributions of source code must retain the above copyright
19##     notice, this list of conditions and the following disclaimer.
20##   * Redistributions in binary form must reproduce the above copyright
21##     notice, this list of conditions and the following disclaimer in
22##     the documentation and/or other materials provided with the
23##     distribution.
24##   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
25##     the names of its contributors may be used to endorse or promote
26##     products derived from this software without specific prior written
27##     permission.
28##
29## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
40## $QT_END_LICENSE$
41##
42#############################################################################
43
44
45from PyQt5.QtCore import (QFile, QFileInfo, QPoint, QSettings, QSize, Qt,
46        QTextStream)
47from PyQt5.QtGui import QIcon, QKeySequence
48from PyQt5.QtWidgets import (QAction, QApplication, QFileDialog, QMainWindow,
49        QMessageBox, QTextEdit)
50
51import sdi_rc
52
53
54class MainWindow(QMainWindow):
55    sequenceNumber = 1
56    windowList = []
57
58    def __init__(self, fileName=None):
59        super(MainWindow, self).__init__()
60
61        self.init()
62        if fileName:
63            self.loadFile(fileName)
64        else:
65            self.setCurrentFile('')
66
67    def closeEvent(self, event):
68        if self.maybeSave():
69            self.writeSettings()
70            event.accept()
71        else:
72            event.ignore()
73
74    def newFile(self):
75        other = MainWindow()
76        MainWindow.windowList.append(other)
77        other.move(self.x() + 40, self.y() + 40)
78        other.show()
79
80    def open(self):
81        fileName, _ = QFileDialog.getOpenFileName(self)
82        if fileName:
83            existing = self.findMainWindow(fileName)
84            if existing:
85                existing.show()
86                existing.raise_()
87                existing.activateWindow()
88                return
89
90            if self.isUntitled and self.textEdit.document().isEmpty() and not self.isWindowModified():
91                self.loadFile(fileName)
92            else:
93                other = MainWindow(fileName)
94                if other.isUntitled:
95                    del other
96                    return
97
98                MainWindow.windowList.append(other)
99                other.move(self.x() + 40, self.y() + 40)
100                other.show()
101
102    def save(self):
103        if self.isUntitled:
104            return self.saveAs()
105        else:
106            return self.saveFile(self.curFile)
107
108    def saveAs(self):
109        fileName, _ = QFileDialog.getSaveFileName(self, "Save As",
110                self.curFile)
111        if not fileName:
112            return False
113
114        return self.saveFile(fileName)
115
116    def about(self):
117        QMessageBox.about(self, "About SDI",
118                "The <b>SDI</b> example demonstrates how to write single "
119                "document interface applications using Qt.")
120
121    def documentWasModified(self):
122        self.setWindowModified(True)
123
124    def init(self):
125        self.setAttribute(Qt.WA_DeleteOnClose)
126        self.isUntitled = True
127        self.textEdit = QTextEdit()
128        self.setCentralWidget(self.textEdit)
129
130        self.createActions()
131        self.createMenus()
132        self.createToolBars()
133        self.createStatusBar()
134
135        self.readSettings()
136
137        self.textEdit.document().contentsChanged.connect(self.documentWasModified)
138
139    def createActions(self):
140        self.newAct = QAction(QIcon(':/images/new.png'), "&New", self,
141                shortcut=QKeySequence.New, statusTip="Create a new file",
142                triggered=self.newFile)
143
144        self.openAct = QAction(QIcon(':/images/open.png'), "&Open...", self,
145                shortcut=QKeySequence.Open, statusTip="Open an existing file",
146                triggered=self.open)
147
148        self.saveAct = QAction(QIcon(':/images/save.png'), "&Save", self,
149                shortcut=QKeySequence.Save,
150                statusTip="Save the document to disk", triggered=self.save)
151
152        self.saveAsAct = QAction("Save &As...", self,
153                shortcut=QKeySequence.SaveAs,
154                statusTip="Save the document under a new name",
155                triggered=self.saveAs)
156
157        self.closeAct = QAction("&Close", self, shortcut="Ctrl+W",
158                statusTip="Close this window", triggered=self.close)
159
160        self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
161                statusTip="Exit the application",
162                triggered=QApplication.instance().closeAllWindows)
163
164        self.cutAct = QAction(QIcon(':/images/cut.png'), "Cu&t", self,
165                enabled=False, shortcut=QKeySequence.Cut,
166                statusTip="Cut the current selection's contents to the clipboard",
167                triggered=self.textEdit.cut)
168
169        self.copyAct = QAction(QIcon(':/images/copy.png'), "&Copy", self,
170                enabled=False, shortcut=QKeySequence.Copy,
171                statusTip="Copy the current selection's contents to the clipboard",
172                triggered=self.textEdit.copy)
173
174        self.pasteAct = QAction(QIcon(':/images/paste.png'), "&Paste", self,
175                shortcut=QKeySequence.Paste,
176                statusTip="Paste the clipboard's contents into the current selection",
177                triggered=self.textEdit.paste)
178
179        self.aboutAct = QAction("&About", self,
180                statusTip="Show the application's About box",
181                triggered=self.about)
182
183        self.aboutQtAct = QAction("About &Qt", self,
184                statusTip="Show the Qt library's About box",
185                triggered=QApplication.instance().aboutQt)
186
187        self.textEdit.copyAvailable.connect(self.cutAct.setEnabled)
188        self.textEdit.copyAvailable.connect(self.copyAct.setEnabled)
189
190    def createMenus(self):
191        self.fileMenu = self.menuBar().addMenu("&File")
192        self.fileMenu.addAction(self.newAct)
193        self.fileMenu.addAction(self.openAct)
194        self.fileMenu.addAction(self.saveAct)
195        self.fileMenu.addAction(self.saveAsAct)
196        self.fileMenu.addSeparator()
197        self.fileMenu.addAction(self.closeAct)
198        self.fileMenu.addAction(self.exitAct)
199
200        self.editMenu = self.menuBar().addMenu("&Edit")
201        self.editMenu.addAction(self.cutAct)
202        self.editMenu.addAction(self.copyAct)
203        self.editMenu.addAction(self.pasteAct)
204
205        self.menuBar().addSeparator()
206
207        self.helpMenu = self.menuBar().addMenu("&Help")
208        self.helpMenu.addAction(self.aboutAct)
209        self.helpMenu.addAction(self.aboutQtAct)
210
211    def createToolBars(self):
212        self.fileToolBar = self.addToolBar("File")
213        self.fileToolBar.addAction(self.newAct)
214        self.fileToolBar.addAction(self.openAct)
215        self.fileToolBar.addAction(self.saveAct)
216
217        self.editToolBar = self.addToolBar("Edit")
218        self.editToolBar.addAction(self.cutAct)
219        self.editToolBar.addAction(self.copyAct)
220        self.editToolBar.addAction(self.pasteAct)
221
222    def createStatusBar(self):
223        self.statusBar().showMessage("Ready")
224
225    def readSettings(self):
226        settings = QSettings('Trolltech', 'SDI Example')
227        pos = settings.value('pos', QPoint(200, 200))
228        size = settings.value('size', QSize(400, 400))
229        self.move(pos)
230        self.resize(size)
231
232    def writeSettings(self):
233        settings = QSettings('Trolltech', 'SDI Example')
234        settings.setValue('pos', self.pos())
235        settings.setValue('size', self.size())
236
237    def maybeSave(self):
238        if self.textEdit.document().isModified():
239            ret = QMessageBox.warning(self, "SDI",
240                    "The document has been modified.\nDo you want to save "
241                    "your changes?",
242                    QMessageBox.Save | QMessageBox.Discard |
243                    QMessageBox.Cancel)
244
245            if ret == QMessageBox.Save:
246                return self.save()
247
248            if ret == QMessageBox.Cancel:
249                return False
250
251        return True
252
253    def loadFile(self, fileName):
254        file = QFile(fileName)
255        if not file.open(QFile.ReadOnly | QFile.Text):
256            QMessageBox.warning(self, "SDI",
257                    "Cannot read file %s:\n%s." % (fileName, file.errorString()))
258            return
259
260        instr = QTextStream(file)
261        QApplication.setOverrideCursor(Qt.WaitCursor)
262        self.textEdit.setPlainText(instr.readAll())
263        QApplication.restoreOverrideCursor()
264
265        self.setCurrentFile(fileName)
266        self.statusBar().showMessage("File loaded", 2000)
267
268    def saveFile(self, fileName):
269        file = QFile(fileName)
270        if not file.open(QFile.WriteOnly | QFile.Text):
271            QMessageBox.warning(self, "SDI",
272                    "Cannot write file %s:\n%s." % (fileName, file.errorString()))
273            return False
274
275        outstr = QTextStream(file)
276        QApplication.setOverrideCursor(Qt.WaitCursor)
277        outstr << self.textEdit.toPlainText()
278        QApplication.restoreOverrideCursor()
279
280        self.setCurrentFile(fileName)
281        self.statusBar().showMessage("File saved", 2000)
282        return True
283
284    def setCurrentFile(self, fileName):
285        self.isUntitled = not fileName
286        if self.isUntitled:
287            self.curFile = "document%d.txt" % MainWindow.sequenceNumber
288            MainWindow.sequenceNumber += 1
289        else:
290            self.curFile = QFileInfo(fileName).canonicalFilePath()
291
292        self.textEdit.document().setModified(False)
293        self.setWindowModified(False)
294
295        self.setWindowTitle("%s[*] - SDI" % self.strippedName(self.curFile))
296
297    def strippedName(self, fullFileName):
298        return QFileInfo(fullFileName).fileName()
299
300    def findMainWindow(self, fileName):
301        canonicalFilePath = QFileInfo(fileName).canonicalFilePath()
302
303        for widget in QApplication.instance().topLevelWidgets():
304            if isinstance(widget, MainWindow) and widget.curFile == canonicalFilePath:
305                return widget
306
307        return None
308
309
310if __name__ == '__main__':
311
312    import sys
313
314    app = QApplication(sys.argv)
315    mainWin = MainWindow()
316    mainWin.show()
317    sys.exit(app.exec_())
318