diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ad152f7f..ea6046a3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: numpy-version: "~=1.19.0" - python-version: "3.9" qt-lib: "pyqt" - qt-version: "PyQt5~=5.15" + qt-version: "PyQt6" numpy-version: "~=1.19.0" - python-version: "3.9" qt-lib: "pyside" diff --git a/README.md b/README.md index 7a8e745a..d92402d2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ PyQtGraph [![Total alerts](https://img.shields.io/lgtm/alerts/g/pyqtgraph/pyqtgraph.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pyqtgraph/pyqtgraph/alerts/) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/pyqtgraph/pyqtgraph.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/pyqtgraph/pyqtgraph/context:python) -A pure-Python graphics library for PyQt5/PySide2/PySide6 +A pure-Python graphics library for PyQt5/PyQt6/PySide2/PySide6 Copyright 2020 Luke Campagnola, University of North Carolina at Chapel Hill @@ -36,7 +36,7 @@ Currently this means: * Python 3.7+ * Qt 5.12-6.0 * Required - * PyQt5, PySide2 or PySide6 + * PyQt5, PyQt6, PySide2 or PySide6 * `numpy` 1.17+ * Optional * `scipy` for image processing @@ -57,8 +57,9 @@ The following table represents the python environments we test in our CI system. | PySide2-5.12 | :white_check_mark: | :x: | :x: | | PyQt5-5.12 | :white_check_mark: | :x: | :x: | | PySide2-5.15 | :x: | :white_check_mark: | :x: | -| PyQt5-5.15 | :x: | :white_check_mark: | :white_check_mark: | +| PyQt5-5.15 | :x: | :white_check_mark: | :x: | | PySide6-6.0 | :x: | :x: | :white_check_mark: | +| PyQt6-6.0 | :x: | :x: | :white_check_mark: | Support ------- diff --git a/examples/ScatterPlotSpeedTestTemplate_pyqt6.py b/examples/ScatterPlotSpeedTestTemplate_pyqt6.py new file mode 100644 index 00000000..d963d8fc --- /dev/null +++ b/examples/ScatterPlotSpeedTestTemplate_pyqt6.py @@ -0,0 +1,44 @@ +# Form implementation generated from reading ui file 'examples\ScatterPlotSpeedTestTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setObjectName("gridLayout") + self.sizeSpin = QtWidgets.QSpinBox(Form) + self.sizeSpin.setProperty("value", 10) + self.sizeSpin.setObjectName("sizeSpin") + self.gridLayout.addWidget(self.sizeSpin, 1, 1, 1, 1) + self.pixelModeCheck = QtWidgets.QCheckBox(Form) + self.pixelModeCheck.setObjectName("pixelModeCheck") + self.gridLayout.addWidget(self.pixelModeCheck, 1, 3, 1, 1) + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + self.plot = PlotWidget(Form) + self.plot.setObjectName("plot") + self.gridLayout.addWidget(self.plot, 0, 0, 1, 4) + self.randCheck = QtWidgets.QCheckBox(Form) + self.randCheck.setObjectName("randCheck") + self.gridLayout.addWidget(self.randCheck, 1, 2, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.pixelModeCheck.setText(_translate("Form", "pixel mode")) + self.label.setText(_translate("Form", "Size")) + self.randCheck.setText(_translate("Form", "Randomize")) +from pyqtgraph import PlotWidget diff --git a/examples/VideoTemplate_pyqt6.py b/examples/VideoTemplate_pyqt6.py new file mode 100644 index 00000000..f69ebe0f --- /dev/null +++ b/examples/VideoTemplate_pyqt6.py @@ -0,0 +1,203 @@ +# Form implementation generated from reading ui file 'VideoTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + MainWindow.setObjectName("MainWindow") + MainWindow.resize(695, 798) + self.centralwidget = QtWidgets.QWidget(MainWindow) + self.centralwidget.setObjectName("centralwidget") + self.gridLayout_2 = QtWidgets.QGridLayout(self.centralwidget) + self.gridLayout_2.setObjectName("gridLayout_2") + self.cudaCheck = QtWidgets.QCheckBox(self.centralwidget) + self.cudaCheck.setObjectName("cudaCheck") + self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) + self.downsampleCheck = QtWidgets.QCheckBox(self.centralwidget) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_2.addWidget(self.downsampleCheck, 8, 0, 1, 2) + self.scaleCheck = QtWidgets.QCheckBox(self.centralwidget) + self.scaleCheck.setObjectName("scaleCheck") + self.gridLayout_2.addWidget(self.scaleCheck, 4, 0, 1, 1) + self.gridLayout = QtWidgets.QGridLayout() + self.gridLayout.setObjectName("gridLayout") + self.rawRadio = QtWidgets.QRadioButton(self.centralwidget) + self.rawRadio.setObjectName("rawRadio") + self.gridLayout.addWidget(self.rawRadio, 3, 0, 1, 1) + self.gfxRadio = QtWidgets.QRadioButton(self.centralwidget) + self.gfxRadio.setChecked(True) + self.gfxRadio.setObjectName("gfxRadio") + self.gridLayout.addWidget(self.gfxRadio, 2, 0, 1, 1) + self.stack = QtWidgets.QStackedWidget(self.centralwidget) + self.stack.setObjectName("stack") + self.page = QtWidgets.QWidget() + self.page.setObjectName("page") + self.gridLayout_3 = QtWidgets.QGridLayout(self.page) + self.gridLayout_3.setObjectName("gridLayout_3") + self.graphicsView = GraphicsView(self.page) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout_3.addWidget(self.graphicsView, 0, 0, 1, 1) + self.stack.addWidget(self.page) + self.page_2 = QtWidgets.QWidget() + self.page_2.setObjectName("page_2") + self.gridLayout_4 = QtWidgets.QGridLayout(self.page_2) + self.gridLayout_4.setObjectName("gridLayout_4") + self.rawImg = RawImageWidget(self.page_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.rawImg.sizePolicy().hasHeightForWidth()) + self.rawImg.setSizePolicy(sizePolicy) + self.rawImg.setObjectName("rawImg") + self.gridLayout_4.addWidget(self.rawImg, 0, 0, 1, 1) + self.stack.addWidget(self.page_2) + self.gridLayout.addWidget(self.stack, 0, 0, 1, 1) + self.rawGLRadio = QtWidgets.QRadioButton(self.centralwidget) + self.rawGLRadio.setObjectName("rawGLRadio") + self.gridLayout.addWidget(self.rawGLRadio, 4, 0, 1, 1) + self.gridLayout_2.addLayout(self.gridLayout, 1, 0, 1, 4) + self.dtypeCombo = QtWidgets.QComboBox(self.centralwidget) + self.dtypeCombo.setObjectName("dtypeCombo") + self.dtypeCombo.addItem("") + self.dtypeCombo.addItem("") + self.dtypeCombo.addItem("") + self.gridLayout_2.addWidget(self.dtypeCombo, 3, 2, 1, 1) + self.label = QtWidgets.QLabel(self.centralwidget) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 3, 0, 1, 1) + self.rgbLevelsCheck = QtWidgets.QCheckBox(self.centralwidget) + self.rgbLevelsCheck.setObjectName("rgbLevelsCheck") + self.gridLayout_2.addWidget(self.rgbLevelsCheck, 4, 1, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.minSpin2 = SpinBox(self.centralwidget) + self.minSpin2.setEnabled(False) + self.minSpin2.setObjectName("minSpin2") + self.horizontalLayout_2.addWidget(self.minSpin2) + self.label_3 = QtWidgets.QLabel(self.centralwidget) + self.label_3.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.label_3.setObjectName("label_3") + self.horizontalLayout_2.addWidget(self.label_3) + self.maxSpin2 = SpinBox(self.centralwidget) + self.maxSpin2.setEnabled(False) + self.maxSpin2.setObjectName("maxSpin2") + self.horizontalLayout_2.addWidget(self.maxSpin2) + self.gridLayout_2.addLayout(self.horizontalLayout_2, 5, 2, 1, 1) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.minSpin1 = SpinBox(self.centralwidget) + self.minSpin1.setObjectName("minSpin1") + self.horizontalLayout.addWidget(self.minSpin1) + self.label_2 = QtWidgets.QLabel(self.centralwidget) + self.label_2.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.label_2.setObjectName("label_2") + self.horizontalLayout.addWidget(self.label_2) + self.maxSpin1 = SpinBox(self.centralwidget) + self.maxSpin1.setObjectName("maxSpin1") + self.horizontalLayout.addWidget(self.maxSpin1) + self.gridLayout_2.addLayout(self.horizontalLayout, 4, 2, 1, 1) + self.horizontalLayout_3 = QtWidgets.QHBoxLayout() + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.minSpin3 = SpinBox(self.centralwidget) + self.minSpin3.setEnabled(False) + self.minSpin3.setObjectName("minSpin3") + self.horizontalLayout_3.addWidget(self.minSpin3) + self.label_4 = QtWidgets.QLabel(self.centralwidget) + self.label_4.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.label_4.setObjectName("label_4") + self.horizontalLayout_3.addWidget(self.label_4) + self.maxSpin3 = SpinBox(self.centralwidget) + self.maxSpin3.setEnabled(False) + self.maxSpin3.setObjectName("maxSpin3") + self.horizontalLayout_3.addWidget(self.maxSpin3) + self.gridLayout_2.addLayout(self.horizontalLayout_3, 6, 2, 1, 1) + self.lutCheck = QtWidgets.QCheckBox(self.centralwidget) + self.lutCheck.setObjectName("lutCheck") + self.gridLayout_2.addWidget(self.lutCheck, 7, 0, 1, 1) + self.alphaCheck = QtWidgets.QCheckBox(self.centralwidget) + self.alphaCheck.setObjectName("alphaCheck") + self.gridLayout_2.addWidget(self.alphaCheck, 7, 1, 1, 1) + self.gradient = GradientWidget(self.centralwidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.gradient.sizePolicy().hasHeightForWidth()) + self.gradient.setSizePolicy(sizePolicy) + self.gradient.setObjectName("gradient") + self.gridLayout_2.addWidget(self.gradient, 7, 2, 1, 2) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_2.addItem(spacerItem, 3, 3, 1, 1) + self.fpsLabel = QtWidgets.QLabel(self.centralwidget) + font = QtGui.QFont() + font.setPointSize(12) + self.fpsLabel.setFont(font) + self.fpsLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.fpsLabel.setObjectName("fpsLabel") + self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4) + self.rgbCheck = QtWidgets.QCheckBox(self.centralwidget) + self.rgbCheck.setObjectName("rgbCheck") + self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.centralwidget) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 2, 0, 1, 1) + self.horizontalLayout_4 = QtWidgets.QHBoxLayout() + self.horizontalLayout_4.setObjectName("horizontalLayout_4") + self.framesSpin = QtWidgets.QSpinBox(self.centralwidget) + self.framesSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) + self.framesSpin.setProperty("value", 10) + self.framesSpin.setObjectName("framesSpin") + self.horizontalLayout_4.addWidget(self.framesSpin) + self.widthSpin = QtWidgets.QSpinBox(self.centralwidget) + self.widthSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.PlusMinus) + self.widthSpin.setMaximum(10000) + self.widthSpin.setProperty("value", 512) + self.widthSpin.setObjectName("widthSpin") + self.horizontalLayout_4.addWidget(self.widthSpin) + self.heightSpin = QtWidgets.QSpinBox(self.centralwidget) + self.heightSpin.setButtonSymbols(QtWidgets.QAbstractSpinBox.ButtonSymbols.NoButtons) + self.heightSpin.setMaximum(10000) + self.heightSpin.setProperty("value", 512) + self.heightSpin.setObjectName("heightSpin") + self.horizontalLayout_4.addWidget(self.heightSpin) + self.gridLayout_2.addLayout(self.horizontalLayout_4, 2, 1, 1, 2) + self.sizeLabel = QtWidgets.QLabel(self.centralwidget) + self.sizeLabel.setText("") + self.sizeLabel.setObjectName("sizeLabel") + self.gridLayout_2.addWidget(self.sizeLabel, 2, 3, 1, 1) + MainWindow.setCentralWidget(self.centralwidget) + + self.retranslateUi(MainWindow) + self.stack.setCurrentIndex(1) + QtCore.QMetaObject.connectSlotsByName(MainWindow) + + def retranslateUi(self, MainWindow): + _translate = QtCore.QCoreApplication.translate + MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) + self.cudaCheck.setText(_translate("MainWindow", "Use CUDA (GPU) if available")) + self.downsampleCheck.setText(_translate("MainWindow", "Auto downsample")) + self.scaleCheck.setText(_translate("MainWindow", "Scale Data")) + self.rawRadio.setText(_translate("MainWindow", "RawImageWidget")) + self.gfxRadio.setText(_translate("MainWindow", "GraphicsView + ImageItem")) + self.rawGLRadio.setText(_translate("MainWindow", "RawGLImageWidget")) + self.dtypeCombo.setItemText(0, _translate("MainWindow", "uint8")) + self.dtypeCombo.setItemText(1, _translate("MainWindow", "uint16")) + self.dtypeCombo.setItemText(2, _translate("MainWindow", "float")) + self.label.setText(_translate("MainWindow", "Data type")) + self.rgbLevelsCheck.setText(_translate("MainWindow", "RGB")) + self.label_3.setText(_translate("MainWindow", "<--->")) + self.label_2.setText(_translate("MainWindow", "<--->")) + self.label_4.setText(_translate("MainWindow", "<--->")) + self.lutCheck.setText(_translate("MainWindow", "Use Lookup Table")) + self.alphaCheck.setText(_translate("MainWindow", "alpha")) + self.fpsLabel.setText(_translate("MainWindow", "FPS")) + self.rgbCheck.setText(_translate("MainWindow", "RGB")) + self.label_5.setText(_translate("MainWindow", "Image size")) +from pyqtgraph import GradientWidget, GraphicsView, SpinBox +from pyqtgraph.widgets.RawImageWidget import RawImageWidget diff --git a/examples/VideoTemplate_pyside6.py b/examples/VideoTemplate_pyside6.py index 5195583d..a9d386c8 100644 --- a/examples/VideoTemplate_pyside6.py +++ b/examples/VideoTemplate_pyside6.py @@ -27,6 +27,11 @@ class Ui_MainWindow(object): self.centralwidget.setObjectName(u"centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) self.gridLayout_2.setObjectName(u"gridLayout_2") + self.cudaCheck = QCheckBox(self.centralwidget) + self.cudaCheck.setObjectName(u"cudaCheck") + + self.gridLayout_2.addWidget(self.cudaCheck, 9, 0, 1, 2) + self.downsampleCheck = QCheckBox(self.centralwidget) self.downsampleCheck.setObjectName(u"downsampleCheck") @@ -258,6 +263,7 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) + self.cudaCheck.setText(QCoreApplication.translate("MainWindow", u"Use CUDA (GPU) if available", None)) self.downsampleCheck.setText(QCoreApplication.translate("MainWindow", u"Auto downsample", None)) self.scaleCheck.setText(QCoreApplication.translate("MainWindow", u"Scale Data", None)) self.rawRadio.setText(QCoreApplication.translate("MainWindow", u"RawImageWidget", None)) diff --git a/examples/designerExample_pyqt6.py b/examples/designerExample_pyqt6.py new file mode 100644 index 00000000..4145e6a9 --- /dev/null +++ b/examples/designerExample_pyqt6.py @@ -0,0 +1,32 @@ +# Form implementation generated from reading ui file 'examples\designerExample.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(400, 300) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setObjectName("gridLayout") + self.plotBtn = QtWidgets.QPushButton(Form) + self.plotBtn.setObjectName("plotBtn") + self.gridLayout.addWidget(self.plotBtn, 0, 0, 1, 1) + self.plot = PlotWidget(Form) + self.plot.setObjectName("plot") + self.gridLayout.addWidget(self.plot, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.plotBtn.setText(_translate("Form", "Plot!")) +from pyqtgraph import PlotWidget diff --git a/examples/exampleLoaderTemplate_pyqt6.py b/examples/exampleLoaderTemplate_pyqt6.py new file mode 100644 index 00000000..dbfa5e08 --- /dev/null +++ b/examples/exampleLoaderTemplate_pyqt6.py @@ -0,0 +1,93 @@ +# Form implementation generated from reading ui file 'examples\exampleLoaderTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(846, 552) + self.gridLayout_2 = QtWidgets.QGridLayout(Form) + self.gridLayout_2.setObjectName("gridLayout_2") + self.splitter = QtWidgets.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Orientations.Horizontal) + self.splitter.setObjectName("splitter") + self.widget = QtWidgets.QWidget(self.splitter) + self.widget.setObjectName("widget") + self.gridLayout = QtWidgets.QGridLayout(self.widget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.exampleTree = QtWidgets.QTreeWidget(self.widget) + self.exampleTree.setObjectName("exampleTree") + self.exampleTree.headerItem().setText(0, "1") + self.exampleTree.header().setVisible(False) + self.gridLayout.addWidget(self.exampleTree, 0, 0, 1, 2) + self.graphicsSystemCombo = QtWidgets.QComboBox(self.widget) + self.graphicsSystemCombo.setObjectName("graphicsSystemCombo") + self.graphicsSystemCombo.addItem("") + self.graphicsSystemCombo.addItem("") + self.graphicsSystemCombo.addItem("") + self.graphicsSystemCombo.addItem("") + self.gridLayout.addWidget(self.graphicsSystemCombo, 2, 1, 1, 1) + self.qtLibCombo = QtWidgets.QComboBox(self.widget) + self.qtLibCombo.setObjectName("qtLibCombo") + self.qtLibCombo.addItem("") + self.qtLibCombo.addItem("") + self.qtLibCombo.addItem("") + self.qtLibCombo.addItem("") + self.qtLibCombo.addItem("") + self.gridLayout.addWidget(self.qtLibCombo, 1, 1, 1, 1) + self.label_2 = QtWidgets.QLabel(self.widget) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1) + self.label = QtWidgets.QLabel(self.widget) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 1, 0, 1, 1) + self.loadBtn = QtWidgets.QPushButton(self.widget) + self.loadBtn.setObjectName("loadBtn") + self.gridLayout.addWidget(self.loadBtn, 3, 1, 1, 1) + self.widget1 = QtWidgets.QWidget(self.splitter) + self.widget1.setObjectName("widget1") + self.verticalLayout = QtWidgets.QVBoxLayout(self.widget1) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.loadedFileLabel = QtWidgets.QLabel(self.widget1) + font = QtGui.QFont() + font.setBold(True) + self.loadedFileLabel.setFont(font) + self.loadedFileLabel.setText("") + self.loadedFileLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.loadedFileLabel.setObjectName("loadedFileLabel") + self.verticalLayout.addWidget(self.loadedFileLabel) + self.codeView = QtWidgets.QPlainTextEdit(self.widget1) + font = QtGui.QFont() + font.setFamily("Courier New") + self.codeView.setFont(font) + self.codeView.setObjectName("codeView") + self.verticalLayout.addWidget(self.codeView) + self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.graphicsSystemCombo.setItemText(0, _translate("Form", "default")) + self.graphicsSystemCombo.setItemText(1, _translate("Form", "native")) + self.graphicsSystemCombo.setItemText(2, _translate("Form", "raster")) + self.graphicsSystemCombo.setItemText(3, _translate("Form", "opengl")) + self.qtLibCombo.setItemText(0, _translate("Form", "default")) + self.qtLibCombo.setItemText(1, _translate("Form", "PyQt4")) + self.qtLibCombo.setItemText(2, _translate("Form", "PySide")) + self.qtLibCombo.setItemText(3, _translate("Form", "PyQt5")) + self.qtLibCombo.setItemText(4, _translate("Form", "PySide2")) + self.label_2.setText(_translate("Form", "Graphics System:")) + self.label.setText(_translate("Form", "Qt Library:")) + self.loadBtn.setText(_translate("Form", "Run Example")) diff --git a/examples/test_examples.py b/examples/test_examples.py index b5baf76f..f3ffcebd 100644 --- a/examples/test_examples.py +++ b/examples/test_examples.py @@ -35,10 +35,10 @@ def buildFileList(examples, files=None): path = os.path.abspath(os.path.dirname(__file__)) files = sorted(set(buildFileList(examples))) frontends = { - Qt.PYQT4: False, Qt.PYQT5: False, - Qt.PYSIDE: False, - Qt.PYSIDE2: False + Qt.PYQT6: False, + Qt.PYSIDE2: False, + Qt.PYSIDE6: False, } # sort out which of the front ends are available for frontend in frontends.keys(): @@ -66,13 +66,6 @@ conditionalExamples = { False, reason="Test is being problematic on CI machines" ), - "optics_demos.py": exceptionCondition( - not frontends[Qt.PYSIDE], - reason=( - "Test fails due to PySide bug: ", - "https://bugreports.qt.io/browse/PYSIDE-671" - ) - ), 'GLVolumeItem.py': exceptionCondition( not(platform.system() == "Darwin" and tuple(map(int, platform.mac_ver()[0].split("."))) >= (10, 16) and diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py index 6569d57b..71631613 100644 --- a/pyqtgraph/GraphicsScene/GraphicsScene.py +++ b/pyqtgraph/GraphicsScene/GraphicsScene.py @@ -3,7 +3,7 @@ import time import weakref import warnings -from ..Qt import QtCore, QtGui, isQObjectAlive +from ..Qt import QtCore, QtGui, QT_LIB, isQObjectAlive from ..Point import Point from .. import functions as fn from .. import ptime as ptime @@ -14,15 +14,9 @@ from .. import getConfigOption getMillis = lambda: int(round(time.time() * 1000)) -if hasattr(QtCore, 'PYQT_VERSION'): - try: - try: - from PyQt5 import sip - except ImportError: - import sip - HAVE_SIP = True - except ImportError: - HAVE_SIP = False +if QT_LIB.startswith('PyQt'): + from ..Qt import sip + HAVE_SIP = True else: HAVE_SIP = False @@ -158,7 +152,7 @@ class GraphicsScene(QtGui.QGraphicsScene): self._moveDistance = d def mousePressEvent(self, ev): - QtGui.QGraphicsScene.mousePressEvent(self, ev) + super().mousePressEvent(ev) if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events if self.lastHoverEvent is not None: # If the mouse has moved since the last hover event, send a new one. @@ -171,7 +165,7 @@ class GraphicsScene(QtGui.QGraphicsScene): ## set focus on the topmost focusable item under this click items = self.items(ev.scenePos()) for i in items: - if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0: + if i.isEnabled() and i.isVisible() and (i.flags() & i.ItemIsFocusable): i.setFocus(QtCore.Qt.MouseFocusReason) break @@ -197,35 +191,35 @@ class GraphicsScene(QtGui.QGraphicsScene): self.sigMouseMoved.emit(ev.scenePos()) # First allow QGraphicsScene to eliver hoverEvent/Move/Exit Events - QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + super().mouseMoveEvent(ev) # Next Deliver our own Hover Events self.sendHoverEvents(ev) - if int(ev.buttons()) != 0: + if ev.buttons(): # button is pressed' send mouseMoveEvents and mouseDragEvents - QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + super().mouseMoveEvent(ev) if self.mouseGrabberItem() is None: now = ptime.time() init = False ## keep track of which buttons are involved in dragging for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MiddleButton, QtCore.Qt.RightButton]: - if int(ev.buttons() & btn) == 0: + if not (ev.buttons() & btn): continue - if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet - cev = [e for e in self.clickEvents if int(e.button()) == int(btn)] + if btn not in self.dragButtons: ## see if we've dragged far enough yet + cev = [e for e in self.clickEvents if e.button() == btn] if cev: cev = cev[0] dist = Point(ev.scenePos() - cev.scenePos()).length() if dist == 0 or (dist < self._moveDistance and now - cev.time() < self.minDragTime): continue init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True - self.dragButtons.append(int(btn)) + self.dragButtons.append(btn) ## if we have dragged buttons, deliver a drag event if len(self.dragButtons) > 0: if self.sendDragEvent(ev, init=init): ev.accept() else: - QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + super().mouseMoveEvent(ev) # if you do not accept event (which is ignored) then cursor will disappear ev.accept() @@ -241,24 +235,24 @@ class GraphicsScene(QtGui.QGraphicsScene): ev.accept() self.dragButtons.remove(ev.button()) else: - cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())] + cev = [e for e in self.clickEvents if e.button() == ev.button()] if cev: if self.sendClickEvent(cev[0]): #print "sent click event" ev.accept() self.clickEvents.remove(cev[0]) - if int(ev.buttons()) == 0: + if not ev.buttons(): self.dragItem = None self.dragButtons = [] self.clickEvents = [] self.lastDrag = None - QtGui.QGraphicsScene.mouseReleaseEvent(self, ev) + super().mouseReleaseEvent(ev) self.sendHoverEvents(ev) ## let items prepare for next click/drag def mouseDoubleClickEvent(self, ev): - QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev) + super().mouseDoubleClickEvent(ev) if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events self.clickEvents.append(MouseClickEvent(ev, double=True)) @@ -270,7 +264,7 @@ class GraphicsScene(QtGui.QGraphicsScene): items = [] event = HoverEvent(None, acceptable) else: - acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event. + acceptable = not ev.buttons() ## if we are in mid-drag, do not allow items to accept the hover event. event = HoverEvent(ev, acceptable) items = self.itemsNearEvent(event, hoverable=True) self.sigMouseHover.emit(items) @@ -312,7 +306,7 @@ class GraphicsScene(QtGui.QGraphicsScene): # item to continue receiving events until the drag is over # - event is not a mouse event (QEvent.Leave sometimes appears here) if (ev.type() == ev.GraphicsSceneMousePress or - (ev.type() == ev.GraphicsSceneMouseMove and int(ev.buttons()) == 0)): + (ev.type() == ev.GraphicsSceneMouseMove and not ev.buttons())): self.lastHoverEvent = event ## save this so we can ask about accepted events later. def sendDragEvent(self, ev, init=False, final=False): @@ -350,7 +344,7 @@ class GraphicsScene(QtGui.QGraphicsScene): if event.isAccepted(): #print " --> accepted" self.dragItem = item - if int(item.flags() & item.ItemIsFocusable) > 0: + if item.flags() & item.ItemIsFocusable: item.setFocus(QtCore.Qt.MouseFocusReason) break elif self.dragItem is not None: @@ -395,7 +389,7 @@ class GraphicsScene(QtGui.QGraphicsScene): debug.printExc("Error sending click event:") if ev.isAccepted(): - if int(item.flags() & item.ItemIsFocusable) > 0: + if item.flags() & item.ItemIsFocusable: item.setFocus(QtCore.Qt.MouseFocusReason) break self.sigMouseClicked.emit(ev) diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py index 56bd90c0..60cb6cd1 100644 --- a/pyqtgraph/GraphicsScene/exportDialog.py +++ b/pyqtgraph/GraphicsScene/exportDialog.py @@ -140,4 +140,4 @@ class ExportDialog(QtGui.QWidget): def closeEvent(self, event): self.close() - QtGui.QWidget.closeEvent(self, event) + super().closeEvent(event) diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py new file mode 100644 index 00000000..55cbe3a8 --- /dev/null +++ b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt6.py @@ -0,0 +1,63 @@ +# Form implementation generated from reading ui file 'pyqtgraph\GraphicsScene\exportDialogTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(241, 367) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtWidgets.QTreeWidget(Form) + self.itemTree.setObjectName("itemTree") + self.itemTree.headerItem().setText(0, "1") + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtWidgets.QLabel(Form) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtWidgets.QListWidget(Form) + self.formatList.setObjectName("formatList") + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtWidgets.QPushButton(Form) + self.exportBtn.setObjectName("exportBtn") + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtWidgets.QPushButton(Form) + self.closeBtn.setObjectName("closeBtn") + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setObjectName("paramTree") + self.paramTree.headerItem().setText(0, "1") + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtWidgets.QLabel(Form) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtWidgets.QPushButton(Form) + self.copyBtn.setObjectName("copyBtn") + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Export")) + self.label.setText(_translate("Form", "Item to export:")) + self.label_2.setText(_translate("Form", "Export format")) + self.exportBtn.setText(_translate("Form", "Export")) + self.closeBtn.setText(_translate("Form", "Close")) + self.label_3.setText(_translate("Form", "Export options")) + self.copyBtn.setText(_translate("Form", "Copy")) +from ..parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/pyqtgraph/GraphicsScene/mouseEvents.py index 36b53919..b8c210af 100644 --- a/pyqtgraph/GraphicsScene/mouseEvents.py +++ b/pyqtgraph/GraphicsScene/mouseEvents.py @@ -19,8 +19,8 @@ class MouseDragEvent(object): self._buttonDownScenePos = {} self._buttonDownScreenPos = {} for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MiddleButton, QtCore.Qt.RightButton]: - self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn) - self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn) + self._buttonDownScenePos[btn] = moveEvent.buttonDownScenePos(btn) + self._buttonDownScreenPos[btn] = moveEvent.buttonDownScreenPos(btn) self._scenePos = moveEvent.scenePos() self._screenPos = moveEvent.screenPos() if lastEvent is None: @@ -61,7 +61,7 @@ class MouseDragEvent(object): """ if btn is None: btn = self.button() - return Point(self._buttonDownScenePos[int(btn)]) + return Point(self._buttonDownScenePos[btn]) def buttonDownScreenPos(self, btn=None): """ @@ -70,7 +70,7 @@ class MouseDragEvent(object): """ if btn is None: btn = self.button() - return Point(self._buttonDownScreenPos[int(btn)]) + return Point(self._buttonDownScreenPos[btn]) def lastScenePos(self): """ @@ -119,7 +119,7 @@ class MouseDragEvent(object): """ if btn is None: btn = self.button() - return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)])) + return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[btn])) def isStart(self): """Returns True if this event is the first since a drag was initiated.""" @@ -137,7 +137,7 @@ class MouseDragEvent(object): else: lp = self.lastPos() p = self.pos() - return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) + return "(%g,%g) buttons=%s start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isStart()), str(self.isFinish())) def modifiers(self): """Return any keyboard modifiers currently pressed. @@ -230,9 +230,9 @@ class MouseClickEvent(object): p = self._scenePos else: p = self.pos() - return "" % (p.x(), p.y(), int(self.button())) + return "" % (p.x(), p.y(), str(self.button())) except: - return "" % (int(self.button())) + return "" % (str(self.button())) def time(self): return self._time @@ -362,7 +362,7 @@ class HoverEvent(object): else: lp = self.lastPos() p = self.pos() - return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) + return "(%g,%g) buttons=%s enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), str(self.buttons()), str(self.isEnter()), str(self.isExit())) def modifiers(self): """Return any keyboard modifiers currently pressed. diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py index 28672cbc..5323c46c 100644 --- a/pyqtgraph/Qt.py +++ b/pyqtgraph/Qt.py @@ -11,6 +11,8 @@ This module exists to smooth out some of the differences between PySide and PyQt """ import os, sys, re, time, subprocess, warnings +import importlib +import enum from .python2_3 import asUnicode @@ -28,7 +30,7 @@ QT_LIB = os.getenv('PYQTGRAPH_QT_LIB') ## This is done by first checking to see whether one of the libraries ## is already imported. If not, then attempt to import PyQt4, then PySide. if QT_LIB is None: - libOrder = [PYQT4, PYSIDE, PYQT5, PYSIDE2, PYSIDE6] + libOrder = [PYQT4, PYSIDE, PYQT5, PYSIDE2, PYSIDE6, PYQT6] for lib in libOrder: if lib in sys.modules: @@ -45,7 +47,7 @@ if QT_LIB is None: pass if QT_LIB is None: - raise Exception("PyQtGraph requires one of PyQt4, PyQt5, PySide, PySide2 or PySide6; none of these packages could be imported.") + raise Exception("PyQtGraph requires one of PyQt4, PyQt5, PyQt6, PySide, PySide2 or PySide6; none of these packages could be imported.") class FailedImport(object): @@ -221,6 +223,25 @@ elif QT_LIB == PYQT5: VERSION_INFO = 'PyQt5 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR +elif QT_LIB == PYQT6: + from PyQt6 import QtGui, QtCore, QtWidgets, uic + + try: + from PyQt6 import QtSvg + except ImportError as err: + QtSvg = FailedImport(err) + try: + from PyQt6 import QtOpenGLWidgets + except ImportError as err: + QtOpenGLWidgets = FailedImport(err) + try: + from PyQt6 import QtTest + QtTest.QTest.qWaitForWindowShown = QtTest.QTest.qWaitForWindowExposed + except ImportError as err: + QtTest = FailedImport(err) + + VERSION_INFO = 'PyQt6 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR + elif QT_LIB == PYSIDE2: from PySide2 import QtGui, QtCore, QtWidgets @@ -277,8 +298,8 @@ else: raise ValueError("Invalid Qt lib '%s'" % QT_LIB) -# common to PyQt5, PySide2 and PySide6 -if QT_LIB in [PYQT5, PYSIDE2, PYSIDE6]: +# common to PyQt5, PyQt6, PySide2 and PySide6 +if QT_LIB in [PYQT5, PYQT6, PYSIDE2, PYSIDE6]: # We're using Qt5 which has a different structure so we're going to use a shim to # recreate the Qt4 structure @@ -353,13 +374,13 @@ if QT_LIB in [PYSIDE, PYSIDE2, PYSIDE6]: QtTest.QTest.qWait = qWait -# Common to PyQt4 and 5 -if QT_LIB in [PYQT4, PYQT5]: +# Common to PyQt4, PyQt5 and PyQt6 +if QT_LIB in [PYQT4, PYQT5, PYQT6]: QtVersion = QtCore.QT_VERSION_STR try: - from PyQt5 import sip - except ImportError: + sip = importlib.import_module(QT_LIB + '.sip') + except ModuleNotFoundError: import sip def isQObjectAlive(obj): return not sip.isdeleted(obj) @@ -369,6 +390,67 @@ if QT_LIB in [PYQT4, PYQT5]: QtCore.Signal = QtCore.pyqtSignal +if QT_LIB == PYSIDE6: + # PySide6 6.0 has a missing binding + if not hasattr(QtGui.QGradient, 'setStops'): + def __setStops(self, stops): + for pos, color in stops: + self.setColorAt(pos, color) + QtGui.QGradient.setStops = __setStops + + +if QT_LIB == PYQT6: + # module.Class.EnumClass.Enum -> module.Class.Enum + def promote_enums(module): + class_names = [x for x in dir(module) if x[0] == 'Q'] + for class_name in class_names: + klass = getattr(module, class_name) + if not isinstance(klass, sip.wrappertype): + continue + attrib_names = [x for x in dir(klass) if x[0].isupper()] + for attrib_name in attrib_names: + attrib = getattr(klass, attrib_name) + if not isinstance(attrib, enum.EnumMeta): + continue + for e in attrib: + setattr(klass, e.name, e) + + promote_enums(QtCore) + promote_enums(QtGui) + promote_enums(QtWidgets) + + # QKeyEvent::key() returns an int + # so comparison with a Key_* enum will always be False + # here we convert the enum to its int value + for e in QtCore.Qt.Key: + setattr(QtCore.Qt, e.name, e.value) + + # shim the old names for QPointF mouse coords + QtGui.QSinglePointEvent.localPos = lambda o : o.position() + QtGui.QSinglePointEvent.windowPos = lambda o : o.scenePosition() + QtGui.QSinglePointEvent.screenPos = lambda o : o.globalPosition() + QtGui.QDropEvent.posF = lambda o : o.position() + + QtWidgets.QApplication.exec_ = QtWidgets.QApplication.exec + QtWidgets.QDialog.exec_ = lambda o : o.exec() + QtGui.QDrag.exec_ = lambda o : o.exec() + + # PyQt6 6.0.0 has a bug where it can't handle certain Type values returned + # by the Qt library. + try: + # 213 is a known failing value + QtCore.QEvent.Type(213) + except ValueError: + def new_method(self, old_method=QtCore.QEvent.type): + try: + typ = old_method(self) + except ValueError: + typ = QtCore.QEvent.Type.None_ + return typ + QtCore.QEvent.type = new_method + del new_method + + # USE_XXX variables are deprecated USE_PYSIDE = QT_LIB == PYSIDE USE_PYQT4 = QT_LIB == PYQT4 diff --git a/pyqtgraph/WidgetGroup.py b/pyqtgraph/WidgetGroup.py index 4656cc62..6b99f86c 100644 --- a/pyqtgraph/WidgetGroup.py +++ b/pyqtgraph/WidgetGroup.py @@ -218,9 +218,6 @@ class WidgetGroup(QtCore.QObject): v1 = self.cache[n] v2 = self.readWidget(w) if v1 != v2: - if QT_LIB != 'PyQt5': - # Old signal kept for backward compatibility. - self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) self.sigChanged.emit(self.widgetList[w], v2) def state(self): diff --git a/pyqtgraph/canvas/Canvas.py b/pyqtgraph/canvas/Canvas.py index da6a8b3b..69e67608 100644 --- a/pyqtgraph/canvas/Canvas.py +++ b/pyqtgraph/canvas/Canvas.py @@ -107,7 +107,7 @@ class Canvas(QtGui.QWidget): def resizeEvent(self, ev=None): if ev is not None: - QtGui.QWidget.resizeEvent(self, ev) + super().resizeEvent(ev) self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) if not self.sizeApplied: diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt6.py b/pyqtgraph/canvas/CanvasTemplate_pyqt6.py new file mode 100644 index 00000000..79557936 --- /dev/null +++ b/pyqtgraph/canvas/CanvasTemplate_pyqt6.py @@ -0,0 +1,92 @@ +# Form implementation generated from reading ui file 'pyqtgraph\canvas\CanvasTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(821, 578) + self.gridLayout_2 = QtWidgets.QGridLayout(Form) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.splitter = QtWidgets.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Orientations.Horizontal) + self.splitter.setObjectName("splitter") + self.view = GraphicsView(self.splitter) + self.view.setObjectName("view") + self.vsplitter = QtWidgets.QSplitter(self.splitter) + self.vsplitter.setOrientation(QtCore.Qt.Orientations.Vertical) + self.vsplitter.setObjectName("vsplitter") + self.canvasCtrlWidget = QtWidgets.QWidget(self.vsplitter) + self.canvasCtrlWidget.setObjectName("canvasCtrlWidget") + self.gridLayout = QtWidgets.QGridLayout(self.canvasCtrlWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.autoRangeBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName("autoRangeBtn") + self.gridLayout.addWidget(self.autoRangeBtn, 0, 0, 1, 2) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.redirectCheck = QtWidgets.QCheckBox(self.canvasCtrlWidget) + self.redirectCheck.setObjectName("redirectCheck") + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.canvasCtrlWidget) + self.redirectCombo.setObjectName("redirectCombo") + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 2) + self.itemList = TreeWidget(self.canvasCtrlWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName("itemList") + self.itemList.headerItem().setText(0, "1") + self.gridLayout.addWidget(self.itemList, 2, 0, 1, 2) + self.resetTransformsBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) + self.resetTransformsBtn.setObjectName("resetTransformsBtn") + self.gridLayout.addWidget(self.resetTransformsBtn, 3, 0, 1, 2) + self.mirrorSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) + self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") + self.gridLayout.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) + self.reflectSelectionBtn = QtWidgets.QPushButton(self.canvasCtrlWidget) + self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") + self.gridLayout.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) + self.canvasItemCtrl = QtWidgets.QWidget(self.vsplitter) + self.canvasItemCtrl.setObjectName("canvasItemCtrl") + self.ctrlLayout = QtWidgets.QGridLayout(self.canvasItemCtrl) + self.ctrlLayout.setContentsMargins(0, 0, 0, 0) + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName("ctrlLayout") + self.gridLayout_2.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.autoRangeBtn.setText(_translate("Form", "Auto Range")) + self.redirectCheck.setToolTip(_translate("Form", "Check to display all local items in a remote canvas.")) + self.redirectCheck.setText(_translate("Form", "Redirect")) + self.resetTransformsBtn.setText(_translate("Form", "Reset Transforms")) + self.mirrorSelectionBtn.setText(_translate("Form", "Mirror Selection")) + self.reflectSelectionBtn.setText(_translate("Form", "MirrorXY")) +from ..widgets.GraphicsView import GraphicsView +from ..widgets.TreeWidget import TreeWidget +from .CanvasManager import CanvasCombo diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py b/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py new file mode 100644 index 00000000..4b0d9880 --- /dev/null +++ b/pyqtgraph/canvas/TransformGuiTemplate_pyqt6.py @@ -0,0 +1,55 @@ +# Form implementation generated from reading ui file 'pyqtgraph\canvas\TransformGuiTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(224, 117) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtWidgets.QVBoxLayout(Form) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setObjectName("verticalLayout") + self.translateLabel = QtWidgets.QLabel(Form) + self.translateLabel.setObjectName("translateLabel") + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtWidgets.QLabel(Form) + self.rotateLabel.setObjectName("rotateLabel") + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtWidgets.QLabel(Form) + self.scaleLabel.setObjectName("scaleLabel") + self.verticalLayout.addWidget(self.scaleLabel) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.mirrorImageBtn = QtWidgets.QPushButton(Form) + self.mirrorImageBtn.setToolTip("") + self.mirrorImageBtn.setObjectName("mirrorImageBtn") + self.horizontalLayout.addWidget(self.mirrorImageBtn) + self.reflectImageBtn = QtWidgets.QPushButton(Form) + self.reflectImageBtn.setObjectName("reflectImageBtn") + self.horizontalLayout.addWidget(self.reflectImageBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.translateLabel.setText(_translate("Form", "Translate:")) + self.rotateLabel.setText(_translate("Form", "Rotate:")) + self.scaleLabel.setText(_translate("Form", "Scale:")) + self.mirrorImageBtn.setText(_translate("Form", "Mirror")) + self.reflectImageBtn.setText(_translate("Form", "Reflect")) diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py index 741cea08..3661bf75 100644 --- a/pyqtgraph/colormap.py +++ b/pyqtgraph/colormap.py @@ -359,13 +359,7 @@ class ColorMap(object): pos, color = self.getStops(mode=self.BYTE) color = [QtGui.QColor(*x) for x in color] - stops = zip(pos, color) - if hasattr(g, 'setStops'): - g.setStops(list(stops)) - else: - # PySide6 has a missing setStops binding - for pos, col in stops: - g.setColorAt(pos, col) + g.setStops(list(zip(pos, color))) return g def getColors(self, mode=None): diff --git a/pyqtgraph/console/CmdInput.py b/pyqtgraph/console/CmdInput.py index 21d25382..1148e289 100644 --- a/pyqtgraph/console/CmdInput.py +++ b/pyqtgraph/console/CmdInput.py @@ -24,7 +24,7 @@ class CmdInput(QtGui.QLineEdit): elif ev.key() == QtCore.Qt.Key_Return: self.execCmd() else: - QtGui.QLineEdit.keyPressEvent(self, ev) + super().keyPressEvent(ev) self.history[0] = asUnicode(self.text()) def execCmd(self): diff --git a/pyqtgraph/console/template_pyqt6.py b/pyqtgraph/console/template_pyqt6.py new file mode 100644 index 00000000..0208f128 --- /dev/null +++ b/pyqtgraph/console/template_pyqt6.py @@ -0,0 +1,115 @@ +# Form implementation generated from reading ui file 'pyqtgraph\console\template.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(739, 497) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.splitter = QtWidgets.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Orientations.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtWidgets.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.verticalLayout = QtWidgets.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.output = QtWidgets.QPlainTextEdit(self.layoutWidget) + font = QtGui.QFont() + font.setFamily("Monospace") + self.output.setFont(font) + self.output.setReadOnly(True) + self.output.setObjectName("output") + self.verticalLayout.addWidget(self.output) + self.horizontalLayout = QtWidgets.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.input = CmdInput(self.layoutWidget) + self.input.setObjectName("input") + self.horizontalLayout.addWidget(self.input) + self.historyBtn = QtWidgets.QPushButton(self.layoutWidget) + self.historyBtn.setCheckable(True) + self.historyBtn.setObjectName("historyBtn") + self.horizontalLayout.addWidget(self.historyBtn) + self.exceptionBtn = QtWidgets.QPushButton(self.layoutWidget) + self.exceptionBtn.setCheckable(True) + self.exceptionBtn.setObjectName("exceptionBtn") + self.horizontalLayout.addWidget(self.exceptionBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + self.historyList = QtWidgets.QListWidget(self.splitter) + font = QtGui.QFont() + font.setFamily("Monospace") + self.historyList.setFont(font) + self.historyList.setObjectName("historyList") + self.exceptionGroup = QtWidgets.QGroupBox(self.splitter) + self.exceptionGroup.setObjectName("exceptionGroup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.exceptionGroup) + self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) + self.gridLayout_2.setHorizontalSpacing(2) + self.gridLayout_2.setVerticalSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.clearExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) + self.clearExceptionBtn.setEnabled(False) + self.clearExceptionBtn.setObjectName("clearExceptionBtn") + self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) + self.catchAllExceptionsBtn = QtWidgets.QPushButton(self.exceptionGroup) + self.catchAllExceptionsBtn.setCheckable(True) + self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") + self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) + self.catchNextExceptionBtn = QtWidgets.QPushButton(self.exceptionGroup) + self.catchNextExceptionBtn.setCheckable(True) + self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") + self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) + self.onlyUncaughtCheck = QtWidgets.QCheckBox(self.exceptionGroup) + self.onlyUncaughtCheck.setChecked(True) + self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") + self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) + self.exceptionStackList = QtWidgets.QListWidget(self.exceptionGroup) + self.exceptionStackList.setAlternatingRowColors(True) + self.exceptionStackList.setObjectName("exceptionStackList") + self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) + self.runSelectedFrameCheck = QtWidgets.QCheckBox(self.exceptionGroup) + self.runSelectedFrameCheck.setChecked(True) + self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") + self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) + self.exceptionInfoLabel = QtWidgets.QLabel(self.exceptionGroup) + self.exceptionInfoLabel.setWordWrap(True) + self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") + self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) + self.label = QtWidgets.QLabel(self.exceptionGroup) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) + self.filterText = QtWidgets.QLineEdit(self.exceptionGroup) + self.filterText.setObjectName("filterText") + self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "Console")) + self.historyBtn.setText(_translate("Form", "History..")) + self.exceptionBtn.setText(_translate("Form", "Exceptions..")) + self.exceptionGroup.setTitle(_translate("Form", "Exception Handling")) + self.clearExceptionBtn.setText(_translate("Form", "Clear Stack")) + self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions")) + self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception")) + self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions")) + self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame")) + self.exceptionInfoLabel.setText(_translate("Form", "Stack Trace")) + self.label.setText(_translate("Form", "Filter (regex):")) +from .CmdInput import CmdInput diff --git a/pyqtgraph/dockarea/Container.py b/pyqtgraph/dockarea/Container.py index bc0b3648..04b775f9 100644 --- a/pyqtgraph/dockarea/Container.py +++ b/pyqtgraph/dockarea/Container.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui +from ..Qt import QtCore, QtGui, QtWidgets import weakref class Container(object): @@ -76,7 +76,11 @@ class Container(object): self.area.topContainer = None self.containerChanged(None) - def childEvent(self, ev): + def childEvent_(self, ev): + # NOTE: this method has been renamed to avoid having the same method name as + # QSplitter.childEvent() + # this causes problems for PyQt6 since SplitContainer inherits from + # Container and QSplitter. ch = ev.child() if ev.removed() and hasattr(ch, 'sigStretchChanged'): #print "Child", ev.child(), "removed, updating", self @@ -133,8 +137,8 @@ class SplitContainer(Container, QtGui.QSplitter): self.setStretchFactor(i, sizes[i]) def childEvent(self, ev): - QtGui.QSplitter.childEvent(self, ev) - Container.childEvent(self, ev) + super().childEvent(ev) # call QSplitter.childEvent() + Container.childEvent_(self, ev) #def restretchChildren(self): #sizes = self.sizes() @@ -204,6 +208,16 @@ class VContainer(SplitContainer): self.setSizes([int(s*scale) for s in sizes]) +class StackedWidget(QtWidgets.QStackedWidget): + def __init__(self, *, container): + super().__init__() + self.container = container + + def childEvent(self, ev): + super().childEvent(ev) + self.container.childEvent_(ev) + + class TContainer(Container, QtGui.QWidget): sigStretchChanged = QtCore.Signal() def __init__(self, area): @@ -221,9 +235,8 @@ class TContainer(Container, QtGui.QWidget): self.hTabLayout.setContentsMargins(0,0,0,0) self.layout.addWidget(self.hTabBox, 0, 1) - self.stack = QtGui.QStackedWidget() + self.stack = StackedWidget(container=self) self.layout.addWidget(self.stack, 1, 1) - self.stack.childEvent = self.stackChildEvent self.setLayout(self.layout) @@ -276,8 +289,4 @@ class TContainer(Container, QtGui.QWidget): y = max(y, wy) self.setStretch(x, y) - def stackChildEvent(self, ev): - QtGui.QStackedWidget.childEvent(self.stack, ev) - Container.childEvent(self, ev) - from . import Dock diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py index f1dd64f3..a4997b8c 100644 --- a/pyqtgraph/dockarea/Dock.py +++ b/pyqtgraph/dockarea/Dock.py @@ -5,6 +5,7 @@ from .DockDrop import * from ..widgets.VerticalLabel import VerticalLabel from ..python2_3 import asUnicode + class Dock(QtGui.QWidget, DockDrop): sigStretchChanged = QtCore.Signal() @@ -328,13 +329,13 @@ class DockLabel(VerticalLabel): self.updateStyle() def mousePressEvent(self, ev): - self.pressPos = ev.pos() + self.pressPos = ev.localPos() self.mouseMoved = False ev.accept() def mouseMoveEvent(self, ev): if not self.mouseMoved: - self.mouseMoved = (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance() + self.mouseMoved = (ev.localPos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance() if self.mouseMoved and ev.buttons() == QtCore.Qt.LeftButton: self.dock.startDrag() diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py index 93841e0a..ee9a5f53 100644 --- a/pyqtgraph/dockarea/DockArea.py +++ b/pyqtgraph/dockarea/DockArea.py @@ -382,4 +382,4 @@ class TempAreaWindow(QtGui.QWidget): dock.orig_area.addDock(dock, ) # clear dock area, and close remaining docks self.dockarea.clear() - QtGui.QWidget.closeEvent(self, *args) + super().closeEvent(*args) diff --git a/pyqtgraph/dockarea/DockDrop.py b/pyqtgraph/dockarea/DockDrop.py index bd364f50..e16f3e7a 100644 --- a/pyqtgraph/dockarea/DockDrop.py +++ b/pyqtgraph/dockarea/DockDrop.py @@ -30,9 +30,9 @@ class DockDrop(object): def dragMoveEvent(self, ev): #print "drag move" - ld = ev.pos().x() + ld = ev.posF().x() rd = self.width() - ld - td = ev.pos().y() + td = ev.posF().y() bd = self.height() - td mn = min(ld, rd, td, bd) diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py index 792e36bd..c89ef050 100644 --- a/pyqtgraph/exporters/Exporter.py +++ b/pyqtgraph/exporters/Exporter.py @@ -128,7 +128,8 @@ class Exporter(object): while len(childs) > 0: ch = childs.pop(0) tree = self.getPaintItems(ch) - if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + if (ch.flags() & ch.ItemStacksBehindParent) or \ + (ch.zValue() < 0 and (ch.flags() & ch.ItemNegativeZStacksBehindParent)): preItems.extend(tree) else: postItems.extend(tree) diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py index a8f07582..a8f0a17b 100644 --- a/pyqtgraph/exporters/SVGExporter.py +++ b/pyqtgraph/exporters/SVGExporter.py @@ -251,7 +251,7 @@ def _generateItemSvg(item, nodes=None, root=None, options={}): childGroup = g1 ## add children directly to this node unless we are clipping if not isinstance(item, QtGui.QGraphicsScene): ## See if this item clips its children - if int(item.flags() & item.ItemClipsChildrenToShape) > 0: + if item.flags() & item.ItemClipsChildrenToShape: ## Generate svg for just the path path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) item.scene().addItem(path) @@ -377,11 +377,11 @@ def correctCoordinates(node, defs, item, options): families = ch.getAttribute('font-family').split(',') if len(families) == 1: font = QtGui.QFont(families[0].strip('" ')) - if font.style() == font.SansSerif: + if font.styleHint() == font.StyleHint.SansSerif: families.append('sans-serif') - elif font.style() == font.Serif: + elif font.styleHint() == font.StyleHint.Serif: families.append('serif') - elif font.style() == font.Courier: + elif font.styleHint() == font.StyleHint.Courier: families.append('monospace') ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) @@ -414,7 +414,7 @@ def itemTransform(item, root): return tr - if int(item.flags() & item.ItemIgnoresTransformations) > 0: + if item.flags() & item.ItemIgnoresTransformations: pos = item.pos() parent = item.parentItem() if parent is not None: @@ -431,7 +431,7 @@ def itemTransform(item, root): if nextRoot is None: nextRoot = root break - if nextRoot is root or int(nextRoot.flags() & nextRoot.ItemIgnoresTransformations) > 0: + if nextRoot is root or (nextRoot.flags() & nextRoot.ItemIgnoresTransformations): break if isinstance(nextRoot, QtGui.QGraphicsScene): diff --git a/pyqtgraph/exporters/tests/test_matplotlib.py b/pyqtgraph/exporters/tests/test_matplotlib.py index fbff04f3..3e3e98dd 100644 --- a/pyqtgraph/exporters/tests/test_matplotlib.py +++ b/pyqtgraph/exporters/tests/test_matplotlib.py @@ -7,7 +7,7 @@ pytest.importorskip("matplotlib") app = pg.mkQApp() skip_qt6 = pytest.mark.skipif( - pg.QT_LIB == "PySide6", + pg.QT_LIB in ["PySide6", "PyQt6"], reason= ( "Matplotlib has no Qt6 support yet, " "see https://github.com/matplotlib/matplotlib/pull/19255" diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py new file mode 100644 index 00000000..5cb9199b --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt6.py @@ -0,0 +1,65 @@ +# Form implementation generated from reading ui file 'pyqtgraph\flowchart\FlowchartCtrlTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(217, 499) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.loadBtn = QtWidgets.QPushButton(Form) + self.loadBtn.setObjectName("loadBtn") + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName("saveBtn") + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName("saveAsBtn") + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName("reloadBtn") + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtWidgets.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName("showChartBtn") + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName("ctrlList") + self.ctrlList.headerItem().setText(0, "1") + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtWidgets.QLabel(Form) + font = QtGui.QFont() + font.setBold(True) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText("") + self.fileNameLabel.setAlignment(QtCore.Qt.Alignment.AlignCenter) + self.fileNameLabel.setObjectName("fileNameLabel") + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.loadBtn.setText(_translate("Form", "Load..")) + self.saveBtn.setText(_translate("Form", "Save")) + self.saveAsBtn.setText(_translate("Form", "As..")) + self.reloadBtn.setText(_translate("Form", "Reload Libs")) + self.showChartBtn.setText(_translate("Form", "Flowchart")) +from ..widgets.FeedbackButton import FeedbackButton +from ..widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py b/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py new file mode 100644 index 00000000..7296a8da --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartTemplate_pyqt6.py @@ -0,0 +1,53 @@ +# Form implementation generated from reading ui file 'pyqtgraph\flowchart\FlowchartTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(529, 329) + self.selInfoWidget = QtWidgets.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName("selInfoWidget") + self.gridLayout = QtWidgets.QGridLayout(self.selInfoWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.selDescLabel = QtWidgets.QLabel(self.selInfoWidget) + self.selDescLabel.setText("") + self.selDescLabel.setAlignment(QtCore.Qt.Alignment.AlignLeading|QtCore.Qt.Alignment.AlignLeft|QtCore.Qt.Alignment.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName("selDescLabel") + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtWidgets.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setBold(True) + self.selNameLabel.setFont(font) + self.selNameLabel.setText("") + self.selNameLabel.setObjectName("selNameLabel") + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName("selectedTree") + self.selectedTree.headerItem().setText(0, "1") + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtWidgets.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName("hoverText") + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName("view") + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) +from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView +from ..widgets.DataTreeWidget import DataTreeWidget diff --git a/pyqtgraph/flowchart/Node.py b/pyqtgraph/flowchart/Node.py index dcda62d7..eebbfac4 100644 --- a/pyqtgraph/flowchart/Node.py +++ b/pyqtgraph/flowchart/Node.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui +from ..Qt import QtCore, QtGui, QtWidgets from ..graphicsItems.GraphicsObject import GraphicsObject from .. import functions as fn from .Terminal import * @@ -436,6 +436,24 @@ class Node(QtCore.QObject): t.disconnectAll() +class TextItem(QtWidgets.QGraphicsTextItem): + def __init__(self, text, parent, on_update): + super().__init__(text, parent) + self.on_update = on_update + + def focusOutEvent(self, ev): + super().focusOutEvent(ev) + if self.on_update is not None: + self.on_update() + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + if self.on_update is not None: + self.on_update() + return + super().keyPressEvent(ev) + + #class NodeGraphicsItem(QtGui.QGraphicsItem): class NodeGraphicsItem(GraphicsObject): def __init__(self, node): @@ -461,16 +479,13 @@ class NodeGraphicsItem(GraphicsObject): self.setFlags(flags) self.bounds = QtCore.QRectF(0, 0, 100, 100) - self.nameItem = QtGui.QGraphicsTextItem(self.node.name(), self) + self.nameItem = TextItem(self.node.name(), self, self.labelChanged) self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) self.updateTerminals() #self.setZValue(10) - self.nameItem.focusOutEvent = self.labelFocusOut - self.nameItem.keyPressEvent = self.labelKeyPress - self.menu = None self.buildMenu() @@ -481,16 +496,6 @@ class NodeGraphicsItem(GraphicsObject): #item.setZValue(z+1) #GraphicsObject.setZValue(self, z) - def labelFocusOut(self, ev): - QtGui.QGraphicsTextItem.focusOutEvent(self.nameItem, ev) - self.labelChanged() - - def labelKeyPress(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - self.labelChanged() - else: - QtGui.QGraphicsTextItem.keyPressEvent(self.nameItem, ev) - def labelChanged(self): newName = str(self.nameItem.toPlainText()) if newName != self.node.name(): @@ -574,7 +579,7 @@ class NodeGraphicsItem(GraphicsObject): def mouseClickEvent(self, ev): #print "Node.mouseClickEvent called." - if int(ev.button()) == int(QtCore.Qt.LeftButton): + if ev.button() == QtCore.Qt.LeftButton: ev.accept() #print " ev.button: left" sel = self.isSelected() @@ -587,7 +592,7 @@ class NodeGraphicsItem(GraphicsObject): self.update() #return ret - elif int(ev.button()) == int(QtCore.Qt.RightButton): + elif ev.button() == QtCore.Qt.RightButton: #print " ev.button: right" ev.accept() #pos = ev.screenPos() diff --git a/pyqtgraph/flowchart/Terminal.py b/pyqtgraph/flowchart/Terminal.py index 7c512dda..c2e60a55 100644 --- a/pyqtgraph/flowchart/Terminal.py +++ b/pyqtgraph/flowchart/Terminal.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..Qt import QtCore, QtGui +from ..Qt import QtCore, QtGui, QtWidgets import weakref from ..graphicsItems.GraphicsObject import GraphicsObject from .. import functions as fn @@ -273,6 +273,25 @@ class Terminal(object): """ return self._name < other._name + +class TextItem(QtWidgets.QGraphicsTextItem): + def __init__(self, text, parent, on_update): + super().__init__(text, parent) + self.on_update = on_update + + def focusOutEvent(self, ev): + super().focusOutEvent(ev) + if self.on_update is not None: + self.on_update() + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + if self.on_update is not None: + self.on_update() + return + super().keyPressEvent(ev) + + class TerminalGraphicsItem(GraphicsObject): def __init__(self, term, parent=None): @@ -280,27 +299,16 @@ class TerminalGraphicsItem(GraphicsObject): GraphicsObject.__init__(self, parent) self.brush = fn.mkBrush(0,0,0) self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) - self.label = QtGui.QGraphicsTextItem(self.term.name(), self) + on_update = self.labelChanged if self.term.isRenamable() else None + self.label = TextItem(self.term.name(), self, on_update) self.label.scale(0.7, 0.7) self.newConnection = None self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem if self.term.isRenamable(): self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) - self.label.focusOutEvent = self.labelFocusOut - self.label.keyPressEvent = self.labelKeyPress self.setZValue(1) self.menu = None - def labelFocusOut(self, ev): - QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) - self.labelChanged() - - def labelKeyPress(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - self.labelChanged() - else: - QtGui.QGraphicsTextItem.keyPressEvent(self.label, ev) - def labelChanged(self): newName = str(self.label.toPlainText()) if newName != self.term.name(): diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py index b133b159..6edd3a80 100644 --- a/pyqtgraph/flowchart/library/Data.py +++ b/pyqtgraph/flowchart/library/Data.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from ..Node import Node -from ...Qt import QtGui, QtCore +from ...Qt import QtGui, QtCore, QtWidgets import numpy as np import sys from .common import * @@ -173,6 +173,20 @@ class RegionSelectNode(CtrlNode): self.update() +class TextEdit(QtWidgets.QTextEdit): + def __init__(self, on_update): + super().__init__() + self.on_update = on_update + self.lastText = None + + def focusOutEvent(self, ev): + text = str(self.toPlainText()) + if text != self.lastText: + self.lastText = text + self.on_update() + super().focusOutEvent(ev) + + class EvalNode(Node): """Return the output of a string evaluated/executed by the python interpreter. The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. @@ -190,15 +204,12 @@ class EvalNode(Node): self.ui = QtGui.QWidget() self.layout = QtGui.QGridLayout() - self.text = QtGui.QTextEdit() + self.text = TextEdit(self.update) self.text.setTabStopWidth(30) self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") self.layout.addWidget(self.text, 1, 0, 1, 2) self.ui.setLayout(self.layout) - self.text.focusOutEvent = self.focusOutEvent - self.lastText = None - def ctrlWidget(self): return self.ui @@ -221,13 +232,6 @@ class EvalNode(Node): def code(self): return self.text.toPlainText() - def focusOutEvent(self, ev): - text = str(self.text.toPlainText()) - if text != self.lastText: - self.lastText = text - self.update() - return QtGui.QTextEdit.focusOutEvent(self.text, ev) - def process(self, display=True, **args): l = locals() l.update(args) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 6bcf8113..c3257f23 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -19,6 +19,7 @@ from pyqtgraph.util.cupy_helper import getCupy from . import debug, reload from .Qt import QtGui, QtCore, QT_LIB, QtVersion +from . import Qt from .metaarray import MetaArray from .pgcollections import OrderedDict from .python2_3 import asUnicode, basestring @@ -1254,23 +1255,28 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True): imgData = imgData.copy() profile("copy") - if QT_LIB == 'PySide': - ch = ctypes.c_char.from_buffer(imgData, 0) - img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat) - elif QT_LIB in ['PySide2', 'PySide6']: - img = QtGui.QImage(imgData, imgData.shape[1], imgData.shape[0], imgFormat) + + # C++ QImage has two kind of constructors + # - QImage(const uchar*, ...) + # - QImage(uchar*, ...) + # If the const constructor is used, subsequently calling any non-const method + # will trigger the COW mechanism, i.e. a copy is made under the hood. + + if QT_LIB == 'PyQt5': + # PyQt5 -> non-const constructor + img_ptr = imgData.ctypes.data + elif QT_LIB == 'PyQt6': + # PyQt5 -> const constructor + # PyQt6 -> non-const constructor + img_ptr = Qt.sip.voidptr(imgData) else: - ## PyQt API for QImage changed between 4.9.3 and 4.9.6 (I don't know exactly which version it was) - ## So we first attempt the 4.9.6 API, then fall back to 4.9.3 - try: - img = QtGui.QImage(imgData.ctypes.data, imgData.shape[1], imgData.shape[0], imgFormat) - except: - if copy: - # does not leak memory, is not mutable - img = QtGui.QImage(buffer(imgData), imgData.shape[1], imgData.shape[0], imgFormat) - else: - # mutable, but leaks memory - img = QtGui.QImage(memoryview(imgData), imgData.shape[1], imgData.shape[0], imgFormat) + # bindings that support ndarray + # PyQt5 -> const constructor + # PySide2 -> non-const constructor + # PySide6 -> non-const constructor + img_ptr = imgData + + img = QtGui.QImage(img_ptr, imgData.shape[1], imgData.shape[0], imgFormat) img.data = imgData return img @@ -1287,12 +1293,16 @@ def imageToArray(img, copy=False, transpose=True): if QT_LIB in ['PySide', 'PySide2', 'PySide6']: arr = np.frombuffer(ptr, dtype=np.ubyte) else: - ptr.setsize(img.byteCount()) + try: + # removed in Qt6 + nbytes = img.byteCount() + except AttributeError: + # introduced in Qt 5.10 + # however Python 3.7 + PyQt5-5.12 in the CI fails with + # "TypeError: QImage.sizeInBytes() is a private method" + nbytes = img.sizeInBytes() + ptr.setsize(nbytes) arr = np.asarray(ptr) - if img.byteCount() != arr.size * arr.itemsize: - # Required for Python 2.6, PyQt 4.10 - # If this works on all platforms, then there is no need to use np.asarray.. - arr = np.frombuffer(ptr, np.ubyte, img.byteCount()) arr = arr.reshape(img.height(), img.width(), 4) if fmt == img.Format_RGB32: @@ -1546,6 +1556,9 @@ def arrayToQPath(x, y, connect='all'): buf = QtCore.QByteArray.fromRawData(path.strn) except TypeError: buf = QtCore.QByteArray(bytes(path.strn)) + except AttributeError: + # PyQt6 raises AttributeError + buf = QtCore.QByteArray(path.strn, path.strn.nbytes) ds = QtCore.QDataStream(buf) ds >> path diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py index a7cc831c..77b6c44c 100644 --- a/pyqtgraph/graphicsItems/ArrowItem.py +++ b/pyqtgraph/graphicsItems/ArrowItem.py @@ -104,7 +104,7 @@ class ArrowItem(QtGui.QGraphicsPathItem): def paint(self, p, *args): p.setRenderHint(QtGui.QPainter.Antialiasing) - QtGui.QGraphicsPathItem.paint(self, p, *args) + super().paint(p, *args) #p.setPen(fn.mkPen('r')) #p.setBrush(fn.mkBrush(None)) diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py index 4c0f0de2..92cd84f2 100644 --- a/pyqtgraph/graphicsItems/AxisItem.py +++ b/pyqtgraph/graphicsItems/AxisItem.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore +from ..Qt import QtGui, QtCore, QT_LIB from ..python2_3 import asUnicode import numpy as np from ..Point import Point @@ -1103,19 +1103,28 @@ class AxisItem(GraphicsWidget): width = textRect.width() #self.textHeight = height offset = max(0,self.style['tickLength']) + textOffset + if self.orientation == 'left': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter + alignFlags = QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) elif self.orientation == 'right': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter + alignFlags = QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) elif self.orientation == 'top': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignHCenter|QtCore.Qt.AlignBottom + alignFlags = QtCore.Qt.AlignHCenter|QtCore.Qt.AlignBottom rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) elif self.orientation == 'bottom': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop + alignFlags = QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) + if QT_LIB == 'PyQt6': + # PyQt6 doesn't allow or-ing of different enum types + # so we need to take its value property + textFlags = alignFlags.value | QtCore.Qt.TextDontClip.value + else: + # for PyQt5, the following expression is not commutative! + textFlags = alignFlags | QtCore.Qt.TextDontClip + #p.setPen(self.pen()) #p.drawText(rect, textFlags, vstr) textSpecs.append((rect, textFlags, vstr)) diff --git a/pyqtgraph/graphicsItems/GradientEditorItem.py b/pyqtgraph/graphicsItems/GradientEditorItem.py index d763e965..6f5d9132 100644 --- a/pyqtgraph/graphicsItems/GradientEditorItem.py +++ b/pyqtgraph/graphicsItems/GradientEditorItem.py @@ -626,10 +626,9 @@ class GradientEditorItem(TickSliderItem): def getGradient(self): """Return a QLinearGradient object.""" g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) - stops = [] if self.colorMode == 'rgb': ticks = self.listTicks() - stops = [(x, QtGui.QColor(t.color)) for t,x in ticks] + g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop ticks = self.listTicks() stops = [] @@ -642,12 +641,7 @@ class GradientEditorItem(TickSliderItem): x = x1 + dx*j stops.append((x, self.getColor(x))) stops.append((x2, self.getColor(x2))) - if hasattr(g, 'setStops'): g.setStops(stops) - else: - # PySide6 has a missing setStops binding - for pos, col in stops: - g.setColorAt(pos, col) return g def getColor(self, x, toQColor=True): diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/pyqtgraph/graphicsItems/GraphicsObject.py index fac8e819..2e63b531 100644 --- a/pyqtgraph/graphicsItems/GraphicsObject.py +++ b/pyqtgraph/graphicsItems/GraphicsObject.py @@ -1,9 +1,6 @@ from ..Qt import QtGui, QtCore, QT_LIB -if QT_LIB in ['PyQt4', 'PyQt5']: - try: - from PyQt5 import sip - except ImportError: - import sip +if QT_LIB.startswith('PyQt'): + from ..Qt import sip from .GraphicsItem import GraphicsItem __all__ = ['GraphicsObject'] @@ -21,7 +18,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): GraphicsItem.__init__(self) def itemChange(self, change, value): - ret = QtGui.QGraphicsObject.itemChange(self, change, value) + ret = super().itemChange(change, value) if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: self.parentChanged() try: diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/pyqtgraph/graphicsItems/LinearRegionItem.py index e0fe53c0..93527d14 100644 --- a/pyqtgraph/graphicsItems/LinearRegionItem.py +++ b/pyqtgraph/graphicsItems/LinearRegionItem.py @@ -245,7 +245,7 @@ class LinearRegionItem(GraphicsObject): self.sigRegionChangeFinished.emit(self) def mouseDragEvent(self, ev): - if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: + if not self.movable or ev.button() != QtCore.Qt.LeftButton: return ev.accept() diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py new file mode 100644 index 00000000..213f4db5 --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt6.py @@ -0,0 +1,178 @@ +# Form implementation generated from reading ui file 'pyqtgraph\graphicsItems\PlotItem\plotConfigTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(481, 840) + self.averageGroup = QtWidgets.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName("averageGroup") + self.gridLayout_5 = QtWidgets.QGridLayout(self.averageGroup) + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName("gridLayout_5") + self.avgParamList = QtWidgets.QListWidget(self.averageGroup) + self.avgParamList.setObjectName("avgParamList") + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtWidgets.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName("decimateGroup") + self.gridLayout_4 = QtWidgets.QGridLayout(self.decimateGroup) + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName("gridLayout_4") + self.clipToViewCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName("clipToViewCheck") + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName("maxTracesCheck") + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName("peakRadio") + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtWidgets.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName("maxTracesSpin") + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName("forgetTracesCheck") + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName("meanRadio") + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtWidgets.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName("subsampleRadio") + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtWidgets.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtWidgets.QSpacerItem(30, 20, QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Policy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtWidgets.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName("downsampleSpin") + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtWidgets.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(10, 10, 171, 101)) + self.transformGroup.setObjectName("transformGroup") + self.gridLayout = QtWidgets.QGridLayout(self.transformGroup) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.logYCheck = QtWidgets.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName("logYCheck") + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.logXCheck = QtWidgets.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName("logXCheck") + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.fftCheck = QtWidgets.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName("fftCheck") + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.derivativeCheck = QtWidgets.QCheckBox(self.transformGroup) + self.derivativeCheck.setObjectName("derivativeCheck") + self.gridLayout.addWidget(self.derivativeCheck, 3, 0, 1, 1) + self.phasemapCheck = QtWidgets.QCheckBox(self.transformGroup) + self.phasemapCheck.setObjectName("phasemapCheck") + self.gridLayout.addWidget(self.phasemapCheck, 4, 0, 1, 1) + self.pointsGroup = QtWidgets.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName("pointsGroup") + self.verticalLayout_5 = QtWidgets.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.autoPointsCheck = QtWidgets.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName("autoPointsCheck") + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtWidgets.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName("gridGroup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName("gridLayout_2") + self.xGridCheck = QtWidgets.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName("xGridCheck") + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtWidgets.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName("yGridCheck") + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtWidgets.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Orientations.Horizontal) + self.gridAlphaSlider.setObjectName("gridAlphaSlider") + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtWidgets.QLabel(self.gridGroup) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtWidgets.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName("alphaGroup") + self.horizontalLayout = QtWidgets.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName("horizontalLayout") + self.autoAlphaCheck = QtWidgets.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName("autoAlphaCheck") + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtWidgets.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Orientations.Horizontal) + self.alphaSlider.setObjectName("alphaSlider") + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).")) + self.averageGroup.setTitle(_translate("Form", "Average")) + self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.")) + self.clipToViewCheck.setText(_translate("Form", "Clip to View")) + self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.")) + self.maxTracesCheck.setText(_translate("Form", "Max Traces:")) + self.downsampleCheck.setText(_translate("Form", "Downsample")) + self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.")) + self.peakRadio.setText(_translate("Form", "Peak")) + self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.")) + self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).")) + self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces")) + self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.")) + self.meanRadio.setText(_translate("Form", "Mean")) + self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.")) + self.subsampleRadio.setText(_translate("Form", "Subsample")) + self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.")) + self.autoDownsampleCheck.setText(_translate("Form", "Auto")) + self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)")) + self.downsampleSpin.setSuffix(_translate("Form", "x")) + self.logYCheck.setText(_translate("Form", "Log Y")) + self.logXCheck.setText(_translate("Form", "Log X")) + self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)")) + self.derivativeCheck.setText(_translate("Form", "dy/dx")) + self.phasemapCheck.setText(_translate("Form", "Y vs. Y\'")) + self.pointsGroup.setTitle(_translate("Form", "Points")) + self.autoPointsCheck.setText(_translate("Form", "Auto")) + self.xGridCheck.setText(_translate("Form", "Show X Grid")) + self.yGridCheck.setText(_translate("Form", "Show Y Grid")) + self.label.setText(_translate("Form", "Opacity")) + self.alphaGroup.setTitle(_translate("Form", "Alpha")) + self.autoAlphaCheck.setText(_translate("Form", "Auto")) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 1025c5e3..b775a4ad 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -722,7 +722,7 @@ class ROI(GraphicsObject): hover=True for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MiddleButton]: - if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): hover=True if self.contextMenuEnabled(): ev.acceptClicks(QtCore.Qt.RightButton) @@ -794,7 +794,7 @@ class ROI(GraphicsObject): if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled(): self.raiseContextMenu(ev) ev.accept() - elif int(ev.button() & self.acceptedMouseButtons()) > 0: + elif ev.button() & self.acceptedMouseButtons(): ev.accept() self.sigClicked.emit(self, ev) else: @@ -820,7 +820,7 @@ class ROI(GraphicsObject): """ return True - def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'): + def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifiers(0), finish=True, coords='parent'): ## called by Handles when they are moved. ## pos is the new position of the handle in scene coords, as requested by the handle. @@ -1343,7 +1343,7 @@ class Handle(UIGraphicsItem): if ev.acceptDrags(QtCore.Qt.LeftButton): hover=True for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MiddleButton]: - if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + if (self.acceptedMouseButtons() & btn) and ev.acceptClicks(btn): hover=True if hover: @@ -1358,7 +1358,7 @@ class Handle(UIGraphicsItem): self.isMoving = False ## prevents any further motion self.movePoint(self.startPos, finish=True) ev.accept() - elif int(ev.button() & self.acceptedMouseButtons()) > 0: + elif ev.button() & self.acceptedMouseButtons(): ev.accept() if ev.button() == QtCore.Qt.RightButton and self.deletable: self.raiseContextMenu(ev) @@ -1415,7 +1415,7 @@ class Handle(UIGraphicsItem): self.currentPen = self.hoverPen self.movePoint(pos, ev.modifiers(), finish=False) - def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): + def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifiers(0), finish=True): for r in self.rois: if not r.checkPointMove(self, pos, modifiers): return diff --git a/pyqtgraph/graphicsItems/UIGraphicsItem.py b/pyqtgraph/graphicsItems/UIGraphicsItem.py index 07d32944..2f9a3bfe 100644 --- a/pyqtgraph/graphicsItems/UIGraphicsItem.py +++ b/pyqtgraph/graphicsItems/UIGraphicsItem.py @@ -1,11 +1,8 @@ from ..Qt import QtGui, QtCore, QT_LIB import weakref from .GraphicsObject import GraphicsObject -if QT_LIB in ['PyQt4', 'PyQt5']: - try: - from PyQt5 import sip - except ImportError: - import sip +if QT_LIB.startswith('PyQt'): + from ..Qt import sip __all__ = ['UIGraphicsItem'] class UIGraphicsItem(GraphicsObject): diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index 0e17e008..2fd6ea61 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -1397,7 +1397,7 @@ class ViewBox(GraphicsWidget): itemBounds.append((bounds, useX, useY, pxPad)) else: - if int(item.flags() & item.ItemHasNoContents) > 0: + if item.flags() & item.ItemHasNoContents: continue bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() itemBounds.append((bounds, True, True, 0)) diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py new file mode 100644 index 00000000..6c883610 --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt6.py @@ -0,0 +1,88 @@ +# Form implementation generated from reading ui file 'pyqtgraph\graphicsItems\ViewBox\axisCtrlTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtWidgets.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtWidgets.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtWidgets.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtWidgets.QComboBox.SizeAdjustPolicy.AdjustToContents) + self.linkCombo.setObjectName("linkCombo") + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtWidgets.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName("autoPercentSpin") + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtWidgets.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName("autoRadio") + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtWidgets.QRadioButton(Form) + self.manualRadio.setObjectName("manualRadio") + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtWidgets.QLineEdit(Form) + self.minText.setObjectName("minText") + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtWidgets.QLineEdit(Form) + self.maxText.setObjectName("maxText") + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtWidgets.QCheckBox(Form) + self.invertCheck.setObjectName("invertCheck") + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtWidgets.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName("mouseCheck") + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtWidgets.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtWidgets.QCheckBox(Form) + self.autoPanCheck.setObjectName("autoPanCheck") + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.label.setText(_translate("Form", "Link Axis:")) + self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

")) + self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

")) + self.autoPercentSpin.setSuffix(_translate("Form", "%")) + self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

")) + self.autoRadio.setText(_translate("Form", "Auto")) + self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

")) + self.manualRadio.setText(_translate("Form", "Manual")) + self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

")) + self.minText.setText(_translate("Form", "0")) + self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

")) + self.maxText.setText(_translate("Form", "0")) + self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

")) + self.invertCheck.setText(_translate("Form", "Invert Axis")) + self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

")) + self.mouseCheck.setText(_translate("Form", "Mouse Enabled")) + self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

")) + self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only")) + self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

")) + self.autoPanCheck.setText(_translate("Form", "Auto Pan Only")) diff --git a/pyqtgraph/graphicsItems/tests/test_InfiniteLine.py b/pyqtgraph/graphicsItems/tests/test_InfiniteLine.py index 5d9a2184..689e1963 100644 --- a/pyqtgraph/graphicsItems/tests/test_InfiniteLine.py +++ b/pyqtgraph/graphicsItems/tests/test_InfiniteLine.py @@ -63,8 +63,8 @@ def test_mouseInteraction(): plt.setYRange(-10, 10) # test horizontal drag - pos = plt.plotItem.vb.mapViewToScene(pg.Point(0,5)).toPoint() - pos2 = pos - QtCore.QPoint(200, 200) + pos = plt.plotItem.vb.mapViewToScene(pg.Point(0,5)) + pos2 = pos - QtCore.QPointF(200, 200) mouseMove(plt, pos) assert vline.mouseHovering is True and hline.mouseHovering is False mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton) @@ -72,17 +72,17 @@ def test_mouseInteraction(): assert abs(vline.value() - plt.plotItem.vb.mapSceneToView(pos2).x()) <= px # test missed drag - pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)).toPoint() - pos = pos + QtCore.QPoint(0, 6) - pos2 = pos + QtCore.QPoint(-20, -20) + pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)) + pos = pos + QtCore.QPointF(0, 6) + pos2 = pos + QtCore.QPointF(-20, -20) mouseMove(plt, pos) assert vline.mouseHovering is False and hline.mouseHovering is False mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton) assert hline.value() == 0 # test vertical drag - pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)).toPoint() - pos2 = pos - QtCore.QPoint(50, 50) + pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,0)) + pos2 = pos - QtCore.QPointF(50, 50) mouseMove(plt, pos) assert vline.mouseHovering is False and hline.mouseHovering is True mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton) @@ -90,8 +90,8 @@ def test_mouseInteraction(): assert abs(hline.value() - plt.plotItem.vb.mapSceneToView(pos2).y()) <= px # test non-interactive line - pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,-1)).toPoint() - pos2 = pos - QtCore.QPoint(50, 50) + pos = plt.plotItem.vb.mapViewToScene(pg.Point(5,-1)) + pos2 = pos - QtCore.QPointF(50, 50) mouseMove(plt, pos) assert hline2.mouseHovering == False mouseDrag(plt, pos, pos2, QtCore.Qt.LeftButton) diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 7b52880e..ca29d1f9 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -450,7 +450,7 @@ class ImageView(QtGui.QWidget): self.keysPressed[ev.key()] = 1 self.evalKeyState() else: - QtGui.QWidget.keyPressEvent(self, ev) + super().keyPressEvent(ev) def keyReleaseEvent(self, ev): if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]: @@ -465,7 +465,7 @@ class ImageView(QtGui.QWidget): self.keysPressed = {} self.evalKeyState() else: - QtGui.QWidget.keyReleaseEvent(self, ev) + super().keyReleaseEvent(ev) def evalKeyState(self): if len(self.keysPressed) == 1: diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py b/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py new file mode 100644 index 00000000..c27aa232 --- /dev/null +++ b/pyqtgraph/imageview/ImageViewTemplate_pyqt6.py @@ -0,0 +1,151 @@ +# Form implementation generated from reading ui file 'pyqtgraph\imageview\ImageViewTemplate.ui' +# +# Created by: PyQt6 UI code generator 6.0.0 +# +# WARNING: Any manual changes made to this file will be lost when pyuic6 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt6 import QtCore, QtGui, QtWidgets + + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(726, 588) + self.gridLayout_3 = QtWidgets.QGridLayout(Form) + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.splitter = QtWidgets.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Orientations.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtWidgets.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout = QtWidgets.QGridLayout(self.layoutWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.graphicsView = GraphicsView(self.layoutWidget) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName("histogram") + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) + self.roiBtn = QtWidgets.QPushButton(self.layoutWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName("roiBtn") + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) + self.menuBtn = QtWidgets.QPushButton(self.layoutWidget) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) + self.menuBtn.setSizePolicy(sizePolicy) + self.menuBtn.setObjectName("menuBtn") + self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName("roiPlot") + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtWidgets.QGroupBox(Form) + self.normGroup.setObjectName("normGroup") + self.gridLayout_2 = QtWidgets.QGridLayout(self.normGroup) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.normSubtractRadio = QtWidgets.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName("normSubtractRadio") + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtWidgets.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName("normDivideRadio") + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtWidgets.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + self.label_5.setFont(font) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtWidgets.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtWidgets.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + self.label_4.setFont(font) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtWidgets.QCheckBox(self.normGroup) + self.normROICheck.setObjectName("normROICheck") + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName("normXBlurSpin") + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtWidgets.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtWidgets.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) + self.label_9.setObjectName("label_9") + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName("normYBlurSpin") + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtWidgets.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.Alignment.AlignRight|QtCore.Qt.Alignment.AlignTrailing|QtCore.Qt.Alignment.AlignVCenter) + self.label_10.setObjectName("label_10") + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtWidgets.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName("normOffRadio") + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtWidgets.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtWidgets.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName("normFrameCheck") + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtWidgets.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName("normTBlurSpin") + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + Form.setWindowTitle(_translate("Form", "PyQtGraph")) + self.roiBtn.setText(_translate("Form", "ROI")) + self.menuBtn.setText(_translate("Form", "Menu")) + self.normGroup.setTitle(_translate("Form", "Normalization")) + self.normSubtractRadio.setText(_translate("Form", "Subtract")) + self.normDivideRadio.setText(_translate("Form", "Divide")) + self.label_5.setText(_translate("Form", "Operation:")) + self.label_3.setText(_translate("Form", "Mean:")) + self.label_4.setText(_translate("Form", "Blur:")) + self.normROICheck.setText(_translate("Form", "ROI")) + self.label_8.setText(_translate("Form", "X")) + self.label_9.setText(_translate("Form", "Y")) + self.label_10.setText(_translate("Form", "T")) + self.normOffRadio.setText(_translate("Form", "Off")) + self.normTimeRangeCheck.setText(_translate("Form", "Time range")) + self.normFrameCheck.setText(_translate("Form", "Frame")) +from ..widgets.GraphicsView import GraphicsView +from ..widgets.HistogramLUTWidget import HistogramLUTWidget +from ..widgets.PlotWidget import PlotWidget diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 2c9bff01..1426a424 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -401,11 +401,12 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): return xDist / self.width() def mousePressEvent(self, ev): - self.mousePos = ev.pos() + self.mousePos = ev.localPos() def mouseMoveEvent(self, ev): - diff = ev.pos() - self.mousePos - self.mousePos = ev.pos() + lpos = ev.localPos() + diff = lpos - self.mousePos + self.mousePos = lpos if ev.buttons() == QtCore.Qt.LeftButton: if (ev.modifiers() & QtCore.Qt.ControlModifier): diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py index de6ab126..8c89d37e 100644 --- a/pyqtgraph/parametertree/ParameterTree.py +++ b/pyqtgraph/parametertree/ParameterTree.py @@ -157,8 +157,8 @@ class ParameterTree(TreeWidget): self.lastSel = sel[0] if hasattr(sel[0], 'selected'): sel[0].selected(True) - return TreeWidget.selectionChanged(self, *args) + return super().selectionChanged(*args) def wheelEvent(self, ev): self.clearSelection() - return TreeWidget.wheelEvent(self, ev) + return super().wheelEvent(ev) diff --git a/pyqtgraph/tests/image_testing.py b/pyqtgraph/tests/image_testing.py index 7cb993f9..1245bb02 100644 --- a/pyqtgraph/tests/image_testing.py +++ b/pyqtgraph/tests/image_testing.py @@ -257,7 +257,7 @@ def assertImageMatch(im1, im2, minCorr=None, pxThreshold=50., assert im1.dtype == im2.dtype if pxCount == -1: - if QT_LIB in {'PyQt5', 'PySide2', 'PySide6'}: + if QT_LIB in {'PyQt5', 'PySide2', 'PySide6', 'PyQt6'}: # Qt5 generates slightly different results; relax the tolerance # until test images are updated. pxCount = int(im1.shape[0] * im1.shape[1] * 0.01) diff --git a/pyqtgraph/tests/ui_testing.py b/pyqtgraph/tests/ui_testing.py index 4bcb7606..747aadf9 100644 --- a/pyqtgraph/tests/ui_testing.py +++ b/pyqtgraph/tests/ui_testing.py @@ -1,5 +1,5 @@ import time -from ..Qt import QtCore, QtGui, QtTest, QT_LIB +from ..Qt import QtCore, QtGui, QtTest def resizeWindow(win, w, h, timeout=2.0): @@ -32,8 +32,6 @@ def mousePress(widget, pos, button, modifier=None): widget = widget.viewport() if modifier is None: modifier = QtCore.Qt.NoModifier - if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF): - pos = pos.toPoint() event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, pos, button, QtCore.Qt.NoButton, modifier) QtGui.QApplication.sendEvent(widget, event) @@ -43,8 +41,6 @@ def mouseRelease(widget, pos, button, modifier=None): widget = widget.viewport() if modifier is None: modifier = QtCore.Qt.NoModifier - if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF): - pos = pos.toPoint() event = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, pos, button, QtCore.Qt.NoButton, modifier) QtGui.QApplication.sendEvent(widget, event) @@ -56,8 +52,6 @@ def mouseMove(widget, pos, buttons=None, modifier=None): modifier = QtCore.Qt.NoModifier if buttons is None: buttons = QtCore.Qt.NoButton - if QT_LIB != 'PyQt5' and isinstance(pos, QtCore.QPointF): - pos = pos.toPoint() event = QtGui.QMouseEvent(QtCore.QEvent.MouseMove, pos, QtCore.Qt.NoButton, buttons, modifier) QtGui.QApplication.sendEvent(widget, event) diff --git a/pyqtgraph/widgets/ColorButton.py b/pyqtgraph/widgets/ColorButton.py index 43dd16f6..11a5ac14 100644 --- a/pyqtgraph/widgets/ColorButton.py +++ b/pyqtgraph/widgets/ColorButton.py @@ -35,7 +35,7 @@ class ColorButton(QtGui.QPushButton): self.setMinimumWidth(15) def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) + super().paintEvent(ev) p = QtGui.QPainter(self) rect = self.rect().adjusted(6, 6, -6, -6) ## draw white base, then texture for indicating transparency, then actual color diff --git a/pyqtgraph/widgets/GradientWidget.py b/pyqtgraph/widgets/GradientWidget.py index 77881b30..c5396d75 100644 --- a/pyqtgraph/widgets/GradientWidget.py +++ b/pyqtgraph/widgets/GradientWidget.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from ..Qt import QtGui, QtCore +from ..Qt import QtGui, QtCore, QtWidgets, QT_LIB from .GraphicsView import GraphicsView from ..graphicsItems.GradientEditorItem import GradientEditorItem import weakref @@ -40,7 +40,18 @@ class GradientWidget(GraphicsView): self.setOrientation(orientation) self.setCacheMode(self.CacheNone) self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) - self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + + if QT_LIB == 'PyQt6': + # PyQt6 doesn't allow or-ing of different enum types + # so we need to take its value property + NoFrame = QtWidgets.QFrame.Shape.NoFrame.value + Plain = QtWidgets.QFrame.Shadow.Plain.value + else: + NoFrame = QtWidgets.QFrame.NoFrame + Plain = QtWidgets.QFrame.Plain + frame_style = NoFrame | Plain + + self.setFrameStyle(frame_style) #self.setBackgroundRole(QtGui.QPalette.NoRole) #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) #self.setAutoFillBackground(False) diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index accaf2a9..4490d4de 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -25,6 +25,7 @@ from .. import getConfigOption __all__ = ['GraphicsView'] + class GraphicsView(QtGui.QGraphicsView): """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget @@ -157,11 +158,11 @@ class GraphicsView(QtGui.QGraphicsView): def paintEvent(self, ev): self.scene().prepareForPaint() - return QtGui.QGraphicsView.paintEvent(self, ev) + return super().paintEvent(ev) def render(self, *args, **kwds): self.scene().prepareForPaint() - return QtGui.QGraphicsView.render(self, *args, **kwds) + return super().render(*args, **kwds) def close(self): @@ -328,10 +329,9 @@ class GraphicsView(QtGui.QGraphicsView): GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) def wheelEvent(self, ev): - QtGui.QGraphicsView.wheelEvent(self, ev) + super().wheelEvent(ev) if not self.mouseEnabled: return - delta = 0 if QT_LIB in ['PyQt4', 'PySide']: delta = ev.delta() @@ -352,20 +352,21 @@ class GraphicsView(QtGui.QGraphicsView): self.scene().leaveEvent(ev) ## inform scene when mouse leaves def mousePressEvent(self, ev): - QtGui.QGraphicsView.mousePressEvent(self, ev) + super().mousePressEvent(ev) if not self.mouseEnabled: return - self.lastMousePos = Point(ev.pos()) - self.mousePressPos = ev.pos() + lpos = ev.localPos() + self.lastMousePos = lpos + self.mousePressPos = lpos self.clickAccepted = ev.isAccepted() if not self.clickAccepted: self.scene().clearSelection() return ## Everything below disabled for now.. def mouseReleaseEvent(self, ev): - QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + super().mouseReleaseEvent(ev) if not self.mouseEnabled: return self.sigMouseReleased.emit(ev) @@ -373,15 +374,16 @@ class GraphicsView(QtGui.QGraphicsView): return ## Everything below disabled for now.. def mouseMoveEvent(self, ev): + lpos = ev.localPos() if self.lastMousePos is None: - self.lastMousePos = Point(ev.pos()) - delta = Point(ev.pos() - self.lastMousePos.toQPoint()) - self.lastMousePos = Point(ev.pos()) + self.lastMousePos = lpos + delta = Point(lpos - self.lastMousePos) + self.lastMousePos = lpos - QtGui.QGraphicsView.mouseMoveEvent(self, ev) + super().mouseMoveEvent(ev) if not self.mouseEnabled: return - self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos())) + self.sigSceneMouseMoved.emit(self.mapToScene(lpos)) if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. return diff --git a/pyqtgraph/widgets/JoystickButton.py b/pyqtgraph/widgets/JoystickButton.py index 6f73c8dc..b668d82d 100644 --- a/pyqtgraph/widgets/JoystickButton.py +++ b/pyqtgraph/widgets/JoystickButton.py @@ -18,11 +18,11 @@ class JoystickButton(QtGui.QPushButton): def mousePressEvent(self, ev): self.setChecked(True) - self.pressPos = ev.pos() + self.pressPos = ev.localPos() ev.accept() def mouseMoveEvent(self, ev): - dif = ev.pos()-self.pressPos + dif = ev.localPos()-self.pressPos self.setState(dif.x(), -dif.y()) def mouseReleaseEvent(self, ev): @@ -64,14 +64,14 @@ class JoystickButton(QtGui.QPushButton): self.sigStateChanged.emit(self, self.state) def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) + super().paintEvent(ev) p = QtGui.QPainter(self) p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) def resizeEvent(self, ev): self.setState(*self.state) - QtGui.QPushButton.resizeEvent(self, ev) + super().resizeEvent(ev) diff --git a/pyqtgraph/widgets/PathButton.py b/pyqtgraph/widgets/PathButton.py index ee2e0bca..06fe131f 100644 --- a/pyqtgraph/widgets/PathButton.py +++ b/pyqtgraph/widgets/PathButton.py @@ -32,7 +32,7 @@ class PathButton(QtGui.QPushButton): self.update() def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) + super().paintEvent(ev) margin = self.margin geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) rect = self.path.boundingRect() diff --git a/pyqtgraph/widgets/ProgressDialog.py b/pyqtgraph/widgets/ProgressDialog.py index 7d2ef8a4..989a8d61 100644 --- a/pyqtgraph/widgets/ProgressDialog.py +++ b/pyqtgraph/widgets/ProgressDialog.py @@ -195,7 +195,7 @@ class ProgressDialog(QtGui.QProgressDialog): if self._nestingReady: # don't let progress dialog manage widgets anymore. return - return QtGui.QProgressDialog.resizeEvent(self, ev) + return super().resizeEvent(ev) ## wrap all other functions to make sure they aren't being called from non-gui threads diff --git a/pyqtgraph/widgets/RemoteGraphicsView.py b/pyqtgraph/widgets/RemoteGraphicsView.py index 877425d1..620f4085 100644 --- a/pyqtgraph/widgets/RemoteGraphicsView.py +++ b/pyqtgraph/widgets/RemoteGraphicsView.py @@ -1,9 +1,6 @@ from ..Qt import QtGui, QtCore, QT_LIB -if QT_LIB in ['PyQt4', 'PyQt5']: - try: - from PyQt5 import sip - except ImportError: - import sip +if QT_LIB.startswith('PyQt'): + from ..Qt import sip from .. import multiprocess as mp from .GraphicsView import GraphicsView from .. import CONFIG_OPTIONS @@ -12,55 +9,6 @@ import mmap, tempfile, ctypes, atexit, sys, random __all__ = ['RemoteGraphicsView'] -class SerializableWheelEvent: - """ - Contains all information of a QWheelEvent, is serializable and can generate QWheelEvents. - - Methods have the functionality of their QWheelEvent equivalent. - """ - def __init__(self, _pos, _globalPos, _delta, _buttons, _modifiers, _orientation): - self._pos = _pos - self._globalPos = _globalPos - self._delta = _delta - self._buttons = _buttons - self._modifiers = _modifiers - self._orientation_vertical = _orientation == QtCore.Qt.Vertical - - def pos(self): - return self._pos - - def globalPos(self): - return self._globalPos - - def delta(self): - return self._delta - - def orientation(self): - if self._orientation_vertical: - return QtCore.Qt.Vertical - else: - return QtCore.Qt.Horizontal - - def angleDelta(self): - if self._orientation_vertical: - return QtCore.QPoint(0, self._delta) - else: - return QtCore.QPoint(self._delta, 0) - - def buttons(self): - return QtCore.Qt.MouseButtons(self._buttons) - - def modifiers(self): - return QtCore.Qt.KeyboardModifiers(self._modifiers) - - def toQWheelEvent(self): - """ - Generate QWheelEvent from SerializableWheelEvent. - """ - if QT_LIB in ['PyQt4', 'PySide']: - return QtGui.QWheelEvent(self.pos(), self.globalPos(), self.delta(), self.buttons(), self.modifiers(), self.orientation()) - else: - return QtGui.QWheelEvent(self.pos(), self.globalPos(), QtCore.QPoint(), self.angleDelta(), self.delta(), self.orientation(), self.buttons(), self.modifiers()) class RemoteGraphicsView(QtGui.QWidget): """ @@ -114,7 +62,7 @@ class RemoteGraphicsView(QtGui.QWidget): setattr(self, method, getattr(self._view, method)) def resizeEvent(self, ev): - ret = QtGui.QWidget.resizeEvent(self, ev) + ret = super().resizeEvent(ev) self._view.resize(self.size(), _callSync='off') return ret @@ -148,52 +96,60 @@ class RemoteGraphicsView(QtGui.QWidget): p = QtGui.QPainter(self) p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height())) p.end() - + + def serialize_mouse_common(self, ev): + if QT_LIB == 'PyQt6': + # PyQt6 can pickle MouseButtons and KeyboardModifiers but cannot cast to int + btns = ev.buttons() + mods = ev.modifiers() + else: + # PyQt5, PySide2, PySide6 cannot pickle MouseButtons and KeyboardModifiers + btns = int(ev.buttons()) + mods = int(ev.modifiers()) + return (btns, mods) + + def serialize_mouse_event(self, ev): + # lpos, gpos = ev.localPos(), ev.screenPos() + # RemoteGraphicsView Renderer assumes to be at (0, 0) + gpos = lpos = ev.localPos() + btns, mods = self.serialize_mouse_common(ev) + return (ev.type(), lpos, gpos, ev.button(), btns, mods) + + def serialize_wheel_event(self, ev): + # lpos, gpos = ev.position(), globalPosition() + # RemoteGraphicsView Renderer assumes to be at (0, 0) + gpos = lpos = ev.position() + btns, mods = self.serialize_mouse_common(ev) + return (lpos, gpos, ev.pixelDelta(), ev.angleDelta(), btns, mods, ev.phase(), ev.inverted()) + def mousePressEvent(self, ev): - self._view.mousePressEvent(int(ev.type()), ev.pos(), ev.pos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + self._view.mousePressEvent(self.serialize_mouse_event(ev), _callSync='off') ev.accept() - return QtGui.QWidget.mousePressEvent(self, ev) + return super().mousePressEvent(ev) def mouseReleaseEvent(self, ev): - self._view.mouseReleaseEvent(int(ev.type()), ev.pos(), ev.pos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + self._view.mouseReleaseEvent(self.serialize_mouse_event(ev), _callSync='off') ev.accept() - return QtGui.QWidget.mouseReleaseEvent(self, ev) + return super().mouseReleaseEvent(ev) def mouseMoveEvent(self, ev): - self._view.mouseMoveEvent(int(ev.type()), ev.pos(), ev.pos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + self._view.mouseMoveEvent(self.serialize_mouse_event(ev), _callSync='off') ev.accept() - return QtGui.QWidget.mouseMoveEvent(self, ev) + return super().mouseMoveEvent(ev) def wheelEvent(self, ev): - delta = 0 - orientation = QtCore.Qt.Horizontal - if QT_LIB in ['PyQt4', 'PySide']: - delta = ev.delta() - orientation = ev.orientation() - else: - delta = ev.angleDelta().x() - if delta == 0: - orientation = QtCore.Qt.Vertical - delta = ev.angleDelta().y() - - serializableEvent = SerializableWheelEvent(ev.pos(), ev.pos(), delta, int(ev.buttons()), int(ev.modifiers()), orientation) - self._view.wheelEvent(serializableEvent, _callSync='off') + self._view.wheelEvent(self.serialize_wheel_event(ev), _callSync='off') ev.accept() - return QtGui.QWidget.wheelEvent(self, ev) - - def keyEvent(self, ev): - if self._view.keyEvent(int(ev.type()), int(ev.modifiers()), text, autorep, count): - ev.accept() - return QtGui.QWidget.keyEvent(self, ev) - + return super().wheelEvent(ev) + def enterEvent(self, ev): lws = ev.localPos(), ev.windowPos(), ev.screenPos() self._view.enterEvent(lws, _callSync='off') - return QtGui.QWidget.enterEvent(self, ev) + return super().enterEvent(ev) def leaveEvent(self, ev): - self._view.leaveEvent(int(ev.type()), _callSync='off') - return QtGui.QWidget.leaveEvent(self, ev) + self._view.leaveEvent(ev.type(), _callSync='off') + return super().leaveEvent(ev) def remoteProcess(self): """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" @@ -243,11 +199,11 @@ class Renderer(GraphicsView): def update(self): self.img = None - return GraphicsView.update(self) + return super().update() def resize(self, size): oldSize = self.size() - GraphicsView.resize(self, size) + super().resize(size) self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) self.update() @@ -275,62 +231,60 @@ class Renderer(GraphicsView): self.shm.resize(size) ## render the scene directly to shared memory + ctypes_obj = ctypes.c_char.from_buffer(self.shm, 0) if QT_LIB.startswith('PySide'): - ch = ctypes.c_char.from_buffer(self.shm, 0) - self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32) + # PySide2, PySide6 + img_ptr = ctypes_obj else: - address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0)) + # PyQt5, PyQt6 + img_ptr = sip.voidptr(ctypes.addressof(ctypes_obj)) + + if QT_LIB == 'PyQt6': + img_ptr.setsize(size) + + self.img = QtGui.QImage(img_ptr, self.width(), self.height(), QtGui.QImage.Format_ARGB32) - # different versions of pyqt have different requirements here.. - try: - self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32) - except TypeError: - try: - self.img = QtGui.QImage(memoryview(buffer(self.shm)), self.width(), self.height(), QtGui.QImage.Format_ARGB32) - except TypeError: - # Works on PyQt 4.9.6 - self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32) self.img.fill(0xffffffff) p = QtGui.QPainter(self.img) self.render(p, self.viewRect(), self.rect()) p.end() self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName())) - def mousePressEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) + def deserialize_mouse_event(self, mouse_event): + typ, pos, gpos, btn, btns, mods = mouse_event + typ = QtCore.QEvent.Type(typ) # this line needed by PyQt5 only btns = QtCore.Qt.MouseButtons(btns) mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + return QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods) - def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) + def deserialize_wheel_event(self, wheel_event): + pos, gpos, pixelDelta, angleDelta, btns, mods, scrollPhase, inverted = wheel_event btns = QtCore.Qt.MouseButtons(btns) mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + return QtGui.QWheelEvent(pos, gpos, pixelDelta, angleDelta, btns, mods, scrollPhase, inverted) - def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + def mousePressEvent(self, mouse_event): + ev = self.deserialize_mouse_event(mouse_event) + return super().mousePressEvent(ev) + + def mouseMoveEvent(self, mouse_event): + ev = self.deserialize_mouse_event(mouse_event) + return super().mouseMoveEvent(ev) + + def mouseReleaseEvent(self, mouse_event): + ev = self.deserialize_mouse_event(mouse_event) + return super().mouseReleaseEvent(ev) - def wheelEvent(self, ev): - return GraphicsView.wheelEvent(self, ev.toQWheelEvent()) + def wheelEvent(self, wheel_event): + ev = self.deserialize_wheel_event(wheel_event) + return super().wheelEvent(ev) - def keyEvent(self, typ, mods, text, autorep, count): - typ = QtCore.QEvent.Type(typ) - mods = QtCore.Qt.KeyboardModifiers(mods) - GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count)) - return ev.accepted() - def enterEvent(self, lws): ev = QtGui.QEnterEvent(*lws) - return GraphicsView.enterEvent(self, ev) + return super().enterEvent(ev) def leaveEvent(self, typ): - ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) - return GraphicsView.leaveEvent(self, ev) + typ = QtCore.QEvent.Type(typ) # this line needed by PyQt5 only + ev = QtCore.QEvent(typ) + return super().leaveEvent(ev) diff --git a/pyqtgraph/widgets/SpinBox.py b/pyqtgraph/widgets/SpinBox.py index bc83bb28..10c40c8e 100644 --- a/pyqtgraph/widgets/SpinBox.py +++ b/pyqtgraph/widgets/SpinBox.py @@ -114,7 +114,7 @@ class SpinBox(QtGui.QAbstractSpinBox): self.editingFinished.connect(self.editingFinishedEvent) def event(self, ev): - ret = QtGui.QAbstractSpinBox.event(self, ev) + ret = super().event(ev) if ev.type() == QtCore.QEvent.KeyPress and ev.key() == QtCore.Qt.Key_Return: ret = True ## For some reason, spinbox pretends to ignore return key press return ret @@ -596,7 +596,7 @@ class SpinBox(QtGui.QAbstractSpinBox): def paintEvent(self, ev): self._updateHeight() - QtGui.QAbstractSpinBox.paintEvent(self, ev) + super().paintEvent(ev) class ErrorBox(QtGui.QWidget): diff --git a/pyqtgraph/widgets/TableWidget.py b/pyqtgraph/widgets/TableWidget.py index c0e6ea90..3fc526ab 100644 --- a/pyqtgraph/widgets/TableWidget.py +++ b/pyqtgraph/widgets/TableWidget.py @@ -366,7 +366,7 @@ class TableWidget(QtGui.QTableWidget): ev.accept() self.copySel() else: - QtGui.QTableWidget.keyPressEvent(self, ev) + super().keyPressEvent(ev) def handleItemChanged(self, item): item.itemChanged() diff --git a/pyqtgraph/widgets/TreeWidget.py b/pyqtgraph/widgets/TreeWidget.py index 8c55ae2f..2f8ce544 100644 --- a/pyqtgraph/widgets/TreeWidget.py +++ b/pyqtgraph/widgets/TreeWidget.py @@ -144,7 +144,7 @@ class TreeWidget(QtGui.QTreeWidget): return items def dropEvent(self, ev): - QtGui.QTreeWidget.dropEvent(self, ev) + super().dropEvent(ev) self.updateDropFlags() def updateDropFlags(self): diff --git a/pyqtgraph/widgets/ValueLabel.py b/pyqtgraph/widgets/ValueLabel.py index b24fb16c..0714689f 100644 --- a/pyqtgraph/widgets/ValueLabel.py +++ b/pyqtgraph/widgets/ValueLabel.py @@ -58,7 +58,7 @@ class ValueLabel(QtGui.QLabel): def paintEvent(self, ev): self.setText(self.generateText()) - return QtGui.QLabel.paintEvent(self, ev) + return super().paintEvent(ev) def generateText(self): if len(self.values) == 0: