Cascade of dialog sheets broken on OSX, PyQt5.1, Qt5.02.
-
This is about a cascade of dialog sheets (window modal) and a possible bug on OSX, for a document editing app.
The problem is with this sequence of dialog sheets for an open document operation when the current doc is dirty:
- maybeSave: save the current doc?
- chooseSaveName: name the currently untitled doc
- maybeReplace: replace an existing file of the same name?
- chooseOpenName: choose a file to open
(They all should be dialog sheets, since they are all about the document.)
On Linux it seems to work. On OSX, the last dialog, chooseOpenName, opens and works but then a non-working ghost of the second dialog, chooseSaveName "Save As", remains.
I thought maybe I should processEvents() in the middle of the cascade but that doesn't seem to help. Next I will try breaking the cascade in two by emitting an open event instead of a signal, in the middle.
I guess that this is a common thing to do, and that someone has already done this in C++, so maybe it is a bug specific to PyQt or the platform?
Code that demonstrates:
@
'''
Cascade of window modal dialogs to exhibit bug on OSX:
After the final "open" dialog, the "maybe save" dialog is still open.To test fully, choose an existing .txt file to save to (we only pretend to save and open files.)
'''import sys
from PyQt5.QtCore import Qt, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QMessageBox, QFileDialogmainWindow = None
class PrintConverser(QObject):
def doCascade(self):
# pretend doc is dirty
maybeSaveDialog = QMessageBox( QMessageBox.Warning,
self.tr("Test"),
self.tr("Do you want to save your changes?"),
buttons=QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel,
parent=mainWindow,
flags=Qt.Sheet)
maybeSaveDialog.setDefaultButton(QMessageBox.Save)
# !!! accepted and rejected never emitted for StandardButtons
maybeSaveDialog.finished.connect(self.finishedMaybeSave)
maybeSaveDialog.open() # window modal sheet on OSXdef finishedMaybeSave(self, result):
''' Slot for maybe save finished() signal.'''
# pretend user chose "save"
chooseFileDialog = self.getChooseSaveFileDialog()
chooseFileDialog.fileSelected.connect(self.saveAndFinishOpen)
chooseFileDialog.rejected.connect(self.cancelAction)
chooseFileDialog.show()Another, automatic dialog may open: "Replace existing file?"
def saveAndFinishOpen(self, filename):
''' Slot for fileSelected signal from SaveFileDialog, when user wants subsequently to open. '''
print("Pretend save file", filename)
# This doesn't help problem on OSX
# QCoreApplication.instance().processEvents()
self._chooseFileThenFinishLoad()def _chooseFileThenFinishLoad(self):
'''
Let user choose existing file to open into document.
Assert user has been given chance to save (but user could be abandoning doc.)
If user cancels, document might still exist in its original form.Continues, connected to slots to finish loading or cancel. ''' dialog = self.getChooseOpenFileDialog() dialog.fileSelected.connect(self.loadFromFile) dialog.rejected.connect(self.cancelAction) dialog.show()
def loadFromFile(self, fileName):
print("Pretend loading ", fileName)def cancelAction(self):
'''Slot for rejected signal for many dialogs.'''
print('Canceled cascade of dialogs at some stage')def getChooseOpenFileDialog(self):
return self.getChooseFileDialog(mode= QFileDialog.AcceptOpen, # !!!! Input
caption="Open",
fileFilter="Text files (*.txt)",
suffix="txt")def getChooseSaveFileDialog(self):
return self.getChooseFileDialog(mode= QFileDialog.AcceptSave,
caption="Save",
fileFilter="Text files (*.txt)",
suffix="txt")def getChooseFileDialog(self, mode, caption, fileFilter, suffix):
'''
Window modal dialog for choosing file.
'''
dialog = QFileDialog(mainWindow) # , Qt.Sheet) # parent, flags
dialog.setAcceptMode(mode)
# Qt 4.8 dialog.setConfirmOverwrite(True)
# Qt 5.0 confirm defaults to True
dialog.setWindowTitle(caption)
dialog.setFileMode(QFileDialog.AnyFile)
dialog.setDefaultSuffix(suffix)
# Don't set directory, just file name
# dialog.selectFile(_getFileName(suffix))
dialog.setNameFilter(fileFilter)
dialog.setViewMode(QFileDialog.List)
dialog.setLabelText (QFileDialog.Accept , caption)
dialog.setWindowModality(Qt.WindowModal) # !!! sheet on OSXreturn dialog
def main():
app = QApplication(sys.argv)global mainWindow
mainWindow = QMainWindow()
mainWindow.setGeometry(100, 100, 500, 40)printConverser = PrintConverser()
button = QPushButton("Cascade dialogs for open when dirty")
button.clicked.connect(printConverser.doCascade)
mainWindow.setCentralWidget(button)
mainWindow.show()sys.exit(app.exec_())
if name=="main":
@ -
I found a work around is to post a custom event (representing the user's desire to open a file) after the dialogs for saving the current dirty file (instead of continuing the cascade with further window modal dialogs.)
I also tried to call in order: QCoreApplication.flush(), sendPostedEvents(), and processEvents() in the slot for each dialog's finish signal. That didn't help the problem.
Possibly Qt should be queing the signals, so the app main loop gets a chance to execute? My limited understanding of this situation is that the main event loop never gets a chance to execute, since some sort of private event loop is handling events for the window modal dialogs? If so, wouldn't those event loops be nested?
I will post the workaround code if requested.
-
Hi,
I stumbled over exactly the same problem, with Qt 5.2.1 and C++ on Mac OS X 10.8.5. So it has nothing to do with the Python wrapper.
My workaround is to use a single-shot QTimer with a timeout of 0 ms which then shows the second sheet (window modal) dialog.
I created a bug with a small example attached, which also shows my workaround:
https://bugreports.qt-project.org/browse/QTBUG-36721
Note that I cannot reproduce that bug anymore with the same Qt 5.2.1 version on OS X 10.9.1 (Mavericks)! So it must also be some sort of Cocoa bug (possibly triggered by some interaction on the Qt side) which Apple has fixed in Mavericks...