Skip to content
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
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
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
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

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

"""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