Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Unscoped enums access for PyQt6 and other missing PyQt6 compatibility changes #271

Merged
merged 9 commits into from
Nov 22, 2021
18 changes: 17 additions & 1 deletion qtpy/QtCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,34 @@
"""
Provides QtCore classes and functions.
"""

from . import PYQT6, PYQT5, PYSIDE2, PYSIDE6, PythonQtError


if PYQT6:
from PyQt6 import QtCore
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtCore import *
from PyQt6.QtCore import pyqtSignal as Signal
from PyQt6.QtCore import pyqtBoundSignal as SignalInstance
from PyQt6.QtCore import pyqtSlot as Slot
from PyQt6.QtCore import pyqtProperty as Property
from PyQt6.QtCore import QT_VERSION_STR as __version__

# For issue #153
from PyQt6.QtCore import QDateTime
QDateTime.toPython = QDateTime.toPyDateTime

# Map missing methods
QCoreApplication.exec_ = QCoreApplication.exec
QEventLoop.exec_ = QEventLoop.exec
QThread.exec_ = QThread.exec

# Those are imported from `import *`
del pyqtSignal, pyqtBoundSignal, pyqtSlot, pyqtProperty, QT_VERSION_STR

# Allow unscoped access for enums inside the QtCore module
from .enums_compat import promote_enums
promote_enums(QtCore)
del QtCore
elif PYQT5:
from PyQt5.QtCore import *
from PyQt5.QtCore import pyqtSignal as Signal
Expand Down
10 changes: 8 additions & 2 deletions qtpy/QtGui.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@
"""
Provides QtGui classes and functions.
"""
import warnings

from . import PYQT6, PYQT5, PYSIDE2, PYSIDE6, PythonQtError


if PYQT6:
from PyQt6 import QtGui
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtGui import *

# Map missing/renamed methods
QDrag.exec_ = QDrag.exec
QGuiApplication.exec_ = QGuiApplication.exec
QTextDocument.print_ = QTextDocument.print

# Allow unscoped access for enums inside the QtGui module
from .enums_compat import promote_enums
promote_enums(QtGui)
del QtGui
elif PYQT5:
from PyQt5.QtGui import *
elif PYSIDE2:
Expand Down
17 changes: 13 additions & 4 deletions qtpy/QtWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,38 @@
"""
Provides widget classes and functions.
"""

from . import PYQT5, PYQT6, PYSIDE2, PYSIDE6, PythonQtError
from ._patch.qheaderview import introduce_renamed_methods_qheaderview


if PYQT6:
from PyQt6 import QtWidgets
ccordoba12 marked this conversation as resolved.
Show resolved Hide resolved
from PyQt6.QtWidgets import *
from PyQt6.QtGui import QAction, QActionGroup, QShortcut
from PyQt6.QtOpenGLWidgets import QOpenGLWidget

# Map missing/renamed methods
QTextEdit.setTabStopWidth = QTextEdit.setTabStopDistance
QTextEdit.tabStopWidth = QTextEdit.tabStopDistance
QTextEdit.print_ = QTextEdit.print
QPlainTextEdit.setTabStopWidth = QPlainTextEdit.setTabStopDistance
QPlainTextEdit.tabStopWidth = QPlainTextEdit.tabStopDistance
QPlainTextEdit.print_ = QPlainTextEdit.print
QApplication.exec_ = QApplication.exec
QDialog.exec_ = QDialog.exec
QMenu.exec_ = QMenu.exec
QTextEdit.print_ = QTextEdit.print
QPlainTextEdit.print_ = QPlainTextEdit.print

# Allow unscoped access for enums inside the QtWidgets module
from .enums_compat import promote_enums
promote_enums(QtWidgets)
del QtWidgets
elif PYQT5:
from PyQt5.QtWidgets import *
elif PYSIDE6:
from PySide6.QtWidgets import *
from PySide6.QtGui import QAction, QActionGroup, QShortcut
from PySide6.QtOpenGLWidgets import QOpenGLWidget

# Map missing/renamed methods
QTextEdit.setTabStopWidth = QTextEdit.setTabStopDistance
QTextEdit.tabStopWidth = QTextEdit.tabStopDistance
QPlainTextEdit.setTabStopWidth = QPlainTextEdit.setTabStopDistance
Expand Down
2 changes: 0 additions & 2 deletions qtpy/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"""
Compatibility functions
"""

from collections.abc import Callable
import sys

from .QtWidgets import QFileDialog
Expand Down
37 changes: 37 additions & 0 deletions qtpy/enums_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright © 2009- The Spyder Development Team
# Copyright © 2012- University of North Carolina at Chapel Hill
# Luke Campagnola ('luke.campagnola@%s.com' % 'gmail')
# Ogi Moore ('ognyan.moore@%s.com' % 'gmail')
# KIU Shueng Chuan ('nixchuan@%s.com' % 'gmail')
# Licensed under the terms of the MIT License

"""
Compatibility functions for scoped and unscoped enum access.
"""

from . import PYQT6

if PYQT6:
import enum

from . import sip

dalthviz marked this conversation as resolved.
Show resolved Hide resolved
def promote_enums(module):
"""
Search enums in the given module and allow unscoped access.

Taken from:
https://github.com/pyqtgraph/pyqtgraph/blob/pyqtgraph-0.12.1/pyqtgraph/Qt.py#L331-L377
"""
class_names = [name for name in dir(module) if name.startswith('Q')]
for class_name in class_names:
klass = getattr(module, class_name)
if not isinstance(klass, sip.wrappertype):
continue
attrib_names = [name for name in dir(klass) if name[0].isupper()]
for attrib_name in attrib_names:
attrib = getattr(klass, attrib_name)
if not isinstance(attrib, enum.EnumMeta):
continue
for enum_obj in attrib:
setattr(klass, enum_obj.name, enum_obj)
15 changes: 15 additions & 0 deletions qtpy/sip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#
# Copyright © 2009- The Spyder Development Team
#
# Licensed under the terms of the MIT License
# (see LICENSE.txt for details)

from . import PYQT6, PYQT5, PythonQtError

if PYQT6:
from PyQt6.sip import *
elif PYQT5:
from PyQt5.sip import *
else:
raise PythonQtError(
'Currently selected Qt binding does not support this module')
18 changes: 15 additions & 3 deletions qtpy/tests/test_qtcore.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest
from qtpy import PYQT5, PYQT6, PYSIDE2, QtCore

from qtpy import PYQT5, PYQT6, PYSIDE2, PYQT_VERSION, QtCore
dalthviz marked this conversation as resolved.
Show resolved Hide resolved

"""Test QtCore."""

Expand All @@ -9,8 +10,6 @@ def test_qtmsghandler():
assert QtCore.qInstallMessageHandler is not None


@pytest.mark.skipif(not (PYQT5 or PYSIDE2),
reason="Targeted to PyQt5 or PySide2")
def test_DateTime_toPython():
"""Test QDateTime.toPython"""
assert QtCore.QDateTime.toPython is not None
Expand All @@ -25,3 +24,16 @@ class ClassWithSignal(QtCore.QObject):
instance = ClassWithSignal()

assert isinstance(instance.signal, QtCore.SignalInstance)


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""Test scoped and unscoped enum access for qtpy.QtCore.*."""
assert QtCore.QAbstractAnimation.Stopped == QtCore.QAbstractAnimation.State.Stopped
assert QtCore.QEvent.ActionAdded == QtCore.QEvent.Type.ActionAdded
assert QtCore.Qt.AlignLeft == QtCore.Qt.AlignmentFlag.AlignLeft
assert QtCore.Qt.Key_Return == QtCore.Qt.Key.Key_Return
assert QtCore.Qt.transparent == QtCore.Qt.GlobalColor.transparent
assert QtCore.Qt.Widget == QtCore.Qt.WindowType.Widget
30 changes: 30 additions & 0 deletions qtpy/tests/test_qtgui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Test QtGui."""
import pytest

from qtpy import PYQT5, PYQT_VERSION, QtGui


def test_qdrag_functions():
"""Test functions mapping for QtGui.QDrag."""
assert QtGui.QDrag.exec_


def test_qguiapplication_functions():
"""Test functions mapping for QtGui.QGuiApplication."""
assert QtGui.QGuiApplication.exec_


def test_qtextdocument_functions():
"""Test functions mapping for QtGui.QTextDocument."""
assert QtGui.QTextDocument.print_


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""Test scoped and unscoped enum access for qtpy.QtWidgets.*."""
assert QtGui.QColor.Rgb == QtGui.QColor.Spec.Rgb
assert QtGui.QFont.AllUppercase == QtGui.QFont.Capitalization.AllUppercase
assert QtGui.QIcon.Normal == QtGui.QIcon.Mode.Normal
assert QtGui.QImage.Format_Invalid == QtGui.QImage.Format.Format_Invalid
43 changes: 43 additions & 0 deletions qtpy/tests/test_qtwidgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Test QtWidgets."""
import pytest

from qtpy import PYQT5, PYQT_VERSION, QtWidgets


def test_qtextedit_functions():
"""Test functions mapping for QtWidgets.QTextEdit."""
assert QtWidgets.QTextEdit.setTabStopWidth
assert QtWidgets.QTextEdit.tabStopWidth
assert QtWidgets.QTextEdit.print_


def test_qplaintextedit_functions():
"""Test functions mapping for QtWidgets.QPlainTextEdit."""
assert QtWidgets.QPlainTextEdit.setTabStopWidth
assert QtWidgets.QPlainTextEdit.tabStopWidth
assert QtWidgets.QPlainTextEdit.print_


def test_qapplication_functions():
"""Test functions mapping for QtWidgets.QApplication."""
assert QtWidgets.QApplication.exec_


def test_qdialog_functions():
"""Test functions mapping for QtWidgets.QDialog."""
assert QtWidgets.QDialog.exec_


def test_qmenu_functions():
"""Test functions mapping for QtWidgets.QDialog."""
assert QtWidgets.QMenu.exec_


@pytest.mark.skipif(PYQT5 and PYQT_VERSION.startswith('5.9'),
reason="A specific setup with at least sip 4.9.9 is needed for PyQt5 5.9.*"
"to work with scoped enum access")
def test_enum_access():
"""Test scoped and unscoped enum access for qtpy.QtWidgets.*."""
assert QtWidgets.QFileDialog.AcceptOpen == QtWidgets.QFileDialog.AcceptMode.AcceptOpen
assert QtWidgets.QMessageBox.InvalidRole == QtWidgets.QMessageBox.ButtonRole.InvalidRole
assert QtWidgets.QStyle.State_None == QtWidgets.QStyle.StateFlag.State_None