diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f20ae3c6a4..9185b1b7613 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,6 +49,11 @@ jobs: python-version: 3.7 tox_env: orange-oldest name: Oldest dependencies + - os: ubuntu-20.04 + python-version: 3.9 + tox_env: pyqt6 + name: PyQt6 + services: postgres: @@ -120,6 +125,14 @@ jobs: python-version: 3.8 tox_env: orange-latest name: Latest + - os: windows-latest + python-version: 3.9 + tox_env: pyqt6 + name: PyQt6 + - os: macos-10.15 + python-version: 3.9 + tox_env: pyqt6 + name: PyQt6 steps: - uses: actions/checkout@v2 diff --git a/Orange/widgets/data/owcsvimport.py b/Orange/widgets/data/owcsvimport.py index b88fb5ad8ca..f9945838057 100644 --- a/Orange/widgets/data/owcsvimport.py +++ b/Orange/widgets/data/owcsvimport.py @@ -47,9 +47,10 @@ import numpy as np import pandas.errors import pandas as pd - from pandas.api import types as pdtypes +from orangewidget.utils import enum_as_int + import Orange.data from Orange.misc.collections import natural_sorted @@ -753,7 +754,7 @@ def update_buttons(cbindex): self.import_options_button, QDialogButtonBox.ActionRole ) button_box.setStyleSheet( - "button-layout: {:d};".format(QDialogButtonBox.MacLayout) + "button-layout: {:d};".format(enum_as_int(QDialogButtonBox.MacLayout)) ) self.controlArea.layout().addWidget(button_box) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum) diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py index 40da89cab56..9ab562d3acc 100644 --- a/Orange/widgets/data/owdatasets.py +++ b/Orange/widgets/data/owdatasets.py @@ -375,7 +375,7 @@ def __set_index(self, f): ) scw = self.view.setColumnWidth - width = self.view.fontMetrics().width + width = self.view.fontMetrics().horizontalAdvance self.view.resizeColumnToContents(0) scw(self.Header.title, width("X" * 37)) scw(self.Header.size, 20 + max(width("888 bytes "), width("9999.9 MB "))) diff --git a/Orange/widgets/data/owdiscretize.py b/Orange/widgets/data/owdiscretize.py index d9b5de8fc79..2ee8d81a6fe 100644 --- a/Orange/widgets/data/owdiscretize.py +++ b/Orange/widgets/data/owdiscretize.py @@ -5,7 +5,7 @@ from AnyQt.QtCore import ( Qt, QTimer, QPoint, QItemSelectionModel, QSize, QAbstractListModel, - pyqtSignal as Signal) +) from AnyQt.QtGui import ( QValidator, QPalette, QDoubleValidator, QIntValidator, QColor) from AnyQt.QtWidgets import ( @@ -26,18 +26,6 @@ from Orange.widgets.widget import Input, Output from Orange.widgets.data.oweditdomain import FixedSizeButton - -# Remove this when we require PyQt 5.15 -if not hasattr(QButtonGroup, "idClicked"): - class QButtonGroup(QButtonGroup): # pylint: disable=function-redefined - idClicked = Signal(int) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.buttonClicked.connect( - lambda button: self.idClicked.emit(self.id(button))) - - re_custom_sep = re.compile(r"\s*,\s*") time_units = ["year", "month", "day", "week", "hour", "minute", "second"] INVALID_WIDTH = "invalid width" @@ -537,7 +525,6 @@ def __init__(self): self._create_buttons(box) gui.auto_apply(self.buttonsArea, self, "autosend") gui.rubber(self.buttonsArea) - self.varview.select_default() def _create_var_list(self, box): diff --git a/Orange/widgets/data/oweditdomain.py b/Orange/widgets/data/oweditdomain.py index fbf0df8f36f..a1ec4cd6108 100644 --- a/Orange/widgets/data/oweditdomain.py +++ b/Orange/widgets/data/oweditdomain.py @@ -514,7 +514,7 @@ def __init__(self, parent=None, **kwargs): self.unlink_var_cb.toggled.connect(self._set_unlink) form.addRow("", self.unlink_var_cb) - vlayout = QVBoxLayout(margin=0, spacing=1) + vlayout = QVBoxLayout(spacing=1) self.labels_edit = view = QTreeView( objectName="annotation-pairs-edit", rootIsDecorated=False, @@ -1121,7 +1121,7 @@ def __init__(self, *args, **kwargs): flags=Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable ) - vlayout = QVBoxLayout(spacing=1, margin=0) + vlayout = QVBoxLayout(spacing=1) self.values_edit = QListView( editTriggers=QListView.DoubleClicked | QListView.EditKeyPressed, selectionMode=QListView.ExtendedSelection, @@ -1137,7 +1137,7 @@ def __init__(self, *args, **kwargs): self.values_model.rowsMoved.connect(self.on_value_selection_changed) vlayout.addWidget(self.values_edit) - hlayout = QHBoxLayout(spacing=1, margin=0) + hlayout = QHBoxLayout(spacing=1) self.categories_action_group = group = QActionGroup( self, objectName="action-group-categories", enabled=False diff --git a/Orange/widgets/data/owfeatureconstructor.py b/Orange/widgets/data/owfeatureconstructor.py index 5413e144a5d..da1253c9ced 100644 --- a/Orange/widgets/data/owfeatureconstructor.py +++ b/Orange/widgets/data/owfeatureconstructor.py @@ -594,7 +594,7 @@ def generate_newname(fmt): toplayout.addWidget(self.editorstack, 10) # Layout for the list view - layout = QVBoxLayout(spacing=1, margin=0) + layout = QVBoxLayout(spacing=1) self.featuremodel = DescriptorModel(parent=self) self.featureview = QListView( diff --git a/Orange/widgets/data/owfile.py b/Orange/widgets/data/owfile.py index 02d9bce1827..7c3d8af75b0 100644 --- a/Orange/widgets/data/owfile.py +++ b/Orange/widgets/data/owfile.py @@ -214,7 +214,7 @@ def package(w): self.sheet_box = gui.hBox(None, addToLayout=False, margin=0) self.sheet_combo = QComboBox() - self.sheet_combo.activated[str].connect(self.select_sheet) + self.sheet_combo.textActivated.connect(self.select_sheet) self.sheet_combo.setSizePolicy(Policy.Expanding, Policy.Fixed) self.sheet_combo.setMinimumSize(QSize(50, 1)) self.sheet_label = QLabel() @@ -240,7 +240,8 @@ def package(w): url_combo.setEditable(True) url_combo.setInsertPolicy(url_combo.InsertAtTop) url_edit = url_combo.lineEdit() - l, t, r, b = url_edit.getTextMargins() + margins = url_edit.textMargins() + l, t, r, b = margins.left(), margins.top(), margins.right(), margins.bottom() url_edit.setTextMargins(l + 5, t, r, b) layout.addWidget(url_combo, 3, 1, 1, 3) url_combo.activated.connect(self._url_set) diff --git a/Orange/widgets/data/owgroupby.py b/Orange/widgets/data/owgroupby.py index 88dbe851952..c5f4916b9f1 100644 --- a/Orange/widgets/data/owgroupby.py +++ b/Orange/widgets/data/owgroupby.py @@ -24,6 +24,7 @@ from orangewidget.settings import ContextSetting, Setting from orangewidget.utils.listview import ListViewSearch from orangewidget.utils.signals import Input, Output +from orangewidget.utils import enum_as_int from orangewidget.widget import Msg from Orange.data import ( @@ -279,7 +280,7 @@ def nextCheckState(self) -> None: self.setCheckState(Qt.PartiallyChecked) # since checkbox state stay same signal is not emitted # automatically but we need a callback call so we emit it - self.stateChanged.emit(Qt.PartiallyChecked) + self.stateChanged.emit(enum_as_int(Qt.PartiallyChecked)) else: # self.checkState() == Qt.Unchecked # if unchecked: check if all can be checked else partially check self.setCheckState( diff --git a/Orange/widgets/data/owimpute.py b/Orange/widgets/data/owimpute.py index 7d8380e3f85..0b77fe60951 100644 --- a/Orange/widgets/data/owimpute.py +++ b/Orange/widgets/data/owimpute.py @@ -184,7 +184,7 @@ def __init__(self): box.layout().addLayout(box_layout) button_group = QButtonGroup() - button_group.buttonClicked[int].connect(self.set_default_method) + button_group.idClicked.connect(self.set_default_method) for i, (method, _) in enumerate(list(METHODS.items())[1:-1]): imputer = self.create_imputer(method) @@ -257,10 +257,11 @@ def set_default_time(datetime): self.selection = self.varview.selectionModel() box.layout().addWidget(self.varview) - vertical_layout = QVBoxLayout(margin=0) + vertical_layout = QVBoxLayout() self.methods_container = QWidget(enabled=False) - method_layout = QVBoxLayout(margin=0) + method_layout = QVBoxLayout() + method_layout.setContentsMargins(0, 0, 0, 0) self.methods_container.setLayout(method_layout) button_group = QButtonGroup() @@ -284,7 +285,7 @@ def set_default_time(datetime): value_stack.addWidget(self.value_double) method_layout.addWidget(value_stack) - button_group.buttonClicked[int].connect( + button_group.idClicked.connect( self.set_method_for_current_selection ) diff --git a/Orange/widgets/data/owpythonscript.py b/Orange/widgets/data/owpythonscript.py index b5476832153..5aa8a54257b 100644 --- a/Orange/widgets/data/owpythonscript.py +++ b/Orange/widgets/data/owpythonscript.py @@ -286,12 +286,12 @@ def paintEvent(self, event): p.drawRoundedRect(rect, 5, 5) p.restore() - textstart = (width - fm.width(self.indicator_text)) // 2 + textstart = (width - fm.horizontalAdvance(self.indicator_text)) // 2 p.drawText(textstart, height // 2 + 5, self.indicator_text) def minimumSizeHint(self): fm = QFontMetrics(self.font()) - width = round(fm.width(self.indicator_text)) + 10 + width = int(round(fm.horizontalAdvance(self.indicator_text)) + 10) height = fm.height() + 6 return QSize(width, height) @@ -637,7 +637,6 @@ def _(width): self.editorBox.layout().addWidget(return_stmt) self.editorBox.setAlignment(Qt.AlignVCenter) - self.text.setTabStopWidth(4) self.text.modificationChanged[bool].connect(self.onModificationChanged) diff --git a/Orange/widgets/data/owrank.py b/Orange/widgets/data/owrank.py index 45634864f56..de1a68c5052 100644 --- a/Orange/widgets/data/owrank.py +++ b/Orange/widgets/data/owrank.py @@ -345,7 +345,7 @@ def _set_select_manual(): grid.setContentsMargins(0, 0, 0, 0) grid.setSpacing(6) self.selectButtons = QButtonGroup() - self.selectButtons.buttonClicked[int].connect(self.setSelectionMethod) + self.selectButtons.idClicked.connect(self.setSelectionMethod) def button(text, buttonid, toolTip=None): b = QRadioButton(text) @@ -605,7 +605,8 @@ def headerClick(self, index): sort_column = self.ranksModel.sortColumn() - 2 # -2 for '#' (discrete count) column self.sorting = (sort_column, sort_order) - def methodSelectionChanged(self, state, method_name): + def methodSelectionChanged(self, state: int, method_name): + state = Qt.CheckState(state) if state == Qt.Checked: self.selected_methods.add(method_name) elif method_name in self.selected_methods: diff --git a/Orange/widgets/data/tests/test_owcreateinstance.py b/Orange/widgets/data/tests/test_owcreateinstance.py index cb0b504f26c..2e12876f689 100644 --- a/Orange/widgets/data/tests/test_owcreateinstance.py +++ b/Orange/widgets/data/tests/test_owcreateinstance.py @@ -373,6 +373,10 @@ def test_set_value(self): self.callback.assert_called_once() +def _datetime(y, m, d) -> QDateTime: + return QDateTime(QDate(y, m, d), QTime(0, 0)) + + class TestTimeVariableEditor(GuiTest): @classmethod def setUpClass(cls): @@ -387,13 +391,12 @@ def setUp(self): def test_init(self): self.assertEqual(self.editor.value, 0) - self.assertEqual(self.editor._edit.dateTime(), - QDateTime(QDate(1970, 1, 1))) + self.assertEqual(self.editor._edit.dateTime(), _datetime(1970, 1, 1)) self.callback.assert_not_called() def test_edit(self): """ Edit datetimeedit by user. """ - datetime = QDateTime(QDate(2001, 9, 9)) + datetime = _datetime(2001, 9, 9,) self.editor._edit.setDateTime(datetime) self.assertEqual(self.editor.value, 999993600) self.assertEqual(self.editor._edit.dateTime(), datetime) @@ -403,8 +406,7 @@ def test_set_value(self): """ Programmatically set datetimeedit value. """ value = 999993600 self.editor.value = value - self.assertEqual(self.editor._edit.dateTime(), - QDateTime(QDate(2001, 9, 9))) + self.assertEqual(self.editor._edit.dateTime(), _datetime(2001, 9, 9)) self.assertEqual(self.editor.value, value) self.callback.assert_called_once() @@ -415,8 +417,7 @@ def test_have_date_have_time(self): callback ) self.assertEqual(editor.value, 0) - self.assertEqual(self.editor._edit.dateTime(), - QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0))) + self.assertEqual(self.editor._edit.dateTime(), _datetime(1970, 1, 1)) self.callback.assert_not_called() datetime = QDateTime(QDate(2001, 9, 9), QTime(1, 2, 3)) @@ -431,8 +432,7 @@ def test_have_time(self): self.parent, TimeVariable("var", have_time=1), callback ) self.assertEqual(editor.value, 0) - self.assertEqual(self.editor._edit.dateTime(), - QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0))) + self.assertEqual(self.editor._edit.dateTime(), _datetime(1970, 1, 1)) self.callback.assert_not_called() datetime = QDateTime(QDate(1900, 1, 1), QTime(1, 2, 3)) @@ -445,8 +445,7 @@ def test_no_date_no_time(self): callback = Mock() editor = TimeVariableEditor(self.parent, TimeVariable("var"), callback) self.assertEqual(editor.value, 0) - self.assertEqual(self.editor._edit.dateTime(), - QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0))) + self.assertEqual(self.editor._edit.dateTime(), _datetime(1970, 1, 1)) self.callback.assert_not_called() datetime = QDateTime(QDate(2001, 9, 9), QTime(1, 2, 3)) diff --git a/Orange/widgets/data/tests/test_owfile.py b/Orange/widgets/data/tests/test_owfile.py index 8a533470ed7..f73ee3df1fe 100644 --- a/Orange/widgets/data/tests/test_owfile.py +++ b/Orange/widgets/data/tests/test_owfile.py @@ -12,7 +12,7 @@ import numpy as np import scipy.sparse as sp -from AnyQt.QtCore import QMimeData, QPoint, Qt, QUrl, QThread, QObject +from AnyQt.QtCore import QMimeData, QPoint, Qt, QUrl, QThread, QObject, QPointF from AnyQt.QtGui import QDragEnterEvent, QDropEvent from AnyQt.QtWidgets import QComboBox @@ -116,7 +116,7 @@ def _drop_event(self, url): data.setUrls([QUrl(url)]) return QDropEvent( - QPoint(0, 0), Qt.MoveAction, data, + QPointF(0, 0), Qt.MoveAction, data, Qt.NoButton, Qt.NoModifier, QDropEvent.Drop) def test_check_file_size(self): diff --git a/Orange/widgets/data/tests/test_owpivot.py b/Orange/widgets/data/tests/test_owpivot.py index 8391c2810d5..e086f342a3d 100644 --- a/Orange/widgets/data/tests/test_owpivot.py +++ b/Orange/widgets/data/tests/test_owpivot.py @@ -216,22 +216,29 @@ def test_select_by_click(self): simulate.combobox_activate_item(self.widget.controls.val_feature, self.heart_disease.domain[0].name) + def pos(row, col) -> QPoint: + model = view.model() + rect = view.visualRect( # pylint:disable=protected-access + model.index(row + view._n_leading_rows, + col + view._n_leading_cols)) + return rect.center() + # column in a group - QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=QPoint(208, 154)) + QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=pos(2, 0)) self.assertSetEqual({(3, 0), (2, 0)}, view.get_selection()) # column - QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=QPoint(340, 40)) + QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=pos(-1, 1)) self.assertSetEqual({(0, 1), (3, 1), (1, 1), (2, 1)}, view.get_selection()) # group - QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=QPoint(155, 75)) + QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=pos(0, -1)) self.assertSetEqual({(0, 1), (1, 0), (0, 0), (1, 1)}, view.get_selection()) # all - QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=QPoint(400, 198)) + QTest.mouseClick(view.viewport(), Qt.LeftButton, pos=pos(4, 2)) self.assertSetEqual({(0, 1), (0, 0), (3, 0), (3, 1), (2, 1), (2, 0), (1, 0), (1, 1)}, view.get_selection()) diff --git a/Orange/widgets/data/tests/test_owrank.py b/Orange/widgets/data/tests/test_owrank.py index 4e1a3f1e0fa..92c71a8b0b5 100644 --- a/Orange/widgets/data/tests/test_owrank.py +++ b/Orange/widgets/data/tests/test_owrank.py @@ -7,7 +7,8 @@ import numpy as np from sklearn.exceptions import ConvergenceWarning -from AnyQt.QtCore import Qt, QItemSelection, QItemSelectionModel +from AnyQt.QtCore import Qt, QItemSelection, QItemSelectionModel, \ + QT_VERSION_INFO from AnyQt.QtWidgets import QCheckBox, QApplication from orangewidget.settings import Context, IncompatibleContext @@ -353,6 +354,8 @@ def test_data_which_make_scorer_nan(self): self.widget.selected_methods.add('ANOVA') self.send_signal(self.widget.Inputs.data, table) + @unittest.skipIf(lambda: QT_VERSION_INFO < (6,), + "headerState is not restored in Qt6") def test_setting_migration_fixes_header_state(self): # Settings as of version 3.3.5 settings = { diff --git a/Orange/widgets/data/utils/pythoneditor/editor.py b/Orange/widgets/data/utils/pythoneditor/editor.py index 3dc6dac8aa4..1be74f6d261 100644 --- a/Orange/widgets/data/utils/pythoneditor/editor.py +++ b/Orange/widgets/data/utils/pythoneditor/editor.py @@ -23,7 +23,7 @@ from Orange.widgets.data.utils.pythoneditor.indenter import Indenter from Orange.widgets.data.utils.pythoneditor.lines import Lines from Orange.widgets.data.utils.pythoneditor.rectangularselection import RectangularSelection -from Orange.widgets.data.utils.pythoneditor.vim import Vim, isChar +from Orange.widgets.data.utils.pythoneditor.vim import Vim, isChar, code, key_code # pylint: disable=protected-access @@ -952,7 +952,7 @@ def typeOverwrite(text): # make action shortcuts override keyboard events (non-default Qt behaviour) for action in self.actions(): seq = action.shortcut() - if seq.count() == 1 and seq[0] == event.key() | int(event.modifiers()): + if seq.count() == 1 and key_code(seq[0]) == code(event): action.trigger() break else: @@ -1023,7 +1023,7 @@ def showEvent(self, ev): def _updateTabStopWidth(self): """Update tabstop width after font or indentation changed """ - self.setTabStopWidth(self.fontMetrics().horizontalAdvance(' ' * self._indenter.width)) + self.setTabStopDistance(self.fontMetrics().horizontalAdvance(' ' * self._indenter.width)) @property def lines(self): diff --git a/Orange/widgets/data/utils/pythoneditor/indenter.py b/Orange/widgets/data/utils/pythoneditor/indenter.py index 6ec237e3ef1..bf2fd326130 100644 --- a/Orange/widgets/data/utils/pythoneditor/indenter.py +++ b/Orange/widgets/data/utils/pythoneditor/indenter.py @@ -7,7 +7,7 @@ as published by the Free Software Foundation, version 2.1 of the license. This is compatible with Orange3's GPL-3.0 license. """ -from PyQt5.QtGui import QTextCursor +from AnyQt.QtGui import QTextCursor # pylint: disable=pointless-string-statement diff --git a/Orange/widgets/data/utils/pythoneditor/lines.py b/Orange/widgets/data/utils/pythoneditor/lines.py index 8e7d31cf887..bed65ec6bd2 100644 --- a/Orange/widgets/data/utils/pythoneditor/lines.py +++ b/Orange/widgets/data/utils/pythoneditor/lines.py @@ -7,7 +7,7 @@ as published by the Free Software Foundation, version 2.1 of the license. This is compatible with Orange3's GPL-3.0 license. """ -from PyQt5.QtGui import QTextCursor +from AnyQt.QtGui import QTextCursor # Lines class. # list-like object for access text document lines diff --git a/Orange/widgets/data/utils/pythoneditor/rectangularselection.py b/Orange/widgets/data/utils/pythoneditor/rectangularselection.py index 8dcc70eff54..1977649e55e 100644 --- a/Orange/widgets/data/utils/pythoneditor/rectangularselection.py +++ b/Orange/widgets/data/utils/pythoneditor/rectangularselection.py @@ -7,9 +7,9 @@ as published by the Free Software Foundation, version 2.1 of the license. This is compatible with Orange3's GPL-3.0 license. """ -from PyQt5.QtCore import Qt, QMimeData -from PyQt5.QtWidgets import QApplication, QTextEdit -from PyQt5.QtGui import QKeyEvent, QKeySequence, QPalette, QTextCursor +from AnyQt.QtCore import Qt, QMimeData +from AnyQt.QtWidgets import QApplication, QTextEdit +from AnyQt.QtGui import QKeyEvent, QKeySequence, QPalette, QTextCursor class RectangularSelection: @@ -71,7 +71,7 @@ def onExpandKeyEvent(self, keyEvent): self._qpart.cursorPosition[1]) self._start = (line, visibleColumn) modifiersWithoutAltShift = keyEvent.modifiers() & (~(Qt.AltModifier | Qt.ShiftModifier)) - newEvent = QKeyEvent(keyEvent.type(), + newEvent = QKeyEvent(QKeyEvent.Type(keyEvent.type()), keyEvent.key(), modifiersWithoutAltShift, keyEvent.text(), diff --git a/Orange/widgets/data/utils/pythoneditor/tests/base.py b/Orange/widgets/data/utils/pythoneditor/tests/base.py index 1fd3d4aaec5..704678f7b81 100644 --- a/Orange/widgets/data/utils/pythoneditor/tests/base.py +++ b/Orange/widgets/data/utils/pythoneditor/tests/base.py @@ -14,8 +14,11 @@ from AnyQt.QtTest import QTest from AnyQt.QtCore import Qt, QCoreApplication +from orangewidget.utils import enum_as_int + from Orange.widgets import widget from Orange.widgets.data.utils.pythoneditor.editor import PythonEditor +from Orange.widgets.data.utils.pythoneditor.vim import key_code def _processPendingEvents(app): @@ -23,7 +26,7 @@ def _processPendingEvents(app): Timeout is used, because on Windows hasPendingEvents() always returns True """ t = time.time() - while app.hasPendingEvents() and (time.time() - t < 0.1): + while time.time() - t < 0.1: app.processEvents() @@ -68,12 +71,12 @@ def keySequenceClicks(widget_, keySequence, extraModifiers=Qt.NoModifier): # pylint: disable=line-too-long # This is based on a simplified version of http://stackoverflow.com/questions/14034209/convert-string-representation-of-keycode-to-qtkey-or-any-int-and-back. I added code to handle the case in which the resulting key contains a modifier (for example, Shift+Home). When I execute QTest.keyClick(widget, keyWithModifier), I get the error "ASSERT: "false" in file .\qasciikey.cpp, line 495". To fix this, the following code splits the key into a key and its modifier. # Bitmask for all modifier keys. - modifierMask = int(Qt.ShiftModifier | Qt.ControlModifier | Qt.AltModifier | - Qt.MetaModifier | Qt.KeypadModifier) + modifierMask = enum_as_int(Qt.KeyboardModifierMask) ks = QKeySequence(keySequence) # For now, we don't handle a QKeySequence("Ctrl") or any other modified by itself. assert ks.count() > 0 for _, key in enumerate(ks): - modifiers = Qt.KeyboardModifiers((key & modifierMask) | extraModifiers) + key = key_code(key) + modifiers = Qt.KeyboardModifiers((key & modifierMask) | enum_as_int(extraModifiers)) key = key & ~modifierMask - QTest.keyClick(widget_, key, modifiers, 10) + QTest.keyClick(widget_, Qt.Key(key), modifiers, 10) diff --git a/Orange/widgets/data/utils/pythoneditor/vim.py b/Orange/widgets/data/utils/pythoneditor/vim.py index 65d45579c44..a14da7b50cb 100644 --- a/Orange/widgets/data/utils/pythoneditor/vim.py +++ b/Orange/widgets/data/utils/pythoneditor/vim.py @@ -9,9 +9,11 @@ """ import sys -from PyQt5.QtCore import Qt, pyqtSignal, QObject -from PyQt5.QtWidgets import QTextEdit -from PyQt5.QtGui import QColor, QTextCursor +from AnyQt.QtCore import Qt, pyqtSignal, QObject +from AnyQt.QtWidgets import QTextEdit +from AnyQt.QtGui import QColor, QTextCursor + +from orangewidget.utils import enum_as_int # pylint: disable=protected-access # pylint: disable=unused-argument @@ -24,38 +26,46 @@ for charCode in range(ord('a'), ord('z') + 1): shortName = chr(charCode) longName = 'Key_' + shortName.upper() - qtCode = getattr(Qt, longName) + qtCode = enum_as_int(getattr(Qt, longName)) setattr(thismodule, '_' + shortName, qtCode) - setattr(thismodule, '_' + shortName.upper(), Qt.ShiftModifier + qtCode) - -_0 = Qt.Key_0 -_Dollar = Qt.ShiftModifier + Qt.Key_Dollar -_Percent = Qt.ShiftModifier + Qt.Key_Percent -_Caret = Qt.ShiftModifier + Qt.Key_AsciiCircum -_Esc = Qt.Key_Escape -_Insert = Qt.Key_Insert -_Down = Qt.Key_Down -_Up = Qt.Key_Up -_Left = Qt.Key_Left -_Right = Qt.Key_Right -_Space = Qt.Key_Space -_BackSpace = Qt.Key_Backspace -_Equal = Qt.Key_Equal -_Less = Qt.ShiftModifier + Qt.Key_Less -_Greater = Qt.ShiftModifier + Qt.Key_Greater -_Home = Qt.Key_Home -_End = Qt.Key_End -_PageDown = Qt.Key_PageDown -_PageUp = Qt.Key_PageUp -_Period = Qt.Key_Period -_Enter = Qt.Key_Enter -_Return = Qt.Key_Return + setattr(thismodule, '_' + shortName.upper(), enum_as_int(Qt.ShiftModifier) | qtCode) + + +def key_code(comb): + try: + return comb.toCombined() + except AttributeError: + return enum_as_int(comb) + + +_0 = key_code(Qt.Key_0) +_Dollar = key_code(Qt.ShiftModifier | Qt.Key_Dollar) +_Percent = key_code(Qt.ShiftModifier | Qt.Key_Percent) +_Caret = key_code(Qt.ShiftModifier | Qt.Key_AsciiCircum) +_Esc = key_code(Qt.Key_Escape) +_Insert = key_code(Qt.Key_Insert) +_Down = key_code(Qt.Key_Down) +_Up = key_code(Qt.Key_Up) +_Left = key_code(Qt.Key_Left) +_Right = key_code(Qt.Key_Right) +_Space = key_code(Qt.Key_Space) +_BackSpace = key_code(Qt.Key_Backspace) +_Equal = key_code(Qt.Key_Equal) +_Less = key_code(Qt.ShiftModifier | Qt.Key_Less) +_Greater = key_code(Qt.ShiftModifier | Qt.Key_Greater) +_Home = key_code(Qt.Key_Home) +_End = key_code(Qt.Key_End) +_PageDown = key_code(Qt.Key_PageDown) +_PageUp = key_code(Qt.Key_PageUp) +_Period = key_code(Qt.Key_Period) +_Enter = key_code(Qt.Key_Enter) +_Return = key_code(Qt.Key_Return) def code(ev): modifiers = ev.modifiers() modifiers &= ~Qt.KeypadModifier # ignore keypad modifier to handle both main and numpad numbers - return int(modifiers) + ev.key() + return enum_as_int(modifiers) | ev.key() def isChar(ev): diff --git a/Orange/widgets/evaluate/tests/test_owcalibrationplot.py b/Orange/widgets/evaluate/tests/test_owcalibrationplot.py index 9819e455b4c..a3aed51098f 100644 --- a/Orange/widgets/evaluate/tests/test_owcalibrationplot.py +++ b/Orange/widgets/evaluate/tests/test_owcalibrationplot.py @@ -9,12 +9,13 @@ from sklearn.exceptions import ConvergenceWarning +from orangewidget.utils.combobox import qcombobox_emit_activated + from Orange.data import Table, DiscreteVariable, Domain, ContinuousVariable import Orange.evaluation import Orange.classification from Orange.evaluation import Results from Orange.evaluation.performance_curves import Curves - from Orange.widgets.evaluate.tests.base import EvaluateTest from Orange.widgets.evaluate.owcalibrationplot import OWCalibrationPlot from Orange.widgets.tests.base import WidgetTest @@ -141,8 +142,7 @@ def test_regression_input_error(self): @staticmethod def _set_combo(combo, val): combo.setCurrentIndex(val) - combo.activated[int].emit(val) - combo.activated[str].emit(combo.currentText()) + qcombobox_emit_activated(combo, val) @staticmethod def _set_radio_buttons(radios, val): diff --git a/Orange/widgets/gui.py b/Orange/widgets/gui.py index 59052f914bf..5df440a391f 100644 --- a/Orange/widgets/gui.py +++ b/Orange/widgets/gui.py @@ -650,7 +650,8 @@ def __init__(self, *args, **kwargs): self.horizontalScrollBar().setSingleStep(20) def wheelEvent(self, event: QWheelEvent): - if event.source() == Qt.MouseEventNotSynthesized and \ + if hasattr(event, "source") and \ + event.source() == Qt.MouseEventNotSynthesized and \ (event.modifiers() & Qt.ShiftModifier and sys.platform == 'darwin' or event.modifiers() & Qt.AltModifier and sys.platform != 'darwin'): new_event = QWheelEvent( diff --git a/Orange/widgets/model/tests/test_owrandomforest.py b/Orange/widgets/model/tests/test_owrandomforest.py index 32d10e98dfe..c360f06a9f6 100644 --- a/Orange/widgets/model/tests/test_owrandomforest.py +++ b/Orange/widgets/model/tests/test_owrandomforest.py @@ -1,5 +1,6 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring +from AnyQt.QtCore import Qt from Orange.data import Table from Orange.widgets.model.owrandomforest import OWRandomForest from Orange.widgets.tests.base import ( @@ -26,8 +27,8 @@ def test_parameters_checked(self): """Check learner and model for various values of all parameters when all properties are checked """ - self.widget.max_features_spin[0].setCheckState(True) - self.widget.max_depth_spin[0].setCheckState(True) + self.widget.max_features_spin[0].setCheckState(Qt.Checked) + self.widget.max_depth_spin[0].setCheckState(Qt.Checked) self.parameters.extend([ ParameterMapping("max_features", self.widget.max_features_spin[1]), ParameterMapping("max_depth", self.widget.max_depth_spin[1])]) @@ -37,7 +38,7 @@ def test_parameters_unchecked(self): """Check learner and model for various values of all parameters when properties are not checked """ - self.widget.min_samples_split_spin[0].setCheckState(False) + self.widget.min_samples_split_spin[0].setCheckState(Qt.Unchecked) self.parameters = self.parameters[:1] self.parameters.extend([ DefaultParameterMapping("max_features", "auto"), diff --git a/Orange/widgets/model/tests/test_owsvm.py b/Orange/widgets/model/tests/test_owsvm.py index af84ec49351..38f231f85a6 100644 --- a/Orange/widgets/model/tests/test_owsvm.py +++ b/Orange/widgets/model/tests/test_owsvm.py @@ -2,6 +2,7 @@ # pylint: disable=missing-docstring from scipy.sparse import csr_matrix +from AnyQt.QtCore import Qt from Orange.widgets.model.owsvm import OWSVM from Orange.widgets.tests.base import ( WidgetTest, @@ -47,7 +48,7 @@ def test_parameters_unchecked(self): """Check learner and model for various values of all parameters when Iteration limit is not checked """ - self.widget.max_iter_spin[0].setCheckState(False) + self.widget.max_iter_spin[0].setCheckState(Qt.Unchecked) self.parameters[-1] = DefaultParameterMapping("max_iter", -1) self.test_parameters() diff --git a/Orange/widgets/model/tests/test_tree.py b/Orange/widgets/model/tests/test_tree.py index 2f3d919d39b..d9702e7578b 100644 --- a/Orange/widgets/model/tests/test_tree.py +++ b/Orange/widgets/model/tests/test_tree.py @@ -1,5 +1,6 @@ # pylint: disable=protected-access import numpy as np +from AnyQt.QtCore import Qt from Orange.base import Model from Orange.data import Table @@ -35,7 +36,7 @@ def test_parameters_unchecked(self): when pruning parameters are not checked """ for cb in self.checks: - cb.setCheckState(False) + cb.setCheckState(Qt.Unchecked) self.parameters = [DefaultParameterMapping(par.name, val) for par, val in zip(self.parameters, (None, 2, 1))] self.test_parameters() diff --git a/Orange/widgets/tests/base.py b/Orange/widgets/tests/base.py index 5c6016f5469..6960172a9e4 100644 --- a/Orange/widgets/tests/base.py +++ b/Orange/widgets/tests/base.py @@ -7,8 +7,8 @@ import numpy as np import scipy.sparse as sp +from AnyQt.QtCore import Qt, QRectF, QPointF from AnyQt.QtGui import QFont, QTextDocumentFragment -from AnyQt.QtCore import QRectF, QPointF from AnyQt.QtTest import QSignalSpy from AnyQt.QtWidgets import ( QComboBox, QSpinBox, QDoubleSpinBox, QSlider @@ -691,7 +691,8 @@ def test_class_density(self, timeout=DEFAULT_TIMEOUT): def test_dragging_tooltip(self): """Dragging tooltip depends on data being jittered""" - text = QTextDocumentFragment.fromHtml(self.widget.graph.tiptexts[0]).toPlainText() + text = QTextDocumentFragment.fromHtml( + self.widget.graph.tiptexts[Qt.NoModifier]).toPlainText() self.send_signal(self.widget.Inputs.data, Table("heart_disease")) self.assertEqual(self.widget.graph.tip_textitem.toPlainText(), text) diff --git a/Orange/widgets/tests/utils.py b/Orange/widgets/tests/utils.py index 91ad53b9d66..51a0d816b74 100644 --- a/Orange/widgets/tests/utils.py +++ b/Orange/widgets/tests/utils.py @@ -1,305 +1,21 @@ -import sys from functools import wraps -import warnings -import contextlib +from AnyQt.QtCore import Qt, QLocale, QPoint, QT_VERSION_INFO +from AnyQt.QtTest import QTest +from AnyQt.QtGui import QContextMenuEvent +from AnyQt.QtWidgets import QApplication, QWidget, QButtonGroup -from AnyQt.QtCore import ( - Qt, QObject, QEventLoop, QTimer, QLocale, QPoint, QT_VERSION_INFO +from orangewidget.tests.utils import ( + simulate, excepthook_catch, EventSpy, mouseMove ) -from AnyQt.QtTest import QTest -from AnyQt.QtGui import QMouseEvent, QContextMenuEvent -from AnyQt.QtWidgets import QApplication, QWidget, QButtonGroup, QComboBox from Orange.data import Table, Domain, ContinuousVariable - -class EventSpy(QObject): - """ - A testing utility class (similar to QSignalSpy) to record events - delivered to a QObject instance. - - Note - ---- - Only event types can be recorded (as QEvent instances are deleted - on delivery). - - Note - ---- - Can only be used with a QCoreApplication running. - - Parameters - ---------- - object : QObject - An object whose events need to be recorded. - etype : Union[QEvent.Type, Sequence[QEvent.Type] - A event type (or types) that should be recorded - """ - def __init__(self, object, etype, **kwargs): - super().__init__(**kwargs) - if not isinstance(object, QObject): - raise TypeError - - self.__object = object - try: - len(etype) - except TypeError: - etypes = {etype} - else: - etypes = set(etype) - - self.__etypes = etypes - self.__record = [] - self.__loop = QEventLoop() - self.__timer = QTimer(self, singleShot=True) - self.__timer.timeout.connect(self.__loop.quit) - self.__object.installEventFilter(self) - - def wait(self, timeout=5000): - """ - Start an event loop that runs until a spied event or a timeout occurred. - - Parameters - ---------- - timeout : int - Timeout in milliseconds. - - Returns - ------- - res : bool - True if the event occurred and False otherwise. - - Example - ------- - >>> app = QCoreApplication.instance() or QCoreApplication([]) - >>> obj = QObject() - >>> spy = EventSpy(obj, QEvent.User) - >>> app.postEvent(obj, QEvent(QEvent.User)) - >>> spy.wait() - True - >>> print(spy.events()) - [1000] - """ - count = len(self.__record) - self.__timer.stop() - self.__timer.setInterval(timeout) - self.__timer.start() - self.__loop.exec() - self.__timer.stop() - return len(self.__record) != count - - def eventFilter(self, reciever, event): - if reciever is self.__object and event.type() in self.__etypes: - self.__record.append(event.type()) - if self.__loop.isRunning(): - self.__loop.quit() - return super().eventFilter(reciever, event) - - def events(self): - """ - Return a list of all (listened to) event types that occurred. - - Returns - ------- - events : List[QEvent.Type] - """ - return list(self.__record) - - -@contextlib.contextmanager -def excepthook_catch(raise_on_exit=True): - """ - Override `sys.excepthook` with a custom handler to record unhandled - exceptions. - - Use this to capture or note exceptions that are raised and - unhandled within PyQt slots or virtual function overrides. - - Note - ---- - The exceptions are still dispatched to the original `sys.excepthook` - - Parameters - ---------- - raise_on_exit : bool - If True then the (first) exception that was captured will be - reraised on context exit - - Returns - ------- - ctx : ContextManager - A context manager - - Example - ------- - >>> class Obj(QObject): - ... signal = pyqtSignal() - ... - >>> o = Obj() - >>> o.signal.connect(lambda : 1/0) - >>> with excepthook_catch(raise_on_exit=False) as exc_list: - ... o.signal.emit() - ... - >>> print(exc_list) # doctest: +ELLIPSIS - [(, ZeroDivisionError('division by zero',), ... - """ - excepthook = sys.excepthook - seen = [] - - def excepthook_handle(exctype, value, traceback): - seen.append((exctype, value, traceback)) - excepthook(exctype, value, traceback) - - sys.excepthook = excepthook_handle - shouldraise = raise_on_exit - try: - yield seen - except BaseException: - # propagate/preserve exceptions from within the ctx - shouldraise = False - raise - finally: - if sys.excepthook == excepthook_handle: - sys.excepthook = excepthook - else: - raise RuntimeError( - "The sys.excepthook that was installed by " - "'excepthook_catch' context at enter is not " - "the one present at exit.") - if shouldraise and seen: - raise seen[0][1] - - -class simulate: - """ - Utility functions for simulating user interactions with Qt widgets. - """ - @staticmethod - def combobox_run_through_all(cbox, delay=-1, callback=None): - """ - Run through all items in a given combo box, simulating the user - focusing the combo box and pressing the Down arrow key activating - all the items on the way. - - Unhandled exceptions from invoked PyQt slots/virtual function overrides - are captured and reraised. - - Parameters - ---------- - cbox : QComboBox - delay : int - Run the event loop after the simulated key press (-1, the default, - means no delay) - callback : callable - A callback that will be executed after every item change. Takes no - parameters. - - See Also - -------- - QTest.keyClick - """ - assert cbox.focusPolicy() & Qt.TabFocus - cbox.setFocus(Qt.TabFocusReason) - cbox.setCurrentIndex(-1) - for i in range(cbox.count()): - with excepthook_catch() as exlist: - QTest.keyClick(cbox, Qt.Key_Down, delay=delay) - if callback: - callback() - if exlist: - raise exlist[0][1] from exlist[0][1] - - @staticmethod - def combobox_activate_index(cbox, index, delay=-1): - """ - Activate an item at `index` in a given combo box. - - The item at index **must** be enabled and selectable. - - Parameters - ---------- - cbox : QComboBox - index : int - delay : int - Run the event loop after the signals are emitted for `delay` - milliseconds (-1, the default, means no delay). - """ - assert 0 <= index < cbox.count() - model = cbox.model() - column = cbox.modelColumn() - root = cbox.rootModelIndex() - mindex = model.index(index, column, root) - assert mindex.flags() & Qt.ItemIsEnabled - cbox.setCurrentIndex(index) - # QComboBox does not have an interface which would allow selecting - # the current item as if a user would. Only setCurrentIndex which - # does not emit the activated signals. - qcombobox_emit_activated(cbox, index) - if delay >= 0: - QTest.qWait(delay) - - @staticmethod - def combobox_index_of(cbox, value, role=Qt.DisplayRole): - """ - Find the index of an **selectable** item in a combo box whose `role` - data contains the given `value`. - - Parameters - ---------- - cbox : QComboBox - value : Any - role : Qt.ItemDataRole - - Returns - ------- - index : int - An index such that `cbox.itemData(index, role) == value` **and** - the item is enabled for selection or -1 if such an index could - not be found. - """ - model = cbox.model() - column = cbox.modelColumn() - root = cbox.rootModelIndex() - for i in range(model.rowCount(root)): - index = model.index(i, column, root) - if index.data(role) == value and \ - index.flags() & Qt.ItemIsEnabled: - pos = i - break - else: - pos = -1 - return pos - - @staticmethod - def combobox_activate_item(cbox, value, role=Qt.DisplayRole, delay=-1): - """ - Find an **selectable** item in a combo box whose `role` data - contains the given value and activate it. - - Raise an ValueError if the item could not be found. - - Parameters - ---------- - cbox : QComboBox - value : Any - role : Qt.ItemDataRole - delay : int - Run the event loop after the signals are emitted for `delay` - milliseconds (-1, the default, means no delay). - """ - index = simulate.combobox_index_of(cbox, value, role) - if index < 0: - raise ValueError("{!r} not in {}".format(value, cbox)) - simulate.combobox_activate_index(cbox, index, delay) - - -def qcombobox_emit_activated(cb: QComboBox, index: int): - cb.activated[int].emit(index) - text = cb.itemText(index) - if QT_VERSION_INFO >= (5, 15): - cb.textActivated.emit(text) - if QT_VERSION_INFO < (6, 0): - cb.activated[str].emit(text) +# pylint: disable=self-assigning-variable,invalid-name +EventSpy = EventSpy +excepthook_catch = excepthook_catch +simulate = simulate +mouseMove = mouseMove def qbuttongroup_emit_clicked(bg: QButtonGroup, id_: int): @@ -324,18 +40,6 @@ def wrap(*args, **kwargs): return wrapper -def mouseMove(widget, pos=QPoint(), delay=-1): # pragma: no-cover - # Like QTest.mouseMove, but functional without QCursor.setPos - if pos.isNull(): - pos = widget.rect().center() - me = QMouseEvent(QMouseEvent.MouseMove, pos, widget.mapToGlobal(pos), - Qt.NoButton, Qt.MouseButtons(0), Qt.NoModifier) - if delay > 0: - QTest.qWait(delay) - - QApplication.sendEvent(widget, me) - - def contextMenu( widget: QWidget, pos=QPoint(), reason=QContextMenuEvent.Mouse, modifiers=Qt.NoModifier, delay=-1 diff --git a/Orange/widgets/unsupervised/owdistancematrix.py b/Orange/widgets/unsupervised/owdistancematrix.py index c043b0d52e1..900e9e77299 100644 --- a/Orange/widgets/unsupervised/owdistancematrix.py +++ b/Orange/widgets/unsupervised/owdistancematrix.py @@ -86,12 +86,12 @@ def data(self, index, role=Qt.DisplayRole): if role == FixedFormatNumericColumnDelegate.ColumnDataSpanRole: return 0., self.span if row == col and self.zero_diag: - if role == Qt.BackgroundColorRole and self.variable: + if role == Qt.BackgroundRole and self.variable: return self.color_for_label(row, 200) return if role == Qt.DisplayRole: return float(self.distances[row, col]) - if role == Qt.BackgroundColorRole: + if role == Qt.BackgroundRole: return self.color_for_cell(row, col) if role == Qt.ForegroundRole: return QColor(Qt.black) # the background is light-ish diff --git a/Orange/widgets/utils/colorpalette.py b/Orange/widgets/utils/colorpalette.py index a08472d8c7d..e595f86fed0 100644 --- a/Orange/widgets/utils/colorpalette.py +++ b/Orange/widgets/utils/colorpalette.py @@ -915,7 +915,7 @@ def __init__(self, master=None, parent=None, label=None, color=None): def setColor(self, color): self.color = color palette = QPalette() - palette.setBrush(QPalette.Background, color) + palette.setBrush(QPalette.Window, color) self.icon.setPalette(palette) def getColor(self): diff --git a/Orange/widgets/utils/dendrogram.py b/Orange/widgets/utils/dendrogram.py index d7ddca99142..6624d099124 100644 --- a/Orange/widgets/utils/dendrogram.py +++ b/Orange/widgets/utils/dendrogram.py @@ -347,7 +347,7 @@ def setRoot(self, root): self.clear() self._root = root if root is not None: - foreground = self.palette().color(QPalette.Foreground) + foreground = self.palette().color(QPalette.WindowText) pen = make_pen(foreground, width=1, cosmetic=True, join_style=Qt.MiterJoin) for node in postorder(root): @@ -445,7 +445,7 @@ def branches(item): if self._highlighted_item: # Restore the previous item - highlight = self.palette().color(QPalette.Foreground) + highlight = self.palette().color(QPalette.WindowText) set_pen(self._highlighted_item, make_pen(highlight, width=1, cosmetic=True)) @@ -856,7 +856,7 @@ def branches(item): for it in postorder(item, branches): it.setPen(update_pen(it.pen(), brush=color)) if self._root is not None: - foreground = self.palette().color(QPalette.Foreground) + foreground = self.palette().color(QPalette.WindowText) item = self.item(self._root) set_color(item, foreground) highlight = self.palette().color(QPalette.Highlight) diff --git a/Orange/widgets/utils/domaineditor.py b/Orange/widgets/utils/domaineditor.py index d9f9b016540..7be5c5014c5 100644 --- a/Orange/widgets/utils/domaineditor.py +++ b/Orange/widgets/utils/domaineditor.py @@ -161,7 +161,7 @@ def hidePopup(me): self.view.closeEditor(me, self.NoHint) combo = Combo(parent) - combo.highlighted[str].connect(combo.highlight) + combo.textHighlighted.connect(combo.highlight) return combo diff --git a/Orange/widgets/utils/graphicspixmapwidget.py b/Orange/widgets/utils/graphicspixmapwidget.py index aff86b77852..09934b97845 100644 --- a/Orange/widgets/utils/graphicspixmapwidget.py +++ b/Orange/widgets/utils/graphicspixmapwidget.py @@ -112,7 +112,9 @@ def paint( exposedcrect = crect.intersected(exposed) pixmaptransform = self.pixmapTransform() # map exposed rect to exposed pixmap coords - assert pixmaptransform.type() <= QTransform.TxRotate + assert pixmaptransform.type() in ( + QTransform.TxNone, QTransform.TxTranslate, QTransform.TxScale + ) pixmaptransform, ok = pixmaptransform.inverted() if not ok: painter.drawPixmap( diff --git a/Orange/widgets/utils/graphicsview.py b/Orange/widgets/utils/graphicsview.py index 01c41b898a5..93e7805c6a5 100644 --- a/Orange/widgets/utils/graphicsview.py +++ b/Orange/widgets/utils/graphicsview.py @@ -9,6 +9,8 @@ from AnyQt.QtCore import ( pyqtSignal as Signal, pyqtProperty as Property, pyqtSlot as Slot ) +from orangecanvas.utils import qsizepolicy_is_expanding, \ + qsizepolicy_is_shrinking from Orange.widgets.utils.graphicslayoutitem import scaled @@ -282,9 +284,10 @@ def adjusted_size( elif policy == QSizePolicy.Ignored: return min(max(available, minimum), maximum) size = hint - if policy & QSizePolicy.ExpandFlag and hint < available: + + if qsizepolicy_is_expanding(policy) and hint < available: size = min(max(size, available), maximum) - if policy & QSizePolicy.ShrinkFlag and hint > available: + if qsizepolicy_is_shrinking(policy) and hint > available: size = max(min(size, available), minimum) return size diff --git a/Orange/widgets/utils/headerview.py b/Orange/widgets/utils/headerview.py index 75fc474ab70..41f9782e2b2 100644 --- a/Orange/widgets/utils/headerview.py +++ b/Orange/widgets/utils/headerview.py @@ -107,8 +107,8 @@ def initStyleOptionForIndex( else defaultAlignment) option.section = logicalIndex - option.state = QStyle.State(int(option.state) | int(state)) - option.textAlignment = Qt.Alignment(int(textAlignment)) + option.state = QStyle.State(option.state | state) + option.textAlignment = Qt.Alignment(textAlignment) option.iconAlignment = Qt.AlignVCenter text = model.headerData(logicalIndex, self.orientation(), diff --git a/Orange/widgets/utils/stickygraphicsview.py b/Orange/widgets/utils/stickygraphicsview.py index dee2a042958..d228c0f5edb 100644 --- a/Orange/widgets/utils/stickygraphicsview.py +++ b/Orange/widgets/utils/stickygraphicsview.py @@ -24,7 +24,7 @@ def __init__(self, *args, **kwargs) -> None: ds = QGraphicsDropShadowEffect( parent=self, objectName="sticky-view-shadow", - color=palette.color(QPalette.Foreground), + color=palette.color(QPalette.WindowText), blurRadius=15, offset=QPointF(0, 0), enabled=True @@ -38,7 +38,7 @@ def changeEvent(self, event: QEvent) -> None: QGraphicsDropShadowEffect, "sticky-view-shadow") if effect is not None: palette = self.palette() - effect.setColor(palette.color(QPalette.Foreground)) + effect.setColor(palette.color(QPalette.WindowText)) def eventFilter(self, recv: QObject, event: QEvent) -> bool: if event.type() in (QEvent.Show, QEvent.Hide) and recv is self.widget(): @@ -151,8 +151,10 @@ def setupViewport(self, widget: QWidget) -> None: sizePolicy=sp, visible=False, ) - over.setLayout(QVBoxLayout(margin=0)) - over.layout().addWidget(header) + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(header) + over.setLayout(layout) over.setWidget(widget) over = _OverlayWidget( @@ -161,8 +163,10 @@ def setupViewport(self, widget: QWidget) -> None: sizePolicy=sp, visible=False ) - over.setLayout(QVBoxLayout(margin=0)) - over.layout().addWidget(footer) + layout = QVBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(footer) + over.setLayout(layout) over.setWidget(widget) def bind(source: QScrollBar, target: QScrollBar) -> None: diff --git a/Orange/widgets/utils/tests/test_graphicspixmapwidget.py b/Orange/widgets/utils/tests/test_graphicspixmapwidget.py new file mode 100644 index 00000000000..dd9d8721556 --- /dev/null +++ b/Orange/widgets/utils/tests/test_graphicspixmapwidget.py @@ -0,0 +1,37 @@ +from AnyQt.QtCore import Qt, QSize, QSizeF +from AnyQt.QtGui import QPixmap +from AnyQt.QtWidgets import QGraphicsScene, QGraphicsView + +from orangewidget.tests.base import GuiTest +from Orange.widgets.utils.graphicspixmapwidget import GraphicsPixmapWidget + + +class TestGraphicsPixmapWidget(GuiTest): + def setUp(self) -> None: + super().setUp() + self.scene = QGraphicsScene() + self.view = QGraphicsView(self.scene) + + def tearDown(self) -> None: + self.scene.clear() + self.scene.deleteLater() + self.view.deleteLater() + del self.scene + del self.view + + def test_graphicspixmapwidget(self): + w = GraphicsPixmapWidget() + self.scene.addItem(w) + w.setPixmap(QPixmap(100, 100)) + p = w.pixmap() + self.assertEqual(p.size(), QSize(100, 100)) + self.view.grab() + w.setScaleContents(True) + w.setAspectRatioMode(Qt.KeepAspectRatio) + s = w.sizeHint(Qt.PreferredSize) + self.assertEqual(s, QSizeF(100., 100.)) + s = w.sizeHint(Qt.PreferredSize, QSizeF(200., -1.)) + self.assertEqual(s, QSizeF(200., 200.)) + s = w.sizeHint(Qt.PreferredSize, QSizeF(-1., 200.)) + self.assertEqual(s, QSizeF(200., 200.)) + self.view.grab() diff --git a/Orange/widgets/utils/tests/test_itemmodels.py b/Orange/widgets/utils/tests/test_itemmodels.py index cdc8806b5d2..a377d2b3bde 100644 --- a/Orange/widgets/utils/tests/test_itemmodels.py +++ b/Orange/widgets/utils/tests/test_itemmodels.py @@ -62,8 +62,8 @@ def test_data(self): def test_editable(self): editable_model = PyTableModel([[0]], editable=True) - self.assertFalse(int(self.model.flags(self.model.index(0, 0)) & Qt.ItemIsEditable)) - self.assertTrue(int(editable_model.flags(editable_model.index(0, 0)) & Qt.ItemIsEditable)) + self.assertFalse(bool(self.model.flags(self.model.index(0, 0)) & Qt.ItemIsEditable)) + self.assertTrue(bool(editable_model.flags(editable_model.index(0, 0)) & Qt.ItemIsEditable)) def test_sort(self): self.model.sort(1) diff --git a/Orange/widgets/utils/tests/test_stickygraphicsview.py b/Orange/widgets/utils/tests/test_stickygraphicsview.py index 9d5f1b468ea..db2cbc80e6b 100644 --- a/Orange/widgets/utils/tests/test_stickygraphicsview.py +++ b/Orange/widgets/utils/tests/test_stickygraphicsview.py @@ -121,16 +121,8 @@ def qWheelScroll( if pos.isNull(): pos = widget.rect().center() globalPos = widget.mapToGlobal(pos) - - if angleDelta.y() >= angleDelta.x(): - qt4orient = Qt.Vertical - qt4delta = angleDelta.y() - else: - qt4orient = Qt.Horizontal - qt4delta = angleDelta.x() - event = QWheelEvent( QPointF(pos), QPointF(globalPos), QPoint(), angleDelta, - qt4delta, qt4orient, buttons, modifiers + buttons, modifiers, Qt.NoScrollPhase, False ) QApplication.sendEvent(widget, event) diff --git a/Orange/widgets/utils/textimport.py b/Orange/widgets/utils/textimport.py index ffc82fbf06f..6d3fdfbbcfb 100644 --- a/Orange/widgets/utils/textimport.py +++ b/Orange/widgets/utils/textimport.py @@ -668,7 +668,7 @@ def __init__(self, *args, **kwargs): self.grouping_sep_edit_cb.setValidator( QRegularExpressionValidator(QRegularExpression(r"(\.|,| |')?"), self) ) - self.grouping_sep_edit_cb.activated[str].connect( + self.grouping_sep_edit_cb.textActivated.connect( self.__group_sep_activated) self.decimal_sep_edit_cb = TextEditCombo( @@ -680,7 +680,7 @@ def __init__(self, *args, **kwargs): self.decimal_sep_edit_cb.setValidator( QRegularExpressionValidator(QRegularExpression(r"(\.|,)"), self)) self.decimal_sep_edit_cb.addItems([".", ","]) - self.decimal_sep_edit_cb.activated[str].connect( + self.decimal_sep_edit_cb.textActivated.connect( self.__decimal_sep_activated) number_sep_layout.addWidget(QLabel("Grouping:")) @@ -731,7 +731,7 @@ def __init__(self, *args, **kwargs): objectName="-error-overlay", visible=False, ) - overlay.setLayout(QVBoxLayout(margin=0)) + overlay.setLayout(QVBoxLayout()) self.__error_label = label = QLabel(objectName="-error-text-label") overlay.layout().addWidget(label) overlay.setWidget(self.dataview.viewport()) @@ -1338,14 +1338,14 @@ def rowsInserted(self, parent, start, end): # type: (QModelIndex, int, int) -> None super().rowsInserted(parent, start, end) behavior = self.selectionBehavior() - if behavior & (QTableView.SelectColumns | QTableView.SelectRows): + if behavior in (QTableView.SelectColumns, QTableView.SelectRows): # extend the selection to the new rows smodel = self.selectionModel() selection = smodel.selection() command = QItemSelectionModel.Select - if behavior & QTableView.SelectRows: + if behavior == QTableView.SelectRows: command |= QItemSelectionModel.Rows - if behavior & QTableView.SelectColumns: + if behavior == QTableView.SelectColumns: command |= QItemSelectionModel.Columns smodel.select(selection, command) @@ -1436,7 +1436,7 @@ def initStyleOption(self, option, index): # type: (QStyleOptionViewItem, QModelIndex) -> None super().initStyleOption(option, index) palette = option.palette - shadow = palette.color(QPalette.Foreground) # type: QColor + shadow = palette.color(QPalette.WindowText) # type: QColor if shadow.isValid(): shadow.setAlphaF(0.1) option.backgroundBrush = QBrush(shadow, Qt.SolidPattern) diff --git a/Orange/widgets/visualize/owsilhouetteplot.py b/Orange/widgets/visualize/owsilhouetteplot.py index 19513e44e4f..30a67efc4b8 100644 --- a/Orange/widgets/visualize/owsilhouetteplot.py +++ b/Orange/widgets/visualize/owsilhouetteplot.py @@ -742,7 +742,7 @@ def __setup(self): font = self.font() font.setPixelSize(self.__barHeight) - foreground = self.palette().brush(QPalette.Foreground) + foreground = self.palette().brush(QPalette.WindowText) ax = AxisItem(parent=self, orientation="top", maxTickLength=7) ax.setRange(smin, smax) self.__topScale = ax @@ -1151,7 +1151,7 @@ def sizeHint(self, which, constraint=QRectF()): def paint(self, painter, option, widget=None): # type: (QPainter, QStyleOptionGraphicsItem, Optional[QWidget]) -> None palette = self.palette() # type: QPalette - color = palette.color(QPalette.Foreground) + color = palette.color(QPalette.WindowText) painter.setPen(QPen(color, 1)) rect = self.contentsRect() center = rect.center() diff --git a/Orange/widgets/visualize/owtreeviewer2d.py b/Orange/widgets/visualize/owtreeviewer2d.py index bace9ec1aea..7ca41395eb1 100644 --- a/Orange/widgets/visualize/owtreeviewer2d.py +++ b/Orange/widgets/visualize/owtreeviewer2d.py @@ -244,7 +244,6 @@ def __init__(self, scene, *args): self.setFocusPolicy(Qt.WheelFocus) self.setRenderHint(QPainter.Antialiasing) self.setRenderHint(QPainter.TextAntialiasing) - self.setRenderHint(QPainter.HighQualityAntialiasing) def resizeEvent(self, event): super().resizeEvent(event) diff --git a/Orange/widgets/visualize/tests/test_owbarplot.py b/Orange/widgets/visualize/tests/test_owbarplot.py index 69b437016a4..0ff75962a85 100644 --- a/Orange/widgets/visualize/tests/test_owbarplot.py +++ b/Orange/widgets/visualize/tests/test_owbarplot.py @@ -374,9 +374,9 @@ def assertSelectedIndices(self, indices, data=None, widget=None): indices = self.widget.grouped_indices_inverted self.assertSetEqual(set(widget.graph.selection), set(indices)) pens = widget.graph.bar_item.opts["pens"] - self.assertTrue(all([pen.style() == 2 for i, pen + self.assertTrue(all([pen.style() == Qt.DashLine for i, pen in enumerate(pens) if i in indices])) - self.assertTrue(all([pen.style() == 1 for i, pen + self.assertTrue(all([pen.style() == Qt.SolidLine for i, pen in enumerate(pens) if i not in indices])) diff --git a/Orange/widgets/visualize/tests/test_owdistributions.py b/Orange/widgets/visualize/tests/test_owdistributions.py index b0f8fdd971e..66c3ca2f1ea 100644 --- a/Orange/widgets/visualize/tests/test_owdistributions.py +++ b/Orange/widgets/visualize/tests/test_owdistributions.py @@ -6,6 +6,9 @@ import numpy as np from AnyQt.QtCore import QItemSelection, Qt +from AnyQt.QtWidgets import QCheckBox + +from orangewidget.utils.combobox import qcombobox_emit_activated from Orange.data import Table, Domain, DiscreteVariable from Orange.widgets.tests.base import WidgetTest @@ -22,14 +25,12 @@ def setUp(self): def _set_cvar(self, cvar): combo = self.widget.controls.cvar self.widget.cvar = cvar - combo.activated[int].emit(combo.currentIndex()) - combo.activated[str].emit(combo.currentText()) + qcombobox_emit_activated(combo, combo.currentIndex()) def _set_fitter(self, i): combo = self.widget.controls.fitted_distribution combo.setCurrentIndex(i) - combo.activated[int].emit(combo.currentIndex()) - combo.activated[str].emit(combo.currentText()) + qcombobox_emit_activated(combo, i) def _set_var(self, var): listview = self.widget.controls.var @@ -43,8 +44,9 @@ def _set_var(self, var): selectionmodel.selectionChanged.emit(newselection, oldselection) @staticmethod - def _set_check(checkbox, value): - checkbox.setCheckState(value) + def _set_check(checkbox: QCheckBox, value: bool): + state = Qt.Checked if value else Qt.Unchecked + checkbox.setCheckState(state) checkbox.toggled[bool].emit(value) def _set_slider(self, i): @@ -415,8 +417,6 @@ def test_controls_disabling(self): # changing them simultaneously doesn't significantly degrade the tests def test_plot_types_combinations(self): """Check that the widget doesn't crash at any plot combination""" - from AnyQt.QtWidgets import qApp - widget = self.widget c = widget.controls self.send_signal(widget.Inputs.data, self.iris) @@ -431,13 +431,11 @@ def test_plot_types_combinations(self): self._set_check(c.stacked_columns, b) self._set_check(c.show_probs, b) self._set_check(c.sort_by_freq, b) - qApp.processEvents() + widget.grab() # run layout and paint else: def test_plot_types_combinations(self): """Check that the widget doesn't crash at any plot combination""" # pylint: disable=too-many-nested-blocks - from AnyQt.QtWidgets import qApp - widget = self.widget c = widget.controls set_chk = self._set_check @@ -458,7 +456,7 @@ def test_plot_types_combinations(self): set_chk(c.stacked_columns, stack) set_chk(c.show_probs, show_probs) set_chk(c.sort_by_freq, sort_by_freq) - qApp.processEvents() + widget.grab() # run layout and paint def test_selection_grouping(self): """Widget groups consecutive selected bars""" diff --git a/Orange/widgets/visualize/tests/test_owmosaic.py b/Orange/widgets/visualize/tests/test_owmosaic.py index 577fdd5192b..262a8f441c3 100644 --- a/Orange/widgets/visualize/tests/test_owmosaic.py +++ b/Orange/widgets/visualize/tests/test_owmosaic.py @@ -4,7 +4,7 @@ import numpy as np -from AnyQt.QtCore import QEvent, QPoint, Qt +from AnyQt.QtCore import QEvent, QPointF, Qt from AnyQt.QtGui import QMouseEvent from Orange.data import Table, DiscreteVariable, Domain, ContinuousVariable, \ @@ -36,8 +36,8 @@ def test_empty_column(self): def _select_data(self): self.widget.select_area(1, QMouseEvent( - QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, - Qt.LeftButton, Qt.KeyboardModifiers())) + QEvent.MouseButtonPress, QPointF(), Qt.LeftButton, + Qt.LeftButton, Qt.NoModifier)) return [2, 3, 9, 23, 29, 30, 34, 35, 37, 42, 47, 49] def test_continuous_metas(self): @@ -150,8 +150,8 @@ def test_selection_setting(self): widget.select_area( 1, - QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, - Qt.LeftButton, Qt.KeyboardModifiers())) + QMouseEvent(QEvent.MouseButtonPress, QPointF(), Qt.LeftButton, + Qt.LeftButton, Qt.NoModifier)) # Changing the data must reset the selection self.send_signal(widget.Inputs.data, Table("titanic")) @@ -164,8 +164,8 @@ def test_selection_setting(self): widget.select_area( 1, - QMouseEvent(QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, - Qt.LeftButton, Qt.KeyboardModifiers())) + QMouseEvent(QEvent.MouseButtonPress, QPointF(), Qt.LeftButton, + Qt.LeftButton, Qt.NoModifier)) settings = self.widget.settingsHandler.pack_data(self.widget) # Setting data to None must reset the selection @@ -372,7 +372,7 @@ def test_max_attr_combo_1_disabling(self): vizrank.max_attrs = 0 simulate.combobox_activate_index(self.widget.controls.variable_color, 0) self.assertEqual(vizrank.max_attrs, 1) - self.assertEqual(int(model.item(0).flags() & enabled), 0) + self.assertEqual(model.item(0).flags() & enabled, Qt.NoItemFlags) simulate.combobox_activate_index(self.widget.controls.variable_color, 1) self.assertEqual(vizrank.max_attrs, 1) diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py index b2974508b1d..b5928674db9 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplot.py +++ b/Orange/widgets/visualize/tests/test_owscatterplot.py @@ -975,13 +975,13 @@ def test_add_line_calls_proper_regressor(self): graph.orthonormal_regression = True graph._add_line(x, y, c) - graph._orthonormal_line.assert_called_once_with(x, y, c, 3, 1) + graph._orthonormal_line.assert_called_once_with(x, y, c, 3, Qt.SolidLine) graph._regression_line.assert_not_called() graph._orthonormal_line.reset_mock() graph.orthonormal_regression = False graph._add_line(x, y, c) - graph._regression_line.assert_called_with(x, y, c, 3, 1) + graph._regression_line.assert_called_with(x, y, c, 3, Qt.SolidLine) graph._orthonormal_line.assert_not_called() def test_no_regression_line(self): diff --git a/Orange/widgets/visualize/tests/test_owscatterplotbase.py b/Orange/widgets/visualize/tests/test_owscatterplotbase.py index eeb8cdcf3f4..07a6baf4b66 100644 --- a/Orange/widgets/visualize/tests/test_owscatterplotbase.py +++ b/Orange/widgets/visualize/tests/test_owscatterplotbase.py @@ -1434,7 +1434,7 @@ def select(modifiers, indices): graph.update_labels.assert_not_called() self.master.selection_changed.assert_called_with() - select(0, [7, 8, 9]) + select(Qt.NoModifier, [7, 8, 9]) np.testing.assert_almost_equal( graph.selection, [0, 0, 0, 0, 0, 0, 0, 1, 1, 1]) @@ -1450,12 +1450,12 @@ def select(modifiers, indices): np.testing.assert_almost_equal( graph.selection, [0, 0, 0, 0, 2, 2, 1, 0, 1, 1]) - select(0, [1, 8]) + select(Qt.NoModifier, [1, 8]) np.testing.assert_almost_equal( graph.selection, [0, 1, 0, 0, 0, 0, 0, 0, 1, 0]) graph.label_only_selected = False - select(0, [3, 4]) + select(Qt.NoModifier, [3, 4]) def test_unselect_all(self): graph = self.graph diff --git a/Orange/widgets/visualize/tests/test_owsieve.py b/Orange/widgets/visualize/tests/test_owsieve.py index 3aab7721e04..3d6b21f28f0 100644 --- a/Orange/widgets/visualize/tests/test_owsieve.py +++ b/Orange/widgets/visualize/tests/test_owsieve.py @@ -6,7 +6,7 @@ import numpy as np -from AnyQt.QtCore import QEvent, QPoint, Qt +from AnyQt.QtCore import QEvent, QPointF, Qt from AnyQt.QtGui import QMouseEvent from Orange.data import ContinuousVariable, DiscreteVariable, Domain, Table @@ -79,8 +79,8 @@ def _select_data(self): self.widget.attr_x, self.widget.attr_y = self.data.domain[:2] area = self.widget.areas[0] self.widget.select_area(area, QMouseEvent( - QEvent.MouseButtonPress, QPoint(), Qt.LeftButton, - Qt.LeftButton, Qt.KeyboardModifiers())) + QEvent.MouseButtonPress, QPointF(), Qt.LeftButton, + Qt.LeftButton, Qt.NoModifier)) return [0, 4, 6, 7, 11, 17, 19, 21, 22, 24, 26, 39, 40, 43, 44, 46] def test_missing_values(self): diff --git a/Orange/widgets/visualize/utils/customizableplot.py b/Orange/widgets/visualize/utils/customizableplot.py index 6eb874ef255..e0c982478b9 100644 --- a/Orange/widgets/visualize/utils/customizableplot.py +++ b/Orange/widgets/visualize/utils/customizableplot.py @@ -27,7 +27,7 @@ def available_font_families() -> List: """ if not QApplication.instance(): _ = QApplication(sys.argv) - fonts = QFontDatabase().families() + fonts = QFontDatabase.families() default = default_font_family() defaults = [default] diff --git a/Orange/widgets/visualize/utils/heatmap.py b/Orange/widgets/visualize/utils/heatmap.py index 29782bb946e..4a02765d5bb 100644 --- a/Orange/widgets/visualize/utils/heatmap.py +++ b/Orange/widgets/visualize/utils/heatmap.py @@ -956,7 +956,7 @@ def __update_selection_geometry(self): self.__selection_manager.update_selection_rects() rects = self.__selection_manager.selection_rects palette = self.palette() - pen = QPen(palette.color(QPalette.Foreground), 2) + pen = QPen(palette.color(QPalette.WindowText), 2) pen.setCosmetic(True) brushcolor = QColor(palette.color(QPalette.Highlight)) brushcolor.setAlpha(50) diff --git a/Orange/widgets/visualize/utils/tests/test_customizableplot.py b/Orange/widgets/visualize/utils/tests/test_customizableplot.py index 5863ced5fa3..c3566e26482 100644 --- a/Orange/widgets/visualize/utils/tests/test_customizableplot.py +++ b/Orange/widgets/visualize/utils/tests/test_customizableplot.py @@ -12,13 +12,13 @@ def test_available_font_families(self): font.return_value.family = Mock(return_value="mock regular") db.return_value = Mock() - db.return_value.families = Mock( + db.families = Mock( return_value=["a", ".d", "e", ".b", "mock regular", "c"]) self.assertEqual(customizableplot.available_font_families(), ["mock regular", "", "a", ".b", "c", ".d", "e"]) db.return_value = Mock() - db.return_value.families = Mock( + db.families = Mock( return_value=["a", ".d", "e", ".b", "mock regular", "mock bold", "mock italic", "c", "mock semi"]) self.assertEqual(customizableplot.available_font_families(), diff --git a/requirements-gui.txt b/requirements-gui.txt index a7880c6961d..61a44d798d9 100644 --- a/requirements-gui.txt +++ b/requirements-gui.txt @@ -1,7 +1,7 @@ -orange-canvas-core>=0.1.24,<0.2a -orange-widget-base>=4.16.1 +orange-canvas-core>=0.1.26,<0.2a +orange-widget-base>=4.17.0 -AnyQt>=0.0.13 +AnyQt>=0.1.0 # ignore pyqtgraph 0.12.4 due to https://github.com/pyqtgraph/pyqtgraph/issues/2237 pyqtgraph>=0.12.2,!=0.12.4 diff --git a/tox.ini b/tox.ini index 4871fd44b25..226447346e6 100644 --- a/tox.ini +++ b/tox.ini @@ -40,9 +40,9 @@ deps = latest: https://github.com/pyqtgraph/pyqtgraph/archive/refs/heads/master.zip#egg=pyqtgraph latest: https://github.com/biolab/orange-canvas-core/archive/refs/heads/master.zip#egg=orange-canvas-core latest: https://github.com/biolab/orange-widget-base/archive/refs/heads/master.zip#egg=orange-widget-base - oldest: orange-canvas-core==0.1.24 - oldest: orange-widget-base==4.16.1 - oldest: AnyQt==0.0.13 + oldest: orange-canvas-core==0.1.26 + oldest: orange-widget-base==4.17.0 + oldest: AnyQt==0.1.0 oldest: pyqtgraph==0.11.1 oldest: matplotlib==2.2.5 oldest: qtconsole==4.7.2 @@ -80,6 +80,21 @@ commands = coverage combine coverage report +[testenv:pyqt6] +changedir = + {envsitepackagesdir} +setenv = + QT_API=PyQt6 + ANYQT_HOOK_DENY=pyqt5 +deps = + PyQt6==6.2.* + PyQt6-Qt6==6.2.* + PyQt6-WebEngine==6.2.* + PyQt6-WebEngine-Qt6==6.2.* + +commands = + python -m unittest Orange.widgets.tests + [testenv:add-ons] deps = {[testenv]deps}