diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 61928441..c3e99f91 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,7 +42,7 @@ jobs: shell: bash -el {0} run: | if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt install xvfb herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils + sudo apt install libegl1 xvfb herbstluftwm libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset sleep 3 sudo /sbin/start-stop-daemon --start --pidfile /tmp/custom_herbstluftwm_99.pid --make-pidfile --background --exec /usr/bin/herbstluftwm diff --git a/AGENTS.md b/AGENTS.md index 5479459e..c93a00b7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Overview -Badger is a PyQt5 GUI/CLI optimization tool for particle accelerators. It wraps the [Xopt](https://github.com/xopt-org/Xopt) library and uses a plugin system for environments and interfaces. The package name on PyPI/conda is `badger-opt`, but the Python import is `badger`. +Badger is a qtpy GUI/CLI optimization tool for particle accelerators. It wraps the [Xopt](https://github.com/xopt-org/Xopt) library and uses a plugin system for environments and interfaces. The package name on PyPI/conda is `badger-opt`, but the Python import is `badger`. ## Development Setup @@ -11,7 +11,7 @@ pip install -e ".[dev]" pre-commit install ``` -Python 3.11–3.13 only. PyQt5 is a hard dependency even for non-GUI code paths (`badger.utils` imports `PyQt5.QtWidgets` at the top level). +Python 3.11–3.13 only. qtpy is a hard dependency even for non-GUI code paths (`badger.utils` imports `qtpy.QtWidgets` at the top level). ## Running Tests @@ -144,7 +144,7 @@ Temporary runs (pre-archive) go to `BADGER_ARCHIVE_ROOT/.tmp/` with `.tmp-Badger ## GUI structure -The GUI is a single-window PyQt5 app (`BadgerMainWindow`) with: +The GUI is a single-window qtpy app (`BadgerMainWindow`) with: - One page: `home_page.py` (contains routine editor + run monitor) - Components in `gui/components/` — the naming doesn't always match the visual element (see `GUI_GUIDE.md` for the mapping) - Dialogs in `gui/windows/` @@ -170,4 +170,4 @@ A pre-commit hook (`check-module-docstrings`) enforces presence. Empty `__init__ 4. **The `db.py` module is semi-deprecated** — it requires `BADGER_DB_ROOT` config which is not in the default `BadgerConfig` model. The `x-test_db.py` and `x-test_routine_id.py` files test this functionality but are excluded from normal test runs. -5. **`utils.py` has Qt dependencies** — `BlockSignalsContext` and related utilities import from `PyQt5.QtWidgets` at the module level, so `badger.utils` cannot be imported without PyQt5 installed. +5. **`utils.py` has Qt dependencies** — `BlockSignalsContext` and related utilities import from `qtpy.QtWidgets` at the module level, so `badger.utils` cannot be imported without PyQt5 installed. diff --git a/pyproject.toml b/pyproject.toml index 0b1ad630..83b0ae30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ requires-python = ">=3.11, <3.14" dependencies = [ "coolname", - "pyqt5", + "qtpy", "pyqtgraph", "qdarkstyle>=3.0", "pillow", @@ -37,6 +37,7 @@ version_file = "src/badger/_version.py" [project.optional-dependencies] dev = [ + "pyside6", "pytest", "pytest-cov", "pytest-qt", diff --git a/src/badger/errors.py b/src/badger/errors.py index fdc42237..1bcab326 100644 --- a/src/badger/errors.py +++ b/src/badger/errors.py @@ -2,7 +2,7 @@ with the traceback when raised inside the GUI. Subclasses cover config issues, database errors, plugin failures, and optimization stop signals.""" -from PyQt5.QtWidgets import QMessageBox +from qtpy.QtWidgets import QMessageBox import traceback import sys diff --git a/src/badger/gui/__init__.py b/src/badger/gui/__init__.py index 45976b40..5681b51c 100644 --- a/src/badger/gui/__init__.py +++ b/src/badger/gui/__init__.py @@ -5,9 +5,9 @@ import signal import sys import time -from PyQt5.QtWidgets import QApplication -from PyQt5.QtGui import QFont, QIcon -from PyQt5 import QtCore +from qtpy.QtWidgets import QApplication +from qtpy.QtGui import QFont, QIcon +from qtpy import QtCore from qdarkstyle import load_stylesheet, LightPalette, DarkPalette from badger.settings import init_settings diff --git a/src/badger/gui/components/action_bar.py b/src/badger/gui/components/action_bar.py index 4a0b8c5f..73715b6e 100644 --- a/src/badger/gui/components/action_bar.py +++ b/src/badger/gui/components/action_bar.py @@ -1,10 +1,10 @@ """Toolbar with run-control buttons (start, pause, stop), logbook submission, docs access, and the extensions palette launcher.""" -from PyQt5.QtWidgets import QWidget, QHBoxLayout -from PyQt5.QtWidgets import QToolButton, QMenu, QAction -from PyQt5.QtGui import QIcon, QFont -from PyQt5.QtCore import pyqtSignal, QSize +from qtpy.QtWidgets import QWidget, QHBoxLayout +from qtpy.QtWidgets import QToolButton, QMenu +from qtpy.QtGui import QAction, QIcon, QFont +from qtpy.QtCore import Signal, QSize from importlib import resources from badger.gui.utils import create_button from badger.gui.windows.docs_window import BadgerDocsWindow @@ -88,21 +88,21 @@ class BadgerActionBar(QWidget): - sig_start = pyqtSignal() - sig_start_until = pyqtSignal() - sig_stop = pyqtSignal() - - sig_delete_run = pyqtSignal() - sig_logbook = pyqtSignal() - sig_reset_env = pyqtSignal() - sig_jump_to_optimal = pyqtSignal() - sig_dial_in = pyqtSignal() - sig_ctrl = pyqtSignal(bool) - sig_open_extensions_palette = pyqtSignal() - - sig_save_checkpoint = pyqtSignal() - sig_edit_checkpoint = pyqtSignal() - sig_load_checkpoint = pyqtSignal() + sig_start = Signal() + sig_start_until = Signal() + sig_stop = Signal() + + sig_delete_run = Signal() + sig_logbook = Signal() + sig_reset_env = Signal() + sig_jump_to_optimal = Signal() + sig_dial_in = Signal() + sig_ctrl = Signal(bool) + sig_open_extensions_palette = Signal() + + sig_save_checkpoint = Signal() + sig_edit_checkpoint = Signal() + sig_load_checkpoint = Signal() def __init__(self, parent=None): super().__init__(parent) diff --git a/src/badger/gui/components/analysis_extensions.py b/src/badger/gui/components/analysis_extensions.py index 4a4cbc14..73c4b017 100644 --- a/src/badger/gui/components/analysis_extensions.py +++ b/src/badger/gui/components/analysis_extensions.py @@ -4,9 +4,9 @@ from typing import Optional, cast import logging -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QDialog, QVBoxLayout -from PyQt5.QtGui import QCloseEvent +from qtpy.QtCore import Signal +from qtpy.QtWidgets import QDialog, QVBoxLayout +from qtpy.QtGui import QCloseEvent from badger.gui.components.analysis_widget import AnalysisWidget from badger.gui.components.bo_visualizer.bo_widget import BOPlotWidget from badger.gui.components.pf_viewer.pf_widget import ParetoFrontWidget @@ -20,7 +20,7 @@ class AnalysisExtension(QDialog): - window_closed = pyqtSignal(object) + window_closed = Signal(object) generator_type = Generator widget = AnalysisWidget diff --git a/src/badger/gui/components/analysis_widget.py b/src/badger/gui/components/analysis_widget.py index ecf71238..975c1c3c 100644 --- a/src/badger/gui/components/analysis_widget.py +++ b/src/badger/gui/components/analysis_widget.py @@ -5,7 +5,7 @@ from collections.abc import Callable from typing import Any, Optional -from PyQt5.QtWidgets import QDialog +from qtpy.QtWidgets import QDialog from badger.gui.components.extension_utilities import HandledException from badger.routine import Routine from xopt import Generator diff --git a/src/badger/gui/components/archive_search.py b/src/badger/gui/components/archive_search.py index 541eb95a..dd140132 100644 --- a/src/badger/gui/components/archive_search.py +++ b/src/badger/gui/components/archive_search.py @@ -3,18 +3,17 @@ when building a routine.""" import logging -from typing import List -from PyQt5.QtGui import QDrag, QKeyEvent -from PyQt5.QtCore import ( +from typing import List, Any +from qtpy.QtGui import QDrag, QKeyEvent +from qtpy.QtCore import ( QAbstractTableModel, QMimeData, QModelIndex, QObject, Qt, - QVariant, - pyqtSignal, + Signal, ) -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QAbstractItemView, QHBoxLayout, QHeaderView, @@ -58,17 +57,17 @@ def columnCount(self, parent: QObject) -> int: return 0 return len(self.column_names) - def data(self, index: QModelIndex, role: int) -> QVariant: + def data(self, index: QModelIndex, role: int) -> str | None: """Return the data for the associated role. Currently only supporting DisplayRole.""" if not index.isValid(): - return QVariant() + return None if role != Qt.DisplayRole: - return QVariant() + return None return self.results_list[index.row()] - def headerData(self, section, orientation, role=Qt.DisplayRole) -> QVariant: + def headerData(self, section, orientation, role=Qt.DisplayRole) -> Any: """Return data associated with the header""" if role != Qt.DisplayRole: return super().headerData(section, orientation, role) @@ -121,7 +120,7 @@ class ArchiveSearchWidget(QWidget): The parent item of this widget """ - append_variables_requested = pyqtSignal(str) + append_variables_requested = Signal(str) def __init__(self, environment, parent: QObject = None) -> None: super().__init__(parent=parent) diff --git a/src/badger/gui/components/bo_visualizer/bo_widget.py b/src/badger/gui/components/bo_visualizer/bo_widget.py index 44502ddd..96a871b9 100644 --- a/src/badger/gui/components/bo_visualizer/bo_widget.py +++ b/src/badger/gui/components/bo_visualizer/bo_widget.py @@ -8,14 +8,14 @@ """ from typing import Optional, cast -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QHBoxLayout, QWidget, QVBoxLayout, QMessageBox, QTableWidgetItem, ) -from PyQt5.QtWidgets import QSizePolicy +from qtpy.QtWidgets import QSizePolicy from badger.gui.components.bo_visualizer.types import ConfigurableOptions from badger.gui.components.extension_utilities import ( @@ -29,7 +29,7 @@ from xopt.generator import Generator from badger.gui.components.bo_visualizer.ui_components import UIComponents from badger.gui.components.bo_visualizer.plotting_area import PlottingArea -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from xopt.generators.bayesian.bayesian_generator import BayesianGenerator from xopt.vocs import select_best from badger.gui.components.analysis_widget import AnalysisWidget diff --git a/src/badger/gui/components/bo_visualizer/plotting_area.py b/src/badger/gui/components/bo_visualizer/plotting_area.py index bd569f04..e892c9a0 100644 --- a/src/badger/gui/components/bo_visualizer/plotting_area.py +++ b/src/badger/gui/components/bo_visualizer/plotting_area.py @@ -19,7 +19,7 @@ ) from matplotlib.figure import Figure from matplotlib.axes import Axes -from PyQt5.QtWidgets import QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from badger.utils import BlockSignalsContext from xopt.generators.bayesian.visualize import ( visualize_generator_model, diff --git a/src/badger/gui/components/bo_visualizer/ui_components.py b/src/badger/gui/components/bo_visualizer/ui_components.py index 1ff15f9f..0e45055b 100644 --- a/src/badger/gui/components/bo_visualizer/ui_components.py +++ b/src/badger/gui/components/bo_visualizer/ui_components.py @@ -1,7 +1,7 @@ """Control panel for the BO visualizer — variable selectors, reference point table, grid resolution, and plot option checkboxes.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QVBoxLayout, QHBoxLayout, QComboBox, @@ -14,7 +14,7 @@ QCheckBox, QHeaderView, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from gest_api.vocs import BaseVariable from badger.gui.components.bo_visualizer.types import ConfigurableOptions diff --git a/src/badger/gui/components/bounds_preview.py b/src/badger/gui/components/bounds_preview.py index 5b1c9b09..7b124b0d 100644 --- a/src/badger/gui/components/bounds_preview.py +++ b/src/badger/gui/components/bounds_preview.py @@ -1,8 +1,8 @@ """Widget for visualizing hard bounds, preview scan bounds, and current value.""" -from PyQt5.QtCore import Qt, QRectF -from PyQt5.QtGui import QColor, QPainter, QPen, QBrush, QPaintEvent -from PyQt5.QtWidgets import QWidget +from qtpy.QtCore import Qt, QRectF +from qtpy.QtGui import QColor, QPainter, QPen, QBrush, QPaintEvent +from qtpy.QtWidgets import QWidget class BoundsPreviewBar(QWidget): diff --git a/src/badger/gui/components/collapsible_box.py b/src/badger/gui/components/collapsible_box.py index 85897932..17a6541c 100644 --- a/src/badger/gui/components/collapsible_box.py +++ b/src/badger/gui/components/collapsible_box.py @@ -2,8 +2,8 @@ group environment config, generator parameters, etc. without taking up permanent screen space.""" -from PyQt5 import QtCore, QtGui, QtWidgets -from PyQt5.QtWidgets import QLayout +from qtpy import QtCore, QtGui, QtWidgets +from qtpy.QtWidgets import QLayout stylesheet_toolbutton = """ @@ -16,7 +16,7 @@ # https://stackoverflow.com/a/56293688 class ScrollArea(QtWidgets.QScrollArea): - resized = QtCore.pyqtSignal() + resized = QtCore.Signal() def resizeEvent(self, e): self.resized.emit() @@ -76,7 +76,7 @@ def __init__(self, parent=None, title="", duration=100, tooltip=None): QtCore.QPropertyAnimation(self.content_area, b"maximumHeight") ) - @QtCore.pyqtSlot() + @QtCore.Slot() def start_animation(self): checked = self.toggle_button.isChecked() arrow_type = QtCore.Qt.DownArrow if checked else QtCore.Qt.RightArrow @@ -97,7 +97,7 @@ def expand(self): def updateContentLayout(self): if ( self.toggle_button.isChecked() - and self.toggle_animation.state() != self.toggle_animation.Running + and self.toggle_animation.state() != QtCore.QAbstractAnimation.State.Running ): content_height = self.content_area.layout().sizeHint().height() self.setMinimumHeight(self.collapsed_height + content_height) diff --git a/src/badger/gui/components/con_table.py b/src/badger/gui/components/con_table.py index 10b61c3d..fca83764 100644 --- a/src/badger/gui/components/con_table.py +++ b/src/badger/gui/components/con_table.py @@ -3,7 +3,7 @@ reordering.""" from typing import Any -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QCheckBox, QComboBox, QStyledItemDelegate, diff --git a/src/badger/gui/components/constraint_item.py b/src/badger/gui/components/constraint_item.py index b4882dc7..0ddf5df9 100644 --- a/src/badger/gui/components/constraint_item.py +++ b/src/badger/gui/components/constraint_item.py @@ -1,15 +1,15 @@ """Creates a single constraint row widget: observable selector, relation combo (>, <, =), threshold spinbox, criticality checkbox, and remove button.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QHBoxLayout, QPushButton, QWidget, QDoubleSpinBox, QAbstractSpinBox, ) -from PyQt5.QtWidgets import QComboBox, QCheckBox, QStyledItemDelegate -from PyQt5.QtCore import Qt +from qtpy.QtWidgets import QComboBox, QCheckBox, QStyledItemDelegate +from qtpy.QtCore import Qt from badger.gui.utils import ( MouseWheelWidgetAdjustmentGuard, NoHoverFocusComboBox, diff --git a/src/badger/gui/components/create_process.py b/src/badger/gui/components/create_process.py index b90a8b37..7a514a5d 100644 --- a/src/badger/gui/components/create_process.py +++ b/src/badger/gui/components/create_process.py @@ -3,7 +3,7 @@ from multiprocessing import Event, Pipe, Process, Queue -from PyQt5.QtCore import pyqtSignal, QObject +from qtpy.QtCore import Signal, QObject from badger.settings import init_settings from badger.core_subprocess import run_routine_subprocess @@ -22,8 +22,8 @@ class CreateProcess(QObject): The new process will be started, but will be holding until the wait_event is set. """ - finished = pyqtSignal() - subprocess_prepared = pyqtSignal(object) + finished = Signal() + subprocess_prepared = Signal(object) def create_subprocess(self) -> None: """ diff --git a/src/badger/gui/components/data_panel.py b/src/badger/gui/components/data_panel.py index ba7ec369..5340a2c3 100644 --- a/src/badger/gui/components/data_panel.py +++ b/src/badger/gui/components/data_panel.py @@ -1,7 +1,7 @@ """Panel for viewing and managing pre-loaded optimization data. Lets users load data from archived runs or clear the buffer before starting.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QVBoxLayout, QHBoxLayout, QPushButton, @@ -9,8 +9,8 @@ QMessageBox, ) import pandas as pd -from PyQt5.QtWidgets import QGroupBox, QCheckBox, QLabel -from PyQt5.QtCore import Qt +from qtpy.QtWidgets import QGroupBox, QCheckBox, QLabel +from qtpy.QtCore import Qt from badger.gui.components.data_table import ( TableWithCopy, ) diff --git a/src/badger/gui/components/data_table.py b/src/badger/gui/components/data_table.py index 59b927f5..e2cc4372 100644 --- a/src/badger/gui/components/data_table.py +++ b/src/badger/gui/components/data_table.py @@ -3,8 +3,8 @@ copy and alternating-row styling.""" from pandas import DataFrame -from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem -from PyQt5.QtCore import Qt +from qtpy.QtWidgets import QApplication, QTableWidget, QTableWidgetItem +from qtpy.QtCore import Qt stylesheet = """ diff --git a/src/badger/gui/components/editable_table.py b/src/badger/gui/components/editable_table.py index 66d397c3..11b55d08 100644 --- a/src/badger/gui/components/editable_table.py +++ b/src/badger/gui/components/editable_table.py @@ -25,9 +25,9 @@ from typing import Any, Callable, Dict, List, ParamSpec, cast from pyparsing import TypeVar -from PyQt5.QtCore import QRegExp, Qt, pyqtSignal -from PyQt5.QtGui import QColor, QDragEnterEvent, QDragMoveEvent, QDropEvent -from PyQt5.QtWidgets import ( +from qtpy.QtCore import Qt, QRegularExpression, Signal +from qtpy.QtGui import QColor, QDragEnterEvent, QDragMoveEvent, QDropEvent +from qtpy.QtWidgets import ( QAbstractItemView, QCheckBox, QComboBox, @@ -51,7 +51,7 @@ class EditableTable(QTableWidget): A custom QTableWidget that supports editing and managing tabular data. """ - data_changed = pyqtSignal() + data_changed = Signal() def __init__(self, *args: Any, **kwargs: Any) -> None: """ @@ -403,7 +403,10 @@ def add_formula_item(self, formula_tuple: tuple[str, str, dict[str, str]]) -> No } # Only insert if the name matches the keyword - if self.keyword and QRegExp(self.keyword).indexIn(name, 0) == -1: + if ( + self.keyword + and not QRegularExpression(self.keyword).match(name).hasMatch() + ): return # Remove the last row if it exists @@ -440,11 +443,11 @@ def get_visible_items(self) -> List[str]: A list of visible item names. """ visible_items: list[str] = [] - rx = QRegExp(self.keyword) + rx = QRegularExpression(self.keyword) for item in self.data: name = next(iter(item)) - visible = rx.indexIn(name, 0) != -1 + visible = rx.match(name).hasMatch() if not visible: continue @@ -486,7 +489,10 @@ def on_edit_table_item(self, item: QTableWidgetItem) -> None: } # Only insert if the name matches the keyword - if self.keyword and QRegExp(self.keyword).indexIn(name, 0) == -1: + if ( + self.keyword + and not QRegularExpression(self.keyword).match(name).hasMatch() + ): self.removeRow(row) self.add_empty_row() return @@ -536,7 +542,10 @@ def on_edit_table_item(self, item: QTableWidgetItem) -> None: self.status[name] = self.status.pop(original_name) self.formulas[name] = self.formulas.pop(original_name) # Check if the new name is visible under the current filters - if self.keyword and QRegExp(self.keyword).indexIn(name, 0) == -1: + if ( + self.keyword + and not QRegularExpression(self.keyword).match(name).hasMatch() + ): self.removeRow(row) def add_empty_row(self): @@ -662,13 +671,13 @@ def update_items_wrapper( if formulas is not None: self.formulas = formulas - rx = QRegExp(self.keyword) + rx = QRegularExpression(self.keyword) for item in self.data: row = self.rowCount() name = next(iter(item)) - visible = rx.indexIn(name, 0) != -1 + visible = rx.match(name).hasMatch() if not visible: continue diff --git a/src/badger/gui/components/eliding_label.py b/src/badger/gui/components/eliding_label.py index a7a8c865..b8bf8a8a 100644 --- a/src/badger/gui/components/eliding_label.py +++ b/src/badger/gui/components/eliding_label.py @@ -1,7 +1,7 @@ """QLabel that truncates text with "..." when it doesn't fit. Used for long routine names and status messages in tight layouts.""" -from PyQt5 import QtCore, QtWidgets, QtGui +from qtpy import QtCore, QtWidgets, QtGui class ElidingLabel(QtWidgets.QLabel): @@ -38,7 +38,7 @@ class ElidingLabel(QtWidgets.QLabel): """ - elision_changed = QtCore.pyqtSignal(bool) + elision_changed = QtCore.Signal(bool) def __init__(self, text="", mode=QtCore.Qt.ElideMiddle, **kwargs): super().__init__(**kwargs) diff --git a/src/badger/gui/components/env_cbox.py b/src/badger/gui/components/env_cbox.py index 0fc1ed5b..1f9249bc 100644 --- a/src/badger/gui/components/env_cbox.py +++ b/src/badger/gui/components/env_cbox.py @@ -24,9 +24,9 @@ from gest_api.vocs import ContinuousVariable from pydantic_core import ValidationError -from PyQt5.QtCore import QPropertyAnimation, QRegExp, pyqtSignal -from PyQt5.QtGui import QFont, QIcon -from PyQt5.QtWidgets import ( +from qtpy.QtCore import QPropertyAnimation, QRegularExpression, Signal +from qtpy.QtGui import QFont, QIcon +from qtpy.QtWidgets import ( QCheckBox, QHBoxLayout, QLabel, @@ -147,7 +147,7 @@ def format_validation_error(e: ValidationError) -> str: class BadgerEnvBox(QWidget): - vocs_updated = pyqtSignal(object) # or use a more specific type if you want + vocs_updated = Signal(object) # or use a more specific type if you want def __init__( self, @@ -566,12 +566,12 @@ def add_var(self, name, lb, ub): def filter_var(self): keyword = self.edit_var.text() - rx = QRegExp(keyword) + rx = QRegularExpression(keyword) _variables = [] for var in self.var_table.all_variables: vname = next(iter(var)) - if rx.indexIn(vname, 0) != -1: + if rx.match(vname).hasMatch(): _variables.append(var) self.var_table.update_variables(_variables, 1) diff --git a/src/badger/gui/components/extension_utilities.py b/src/badger/gui/components/extension_utilities.py index be9e2015..d122043f 100644 --- a/src/badger/gui/components/extension_utilities.py +++ b/src/badger/gui/components/extension_utilities.py @@ -6,7 +6,7 @@ import traceback from typing import Any, Callable, Optional, ParamSpec from types import TracebackType -from PyQt5.QtWidgets import QLayout, QTabWidget +from qtpy.QtWidgets import QLayout, QTabWidget from matplotlib.axes import Axes from matplotlib.figure import Figure diff --git a/src/badger/gui/components/extensions_palette.py b/src/badger/gui/components/extensions_palette.py index 96b23a58..708bc764 100644 --- a/src/badger/gui/components/extensions_palette.py +++ b/src/badger/gui/components/extensions_palette.py @@ -3,7 +3,7 @@ import traceback -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QMainWindow, QPushButton, QVBoxLayout, @@ -12,7 +12,7 @@ QMessageBox, QSizePolicy, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from badger.gui.components.analysis_extensions import ( AnalysisExtension, ParetoFrontViewer, diff --git a/src/badger/gui/components/filter_cbox.py b/src/badger/gui/components/filter_cbox.py index f04413ac..55639cd5 100644 --- a/src/badger/gui/components/filter_cbox.py +++ b/src/badger/gui/components/filter_cbox.py @@ -1,8 +1,8 @@ """Combo-box filters for narrowing the routine list by objective or region, shown in the navigator sidebar.""" -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget -from PyQt5.QtWidgets import QComboBox, QStyledItemDelegate, QLabel +from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget +from qtpy.QtWidgets import QComboBox, QStyledItemDelegate, QLabel from badger.gui.components.collapsible_box import CollapsibleBox diff --git a/src/badger/gui/components/generator_cbox.py b/src/badger/gui/components/generator_cbox.py index 8376f78a..5bd5572e 100644 --- a/src/badger/gui/components/generator_cbox.py +++ b/src/badger/gui/components/generator_cbox.py @@ -1,14 +1,17 @@ """Panel where users pick an optimization algorithm from the Xopt registry and configure its parameters via the Pydantic tree editor.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QVBoxLayout, QHBoxLayout, QPushButton, QWidget, QPlainTextEdit, + QComboBox, + QCheckBox, + QStyledItemDelegate, + QLabel, ) -from PyQt5.QtWidgets import QComboBox, QCheckBox, QStyledItemDelegate, QLabel from badger.gui.components.collapsible_box import CollapsibleBox from badger.gui.components.pydantic_editor import BadgerPydanticEditor from badger.settings import init_settings diff --git a/src/badger/gui/components/labeled_lineedit.py b/src/badger/gui/components/labeled_lineedit.py index 085b3f84..814902f5 100644 --- a/src/badger/gui/components/labeled_lineedit.py +++ b/src/badger/gui/components/labeled_lineedit.py @@ -1,7 +1,7 @@ """Creates a compact label + QLineEdit pair used throughout the GUI for key-value fields.""" -from PyQt5.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QWidget +from qtpy.QtWidgets import QHBoxLayout, QLabel, QLineEdit, QWidget def labeled_lineedit(name, text, width_name=64, placeholder=None, readonly=True): diff --git a/src/badger/gui/components/navigators.py b/src/badger/gui/components/navigators.py index 4b4516ea..7c4a9f1c 100644 --- a/src/badger/gui/components/navigators.py +++ b/src/badger/gui/components/navigators.py @@ -4,20 +4,19 @@ context menus (open, copy path, etc.).""" import os -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QWidget, QVBoxLayout, QTreeView, QTreeWidget, QTreeWidgetItem, QMenu, - QAction, QApplication, QToolTip, QFileSystemModel, ) -from PyQt5.QtGui import QFont, QDesktopServices, QCursor -from PyQt5.QtCore import Qt, QUrl, QTimer, QDir +from qtpy.QtGui import QAction, QFont, QDesktopServices, QCursor +from qtpy.QtCore import Qt, QUrl, QTimer, QDir from badger.archive import get_base_run_filename, get_runs from badger.utils import run_names_to_dict from badger.settings import init_settings diff --git a/src/badger/gui/components/obj_table.py b/src/badger/gui/components/obj_table.py index b5e3ab40..95d1e643 100644 --- a/src/badger/gui/components/obj_table.py +++ b/src/badger/gui/components/obj_table.py @@ -2,7 +2,7 @@ MAXIMIZE rule. Supports drag-and-drop reordering and text drops.""" from typing import Any -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QComboBox, QStyledItemDelegate, QMessageBox, diff --git a/src/badger/gui/components/obs_table.py b/src/badger/gui/components/obs_table.py index 69dcd8a8..1fb88d77 100644 --- a/src/badger/gui/components/obs_table.py +++ b/src/badger/gui/components/obs_table.py @@ -2,7 +2,7 @@ optimized. Supports drag-and-drop reordering and text drops.""" from typing import Any -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QMessageBox, ) diff --git a/src/badger/gui/components/pf_viewer/pf_widget.py b/src/badger/gui/components/pf_viewer/pf_widget.py index f56cb2f1..f5bcc194 100644 --- a/src/badger/gui/components/pf_viewer/pf_widget.py +++ b/src/badger/gui/components/pf_viewer/pf_widget.py @@ -5,7 +5,7 @@ import time from typing import Optional -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QWidget, QSizePolicy, QVBoxLayout, @@ -19,7 +19,7 @@ QTabWidget, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from badger.gui.components.plot_event_handlers import ( MatplotlibInteractionHandler, ) diff --git a/src/badger/gui/components/pf_viewer/types.py b/src/badger/gui/components/pf_viewer/types.py index aaa80299..3f1cde5c 100644 --- a/src/badger/gui/components/pf_viewer/types.py +++ b/src/badger/gui/components/pf_viewer/types.py @@ -3,7 +3,7 @@ from typing import TypedDict -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QRadioButton, QComboBox, QVBoxLayout, diff --git a/src/badger/gui/components/process_manager.py b/src/badger/gui/components/process_manager.py index ddfebeb9..99d19840 100644 --- a/src/badger/gui/components/process_manager.py +++ b/src/badger/gui/components/process_manager.py @@ -3,7 +3,7 @@ from typing import Dict, Optional -from PyQt5.QtCore import pyqtSignal, QObject +from qtpy.QtCore import Signal, QObject class ProcessManager(QObject): @@ -12,7 +12,7 @@ class ProcessManager(QObject): which can be used by Badger to run optimizations. """ - processQueueUpdated = pyqtSignal(object) + processQueueUpdated = Signal(object) def __init__(self) -> None: super().__init__() diff --git a/src/badger/gui/components/pydantic_editor.py b/src/badger/gui/components/pydantic_editor.py index 93310373..7881f665 100644 --- a/src/badger/gui/components/pydantic_editor.py +++ b/src/badger/gui/components/pydantic_editor.py @@ -30,8 +30,8 @@ from pydantic import BaseModel, Field, ValidationError, create_model from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtWidgets import ( +from qtpy.QtCore import Signal, Qt +from qtpy.QtWidgets import ( QCheckBox, QComboBox, QDoubleSpinBox, @@ -521,7 +521,7 @@ def remove(self) -> None: class BadgerListEditor(QWidget): - listChanged = pyqtSignal() + listChanged = Signal() def __init__( self, diff --git a/src/badger/gui/components/reorderable_table.py b/src/badger/gui/components/reorderable_table.py index 99804c28..e8d7eac2 100644 --- a/src/badger/gui/components/reorderable_table.py +++ b/src/badger/gui/components/reorderable_table.py @@ -8,7 +8,7 @@ # https://creativecommons.org/publicdomain/zero/1.0/ # https://creativecommons.org/publicdomain/zero/1.0/legalcode -from PyQt5 import QtWidgets, QtGui +from qtpy import QtWidgets, QtGui class MyModel(QtGui.QStandardItemModel): diff --git a/src/badger/gui/components/robust_spinbox.py b/src/badger/gui/components/robust_spinbox.py index 918910df..fdfdee3c 100644 --- a/src/badger/gui/components/robust_spinbox.py +++ b/src/badger/gui/components/robust_spinbox.py @@ -2,8 +2,8 @@ and optional read-only mode. Used for variable bounds and constraint thresholds.""" -from PyQt5.QtWidgets import QDoubleSpinBox, QAbstractSpinBox -from PyQt5.QtCore import Qt +from qtpy.QtWidgets import QDoubleSpinBox, QAbstractSpinBox +from qtpy.QtCore import Qt from badger.gui.utils import MouseWheelWidgetAdjustmentGuard diff --git a/src/badger/gui/components/routine_editor.py b/src/badger/gui/components/routine_editor.py index b03da465..469dac05 100644 --- a/src/badger/gui/components/routine_editor.py +++ b/src/badger/gui/components/routine_editor.py @@ -1,19 +1,19 @@ """Container that wraps the routine page in a scrollable area with save/cancel/delete buttons. Coordinates creation, editing, and deletion.""" -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QPushButton -from PyQt5.QtWidgets import QTextEdit, QStackedWidget, QScrollArea -from PyQt5.QtCore import pyqtSignal -from PyQt5.QtGui import QFont +from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QPushButton +from qtpy.QtWidgets import QTextEdit, QStackedWidget, QScrollArea +from qtpy.QtCore import Signal +from qtpy.QtGui import QFont from badger.gui.components.routine_page import BadgerRoutinePage from badger.routine import Routine class BadgerRoutineEditor(QWidget): - sig_saved = pyqtSignal() - sig_canceled = pyqtSignal() - sig_deleted = pyqtSignal() + sig_saved = Signal() + sig_canceled = Signal() + sig_deleted = Signal() def __init__(self): super().__init__() diff --git a/src/badger/gui/components/routine_item.py b/src/badger/gui/components/routine_item.py index e24f22db..588e7364 100644 --- a/src/badger/gui/components/routine_item.py +++ b/src/badger/gui/components/routine_item.py @@ -2,10 +2,10 @@ and environment with hover/selection styling and a delete button.""" from datetime import datetime -from PyQt5.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel -from PyQt5.QtWidgets import QSizePolicy, QMessageBox -from PyQt5.QtCore import Qt, pyqtSignal -from PyQt5.QtGui import QFont +from qtpy.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QLabel +from qtpy.QtWidgets import QSizePolicy, QMessageBox +from qtpy.QtCore import Qt, Signal +from qtpy.QtGui import QFont from badger.gui.components.eliding_label import ElidingLabel from badger.gui.utils import create_button @@ -65,7 +65,7 @@ class BadgerRoutineItem(QWidget): # sig_del carries an id - sig_del = pyqtSignal(str) + sig_del = Signal(str) def __init__( self, id, name, timestamp, environment, env_dict, description="", parent=None diff --git a/src/badger/gui/components/routine_page.py b/src/badger/gui/components/routine_page.py index c7215d0c..ef8d0ae2 100644 --- a/src/badger/gui/components/routine_page.py +++ b/src/badger/gui/components/routine_page.py @@ -35,11 +35,11 @@ import numpy as np import pandas as pd -from PyQt5.QtCore import Qt, pyqtSignal, QTimer -from PyQt5.QtWidgets import QLineEdit, QLabel, QPushButton, QFileDialog -from PyQt5.QtWidgets import QMessageBox, QWidget, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QScrollArea -from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit +from qtpy.QtCore import Qt, Signal, QTimer +from qtpy.QtWidgets import QLineEdit, QLabel, QPushButton, QFileDialog +from qtpy.QtWidgets import QMessageBox, QWidget, QTabWidget +from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout, QScrollArea +from qtpy.QtWidgets import QTableWidgetItem, QPlainTextEdit from coolname import generate_slug from xopt import VOCS from xopt.vocs import random_inputs @@ -146,9 +146,9 @@ def extract_objective_symbol(objective: BaseObjective) -> str: class BadgerRoutinePage(QWidget): - sig_updated = pyqtSignal(str, str) # routine name, routine description - sig_load_template = pyqtSignal(str) # template path - sig_save_template = pyqtSignal(str) # template path + sig_updated = Signal(str, str) # routine name, routine description + sig_load_template = Signal(str) # template path + sig_save_template = Signal(str) # template path def __init__(self): logger.info("Initializing BadgerRoutinePage.") diff --git a/src/badger/gui/components/routine_runner.py b/src/badger/gui/components/routine_runner.py index 63e874cd..8ed38b5b 100644 --- a/src/badger/gui/components/routine_runner.py +++ b/src/badger/gui/components/routine_runner.py @@ -12,8 +12,8 @@ import traceback import pandas as pd -from PyQt5.QtCore import pyqtSignal, QObject, QTimer -from PyQt5.QtWidgets import QDialog +from qtpy.QtCore import Signal, QObject, QTimer +from qtpy.QtWidgets import QDialog from badger.errors import ( BadgerRunTerminated, @@ -34,12 +34,12 @@ class BadgerRoutineSignals(QObject): - env_ready = pyqtSignal(list) - finished = pyqtSignal() - progress = pyqtSignal(object) - error = pyqtSignal(Exception) - info = pyqtSignal(str) - states = pyqtSignal(str) + env_ready = Signal(list) + finished = Signal() + progress = Signal(object) + error = Signal(Exception) + info = Signal(str) + states = Signal(str) class BadgerRoutineSubprocess: diff --git a/src/badger/gui/components/run_monitor.py b/src/badger/gui/components/run_monitor.py index b5e2e7bf..12481958 100644 --- a/src/badger/gui/components/run_monitor.py +++ b/src/badger/gui/components/run_monitor.py @@ -16,11 +16,11 @@ import numpy as np import pandas as pd import pyqtgraph as pg -from PyQt5.QtCore import pyqtSignal +from qtpy.QtCore import Signal from pyqtgraph.Qt import QtGui, QtCore -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import ( +from qtpy.QtGui import QIcon +from qtpy.QtWidgets import ( QCheckBox, QComboBox, QDialog, @@ -55,23 +55,23 @@ class BadgerOptMonitor(QWidget): - sig_pause = pyqtSignal(bool) # True: pause, False: resume - sig_stop = pyqtSignal() - sig_lock = pyqtSignal(bool) # True: lock GUI, False: unlock GUI - sig_new_run = pyqtSignal() # start the new run - sig_run_started = pyqtSignal() # run started - sig_stop_run = pyqtSignal() # stop the run - sig_run_name = pyqtSignal(str) # filename of the new run - sig_status = pyqtSignal(str) # status information - sig_inspect = pyqtSignal(int) # index of the inspector - sig_progress = pyqtSignal(pd.DataFrame) # new evaluated solution - sig_del = pyqtSignal() - sig_routine_finished = pyqtSignal() - sig_lock_action = pyqtSignal() - sig_toggle_reset = pyqtSignal(bool) - sig_toggle_run = pyqtSignal(bool) - sig_toggle_other = pyqtSignal(bool) - sig_env_ready = pyqtSignal() + sig_pause = Signal(bool) # True: pause, False: resume + sig_stop = Signal() + sig_lock = Signal(bool) # True: lock GUI, False: unlock GUI + sig_new_run = Signal() # start the new run + sig_run_started = Signal() # run started + sig_stop_run = Signal() # stop the run + sig_run_name = Signal(str) # filename of the new run + sig_status = Signal(str) # status information + sig_inspect = Signal(int) # index of the inspector + sig_progress = Signal(pd.DataFrame) # new evaluated solution + sig_del = Signal() + sig_routine_finished = Signal() + sig_lock_action = Signal() + sig_toggle_reset = Signal(bool) + sig_toggle_run = Signal(bool) + sig_toggle_other = Signal(bool) + sig_env_ready = Signal() def __init__(self, process_manager=None): super().__init__() diff --git a/src/badger/gui/components/search_bar.py b/src/badger/gui/components/search_bar.py index cc2f66b5..d4f48ea3 100644 --- a/src/badger/gui/components/search_bar.py +++ b/src/badger/gui/components/search_bar.py @@ -1,7 +1,7 @@ """Simple search bar (QLineEdit with placeholder text) for filtering routine lists.""" -from PyQt5.QtWidgets import QLineEdit +from qtpy.QtWidgets import QLineEdit def search_bar(): diff --git a/src/badger/gui/components/state_item.py b/src/badger/gui/components/state_item.py index 1edd875b..8db5eb6e 100644 --- a/src/badger/gui/components/state_item.py +++ b/src/badger/gui/components/state_item.py @@ -1,8 +1,8 @@ """Creates a single observable-state row: a combo box for the observable name and a remove button.""" -from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QWidget -from PyQt5.QtWidgets import QStyledItemDelegate +from qtpy.QtWidgets import QHBoxLayout, QPushButton, QWidget +from qtpy.QtWidgets import QStyledItemDelegate from badger.gui.utils import NoHoverFocusComboBox diff --git a/src/badger/gui/components/status_bar.py b/src/badger/gui/components/status_bar.py index 15b3bfb5..41cb1673 100644 --- a/src/badger/gui/components/status_bar.py +++ b/src/badger/gui/components/status_bar.py @@ -1,9 +1,9 @@ """Bottom status bar showing run status and a settings button.""" from importlib import resources -from PyQt5.QtWidgets import QHBoxLayout, QWidget, QPushButton -from PyQt5.QtGui import QIcon -from PyQt5.QtCore import Qt, QSize +from qtpy.QtWidgets import QHBoxLayout, QWidget, QPushButton +from qtpy.QtGui import QIcon +from qtpy.QtCore import Qt, QSize from badger.gui.windows.settings_dialog import BadgerSettingsDialog from badger.gui.components.eliding_label import SimpleElidedLabel diff --git a/src/badger/gui/components/syntax.py b/src/badger/gui/components/syntax.py index d8195918..999e5cde 100644 --- a/src/badger/gui/components/syntax.py +++ b/src/badger/gui/components/syntax.py @@ -1,7 +1,7 @@ """Python syntax highlighter for the script editor. Colors keywords, strings, comments, numbers, and operators.""" -from PyQt5 import QtCore, QtGui +from qtpy import QtCore, QtGui def format(color, style=""): @@ -120,8 +120,8 @@ def __init__(self, parent: QtGui.QTextDocument) -> None: super().__init__(parent) # Multi-line strings (expression, flag, style) - self.tri_single = (QtCore.QRegExp("'''"), 1, STYLES["string2"]) - self.tri_double = (QtCore.QRegExp('"""'), 2, STYLES["string2"]) + self.tri_single = (QtCore.QRegularExpression("'''"), 1, STYLES["string2"]) + self.tri_double = (QtCore.QRegularExpression('"""'), 2, STYLES["string2"]) rules = [] @@ -154,15 +154,17 @@ def __init__(self, parent: QtGui.QTextDocument) -> None: (r"#[^\n]*", 0, STYLES["comment"]), ] - # Build a QRegExp for each pattern - self.rules = [(QtCore.QRegExp(pat), index, fmt) for (pat, index, fmt) in rules] + # Build a QRegularExpression for each pattern + self.rules = [ + (QtCore.QRegularExpression(pat), index, fmt) for (pat, index, fmt) in rules + ] def highlightBlock(self, text): """Apply syntax highlighting to the given block of text.""" self.tripleQuoutesWithinStrings = [] # Do other syntax formatting for expression, nth, format in self.rules: - index = expression.indexIn(text, 0) + index = expression.match(text).capturedStart() if index >= 0: # if there is a string we check # if there are some triple quotes within the string @@ -171,26 +173,33 @@ def highlightBlock(self, text): r'"[^"\\]*(\\.[^"\\]*)*"', r"'[^'\\]*(\\.[^'\\]*)*'", ]: - innerIndex = self.tri_single[0].indexIn(text, index + 1) + innerIndex = ( + self.tri_single[0].match(text, index + 1).capturedStart() + ) if innerIndex == -1: - innerIndex = self.tri_double[0].indexIn(text, index + 1) + innerIndex = ( + self.tri_double[0].match(text, index + 1).capturedStart() + ) if innerIndex != -1: tripleQuoteIndexes = range(innerIndex, innerIndex + 3) self.tripleQuoutesWithinStrings.extend(tripleQuoteIndexes) + match = None while index >= 0: # skipping triple quotes within strings if index in self.tripleQuoutesWithinStrings: index += 1 - expression.indexIn(text, index) + match = expression.match(text, index) + index = match.capturedStart() continue # We actually want the index of the nth match - index = expression.pos(nth) - length = len(expression.cap(nth)) - self.setFormat(index, length, format) - index = expression.indexIn(text, index + length) + if match is not None: + index = match.capturedStart(nth) + length = match.capturedLength(nth) + self.setFormat(index, length, format) + index = match.capturedEnd() self.setCurrentBlockState(0) @@ -201,8 +210,8 @@ def highlightBlock(self, text): def match_multiline(self, text, delimiter, in_state, style): """Do highlighting of multi-line strings. ``delimiter`` should be a - ``QRegExp`` for triple-single-quotes or triple-double-quotes, and - ``in_state`` should be a unique integer to represent the corresponding + ``QRegularExpression`` for triple-single-quotes or triple-double-quotes, + and ``in_state`` should be a unique integer to represent the corresponding state changes when inside those strings. Returns True if we're still inside a multi-line string when this function is finished. """ diff --git a/src/badger/gui/components/var_table.py b/src/badger/gui/components/var_table.py index b9b5114c..598ae219 100644 --- a/src/badger/gui/components/var_table.py +++ b/src/badger/gui/components/var_table.py @@ -25,7 +25,7 @@ from importlib import resources import traceback from typing import Any, cast -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QTableWidget, QTableWidgetItem, QHeaderView, @@ -40,8 +40,8 @@ QLabel, QDialog, ) -from PyQt5.QtCore import pyqtSignal, Qt, QSize, QPoint -from PyQt5.QtGui import ( +from qtpy.QtCore import Signal, Qt, QSize, QPoint +from qtpy.QtGui import ( QColor, QIcon, QGuiApplication, @@ -63,10 +63,10 @@ class VariableTable(QTableWidget): - sig_sel_changed = pyqtSignal() - sig_pv_added = pyqtSignal() - sig_var_config = pyqtSignal(str) - data_changed = pyqtSignal() + sig_sel_changed = Signal() + sig_pv_added = Signal() + sig_var_config = Signal(str) + data_changed = Signal() PLACEHOLDER_TEXT = "Enter new variable here...." diff --git a/src/badger/gui/mini/__init__.py b/src/badger/gui/mini/__init__.py index c8d508fb..05dcf9c2 100644 --- a/src/badger/gui/mini/__init__.py +++ b/src/badger/gui/mini/__init__.py @@ -5,9 +5,9 @@ import os import sys import time -from PyQt5.QtWidgets import QApplication -from PyQt5.QtGui import QFont, QIcon -from PyQt5 import QtCore +from qtpy.QtWidgets import QApplication +from qtpy.QtGui import QFont, QIcon +from qtpy import QtCore from qdarkstyle import load_stylesheet, DarkPalette from badger.settings import init_settings diff --git a/src/badger/gui/mini/components/env_cbox.py b/src/badger/gui/mini/components/env_cbox.py index 29327a0a..dcbaf807 100644 --- a/src/badger/gui/mini/components/env_cbox.py +++ b/src/badger/gui/mini/components/env_cbox.py @@ -10,7 +10,7 @@ from pathlib import Path from typing import Any -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QFrame, QVBoxLayout, QHBoxLayout, @@ -20,13 +20,11 @@ QWidget, QLineEdit, QTreeWidget, -) -from PyQt5.QtWidgets import ( QCheckBox, QStyledItemDelegate, QLabel, ) -from PyQt5.QtCore import QRegExp, pyqtSignal +from qtpy.QtCore import Signal, QRegularExpression from badger.gui.components.obs_table import ObservableTable from badger.settings import init_settings from pydantic_core import ValidationError @@ -226,7 +224,7 @@ def sync_selection_state(self): class BadgerEnvBox(QWidget): - vocs_updated = pyqtSignal(object) # or use a more specific type if you want + vocs_updated = Signal(object) # or use a more specific type if you want def __init__( self, @@ -649,12 +647,12 @@ def add_var(self, name, lb, ub): def filter_var(self): keyword = self.edit_var.text() - rx = QRegExp(keyword) + rx = QRegularExpression(keyword) _variables = [] for var in self.var_table.all_variables: vname = next(iter(var)) - if rx.indexIn(vname, 0) != -1: + if rx.match(vname, 0).hasMatch(): _variables.append(var) self.var_table.update_variables(_variables, 1) diff --git a/src/badger/gui/mini/components/var_table.py b/src/badger/gui/mini/components/var_table.py index ac353204..9df84ff0 100644 --- a/src/badger/gui/mini/components/var_table.py +++ b/src/badger/gui/mini/components/var_table.py @@ -1,7 +1,7 @@ """ Variable table widget for the Badger mini GUI. -Provides class VariableTable, a PyQt5.QtWidgets.QTableWidget +Provides class VariableTable, a qtpy.QtWidgets.QTableWidget subclass that displays optimizer variables with their saved values, current values, and adjustable scan ranges. Also defines the supporting ui cell widgets (SavedValueCell, CurrentValueCell, ScanRangeCell) and the @@ -13,7 +13,7 @@ import math import traceback from typing import Any, cast -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QTableWidget, QTableWidgetItem, QHeaderView, @@ -30,8 +30,8 @@ QLabel, QDialog, ) -from PyQt5.QtCore import pyqtSignal, Qt, QSize, QPoint, QTimer -from PyQt5.QtGui import ( +from qtpy.QtCore import pyqtSignal, Qt, QSize, QPoint, QTimer +from qtpy.QtGui import ( QColor, QIcon, QGuiApplication, diff --git a/src/badger/gui/mini/pages/home_page.py b/src/badger/gui/mini/pages/home_page.py index e9b7989b..76452595 100644 --- a/src/badger/gui/mini/pages/home_page.py +++ b/src/badger/gui/mini/pages/home_page.py @@ -12,9 +12,9 @@ from importlib import resources from pandas import DataFrame -from PyQt5.QtCore import pyqtSignal, Qt, QModelIndex -from PyQt5.QtGui import QIcon -from PyQt5.QtWidgets import ( +from qtpy.QtCore import Signal, Qt, QModelIndex +from qtpy.QtGui import QIcon +from qtpy.QtWidgets import ( QMessageBox, QSplitter, QVBoxLayout, @@ -44,7 +44,7 @@ from badger.utils import get_header from badger.settings import init_settings -# from PyQt5.QtGui import QBrush, QColor +# from qtpy.QtGui import QBrush, QColor from badger.gui.windows.message_dialog import BadgerScrollableMessageBox from badger.gui.windows.terminition_condition_dialog import ( BadgerTerminationConditionDialog, @@ -72,8 +72,8 @@ class BadgerHomePage(QWidget): - sig_routine_activated = pyqtSignal(bool) - sig_routine_invalid = pyqtSignal() + sig_routine_activated = Signal(bool) + sig_routine_invalid = Signal() def __init__(self, process_manager=None): logger.info("Initializing BadgerHomePage.") diff --git a/src/badger/gui/mini/pages/routine_page.py b/src/badger/gui/mini/pages/routine_page.py index 4bed3b91..d3435290 100644 --- a/src/badger/gui/mini/pages/routine_page.py +++ b/src/badger/gui/mini/pages/routine_page.py @@ -18,11 +18,19 @@ import numpy as np import pandas as pd -from PyQt5.QtCore import pyqtSignal, QTimer -from PyQt5.QtWidgets import QLineEdit, QPushButton, QFileDialog -from PyQt5.QtWidgets import QMessageBox, QWidget, QTabWidget -from PyQt5.QtWidgets import QVBoxLayout, QScrollArea -from PyQt5.QtWidgets import QTableWidgetItem, QPlainTextEdit +from qtpy.QtCore import Signal, QTimer +from qtpy.QtWidgets import ( + QLineEdit, + QPushButton, + QFileDialog, + QMessageBox, + QWidget, + QTabWidget, + QVBoxLayout, + QScrollArea, + QTableWidgetItem, + QPlainTextEdit, +) from badger.gui.components.navigators import HistoryNavigator from coolname import generate_slug from xopt import VOCS @@ -121,11 +129,11 @@ def extract_objective_symbol(objective: BaseObjective) -> str: class BadgerRoutinePage(QWidget): - sig_updated = pyqtSignal(str, str) # routine name, routine description - sig_load_template = pyqtSignal(str) # template path - sig_save_template = pyqtSignal(str) # template path - sig_go_run = pyqtSignal() - sig_select_env = pyqtSignal(str) + sig_updated = Signal(str, str) # routine name, routine description + sig_load_template = Signal(str) # template path + sig_save_template = Signal(str) # template path + sig_go_run = Signal() + sig_select_env = Signal(str) def __init__(self): logger.info("Initializing BadgerRoutinePage.") diff --git a/src/badger/gui/mini/windows/main_window.py b/src/badger/gui/mini/windows/main_window.py index 1b3c4fec..4f91126c 100644 --- a/src/badger/gui/mini/windows/main_window.py +++ b/src/badger/gui/mini/windows/main_window.py @@ -3,8 +3,8 @@ import logging from importlib import metadata from typing import Dict -from PyQt5.QtCore import QThread -from PyQt5.QtWidgets import QDesktopWidget, QMainWindow, QMessageBox, QStackedWidget +from qtpy.QtCore import QThread +from qtpy.QtWidgets import QDesktopWidget, QMainWindow, QMessageBox, QStackedWidget from badger.gui.components.create_process import CreateProcess from badger.gui.components.process_manager import ProcessManager from badger.gui.mini.pages.home_page import BadgerHomePage diff --git a/src/badger/gui/pages/home_page.py b/src/badger/gui/pages/home_page.py index 7dfe8f6f..86a9490f 100644 --- a/src/badger/gui/pages/home_page.py +++ b/src/badger/gui/pages/home_page.py @@ -18,9 +18,9 @@ import numpy as np from pandas import DataFrame -from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal -from PyQt5.QtGui import QIcon, QKeySequence -from PyQt5.QtWidgets import ( +from qtpy.QtCore import Signal, QModelIndex, Qt +from qtpy.QtGui import QIcon, QKeySequence +from qtpy.QtWidgets import ( QLabel, QMessageBox, QShortcut, @@ -52,7 +52,7 @@ from badger.gui.components.status_bar import BadgerStatusBar from badger.gui.utils import ModalOverlay -# from PyQt5.QtGui import QBrush, QColor +# from qtpy.QtGui import QBrush, QColor from badger.gui.windows.message_dialog import BadgerScrollableMessageBox from badger.gui.windows.terminition_condition_dialog import ( BadgerTerminationConditionDialog, @@ -79,8 +79,8 @@ class BadgerHomePage(QWidget): - sig_routine_activated = pyqtSignal(bool) - sig_routine_invalid = pyqtSignal() + sig_routine_activated = Signal(bool) + sig_routine_invalid = Signal() def __init__(self, process_manager=None): logger.info("Initializing BadgerHomePage.") diff --git a/src/badger/gui/utils.py b/src/badger/gui/utils.py index ef5f03a9..a372ce41 100644 --- a/src/badger/gui/utils.py +++ b/src/badger/gui/utils.py @@ -4,10 +4,10 @@ from importlib import resources from typing import Any -from PyQt5.QtWidgets import QAbstractSpinBox, QPushButton, QComboBox, QToolButton -from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel -from PyQt5.QtCore import Qt, QObject, QEvent, QSize -from PyQt5.QtGui import QIcon +from qtpy.QtWidgets import QAbstractSpinBox, QPushButton, QComboBox, QToolButton +from qtpy.QtWidgets import QDialog, QVBoxLayout, QLabel +from qtpy.QtCore import Qt, QObject, QEvent, QSize +from qtpy.QtGui import QIcon import copy diff --git a/src/badger/gui/windows/add_random_dialog.py b/src/badger/gui/windows/add_random_dialog.py index ee19674f..e3f57641 100644 --- a/src/badger/gui/windows/add_random_dialog.py +++ b/src/badger/gui/windows/add_random_dialog.py @@ -4,10 +4,10 @@ from copy import deepcopy -from PyQt5.QtWidgets import QDialog, QWidget, QHBoxLayout, QStackedWidget -from PyQt5.QtWidgets import QVBoxLayout, QSpinBox, QPushButton -from PyQt5.QtWidgets import QGroupBox, QLabel, QComboBox, QStyledItemDelegate -from PyQt5.QtCore import Qt +from qtpy.QtWidgets import QDialog, QWidget, QHBoxLayout, QStackedWidget +from qtpy.QtWidgets import QVBoxLayout, QSpinBox, QPushButton +from qtpy.QtWidgets import QGroupBox, QLabel, QComboBox, QStyledItemDelegate +from qtpy.QtCore import Qt from badger.gui.components.robust_spinbox import RobustSpinBox diff --git a/src/badger/gui/windows/docs_window.py b/src/badger/gui/windows/docs_window.py index 94edf1da..6c63005e 100644 --- a/src/badger/gui/windows/docs_window.py +++ b/src/badger/gui/windows/docs_window.py @@ -2,7 +2,7 @@ environments, and general Badger guides in a QTextBrowser with clickable navigation links.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QHBoxLayout, QVBoxLayout, QCheckBox, @@ -14,7 +14,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from PyQt5.QtCore import QUrl + from qtpy.QtCore import QUrl class BadgerDocsWindow(QMainWindow): diff --git a/src/badger/gui/windows/edit_script_dialog.py b/src/badger/gui/windows/edit_script_dialog.py index c18de9a7..3fed71f7 100644 --- a/src/badger/gui/windows/edit_script_dialog.py +++ b/src/badger/gui/windows/edit_script_dialog.py @@ -2,9 +2,9 @@ Python code editor for writing custom generator scripts that execute as part of an optimization routine.""" -from PyQt5.QtWidgets import QDialog, QPlainTextEdit, QVBoxLayout, QWidget -from PyQt5.QtWidgets import QHBoxLayout, QPushButton -from PyQt5.QtGui import QFont +from qtpy.QtWidgets import QDialog, QPlainTextEdit, QVBoxLayout, QWidget +from qtpy.QtWidgets import QHBoxLayout, QPushButton +from qtpy.QtGui import QFont from badger.gui.components.syntax import PythonHighlighter diff --git a/src/badger/gui/windows/expandable_message_box.py b/src/badger/gui/windows/expandable_message_box.py index cc887257..271ae88e 100644 --- a/src/badger/gui/windows/expandable_message_box.py +++ b/src/badger/gui/windows/expandable_message_box.py @@ -1,7 +1,7 @@ """Error dialog with an expandable "Details" section. Shows a short error message by default; click to reveal the full traceback.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QMessageBox, QVBoxLayout, @@ -10,8 +10,8 @@ QPushButton, QTextEdit, ) -from PyQt5.QtGui import QTextOption, QFont, QFontDatabase -from PyQt5.QtCore import Qt +from qtpy.QtGui import QTextOption, QFont, QFontDatabase +from qtpy.QtCore import Qt class ExpandableMessageBox(QDialog): diff --git a/src/badger/gui/windows/ind_lim_vrange_dialog.py b/src/badger/gui/windows/ind_lim_vrange_dialog.py index 6f0c0db3..0ff108d3 100644 --- a/src/badger/gui/windows/ind_lim_vrange_dialog.py +++ b/src/badger/gui/windows/ind_lim_vrange_dialog.py @@ -6,7 +6,7 @@ from copy import deepcopy import math -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QWidget, QHBoxLayout, @@ -21,7 +21,7 @@ QLineEdit, QFrame, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from badger.gui.components.bounds_preview import BoundsPreviewBar diff --git a/src/badger/gui/windows/lim_vrange_dialog.py b/src/badger/gui/windows/lim_vrange_dialog.py index 37fa41d1..15d26b16 100644 --- a/src/badger/gui/windows/lim_vrange_dialog.py +++ b/src/badger/gui/windows/lim_vrange_dialog.py @@ -5,7 +5,7 @@ from copy import deepcopy -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QWidget, QHBoxLayout, @@ -14,14 +14,14 @@ QDoubleSpinBox, QRadioButton, ) -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QGroupBox, QLabel, QComboBox, QStyledItemDelegate, QStackedWidget, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt class BadgerLimitVariableRangeDialog(QDialog): diff --git a/src/badger/gui/windows/load_data_from_run_dialog.py b/src/badger/gui/windows/load_data_from_run_dialog.py index 9a5402bd..846302af 100644 --- a/src/badger/gui/windows/load_data_from_run_dialog.py +++ b/src/badger/gui/windows/load_data_from_run_dialog.py @@ -2,7 +2,7 @@ user browse archived runs, preview their data as a plot, and import selected data points to seed the optimizer with prior observations.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QWidget, QHBoxLayout, @@ -12,7 +12,7 @@ QFileDialog, QMessageBox, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore from typing import List, Callable diff --git a/src/badger/gui/windows/main_window.py b/src/badger/gui/windows/main_window.py index 6b92fd73..037a8127 100644 --- a/src/badger/gui/windows/main_window.py +++ b/src/badger/gui/windows/main_window.py @@ -5,8 +5,10 @@ import os from importlib import metadata from typing import Dict -from PyQt5.QtCore import QThread -from PyQt5.QtWidgets import QDesktopWidget, QMainWindow, QMessageBox, QStackedWidget + +from qtpy.QtCore import QThread +from qtpy.QtGui import QGuiApplication +from qtpy.QtWidgets import QMainWindow, QMessageBox, QStackedWidget from badger.gui.components.create_process import CreateProcess from badger.gui.components.process_manager import ProcessManager from badger.gui.pages.home_page import BadgerHomePage @@ -96,7 +98,7 @@ def init_ui(self) -> None: def center(self) -> None: logger.info("Centering main window.") qr = self.frameGeometry() - cp = QDesktopWidget().availableGeometry().center() + cp = QGuiApplication.primaryScreen().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) diff --git a/src/badger/gui/windows/measurement_retry_dialog.py b/src/badger/gui/windows/measurement_retry_dialog.py index b5bed059..92787adf 100644 --- a/src/badger/gui/windows/measurement_retry_dialog.py +++ b/src/badger/gui/windows/measurement_retry_dialog.py @@ -1,7 +1,7 @@ """Error dialog that allows users to retry measurements after errors in setting variable values / getting observables from the environment. Based on the ExpandableMessageBox dialog.""" -from PyQt5.QtWidgets import QDialogButtonBox, QMessageBox +from qtpy.QtWidgets import QDialogButtonBox, QMessageBox from .expandable_message_box import ExpandableMessageBox diff --git a/src/badger/gui/windows/message_dialog.py b/src/badger/gui/windows/message_dialog.py index 788a8911..6058ce07 100644 --- a/src/badger/gui/windows/message_dialog.py +++ b/src/badger/gui/windows/message_dialog.py @@ -2,7 +2,7 @@ with an optional expandable details section in a resizable, scrollable window, used for showing verbose error information or optimization summaries.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QMessageBox, QVBoxLayout, @@ -12,8 +12,8 @@ QScrollArea, QTextEdit, ) -from PyQt5.QtGui import QTextOption, QFont, QFontDatabase -from PyQt5.QtCore import Qt +from qtpy.QtGui import QTextOption, QFont, QFontDatabase +from qtpy.QtCore import Qt # from ..components.eliding_label import ElidingLabel diff --git a/src/badger/gui/windows/review_dialog.py b/src/badger/gui/windows/review_dialog.py index ee6367a7..b96b3280 100644 --- a/src/badger/gui/windows/review_dialog.py +++ b/src/badger/gui/windows/review_dialog.py @@ -2,7 +2,7 @@ preview of a fully-configured routine before it is saved or run, allowing the user to confirm the settings.""" -from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTextBrowser, QVBoxLayout +from qtpy.QtWidgets import QDialog, QDialogButtonBox, QTextBrowser, QVBoxLayout from badger.routine import Routine from badger.utils import get_yaml_string diff --git a/src/badger/gui/windows/settings_dialog.py b/src/badger/gui/windows/settings_dialog.py index b98a8ef0..dbc04992 100644 --- a/src/badger/gui/windows/settings_dialog.py +++ b/src/badger/gui/windows/settings_dialog.py @@ -5,9 +5,7 @@ import logging import os -# from PyQt5.QtCore import QRegExp -# from PyQt5.QtGui import QRegExpValidator -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QComboBox, QGridLayout, QVBoxLayout, @@ -15,12 +13,12 @@ QLabel, QLineEdit, ) -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QDialogButtonBox, QApplication, ) -from PyQt5.QtCore import Qt +from qtpy.QtCore import Qt from qdarkstyle import load_stylesheet, DarkPalette, LightPalette from badger.settings import init_settings from badger.log import get_logging_manager @@ -58,8 +56,6 @@ def init_ui(self): vbox = QVBoxLayout(self) - # validator = QRegExpValidator(QRegExp(r"^[0-9]\d*(\.\d+)?$")) - widget_settings = QWidget(self) grid = QGridLayout(widget_settings) grid.setContentsMargins(0, 0, 0, 0) diff --git a/src/badger/gui/windows/terminition_condition_dialog.py b/src/badger/gui/windows/terminition_condition_dialog.py index 57a1c0a9..e4289bbc 100644 --- a/src/badger/gui/windows/terminition_condition_dialog.py +++ b/src/badger/gui/windows/terminition_condition_dialog.py @@ -2,7 +2,7 @@ when an optimization run should automatically stop — either after a maximum number of evaluations or after a maximum elapsed time.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QWidget, QHBoxLayout, @@ -10,8 +10,6 @@ QVBoxLayout, QSpinBox, QDoubleSpinBox, -) -from PyQt5.QtWidgets import ( QGroupBox, QLabel, QComboBox, diff --git a/src/badger/gui/windows/var_dialog.py b/src/badger/gui/windows/var_dialog.py index 247e96b1..8566410a 100644 --- a/src/badger/gui/windows/var_dialog.py +++ b/src/badger/gui/windows/var_dialog.py @@ -2,7 +2,7 @@ for finding and adding environment variables by name, querying the environment plugin to verify the variable exists and retrieve its current value.""" -from PyQt5.QtWidgets import ( +from qtpy.QtWidgets import ( QDialog, QWidget, QHBoxLayout, @@ -10,7 +10,7 @@ QPushButton, QVBoxLayout, ) -from PyQt5.QtWidgets import QGroupBox, QMessageBox +from qtpy.QtWidgets import QGroupBox, QMessageBox from badger.gui.components.labeled_lineedit import labeled_lineedit from badger.environment import instantiate_env diff --git a/src/badger/tests/test_create_process.py b/src/badger/tests/test_create_process.py index 81e5b6b8..b9e56363 100644 --- a/src/badger/tests/test_create_process.py +++ b/src/badger/tests/test_create_process.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch import pytest -from PyQt5.QtWidgets import QApplication +from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) diff --git a/src/badger/tests/test_gui_basic.py b/src/badger/tests/test_gui_basic.py index 09ddb919..61abf2fa 100644 --- a/src/badger/tests/test_gui_basic.py +++ b/src/badger/tests/test_gui_basic.py @@ -3,8 +3,8 @@ from unittest.mock import patch import pytest -from PyQt5.QtCore import QEventLoop, Qt, QTimer -from PyQt5.QtWidgets import QDialog +from qtpy.QtCore import QEventLoop, Qt, QTimer +from qtpy.QtWidgets import QDialog @pytest.fixture(scope="session") diff --git a/src/badger/tests/test_home_page.py b/src/badger/tests/test_home_page.py index b5bd6192..41b9ce2b 100644 --- a/src/badger/tests/test_home_page.py +++ b/src/badger/tests/test_home_page.py @@ -1,7 +1,7 @@ import multiprocessing import pytest -from PyQt5.QtCore import QEventLoop, QTimer +from qtpy.QtCore import QEventLoop, QTimer @pytest.fixture(scope="session") diff --git a/src/badger/tests/test_measurement_retry_dialog.py b/src/badger/tests/test_measurement_retry_dialog.py index 868399a1..9652989a 100644 --- a/src/badger/tests/test_measurement_retry_dialog.py +++ b/src/badger/tests/test_measurement_retry_dialog.py @@ -1,6 +1,6 @@ import pytest -from PyQt5.QtCore import QTimer -from PyQt5.QtWidgets import QDialog +from qtpy.QtCore import QTimer +from qtpy.QtWidgets import QDialog # conftest suppresses ExpandableMessageBox's exec_ call, but we need it here. diff --git a/src/badger/tests/test_routine_page.py b/src/badger/tests/test_routine_page.py index e286e763..fdc83a9e 100644 --- a/src/badger/tests/test_routine_page.py +++ b/src/badger/tests/test_routine_page.py @@ -8,9 +8,9 @@ MinimizeObjective, Observable, ) -from PyQt5.QtCore import Qt, QTimer -from PyQt5.QtWidgets import QApplication from pytestqt.qtbot import QtBot +from qtpy.QtCore import Qt, QTimer +from qtpy.QtWidgets import QApplication def test_routine_page_init(qtbot: QtBot): diff --git a/src/badger/tests/test_routine_runner.py b/src/badger/tests/test_routine_runner.py index 1f9648a5..e83ca363 100644 --- a/src/badger/tests/test_routine_runner.py +++ b/src/badger/tests/test_routine_runner.py @@ -1,9 +1,9 @@ import multiprocessing import pytest -from PyQt5.QtCore import QEventLoop, Qt, QTimer -from PyQt5.QtTest import QSignalSpy -from PyQt5.QtWidgets import QApplication +from qtpy.QtCore import QEventLoop, Qt, QTimer +from qtpy.QtTest import QSignalSpy +from qtpy.QtWidgets import QApplication from unittest.mock import Mock diff --git a/src/badger/tests/test_run_monitor.py b/src/badger/tests/test_run_monitor.py index fd220c6d..13b5349b 100644 --- a/src/badger/tests/test_run_monitor.py +++ b/src/badger/tests/test_run_monitor.py @@ -4,10 +4,10 @@ import numpy as np import pytest -from PyQt5.QtCore import QEventLoop, QPointF, Qt, QTimer -from PyQt5.QtGui import QMouseEvent -from PyQt5.QtTest import QSignalSpy -from PyQt5.QtWidgets import QApplication, QMessageBox +from qtpy.QtCore import QEventLoop, QPointF, Qt, QTimer +from qtpy.QtGui import QMouseEvent +from qtpy.QtTest import QSignalSpy +from qtpy.QtWidgets import QApplication, QMessageBox class TestRunMonitor: @@ -372,10 +372,8 @@ def test_reset_environment(self, qtbot, init_multiprocessing): monitor.inspector_objective.setValue(9) - with patch( - "PyQt5.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes - ): - # with patch("PyQt5.QtWidgets.QMessageBox.information") as mock_info: + with patch("qtpy.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes): + # with patch("qtpy.QtWidgets.QMessageBox.information") as mock_info: qtbot.mouseClick(action_bar.btn_set, Qt.MouseButton.LeftButton) # mock_info.assert_called_once() @@ -388,10 +386,8 @@ def test_reset_environment(self, qtbot, init_multiprocessing): # Reset env and confirm spy = QSignalSpy(action_bar.btn_reset.clicked) - with patch( - "PyQt5.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes - ): - # with patch("PyQt5.QtWidgets.QMessageBox.information") as mock_info: + with patch("qtpy.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes): + # with patch("qtpy.QtWidgets.QMessageBox.information") as mock_info: qtbot.mouseClick(action_bar.btn_reset, Qt.MouseButton.LeftButton) # mock_info.assert_called_once() @@ -421,9 +417,7 @@ def test_dial_in_solution(self, qtbot, home_page): monitor.inspector_objective.setValue(2) spy = QSignalSpy(action_bar.btn_set.clicked) - with patch( - "PyQt5.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes - ): + with patch("qtpy.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes): qtbot.mouseClick(action_bar.btn_set, Qt.MouseButton.LeftButton) assert len(spy) == 1 @@ -438,7 +432,7 @@ def test_dial_in_solution(self, qtbot, home_page): # monitor.plot_x_axis = False - # with patch("PyQt5.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes): + # with patch("qtpy.QtWidgets.QMessageBox.question", return_value=QMessageBox.Yes): # qtbot.mouseClick(monitor.btn_set, Qt.MouseButton.LeftButton) # not_time_x_view_range = monitor.plot_var.getViewBox().viewRange()[0] @@ -527,7 +521,7 @@ def handle_dialog(): action_bar.run_until_action.trigger() # Check if critical violation alert being triggered - with patch("PyQt5.QtWidgets.QMessageBox.warning", return_value=QMessageBox.Yes): + with patch("qtpy.QtWidgets.QMessageBox.warning", return_value=QMessageBox.Yes): while monitor.running: qtbot.wait(100) diff --git a/src/badger/utils.py b/src/badger/utils.py index 29f08387..88186019 100644 --- a/src/badger/utils.py +++ b/src/badger/utils.py @@ -15,7 +15,7 @@ import yaml from badger.errors import BadgerLoadConfigError -from PyQt5.QtWidgets import QWidget, QLayout +from qtpy.QtWidgets import QWidget, QLayout from decimal import Decimal, ROUND_CEILING, ROUND_FLOOR