Closed grlee77 closed 5 years ago
Thanks @grlee77! I've been hoping to find time to update the code to support Qt5 with PyQT, but I was unaware of qtpy. I also thought the PySide project had gone stagnant, but it's good to see that included as well. I think qtpy is a reasonable additional dependency for this additional flexibility.
This LGTM but I'll test the code before merging and then we can create a new conda package.
@nckz, any thoughts? Maybe you can also comment on the FauxWindow
workaround since that was implemented before my time.
I also thought the PySide project had gone stagnant,
Apparently, it is going to be officially supported going forward, but is now being called "Qt for Python".
I suspect given the official support, it may become more dominant going forward, so we should test against it too. There is a release available at the link below, but I haven't had a chance to try it yet. http://blog.qt.io/blog/2018/06/13/qt-python-5-11-released/
It looks like it is still imported under the name PySide2
so perhaps it will already just work as is with qtpy.
Thanks @grlee77 this looks good. I'm eager to move forward to Qt5 as well.
The FauxMenu class was made to deal with rendering the menu as the user typed characters into the node-search. It was made for some idiosyncrasies in the first version of Qt4. -if it works now without, thats even better :).
I'd like to play around with this branch a little more before pulling.
I have this working and will try using it as my "daily driver" for a bit to shake out any bugs.
The only bug I see so far is upon quitting GPI I get the "Do you want to quit without saving?" dialog twice.
This is kind of a separate issue that should be solved by keeping track of if/when a tab has been saved.
Great. Thanks for the quick review.
By all means, please test more thoroughly to make sure there aren't other issues I may have missed. I think as maintainers you two will have the necessary permissions to push additional commits directly to my branch if you want to make revisions.
I think I'm experiencing the problem the FauxMenu
was meant to solve.
When searching in the node menu, focus is stolen from the text field as soon as the results pop up. This makes it so you have to click the text field to resume typing after each letter is entered. I agree not using the FauxMenu
is preferable if we can work around this in another way - there is an issue with resolution/anti-aliasing in the FauxMenu
that using a real menu resolves.
focus is stolen from the text field as soon as the results pop up
Yes, I see this now too. I'm not sure why I didn't notice it previously, but it is pretty annoying. It looks like we may have to work out how to update the FauxWindow code for Qt5 compatibility.
I have not seen the other issue you reported about "Do you want to quit without saving?" appearing twice.
This is on my list.
I've also been testing this branch with still very few issues so far. Hopefully next week I will have some time to capture some of the minor issues I've been having. I also modified FauxMenu to work without reverting to the raster version. I think I can push that up to this branch somehow...
Glad to hear that this has been working on your end for the most part.
I think I can push that up to this branch somehow...
Yes, any members with write permissions to gpilab/framework
should be able to push commits directly to my branch (I left the "allow edits from mainters" checkbox selected on this PR in GitHub). If that doesn't work for some reason you can make a PR against my fork, but it shouldn't be necessary.
OK, thanks. That was a little confusing but it looks like I got it.
Please test if my new FauxMenu
is working for you. It seems to work well with PyQt5, but feels slow in PyQt4. I'm not sure it's related to this code, however. I have not tested with PySide.
Another problem I found was in the code for dragging/dropping widgets to/from Layout windows (see /lib/gpi/widgets.py:1017
).
In PyQt5 widget
gets a method self.grab()
to replace QtGui.QPixmap().getWidget(self)
, but unfortunately this is not backwards compatible. It works to test the QT_API_NAME
and change the behavior based on that. I'm not too familiar but this feels like a limitation in qtpy
for writing version-agnostic code. Any thoughts?
In the same function, there also seems to be a bug in handling passing of the object id as a str
to QtCore.QByteArray()
. I think this is a Python 3 bytes/string bug, though, so I'm not sure it's related to this branch at all.
I'm giving this a test run on a fairly large node network and it seems to be working pretty well. @aganders3, I'm not having any issues with the FauxMenu — it seems to respond with no delay in PyQt4. I tried using both PyQt4 and PyQt5 (did not try PySide yet).
A couple of things popped up (mostly node-related):
PyQt4
PyQt5 (5.6.0)
Other things:
The latest PyQt5 (5.9.2) seems to break when trying to import certain modules (I get an error with QtMultimedia). Suggestion is to stay with <=5.6.0 for now. See here.
Mojave seems to have issues when trying to change QtPy versions in conda. The ugprade/downgrade will fail unless I manually delete any .app packages inside the envs/[env-name]/bin/ prior to upgrading. Just an FYI for future testing. See here.
Thanks @borupdaniel - this is good stuff. Have you noticed the double-confirmation on quitting or is that a quirk of my system somehow?
I'll try to make the requirements changes for Matplotlib ASAP and push new builds to Anaconda.org. Hopefully I can get around to this tomorrow at least for macOS.
I would not prioritize testing with PySide at this point. I think it's a nice-to-have but that's a pretty big change for us and can wait. I think relaxing the PyQt requirement even to just >4, <5.6 is great progress.
I do get the same issue with double-confirmation on quitting, but only with QtPy5. With 4.11.4 it behaves normally; with 5.6.0 I get the normal confirmation with the canvas open, then an extra one by itself after the canvas has disappeared.
I do get the same issue with double-confirmation on quitting
This doesn't seem like a showstopper but I'd rather not let it through if we can fix it. Super weird to me that it only happens with 5. Ultimately GPI needs to be more document-based so we can including auto-saving and get rid of this dialog if the network on disk is up-to-date. This may work around the issue in the long run but I'd love to see this PR merged sooner than later.
I agree it would be nice to fix. I'm happy to look into it more in the next day or two to help me get some more experience with the framework.
EDIT: okay, I would have sworn this worked yesterday afternoon, but now it makes no difference, so I'm not sure what's up...
ORIGINAL POST: @aganders3 I've traced this to the "close with dialog" function getting called twice, similar to this issue on SO.
A quick two-liner fix to canvasGraphy.py is the following. A new import statement:
from PyQt5.QtCore import pyqtSlot
and later, adding this @ statement .just before closeGraphWithDialog is specified.
@pyqtSlot()
def closeGraphWithDialog(self):
The import statement is obviously not flexible with different PyQt versions, but I think the info at this link should be enough to point me in the right direction to finish this fix.
The fix you propose seems OK to me. Maybe we need to try/except the import and assign a dummy decorator if it fails? I'm not sure if that or an if PYQT5
block or whatever is better.
But I think this is actually two problems. I am getting closeEvent
called twice (the dialogs are very similar) for the MainCanvas
object when I hit ⌘Q or chose "Quit GPI" from the menu bar (all my testing so far has been on a mac). Seems like this may be a Qt bug so we need a workaround or to ignore it.
https://bugreports.qt.io/browse/QTBUG-43344
Ah, this explains things — the issue doesn't happen if you close from the button at top left of the window. So, my fix above doesn't make any difference, I just happened to be testing it by clicking the button instead of command-Q.
This seems to work in Qt 5.6 but it's a bit ugly.
diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py
index cb88656..7baf8e0 100644
--- a/lib/gpi/mainWindow.py
+++ b/lib/gpi/mainWindow.py
@@ -157,6 +157,9 @@ class MainCanvas(QtWidgets.QMainWindow):
self.updateCanvasStatus()
+ # fix for Qt5 bug https://bugreports.qt.io/browse/QTBUG-43344
+ self.close_event_accepted = False
+
def setStatusTip(self, msg):
self.statusBar().showMessage(msg)
@@ -236,14 +239,16 @@ class MainCanvas(QtWidgets.QMainWindow):
def closeEvent(self, event):
'''close all graphs before shutting down.
'''
- if not self.quitConfirmed():
+ if self.close_event_accepted:
event.ignore()
- return
-
- while self.tabs.count():
- self.tabs.widget(0).close()
- self.tabs.removeTab(0)
- event.accept()
+ elif not self.quitConfirmed():
+ event.ignore()
+ else:
+ while self.tabs.count():
+ self.tabs.widget(0).close()
+ self.tabs.removeTab(0)
+ self.close_event_accepted = True
+ event.accept()
def closeCanvasTab(self, index):
'''Leave at least one tab open.
I have the following, similarly ugly but functional.
diff --git a/lib/gpi/mainWindow.py b/lib/gpi/mainWindow.py
index cb88656..de9b9d3 100644
--- a/lib/gpi/mainWindow.py
+++ b/lib/gpi/mainWindow.py
@@ -215,6 +215,7 @@ class MainCanvas(QtWidgets.QMainWindow):
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
+ self.already_closed = True
return True
else:
return False
@@ -234,11 +235,14 @@ class MainCanvas(QtWidgets.QMainWindow):
self.updateCanvasStatus()
def closeEvent(self, event):
- '''close all graphs before shutting down.
- '''
- if not self.quitConfirmed():
- event.ignore()
- return
+ try:
+ self.already_closed
+ except:
+ '''close all graphs before shutting down.
+ '''
+ if not self.quitConfirmed():
+ event.ignore()
+ return
@aganders3, is there a way to specify a specific build for a package? The issue with qtpy 5.9.2 doesn't show up with an earlier build, so it would be nice to say something like "use pyqt 4 or 5, but for 5.9.2, don't use build py36h655552a_2", or something. This would improve compatibility downstream, although it's a bit more complicated on our end.
Here's the change in conda that addresses this issue:
The following packages will be DOWNGRADED:
pyqt 5.9.2-py36h655552a_2 --> 5.9.2-py36h655552a_0
I think we're probably ready to merge this unless you want to do any additional testing, @aganders3. I think we can specify PyQt <= 5.6 for the build and keep an eye on the 5.9.2 bug separately. What do you think — should we go ahead with this?
I think yes but will allow others to chime in if there are any protestations. We can merge without doing a release as well for now.
Let's discuss this week about building/uploading a release, but actually doing this should probably wait until after the ISMRM demo.
I've created a new "qt5" branch and am going to go ahead with this pull request. We're thinking to make this GPI v1.1.0 and wanted to hold off on putting it in the main development branch while we get a v1.0.6 built as we work to getting GPI on conda forge.
Thanks @grlee77 for your work on this — great addition to the project!
@aganders3, is there a way to specify a specific build for a package? The issue with qtpy 5.9.2 doesn't show up with an earlier build, so it would be nice to say something like "use pyqt 4 or 5, but for 5.9.2, don't use build py36h655552a_2", or something. This would improve compatibility downstream, although it's a bit more complicated on our end.
Here's the change in conda that addresses this issue:
The following packages will be DOWNGRADED: pyqt 5.9.2-py36h655552a_2 --> 5.9.2-py36h655552a_0
Following up on this, you can use conda install <package_name>=<version>=<build_string>
to specify a specific build when installing. I don't think there's a way to exclude a specific build from consideration but maybe you can submit it as an issue to the packager?
@aganders3 is there anything you still want to do with v1.0.6? If not I'll go ahead and merge this into the develop branch preparing for 1.1.0.
I don't think so...I merged and tagged 1.0.6 in master so I think we're OK to merge this into develop even though we don't have 1.0.6 packages yet.
We should do this in the core nodes repo as well, and then we can create conda packages from the develop branches for final testing.
I went ahead and merged this into develop, as well as in the core nodes repo. I'm going to switch over to a new issue for preparing the v1.1.0 release on conda-forge.
Thanks @borupdaniel - I'm working on release candidate builds for 1.0.6 as I update the build recipes to...actually work again.
Sounds good.
Future reference: see #22 for the new discussion.
This PR uses qtpy to add support for Qt 5 via either PyQt5 or PySide2 and Qt 4 support via PySide. The previously existing PyQt4 support has been maintained. qtpy is a small python package that is easily installable via pip or conda and is used by the Spyder IDE and other projects to support multiple Qt variants.
Adding PySide support also provides the benefit of supporting an LGPL-licensed Qt backend (PyQt instead uses a GPLv3 license).
I have tested this code on the provided examples in pyqt 4.11.4 (Qt 4.8.7), pyqt 5.6.0 (Qt 5.6.2), pyqt 5.9.2 (Qt 5.9.4) and PySide2 (Qt 5.6.2).
The biggest share of changes are the move of widgets out of the
QtGui
module into their ownQtWidgets
module. Here the Qt5 API is used andqtpy
takes care of making pyqt4 and pyside work with the Qt 5 API conventions. There are a couple other places where object attributes changed between Qt 4 and 5. For these atry/except AttributeError
was used where needed to support both versions. These cases would not be necessary if it is desired to just drop Qt 4 support going forward.There are only a couple areas that were a bit tricky to convert. 1.)
QGraphicsItemAnimation
as used by the "Macro Node" is no longer available in recent Qt. It has been replaced with equivalent code usingQPropertyAnimation
instead.2.) The library node search results pop-up window was modified in 6d6f298 so that it also works in Qt 5. I wasn't entirely clear on the purpose of the
FauxWindow
class, but for some reason it would not display properly in Qt 5 (it was ending up hidden under the main canvas and the buttons were not functional). The new approach is simpler and works on Qt 4 and 5, but perhaps there is some subtle difference?3.)
QtWebKit
is deprecated, but is still present in PyQt5 and PySide2. It was just necessary to import it fromQtWebKitWidgets
instead.Finally, commit e7e444a here changes a font name from "times" to "Times New Roman". In
PyQt5
, I got very poorly rendered text with "times", which is fixed by changing the name. The buttons look fine for either name inPyQt4
.before:
after:
If this is going to be merged, there is a smaller companion PR to
core-nodes
so that those also support Qt 5 and are compatible with the changes made here.TODO: