0

I've run into a snag porting a program example from "Rapid GUI Programming with Python and Qt" from PyQt4 to PyQt5. The sample program demonstrates an MDI application from which multiple text edit windows can be run within the main window.

I used python 3.4.4 and PyQt 4.8.7 for the PyQt4 version. I used python 3.4.4 and PyQt 5.5.1 for the PyQt5 version.

I started by changing all old-style signal definitions to new style signals in the original PyQt4 program. New style signals were implemented in PyQt 4.5 so I was able to run the original program with these changes. The application ran successfully after updating all old-style signals to new-style signals.

The original program uses the PyQt4.QtGui.QWidget.QWorkspace class to implement the MDI workspace. QWorkspace was replaced by the PyQt5.QtWidgets.QMdiArea class in PyQt4.3. My problem surfaced in trying to modify the original code to work with QMdiArea.

Each text document is presented and edited using an instance of a custom TextEdit widget, a subclass of QTextEdit.

Minimal PyQt5 version of MDI application -- texteditor.py

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

class TextEdit(QTextEdit):
    NextId = 1

    def __init__(self, filename="", parent=None):
        print("TextEdit __init__")
        super(TextEdit, self).__init__(parent)
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.filename = filename
        if not self.filename:
            self.filename = "Unnamed-{}.txt".format(TextEdit.NextId)
            TextEdit.NextId += 1
        self.document().setModified(False)
        self.setWindowTitle(QFileInfo(self.filename).fileName())

    def load(self):
        print("load - TextEdit")
        exception = None
        fh = None
        try:
            fh = QFile(self.filename)
            if not fh.open(QIODevice.ReadOnly):
                raise IOError(fh.errorString())
            stream = QTextStream(fh)
            stream.setCodec("UTF-8")
            self.setPlainText(stream.readAll())
            self.document().setModified(False)
        except EnvironmentError as e:
            exception = e
        finally:
            if fh is not None:
                fh.close()
            if exception is not None:
                raise exception

import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

__version__ = "1.0.0"

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.mdi = QMdiArea()
        self.setCentralWidget(self.mdi)
        fileOpenAction = QAction("&Open...", self)
        fileOpenAction.setShortcut(QKeySequence.Open)
        fileOpenAction.triggered.connect(self.fileOpen)
        fileMenu = self.menuBar().addMenu("&File")
        fileMenu.addAction(fileOpenAction)
        settings = QSettings()
        self.restoreGeometry(settings.value("MainWindow/Geometry",
                QByteArray()))
        self.restoreState(settings.value("MainWindow/State",
                QByteArray()))
        QTimer.singleShot(0, self.loadFiles)


    def loadFiles(self):
        if len(sys.argv) > 1:
            for filename in sys.argv[1:31]: # Load at most 30 files
                if QFileInfo(filename).isFile():
                    self.loadFile(filename)
                    QApplication.processEvents()
        else:
            settings = QSettings()
            files = settings.value("CurrentFiles") or []
            for filename in files:
                if QFile.exists(filename):
                    self.loadFile(filename)
                    QApplication.processEvents()  #todo What does this do?

    def fileOpen(self):
        filename, _ = QFileDialog.getOpenFileName(self,
                "Text Editor -- Open File")
        if filename:
            for textEdit in self.mdi.subWindowList():
                print(type(textEdit))
                if textEdit.filename == filename:
                    self.mdi.setActiveSubWindow(textEdit)
                    break
            else:
                self.loadFile(filename)

    def loadFile(self, filename):
        textEdit = TextEdit(filename)
        try:
            textEdit.load()
        except EnvironmentError as e:
            QMessageBox.warning(self, "Text Editor -- Load Error",
                    "Failed to load {}: {}".format(filename, e))
            textEdit.close()
            del textEdit
        else:
            self.mdi.addSubWindow(textEdit)
            textEdit.show()

app = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/icon.png"))
app.setOrganizationName("Qtrac Ltd.")
app.setOrganizationDomain("qtrac.eu")
app.setApplicationName("Text Editor")
form = MainWindow()
form.show()
app.exec_()

The problem occurs in the fileOpen() method:

PyQt4 fileOpen() method

    def fileOpen(self):
        filename = QFileDialog.getOpenFileName(self,
                "Text Editor -- Open File")
        if filename:
            for textEdit in self.mdi.windowList():
                if textEdit.filename == filename:
                    self.mdi.setActiveWindow(textEdit)
                    break
            else:
                self.loadFile(filename)

PyQt5 fileOpen() method

    def fileOpen(self):
        filename, _ = QFileDialog.getOpenFileName(self,
                "Text Editor -- Open File")
        if filename:
            for textEdit in self.mdi.subWindowList():
                if textEdit.filename == filename:
                    self.mdi.setActiveSubWindow(textEdit)
                    break
            else:
                self.loadFile(filename)

windowList() is implemented in PyQt5 as subWindowList(). The problem is that in the PyQt4 version, when for textEdit in self.mdi.windowList(): is executed textEdit is of type TextEdit so the next line

if textEdit.filename == filename

works since TextEdit does have a filename parameter. and textEdit is a {TextEdit}textedit.TextEdit object, but in the PyQt5 version, after for textEdit in self.mdi.subWindowList(): is executed, the type of textEdit is QMdiSubWindow so, of course the traceback generates:

Traceback (most recent call last):
  File "texteditor3.py", line 292, in fileOpen
    if textEdit.filename == filename:
AttributeError: 'QMdiSubWindow' object has no attribute 'filename'

What really baffles me is how textEdit in the PyQt4 version becomes a TextEdit type. I would think it would be a str type.

Kor Kiley
  • 1
  • 4
  • I added a minimal, complete and verifiable example! – Kor Kiley Jun 17 '16 at 16:33
  • In order to keep this question a reasonable length I did not insert the minimal code for the PyQt4 version. If anyone would like to see that, I would be happy to add it or send it to you. – Kor Kiley Jun 21 '16 at 17:00

1 Answers1

0

I'am from Germany I found an answer. See the Code. Sorry about the German Comments.

def fileOpen(self):
    try:
        # PSc QFileDialog.getOpenFileName gibt ein Tuple zurück
        # für die weitere Verwendung filname[0] verwenden
        filename = QFileDialog.getOpenFileName(self,
                "Text Editor -- Open File")
        if filename:
            try:
                # PSc wenn ein zweites Open durchgeführt wird erhält man die Fehlermeldung
                # textEdit has no attribute fileName
                # http://stackoverflow.com/questions/37800036/porting-pyqt4-qworkspace-to-pyqt5-qmdiarea-subwindowlist-method
                # Lösung scheinbar hier gefunden Zeile 268 269
                # http://nullege.com/codes/show/src%40p%40y%40pyqt5-HEAD%40examples%40mainwindows%40mdi%40mdi.py/168/PyQt5.QtWidgets.QMdiArea.subWindowActivated.connect/python
                # Folgende Zeile dementsprechen geändert
                # for textEdit in self.mdi.subWindowList():
                for windows in self.mdi.subWindowList():
                    textEdit = windows.widget()
                    print('In File Open textEdit.filename: ' + textEdit.filename)
                    if textEdit.filename == filename[0]:
                        self.mdi.setActiveWindow(textEdit)
                        break
                else:
                    # PSc filename Tuple daher filename[0] übergeben
                    self.loadFile(filename[0])
            except:
                e = sys.exc_info()
                print('An exception occurred in def fileOpen  if Filename : \n' + str(e) + '\n')
    except:
        e = sys.exc_info()
        print('An exception occurred in def fileOpenin: \n' + str(e) + '\n')

I Have changed it everywher like:

def fileSave(self):
    try:
        # PSc PyQt4 Syntax
        # textEdit = self.mdi.activeSubWindow()
        # geändert laut Zeile 268,269
        # nullege.com/codes/show/src%40p%40y%40pyqt5-HEAD%40examples%40mainwindows%40mdi%40mdi.py/168/PyQt5.QtWidgets.QMdiArea.subWindowActivated.connect/python
        window = self.mdi.activeSubWindow()
        textEdit = window.widget()
        if textEdit is None or not isinstance(textEdit, QTextEdit):
            return True
        try:
            textEdit.save()
            return True
        except EnvironmentError as e:
            QMessageBox.warning(self, "Text Editor -- Save Error",
                    "Failed to save {}: {}".format(textEdit.filename, e))
            return False
    except Exception as error:
        print('An exception occurred in fileSave: {}'.format(error))
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • 1
    You could explain your answer to improve its quality. – eyllanesc Apr 04 '17 at 14:37
  • Thank you for the answer. Unfortunately it's been nine months since I posted this question and I'm very busy with other things now. I will review my question and then try your solution though. I may take me a few days or weeks before I have time though. – Kor Kiley Apr 05 '17 at 17:41