From 4cf1fe4838a6182a5a98f69e17372a0f6c12c912 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Thu, 2 May 2024 17:22:45 +0100 Subject: [PATCH 01/11] Update Qt.py --- Qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qt.py b/Qt.py index 4aadc9ac..0556ac73 100644 --- a/Qt.py +++ b/Qt.py @@ -47,7 +47,7 @@ import json -__version__ = "1.3.10" +__version__ = "1.4.0" # Enable support for `from Qt import *` __all__ = [] From 0b973dcdaaa9c3fcb907117f67dd617d533fad23 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Thu, 2 May 2024 17:23:23 +0100 Subject: [PATCH 02/11] Update README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 1460b0b6..c7948829 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings | Date | Version | Event |:---------|:----------|:---------- +| May 2024 | [1.4.0][] | Added support for Qt 6 | Jan 2024 | [1.3.9][] | Run CI on Github Actions, instead of Travis CI. | Sep 2020 | [1.3.0][] | Stability improvements and greater ability for `QtCompat.wrapInstance` to do its job | Jun 2019 | [1.2.1][] | Bugfixes and [additional members](https://github.com/mottosso/Qt.py/releases/tag/1.2.0) @@ -34,9 +35,11 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings [1.2.1]: https://github.com/mottosso/Qt.py/releases/tag/1.2.1 [1.3.0]: https://github.com/mottosso/Qt.py/releases/tag/1.3.0 [1.3.9]: https://github.com/mottosso/Qt.py/releases/tag/1.3.9 +[1.4.0]: https://github.com/mottosso/Qt.py/releases/tag/1.4.0 ##### Guides +- [Qt 6 Transition Guide](#qt-6-transition-guide) - [Developing with Qt.py](https://fredrikaverpil.github.io/blog/2016/07/25/developing-with-qtpy/) - [Dealing with Maya 2017 and PySide2](https://fredrikaverpil.github.io/blog/2016/07/25/dealing-with-maya-2017-and-pyside2/) - [Vendoring Qt.py](https://fredrikaverpil.github.io/blog/2017/05/04/vendoring-qtpy/) @@ -64,6 +67,7 @@ Qt.py enables you to write software that runs on any of the 4 supported bindings - [Projects using Qt.py](#projects-using-qtpy) - [Projects similar to Qt.py](#projects-similar-to-qtpy) - [Developer guide](#developer-guide) +- [Qt 6 transition guide](#qt-6-transition-guide)

@@ -589,3 +593,23 @@ cd Qt.py python .\setup.py sdist bdist_wheel python -m twine upload .\dist\* ``` + +
+
+
+ +### Qt 6 Transition Guide + +| Replace | With +|:--------|:---------------------------------- +| `QFont().setWeight(...)` | `QtCompat.QFont.setWeight(font, ...)` +| `QtCore.Qt.MidButton` | `QtCompat.QtCore.Qt.MidButton` +| | Submit your known issues here! + +Qt.py 1.4.0, released in May 2024, added support for Qt 6 whilst preserving compatibility with Qt 4 and 5. That means that in most cases, code you've already written for Qt 4 or 5 will now continue to work with Qt 6, such as Maya 2025. + +However, some changes between 5 and 6 require up-front work by you the developer to make your codebase run on Qt 6 whilst continuing to run on Qt 4 and 5. + +The above is what we know, please do submit issues and pull-request with what else you find! + +- https://github.com/mottosso/Qt.py/issues/new From 60000f68aa7748df25c4dd91a4e9bc006a51ec82 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Thu, 2 May 2024 17:23:42 +0100 Subject: [PATCH 03/11] Update tests.py --- tests.py | 71 ++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/tests.py b/tests.py index fef4ea51..0ebb457d 100644 --- a/tests.py +++ b/tests.py @@ -21,7 +21,7 @@ except ImportError: # Fallback: Define assert_raises using unittest if the import fails import unittest - + def assert_raises(expected_exception, callable_obj=None, *args, **kwargs): """ Custom implementation of assert_raises using unittest. @@ -36,7 +36,7 @@ def assert_raises(expected_exception, callable_obj=None, *args, **kwargs): function_that_raises_some_exception() """ context = unittest.TestCase().assertRaises(expected_exception) - + # If callable_obj is provided, directly call the function with the context manager if callable_obj: with context: @@ -337,6 +337,17 @@ def saveUiFile(filename, ui_template): self.ui_qdockwidget = saveUiFile("qdockwidget.ui", qdockwidget_ui) self.ui_qpycustomwidget = saveUiFile("qcustomwidget.ui", qcustomwidget_ui) + def cleanup(): + try: + shutil.rmtree(self.tempdir) + except Exception: + print( + "WARNING: Temporary directory remains: %s" % self.tempdir + ) + + import atexit + atexit.register(cleanup) + def setUpModule(): """Module-wide initialisation @@ -349,7 +360,10 @@ def setUpModule(): def teardown(): - shutil.rmtree(self.tempdir) + if os.path.exists(self.tempdir): + print("Failed") + else: + print("Success") def tearDownModule(): @@ -409,7 +423,7 @@ def test_load_ui_returntype(): import sys from Qt import QtWidgets, QtCore, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -423,7 +437,7 @@ def test_load_ui_baseinstance(): """Tests to see if the baseinstance loading loads a QWidget on properly""" import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -438,7 +452,7 @@ def test_load_ui_signals(): """Tests to see if the baseinstance connects signals properly""" import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -457,7 +471,7 @@ def test_load_ui_mainwindow(): import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -477,7 +491,7 @@ def test_load_ui_dialog(): import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -497,7 +511,7 @@ def test_load_ui_dockwidget(): import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -517,7 +531,7 @@ def test_load_ui_customwidget(): import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -560,12 +574,12 @@ def test_load_ui_pycustomwidget(): # append the path to ensure the future import can be loaded 'relative' to the tempdir sys.path.append(self.tempdir) - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: app = QtWidgets.QApplication.instance() - + win = QtWidgets.QMainWindow() QtCompat.loadUi(self.ui_qpycustomwidget, win) @@ -584,7 +598,7 @@ def test_load_ui_invalidpath(): """Tests to see if loadUi successfully fails on invalid paths""" import sys from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -606,7 +620,7 @@ def test_load_ui_invalidxml(): from xml.etree import ElementTree from Qt import QtWidgets, QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -624,7 +638,7 @@ def test_load_ui_existingLayoutOnDialog(): '"Dialog", which already has a layout' with ignoreQtMessageHandler([msgs]): - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -645,7 +659,7 @@ def test_load_ui_existingLayoutOnMainWindow(): '"", which already has a layout' with ignoreQtMessageHandler([msgs]): - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -666,7 +680,7 @@ def test_load_ui_existingLayoutOnDockWidget(): '"", which already has a layout' with ignoreQtMessageHandler([msgs]): - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -687,7 +701,7 @@ def test_load_ui_existingLayoutOnWidget(): '"Form", which already has a layout' with ignoreQtMessageHandler([msgs]): - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -998,7 +1012,7 @@ def test_qtcompat_base_class(): import Qt from Qt import QtWidgets from Qt import QtCompat - + if not QtWidgets.QApplication.instance(): app = QtWidgets.QApplication(sys.argv) else: @@ -1022,7 +1036,7 @@ def test_qtcompat_base_class(): def test_cli(): """Qt.py is available from the command-line""" env = os.environ.copy() - env.pop("QT_VERBOSE") # Do not include debug messages + env.pop("QT_VERBOSE", None) # Do not include debug messages popen = subprocess.Popen( [sys.executable, "Qt.py", "--help"], @@ -1096,6 +1110,23 @@ def test_unicode_error_messages(): assert str_message in stderr.getvalue() +def test_midbutton_qt6(): + """QtCore.MidButton was renamed QtCore.MiddleButton in Qt 6""" + from Qt import QtCore, QtCompat + + if binding("PySide6"): + assert QtCompat.Qt.MidButton == QtCore.Qt.MiddleButton + else: + assert QtCompat.Qt.MidButton == QtCore.Qt.MidButton + + +def test_set_font_weight(): + """Qt 6 changed font weights from integers to enums""" + from Qt import QtGui, QtCompat + font = QtGui.QFont() + QtCompat.QFont.setWeight(font, 400) + + if sys.version_info < (3, 5): # PySide is not available for Python > 3.4 # Shiboken(1) doesn't support Python 3.5 From d75bee7c7c595a9f7478e314667a3d2c56ed5740 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 5 May 2024 09:12:43 +0100 Subject: [PATCH 04/11] Fix tests --- tests.py | 76 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/tests.py b/tests.py index 0ebb457d..db0f1e2a 100644 --- a/tests.py +++ b/tests.py @@ -370,6 +370,32 @@ def tearDownModule(): teardown() +preferred_binding = None + +# Use whichever binding is currently available +try: + import PySide as __ + preferred_binding = "PySide" +except ImportError: + try: + import PySide2 as __ + preferred_binding = "PySide2" + except ImportError: + try: + import PySide6 as __ + preferred_binding = "PySide6" + except ImportError: + try: + import PyQt4 as __ + preferred_binding = "PyQt4" + except ImportError: + try: + import PyQt5 as __ + preferred_binding = "PyQt5" + except ImportError: + pass + + def binding(binding): """Isolate test to a particular binding @@ -380,7 +406,11 @@ def binding(binding): """ - return os.getenv("QT_PREFERRED_BINDING") == binding + if "QT_PREFERRED_BINDING" in os.environ: + return os.getenv("QT_PREFERRED_BINDING") == binding + + return binding == preferred_binding + @contextlib.contextmanager @@ -404,20 +434,6 @@ def messageOutputHandler(msgType, logContext, msg): QtCompat.qInstallMessageHandler(None) -def test_environment(): - """Tests require all bindings to be installed (except PySide on py3.5+)""" - - if sys.version_info < (3, 5): - # PySide is not available for Python > 3.4 - imp.find_module("PySide") - elif os.environ.get("QT_PREFERRED_BINDING") == "PySide6": - imp.find_module("PySide6") - else: - imp.find_module("PySide2") - imp.find_module("PyQt4") - imp.find_module("PyQt5") - - def test_load_ui_returntype(): """load_ui returns an instance of QObject""" @@ -829,7 +845,7 @@ def test_vendoring(): env = os.environ.copy() env["QT_PREFERRED_BINDING_JSON"] = json.dumps( { - "Qt": ["PySide6", "PyQt5", "PyQt4"], + "Qt": ["PySide6", "PySide2", "PyQt5", "PyQt4"], "default": ["None"] } ) @@ -842,7 +858,7 @@ def test_vendoring(): ) == 0 print("Testing QT_PREFERRED_BINDING_JSON and QT_PREFERRED_BINDING work..") - env["QT_PREFERRED_BINDING_JSON"] = '{"Qt":["PySide6","PyQt5","PyQt4"]}' + env["QT_PREFERRED_BINDING_JSON"] = '{"Qt":["PySide6", "PySide2","PyQt5","PyQt4"]}' env["QT_PREFERRED_BINDING"] = "None" assert subprocess.call( [sys.executable, "-c", cmd], @@ -999,8 +1015,10 @@ def test_binding_and_qt_version(): def test_binding_states(): """Tests to see if the Qt binding enum states are set properly""" import Qt + print(Qt) + print("ADTAD A---- - - -- - - _SDFSD") assert Qt.IsPySide == binding("PySide") - assert Qt.IsPySide2 == binding("PySide2") + assert Qt.IsPySide2 == binding("PySide2"), "%s != %s" % (Qt.IsPySide2, binding("PySide2")) assert Qt.IsPySide6 == binding("PySide6") assert Qt.IsPyQt5 == binding("PyQt5") assert Qt.IsPyQt4 == binding("PyQt4") @@ -1374,26 +1392,26 @@ def test_preferred_pyside(): "PySide should have been picked, " "instead got %s" % Qt.__binding__) - -if binding("PySide2"): - def test_preferred_pyside2(): - """QT_PREFERRED_BINDING = PySide2 properly forces the binding""" - import Qt - assert Qt.__binding__ == "PySide2", ( - "PySide2 should have been picked, " - "instead got %s" % Qt.__binding__) - def test_coexistence(): """Qt.py may be use alongside the actual binding""" from Qt import QtCore - import PySide2.QtGui + import PySide.QtGui # Qt remaps QStringListModel assert QtCore.QStringListModel # But does not delete the original - assert PySide2.QtGui.QStringListModel + assert PySide.QtGui.QStringListModel + + +if binding("PySide2"): + def test_preferred_pyside2(): + """QT_PREFERRED_BINDING = PySide2 properly forces the binding""" + import Qt + assert Qt.__binding__ == "PySide2", ( + "PySide2 should have been picked, " + "instead got %s" % Qt.__binding__) if binding("PySide6"): From 6091b3174aa5f8c25ddd016f98a1a7993f84ce21 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Sun, 5 May 2024 09:27:35 +0100 Subject: [PATCH 05/11] Revert tests --- tests.py | 111 ++++++++++++++++--------------------------------------- 1 file changed, 31 insertions(+), 80 deletions(-) diff --git a/tests.py b/tests.py index db0f1e2a..6439b656 100644 --- a/tests.py +++ b/tests.py @@ -337,17 +337,6 @@ def saveUiFile(filename, ui_template): self.ui_qdockwidget = saveUiFile("qdockwidget.ui", qdockwidget_ui) self.ui_qpycustomwidget = saveUiFile("qcustomwidget.ui", qcustomwidget_ui) - def cleanup(): - try: - shutil.rmtree(self.tempdir) - except Exception: - print( - "WARNING: Temporary directory remains: %s" % self.tempdir - ) - - import atexit - atexit.register(cleanup) - def setUpModule(): """Module-wide initialisation @@ -360,42 +349,13 @@ def setUpModule(): def teardown(): - if os.path.exists(self.tempdir): - print("Failed") - else: - print("Success") + shutil.rmtree(self.tempdir) def tearDownModule(): teardown() -preferred_binding = None - -# Use whichever binding is currently available -try: - import PySide as __ - preferred_binding = "PySide" -except ImportError: - try: - import PySide2 as __ - preferred_binding = "PySide2" - except ImportError: - try: - import PySide6 as __ - preferred_binding = "PySide6" - except ImportError: - try: - import PyQt4 as __ - preferred_binding = "PyQt4" - except ImportError: - try: - import PyQt5 as __ - preferred_binding = "PyQt5" - except ImportError: - pass - - def binding(binding): """Isolate test to a particular binding @@ -406,11 +366,7 @@ def binding(binding): """ - if "QT_PREFERRED_BINDING" in os.environ: - return os.getenv("QT_PREFERRED_BINDING") == binding - - return binding == preferred_binding - + return os.getenv("QT_PREFERRED_BINDING") == binding @contextlib.contextmanager @@ -434,6 +390,20 @@ def messageOutputHandler(msgType, logContext, msg): QtCompat.qInstallMessageHandler(None) +def test_environment(): + """Tests require all bindings to be installed (except PySide on py3.5+)""" + + if sys.version_info < (3, 5): + # PySide is not available for Python > 3.4 + imp.find_module("PySide") + elif os.environ.get("QT_PREFERRED_BINDING") == "PySide6": + imp.find_module("PySide6") + else: + imp.find_module("PySide2") + imp.find_module("PyQt4") + imp.find_module("PyQt5") + + def test_load_ui_returntype(): """load_ui returns an instance of QObject""" @@ -845,7 +815,7 @@ def test_vendoring(): env = os.environ.copy() env["QT_PREFERRED_BINDING_JSON"] = json.dumps( { - "Qt": ["PySide6", "PySide2", "PyQt5", "PyQt4"], + "Qt": ["PySide6", "PyQt5", "PyQt4"], "default": ["None"] } ) @@ -858,7 +828,7 @@ def test_vendoring(): ) == 0 print("Testing QT_PREFERRED_BINDING_JSON and QT_PREFERRED_BINDING work..") - env["QT_PREFERRED_BINDING_JSON"] = '{"Qt":["PySide6", "PySide2","PyQt5","PyQt4"]}' + env["QT_PREFERRED_BINDING_JSON"] = '{"Qt":["PySide6","PyQt5","PyQt4"]}' env["QT_PREFERRED_BINDING"] = "None" assert subprocess.call( [sys.executable, "-c", cmd], @@ -1015,10 +985,8 @@ def test_binding_and_qt_version(): def test_binding_states(): """Tests to see if the Qt binding enum states are set properly""" import Qt - print(Qt) - print("ADTAD A---- - - -- - - _SDFSD") assert Qt.IsPySide == binding("PySide") - assert Qt.IsPySide2 == binding("PySide2"), "%s != %s" % (Qt.IsPySide2, binding("PySide2")) + assert Qt.IsPySide2 == binding("PySide2") assert Qt.IsPySide6 == binding("PySide6") assert Qt.IsPyQt5 == binding("PyQt5") assert Qt.IsPyQt4 == binding("PyQt4") @@ -1054,7 +1022,7 @@ def test_qtcompat_base_class(): def test_cli(): """Qt.py is available from the command-line""" env = os.environ.copy() - env.pop("QT_VERBOSE", None) # Do not include debug messages + env.pop("QT_VERBOSE") # Do not include debug messages popen = subprocess.Popen( [sys.executable, "Qt.py", "--help"], @@ -1128,23 +1096,6 @@ def test_unicode_error_messages(): assert str_message in stderr.getvalue() -def test_midbutton_qt6(): - """QtCore.MidButton was renamed QtCore.MiddleButton in Qt 6""" - from Qt import QtCore, QtCompat - - if binding("PySide6"): - assert QtCompat.Qt.MidButton == QtCore.Qt.MiddleButton - else: - assert QtCompat.Qt.MidButton == QtCore.Qt.MidButton - - -def test_set_font_weight(): - """Qt 6 changed font weights from integers to enums""" - from Qt import QtGui, QtCompat - font = QtGui.QFont() - QtCompat.QFont.setWeight(font, 400) - - if sys.version_info < (3, 5): # PySide is not available for Python > 3.4 # Shiboken(1) doesn't support Python 3.5 @@ -1392,26 +1343,26 @@ def test_preferred_pyside(): "PySide should have been picked, " "instead got %s" % Qt.__binding__) + +if binding("PySide2"): + def test_preferred_pyside2(): + """QT_PREFERRED_BINDING = PySide2 properly forces the binding""" + import Qt + assert Qt.__binding__ == "PySide2", ( + "PySide2 should have been picked, " + "instead got %s" % Qt.__binding__) + def test_coexistence(): """Qt.py may be use alongside the actual binding""" from Qt import QtCore - import PySide.QtGui + import PySide2.QtGui # Qt remaps QStringListModel assert QtCore.QStringListModel # But does not delete the original - assert PySide.QtGui.QStringListModel - - -if binding("PySide2"): - def test_preferred_pyside2(): - """QT_PREFERRED_BINDING = PySide2 properly forces the binding""" - import Qt - assert Qt.__binding__ == "PySide2", ( - "PySide2 should have been picked, " - "instead got %s" % Qt.__binding__) + assert PySide2.QtGui.QStringListModel if binding("PySide6"): From cbce6bceb5b24e326070083ef2b98a690206c5fe Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 11:34:52 +0100 Subject: [PATCH 06/11] Re-add some misplaced members --- Qt.py | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/Qt.py b/Qt.py index 0556ac73..a64d73e4 100644 --- a/Qt.py +++ b/Qt.py @@ -3,8 +3,8 @@ DOCUMENTATION Qt.py was born in the film and visual effects industry to address the growing need for the development of software capable of running - with more than one flavour of the Qt bindings for Python. - + with more than one flavour of the Qt bindings for Python. + Supported Binding: PySide, PySide2, PySide6, PyQt4, PyQt5 1. Build for one, run with all @@ -111,8 +111,7 @@ "QFileSystemWatcher", "QGenericArgument", "QGenericReturnArgument", - "QItemSelection", - "QItemSelectionModel", + "QHistoryState", "QItemSelectionRange", "QIODevice", "QLibraryInfo", @@ -152,7 +151,6 @@ "QSize", "QSizeF", "QSocketNotifier", - "QStringListModel", "QSysInfo", "QSystemSemaphore", "QT_TRANSLATE_NOOP", @@ -197,8 +195,6 @@ "qUnregisterResourceData", "qVersion", "qWarning", - "Signal", - "Slot" ], "QtGui": [ "QAbstractTextDocumentLayout", @@ -271,7 +267,6 @@ "QQuaternion", "QRadialGradient", "QRegion", - "QRegExpValidator", "QResizeEvent", "QSessionManager", "QShortcutEvent", @@ -943,6 +938,9 @@ def createWidget(self, class_name, parent=None, name=""): "QtCore.QItemSelection": "QtCore.QItemSelection", "QtCore.QItemSelectionModel": "QtCore.QItemSelectionModel", "QtCore.QItemSelectionRange": "QtCore.QItemSelectionRange", + "QtCore.QRegularExpression": "QtCore.QRegExp", + "QtStateMachine.QStateMachine": "QtCore.QStateMachine", + "QtStateMachine.QState": "QtCore.QState", "QtGui.QRegularExpressionValidator": "QtGui.QRegExpValidator", "QtGui.QShortcut": "QtWidgets.QShortcut", "QtGui.QAction": "QtWidgets.QAction", @@ -961,10 +959,10 @@ def createWidget(self, class_name, parent=None, name=""): "QtCompat.qInstallMessageHandler", _qInstallMessageHandler ], "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", + "QtWidgets.QActionGroup": "QtGui.QActionGroup", "QtMultimedia.QSound": "QtMultimedia.QSound", }, "PySide2": { - "QtGui.QStringListModel": "QtCore.QStringListModel", "QtGui.QStringListModel": "QtCore.QStringListModel", "QtCore.Property": "QtCore.Property", "QtCore.Signal": "QtCore.Signal", @@ -1141,6 +1139,12 @@ def createWidget(self, class_name, parent=None, name=""): "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", }, + "QFont":{ + "setWeight": "QtGui.QFont.setWeight", + }, + "Qt": { + "MidButton": "QtCore.Qt.MiddleButton", + }, }, "PyQt5": { "QWidget": { @@ -1161,6 +1165,12 @@ def createWidget(self, class_name, parent=None, name=""): "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", }, + "QFont":{ + "setWeight": "QtGui.QFont.setWeight", + }, + "Qt": { + "MidButton": "QtCore.Qt.MiddleButton", + }, }, "PySide": { "QWidget": { @@ -1179,6 +1189,12 @@ def createWidget(self, class_name, parent=None, name=""): "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", }, + "QFont":{ + "setWeight": "QtGui.QFont.setWeight", + }, + "Qt": { + "MidButton": "QtCore.Qt.MiddleButton", + }, }, "PyQt4": { "QWidget": { @@ -1197,6 +1213,12 @@ def createWidget(self, class_name, parent=None, name=""): "getOpenFileNames": "QtWidgets.QFileDialog.getOpenFileNames", "getSaveFileName": "QtWidgets.QFileDialog.getSaveFileName", }, + "QFont":{ + "setWeight": "QtGui.QFont.setWeight", + }, + "Qt": { + "MidButton": "QtCore.Qt.MiddleButton", + }, }, } @@ -1439,7 +1461,7 @@ def _pyside6(): if hasattr(Qt, "_QtWidgets"): Qt.QtCompat.setSectionResizeMode = \ Qt._QtWidgets.QHeaderView.setSectionResizeMode - + def setWeight(func): def wrapper(self, weight): weight = { @@ -1460,7 +1482,7 @@ def wrapper(self, weight): wrapper.__name__ = func.__name__ return wrapper - + decorators = { "QFont": { @@ -1469,7 +1491,6 @@ def wrapper(self, weight): } _reassign_misplaced_members("PySide6") - _build_compatibility_members("PySide6") _build_compatibility_members("PySide6", decorators) From e28e04a9592e54260bbcbefe535b66624a974697 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 11:37:44 +0100 Subject: [PATCH 07/11] Update README.md --- README.md | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c7948829..608d23cf 100644 --- a/README.md +++ b/README.md @@ -600,11 +600,156 @@ python -m twine upload .\dist\* ### Qt 6 Transition Guide -| Replace | With -|:--------|:---------------------------------- +| Replace | With | Notes +|:--------|:-----|:---------------------------- | `QFont().setWeight(...)` | `QtCompat.QFont.setWeight(font, ...)` +| `QFont().setWeight(QFont().Bold)` | `QFont().setWeight(QFont.Bold)` | Instance of class doesn't have the enums, apparently +| `QEvent().Resize` | `QEvent.Resize` | Instance of class doesn't have the enums, seems to apply overall | `QtCore.Qt.MidButton` | `QtCompat.QtCore.Qt.MidButton` -| | Submit your known issues here! +| `QLabel.setPixmap(str)` | `QLabel.setPixmap(QPixmap())` | Can't take a string anymore (tested in Maya 2025.0) +| `QModelIndex.child` | `QModel.index` | This one is apparently from Qt 4 and should not have been in Qt.py to begin with +| | Submit your known issues here! | + +##### Removed Members + +Many members were removed from Qt.py due to no longer existing in PySide 6. + +> If you find where they went, or think some were removed in error, please submit a pull-request! + +```json +"QtCore": [ + "QAbstractState", + "QAbstractTransition", + "QEventTransition", + "QFinalState", + "QSignalTransition", + "QTextCodec", + "QTextDecoder", + "QTextEncoder", + "QtCriticalMsg", + "QtDebugMsg", + "QtFatalMsg", + "QtSystemMsg", + "QtWarningMsg", + "qChecksum", + "QPictureIO", +], +"QtMultimedia": [ + "QAbstractVideoBuffer", + "QAbstractVideoSurface", + "QAudio", + "QAudioDeviceInfo", + "QAudioFormat", + "QAudioInput", + "QAudioOutput", + "QVideoFrame", + "QVideoSurfaceFormat" +], +"QtNetwork": [ + "QNetworkConfiguration", + "QNetworkConfigurationManager", + "QNetworkSession", +], +"QtOpenGL": [ + "QGL", + "QGLContext", + "QGLFormat", + "QGLWidget" +], +"QtSql": [ + "QSql", + "QSqlDatabase", + "QSqlDriver", + "QSqlDriverCreatorBase", + "QSqlError", + "QSqlField", + "QSqlIndex", + "QSqlQuery", + "QSqlQueryModel", + "QSqlRecord", + "QSqlRelation", + "QSqlRelationalDelegate", + "QSqlRelationalTableModel", + "QSqlResult", + "QSqlTableModel" +], +"QtSvg": [ + "QSvgGenerator", + "QSvgRenderer", +], +"QtWidgets": [ + "QActionGroup", + "QDesktopWidget", + "QDirModel", + "QKeyEventTransition", + "QMouseEventTransition", + "QUndoCommand", + "QUndoGroup", + "QUndoStack", +], +"QtX11Extras": [ + "QX11Info" +], +"QtXml": [ + "QXmlAttributes", + "QXmlContentHandler", + "QXmlDTDHandler", + "QXmlDeclHandler", + "QXmlDefaultHandler", + "QXmlEntityResolver", + "QXmlErrorHandler", + "QXmlInputSource", + "QXmlLexicalHandler", + "QXmlLocator", + "QXmlNamespaceSupport", + "QXmlParseException", + "QXmlReader", + "QXmlSimpleReader" +], +"QtXmlPatterns": [ + "QAbstractMessageHandler", + "QAbstractUriResolver", + "QAbstractXmlNodeModel", + "QAbstractXmlReceiver", + "QSourceLocation", + "QXmlFormatter", + "QXmlItem", + "QXmlName", + "QXmlNamePool", + "QXmlNodeModelIndex", + "QXmlQuery", + "QXmlResultItems", + "QXmlSchema", + "QXmlSchemaValidator", + "QXmlSerializer" +] +``` + +##### Static Members Missing from Instances + +An overall change is that instances of classes, like `QFont()` no longer provides access to static members, such as `QFont.Bold`. So things like: + +```py +font = QFont() +font.setWeight(font.Bold) +``` + +Must be replaced with: + +```py +font = QFont() +font.setWeight(QFont.Bold) +``` + +Or: + +```py +font.setWeight(type(font).Bold) +``` + +Tedious and seemingly unnecessary.. But there you have it! + +##### Notes Qt.py 1.4.0, released in May 2024, added support for Qt 6 whilst preserving compatibility with Qt 4 and 5. That means that in most cases, code you've already written for Qt 4 or 5 will now continue to work with Qt 6, such as Maya 2025. @@ -613,3 +758,9 @@ However, some changes between 5 and 6 require up-front work by you the developer The above is what we know, please do submit issues and pull-request with what else you find! - https://github.com/mottosso/Qt.py/issues/new + +**See also** + +The official PySide2 to PySide6 transition guide, which is especially helpful since Qt.py is modeled after PySide2. + +- https://doc.qt.io/qtforpython-6/gettingstarted/porting_from2.html From 3caf23067de7ea80cd3da7f6395c2f3b12143bf4 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 11:57:51 +0100 Subject: [PATCH 08/11] Fix for PySide2 (Maya 2024) --- Qt.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/Qt.py b/Qt.py index a64d73e4..d73bd4b9 100644 --- a/Qt.py +++ b/Qt.py @@ -111,7 +111,7 @@ "QFileSystemWatcher", "QGenericArgument", "QGenericReturnArgument", - "QHistoryState", + "QItemSelection", "QItemSelectionRange", "QIODevice", "QLibraryInfo", @@ -925,6 +925,9 @@ def createWidget(self, class_name, parent=None, name=""): These members from the original submodule are misplaced relative PySide2 +NOTE: For bindings where a member is not replaced, they still + need to be added such that they are added to Qt.py + """ _misplaced_members = { "PySide6": { @@ -967,6 +970,9 @@ def createWidget(self, class_name, parent=None, name=""): "QtCore.Property": "QtCore.Property", "QtCore.Signal": "QtCore.Signal", "QtCore.Slot": "QtCore.Slot", + "QtCore.QRegExp": "QtCore.QRegExp", + "QtWidgets.QShortcut": "QtWidgets.QShortcut", + "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", "QtCore.QAbstractProxyModel": "QtCore.QAbstractProxyModel", "QtCore.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", "QtCore.QItemSelection": "QtCore.QItemSelection", @@ -998,6 +1004,8 @@ def createWidget(self, class_name, parent=None, name=""): "sip.unwrapinstance": ["QtCompat.getCppPointer", _getcpppointer], "sip.isdeleted": ["QtCompat.isValid", _isvalid], "QtWidgets.qApp": "QtWidgets.QApplication.instance()", + "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", + "QtCore.QRegExp": "QtCore.QRegExp", "QtCore.QCoreApplication.translate": [ "QtCompat.translate", _translate ], @@ -1007,6 +1015,7 @@ def createWidget(self, class_name, parent=None, name=""): "QtCore.qInstallMessageHandler": [ "QtCompat.qInstallMessageHandler", _qInstallMessageHandler ], + "QtWidgets.QShortcut": "QtWidgets.QShortcut", "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", "QtMultimedia.QSound": "QtMultimedia.QSound", }, @@ -1018,18 +1027,21 @@ def createWidget(self, class_name, parent=None, name=""): "QtGui.QItemSelectionModel": "QtCore.QItemSelectionModel", "QtGui.QItemSelectionRange": "QtCore.QItemSelectionRange", "QtGui.QAbstractPrintDialog": "QtPrintSupport.QAbstractPrintDialog", + "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", "QtGui.QPageSetupDialog": "QtPrintSupport.QPageSetupDialog", "QtGui.QPrintDialog": "QtPrintSupport.QPrintDialog", "QtGui.QPrintEngine": "QtPrintSupport.QPrintEngine", "QtGui.QPrintPreviewDialog": "QtPrintSupport.QPrintPreviewDialog", "QtGui.QPrintPreviewWidget": "QtPrintSupport.QPrintPreviewWidget", "QtGui.QPrinter": "QtPrintSupport.QPrinter", + "QtWidgets.QShortcut": "QtWidgets.QShortcut", "QtGui.QPrinterInfo": "QtPrintSupport.QPrinterInfo", "QtUiTools.QUiLoader": ["QtCompat.loadUi", _loadUi], "shiboken.wrapInstance": ["QtCompat.wrapInstance", _wrapinstance], "shiboken.unwrapInstance": ["QtCompat.getCppPointer", _getcpppointer], "shiboken.isValid": ["QtCompat.isValid", _isvalid], "QtGui.qApp": "QtWidgets.QApplication.instance()", + "QtCore.QRegExp": "QtCore.QRegExp", "QtCore.QCoreApplication.translate": [ "QtCompat.translate", _translate ], @@ -1053,9 +1065,11 @@ def createWidget(self, class_name, parent=None, name=""): "QtCore.pyqtSlot": "QtCore.Slot", "QtGui.QItemSelectionRange": "QtCore.QItemSelectionRange", "QtGui.QAbstractPrintDialog": "QtPrintSupport.QAbstractPrintDialog", + "QtGui.QRegExpValidator": "QtGui.QRegExpValidator", "QtGui.QPageSetupDialog": "QtPrintSupport.QPageSetupDialog", "QtGui.QPrintDialog": "QtPrintSupport.QPrintDialog", "QtGui.QPrintEngine": "QtPrintSupport.QPrintEngine", + "QtWidgets.QShortcut": "QtWidgets.QShortcut", "QtGui.QPrintPreviewDialog": "QtPrintSupport.QPrintPreviewDialog", "QtGui.QPrintPreviewWidget": "QtPrintSupport.QPrintPreviewWidget", "QtGui.QPrinter": "QtPrintSupport.QPrinter", @@ -1066,6 +1080,7 @@ def createWidget(self, class_name, parent=None, name=""): "sip.isdeleted": ["QtCompat.isValid", _isvalid], "QtCore.QString": "str", "QtGui.qApp": "QtWidgets.QApplication.instance()", + "QtCore.QRegExp": "QtCore.QRegExp", "QtCore.QCoreApplication.translate": [ "QtCompat.translate", _translate ], @@ -1297,6 +1312,8 @@ def _reassign_misplaced_members(binding): """ + print(binding, "HALLO?") + for src, dst in _misplaced_members[binding].items(): dst_value = None @@ -1315,6 +1332,8 @@ def _reassign_misplaced_members(binding): if len(dst_parts) > 1: dst_member = dst_parts[1] + print("replacing %s.%s -> %s.%s" % (src_module, src_member, dst_module, dst_member)) + # Get the member we want to store in the namesapce. if not dst_value: try: @@ -1539,6 +1558,7 @@ def _pyside2(): Qt.QtCompat.setSectionResizeMode = \ Qt._QtWidgets.QHeaderView.setSectionResizeMode + print("ARADFA") _reassign_misplaced_members("PySide2") _build_compatibility_members("PySide2") @@ -1910,6 +1930,7 @@ def _install(): b for b in QT_PREFERRED_BINDING.split(os.pathsep) if b ) + print("Installing Qt.py") order = preferred_order or default_order available = { @@ -1995,6 +2016,7 @@ def _install(): Qt.QtCompat.load_ui = Qt.QtCompat.loadUi +print("RSHRFJsndfljsdnjk") _install() # Setup Binding Enum states From 64a54d152f088d06954df3055cb777e83eb64e78 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 11:58:44 +0100 Subject: [PATCH 09/11] Remove unused --- Qt.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Qt.py b/Qt.py index d73bd4b9..60f50ff3 100644 --- a/Qt.py +++ b/Qt.py @@ -1312,7 +1312,6 @@ def _reassign_misplaced_members(binding): """ - print(binding, "HALLO?") for src, dst in _misplaced_members[binding].items(): dst_value = None @@ -1332,7 +1331,6 @@ def _reassign_misplaced_members(binding): if len(dst_parts) > 1: dst_member = dst_parts[1] - print("replacing %s.%s -> %s.%s" % (src_module, src_member, dst_module, dst_member)) # Get the member we want to store in the namesapce. if not dst_value: @@ -1457,7 +1455,6 @@ def _pyside6(): import shiboken6 extras.append("shiboken6") except ImportError as e: - print("ImportError: %s" % e) _setup(module, extras) Qt.__binding_version__ = module.__version__ @@ -1558,7 +1555,6 @@ def _pyside2(): Qt.QtCompat.setSectionResizeMode = \ Qt._QtWidgets.QHeaderView.setSectionResizeMode - print("ARADFA") _reassign_misplaced_members("PySide2") _build_compatibility_members("PySide2") @@ -1930,7 +1926,6 @@ def _install(): b for b in QT_PREFERRED_BINDING.split(os.pathsep) if b ) - print("Installing Qt.py") order = preferred_order or default_order available = { @@ -2016,7 +2011,6 @@ def _install(): Qt.QtCompat.load_ui = Qt.QtCompat.loadUi -print("RSHRFJsndfljsdnjk") _install() # Setup Binding Enum states From 125fe979617ee587299a6bc59b12a817e78159e0 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 12:00:06 +0100 Subject: [PATCH 10/11] Fix syntaxerror --- Qt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Qt.py b/Qt.py index 60f50ff3..84fd7b6c 100644 --- a/Qt.py +++ b/Qt.py @@ -1455,6 +1455,7 @@ def _pyside6(): import shiboken6 extras.append("shiboken6") except ImportError as e: + print("ImportError: %s" % e) _setup(module, extras) Qt.__binding_version__ = module.__version__ From bee5c0fc02a71c8da54fe70cb14f42bd947f42bc Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 6 May 2024 12:06:06 +0100 Subject: [PATCH 11/11] Fix PySide(1) --- Qt.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Qt.py b/Qt.py index 84fd7b6c..c3c7292e 100644 --- a/Qt.py +++ b/Qt.py @@ -963,7 +963,6 @@ def createWidget(self, class_name, parent=None, name=""): ], "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", "QtWidgets.QActionGroup": "QtGui.QActionGroup", - "QtMultimedia.QSound": "QtMultimedia.QSound", }, "PySide2": { "QtGui.QStringListModel": "QtCore.QStringListModel", @@ -993,7 +992,6 @@ def createWidget(self, class_name, parent=None, name=""): "QtCompat.qInstallMessageHandler", _qInstallMessageHandler ], "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", - "QtMultimedia.QSound": "QtMultimedia.QSound", }, "PyQt5": { "QtCore.pyqtProperty": "QtCore.Property", @@ -1017,9 +1015,11 @@ def createWidget(self, class_name, parent=None, name=""): ], "QtWidgets.QShortcut": "QtWidgets.QShortcut", "QtWidgets.QStyleOptionViewItem": "QtCompat.QStyleOptionViewItemV4", - "QtMultimedia.QSound": "QtMultimedia.QSound", }, "PySide": { + "QtCore.Property": "QtCore.Property", + "QtCore.Signal": "QtCore.Signal", + "QtCore.Slot": "QtCore.Slot", "QtGui.QAbstractProxyModel": "QtCore.QAbstractProxyModel", "QtGui.QSortFilterProxyModel": "QtCore.QSortFilterProxyModel", "QtGui.QStringListModel": "QtCore.QStringListModel", @@ -1052,7 +1052,6 @@ def createWidget(self, class_name, parent=None, name=""): "QtCompat.qInstallMessageHandler", _qInstallMessageHandler ], "QtGui.QStyleOptionViewItemV4": "QtCompat.QStyleOptionViewItemV4", - "QtGui.QSound": "QtMultimedia.QSound", }, "PyQt4": { "QtGui.QAbstractProxyModel": "QtCore.QAbstractProxyModel", @@ -1091,7 +1090,6 @@ def createWidget(self, class_name, parent=None, name=""): "QtCompat.qInstallMessageHandler", _qInstallMessageHandler ], "QtGui.QStyleOptionViewItemV4": "QtCompat.QStyleOptionViewItemV4", - "QtGui.QSound": "QtMultimedia.QSound", } }