From 390cc65891bb558564c645c89f112cab6a605339 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Thu, 9 Jan 2025 14:28:40 -0800 Subject: [PATCH] wip --- pydm/tests/widgets/test_enum_button.py | 10 +++-- pydm/utilities/__init__.py | 29 +++++++++++++- pydm/widgets/datetime.py | 41 +++++++++++++++++--- pydm/widgets/display_format.py | 13 +++++++ pydm/widgets/enum_button.py | 29 ++++++++++++-- pydm/widgets/image.py | 30 ++++++++++++++- pydm/widgets/label.py | 18 +++++++-- pydm/widgets/line_edit.py | 45 +++++++++++++--------- pydm/widgets/logdisplay.py | 43 ++++++++++++++++++++- pydm/widgets/template_repeater.py | 29 ++++++++++++-- pydm/widgets/timeplot.py | 53 ++++++++++++++++++-------- 11 files changed, 285 insertions(+), 55 deletions(-) diff --git a/pydm/tests/widgets/test_enum_button.py b/pydm/tests/widgets/test_enum_button.py index c6983be2c..71657c04c 100644 --- a/pydm/tests/widgets/test_enum_button.py +++ b/pydm/tests/widgets/test_enum_button.py @@ -4,6 +4,7 @@ from ...widgets.enum_button import PyDMEnumButton, WidgetType, class_for_type from ... import data_plugins +from ...utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes def test_construct(qtbot): @@ -43,11 +44,13 @@ def test_widget_type(qtbot, widget_type): qtbot.addWidget(widget) assert widget.widgetType == WidgetType.PushButton - assert isinstance(widget._widgets[0], class_for_type[WidgetType.PushButton.value]) + index = WidgetType.PushButton if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5 else WidgetType.PushButton.value + assert isinstance(widget._widgets[0], class_for_type[index]) widget.widgetType = widget_type assert widget.widgetType == widget_type - assert isinstance(widget._widgets[0], class_for_type[widget_type.value]) + index = widget_type if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5 else widget_type.value + assert isinstance(widget._widgets[0], class_for_type[index]) @pytest.mark.parametrize("orientation", [Qt.Horizontal, Qt.Vertical]) @@ -82,7 +85,8 @@ def test_widget_orientation(qtbot, orientation): w = item.widget() qtbot.addWidget(w) assert w is not None - assert isinstance(w, class_for_type[widget.widgetType.value]) + index = widget.widgetType if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5 else widget.widgetType.value + assert isinstance(w, class_for_type[index]) @pytest.mark.parametrize( diff --git a/pydm/utilities/__init__.py b/pydm/utilities/__init__.py index 1a5a2594d..f888e78b3 100644 --- a/pydm/utilities/__init__.py +++ b/pydm/utilities/__init__.py @@ -12,7 +12,7 @@ import errno from typing import List, Optional - +from enum import IntEnum from qtpy import QtCore, QtGui, QtWidgets from . import colors, macro, shortcuts @@ -39,6 +39,33 @@ logger = logging.getLogger(__name__) +# The qtpy abstraction layer decides which qt python wrapper to use by the QT_API. +# ACTIVE_QT_WRAPPER is basically used to have easier access to the QT_API env variable. +# Atm for pydm we only intend to support PyQt5 (legacy) and PySide6. +class QtWrapperTypes(IntEnum): + UNSUPPORTED = 0 + PYSIDE6 = 1 + PYQT5 = 2 + + +ACTIVE_QT_WRAPPER = QtWrapperTypes.UNSUPPORTED + +# QT_API should be set according to the qtpy docs: https://github.com/spyder-ide/qtpy?tab=readme-ov-file#requirements +qt_api = os.getenv("QT_API", "").lower() +if qt_api == "pyside6": + ACTIVE_QT_WRAPPER = QtWrapperTypes.PYSIDE6 +elif qt_api == "pyqt5": + ACTIVE_QT_WRAPPER = QtWrapperTypes.PYQT5 + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.UNSUPPORTED: + error_message = ( + "The QT_API variable is not set to a supported Qt Python wrapper " + "(PySide6 or PyQt5). Please set QT_API to 'pyside6' or 'pyqt5'." + ) + logger.error(error_message) + raise RuntimeError(error_message) + + def is_ssh_session(): """ Whether or not this is a SSH session. diff --git a/pydm/widgets/datetime.py b/pydm/widgets/datetime.py index a6a96317c..80f790c10 100644 --- a/pydm/widgets/datetime.py +++ b/pydm/widgets/datetime.py @@ -4,16 +4,25 @@ from PyQt5.QtCore import Q_ENUM from .base import PyDMWritableWidget, PyDMWidget +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes logger = logging.getLogger(__name__) +# works with pyside6 class TimeBase(Enum): Milliseconds = 0 Seconds = 1 -class PyDMDateTimeEdit(QtWidgets.QDateTimeEdit, PyDMWritableWidget): +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class TimeBase(object): # noqa F811 + Milliseconds = 0 + Seconds = 1 + + +class PyDMDateTimeEditBase(QtWidgets.QDateTimeEdit, PyDMWritableWidget): Q_ENUM(TimeBase) returnPressed = QtCore.Signal() """ @@ -74,7 +83,7 @@ def blockPastDate(self, block): self._block_past_date = block def keyPressEvent(self, key_event): - ret = super(PyDMDateTimeEdit, self).keyPressEvent(key_event) + ret = super(PyDMDateTimeEditBase, self).keyPressEvent(key_event) if key_event.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]: self.returnPressed.emit() return ret @@ -96,7 +105,7 @@ def send_value(self): self.send_value_signal.emit(new_value) def value_changed(self, new_val): - super(PyDMDateTimeEdit, self).value_changed(new_val) + super(PyDMDateTimeEditBase, self).value_changed(new_val) if self.timeBase == TimeBase.Seconds: new_val *= 1000 @@ -109,7 +118,18 @@ def value_changed(self, new_val): self.setDateTime(val) -class PyDMDateTimeLabel(QtWidgets.QLabel, PyDMWidget): +# works with pyside6 +class PyDMDateTimeEdit(PyDMDateTimeEditBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMDateTimeEdit(PyDMDateTimeEditBase, TimeBase): # noqa F811 + pass + + +class PyDMDateTimeLabelBase(QtWidgets.QLabel, PyDMWidget): Q_ENUM(TimeBase) """ A QLabel with support for setting the text via a PyDM Channel, or @@ -168,7 +188,7 @@ def relative(self, checked): self._relative = checked def value_changed(self, new_val): - super(PyDMDateTimeLabel, self).value_changed(new_val) + super(PyDMDateTimeLabelBase, self).value_changed(new_val) if self.timeBase == TimeBase.Seconds: new_val *= 1000 @@ -179,3 +199,14 @@ def value_changed(self, new_val): else: val.setMSecsSinceEpoch(new_val) self.setText(val.toString(self.textFormat)) + + +# works with pyside6 +class PyDMDateTimeLabel(PyDMDateTimeLabelBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMDateTimeLabel(PyDMDateTimeLabelBase, TimeBase): # noqa F811 + pass diff --git a/pydm/widgets/display_format.py b/pydm/widgets/display_format.py index dcfc55895..c53e83411 100644 --- a/pydm/widgets/display_format.py +++ b/pydm/widgets/display_format.py @@ -5,10 +5,12 @@ import logging import warnings from enum import Enum +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes logger = logging.getLogger(__name__) +# works with pyside6 class DisplayFormat(Enum): """Display format for showing data in a PyDM widget.""" @@ -26,6 +28,17 @@ class DisplayFormat(Enum): Binary = 5 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class DisplayFormat(object): # noqa F811 + Default = 0 + String = 1 + Decimal = 2 + Exponential = 3 + Hex = 4 + Binary = 5 + + def parse_value_for_display( value: Any, precision: int, diff --git a/pydm/widgets/enum_button.py b/pydm/widgets/enum_button.py index 2c1ac6f5a..09709f725 100644 --- a/pydm/widgets/enum_button.py +++ b/pydm/widgets/enum_button.py @@ -8,19 +8,28 @@ from .base import PyDMWritableWidget from .. import data_plugins +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes +# works with pyside6 class WidgetType(Enum): PushButton = 0 RadioButton = 1 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class WidgetType(object): # noqa F811 + PushButton = 0 + RadioButton = 1 + + class_for_type = [QPushButton, QRadioButton] logger = logging.getLogger(__name__) -class PyDMEnumButton(QWidget, PyDMWritableWidget): +class PyDMEnumButtonBase(QWidget, PyDMWritableWidget): """ A QWidget that renders buttons for every option of Enum Items. For now, two types of buttons can be rendered: @@ -401,7 +410,8 @@ def generate_widgets(items): w.deleteLater() for idx, entry in enumerate(items): - w = class_for_type[self._widget_type.value](parent=self) + index = self._widget_type if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5 else self._widget_type.value + w = class_for_type[index](parent=self) w.setCheckable(self.checkable) w.setText(entry) w.setVisible(False) @@ -481,7 +491,7 @@ def value_changed(self, new_val): The new value from the channel. """ if new_val is not None and new_val != self.value: - super(PyDMEnumButton, self).value_changed(new_val) + super(PyDMEnumButtonBase, self).value_changed(new_val) btn = self._btn_group.button(new_val) if btn: btn.setChecked(True) @@ -498,7 +508,7 @@ def enum_strings_changed(self, new_enum_strings): The new list of values """ if new_enum_strings is not None and new_enum_strings != self.enum_strings: - super(PyDMEnumButton, self).enum_strings_changed(new_enum_strings) + super(PyDMEnumButtonBase, self).enum_strings_changed(new_enum_strings) self._has_enums = True self.check_enable_state() self.rebuild_widgets() @@ -522,3 +532,14 @@ def paintEvent(self, _): opt.initFrom(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) painter.setRenderHint(QPainter.Antialiasing) + + +# works with pyside6 +class PyDMEnumButton(PyDMEnumButtonBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMEnumButton(PyDMEnumButtonBase, WidgetType): # noqa F811 + pass diff --git a/pydm/widgets/image.py b/pydm/widgets/image.py index 36edf4baf..8e41782c4 100644 --- a/pydm/widgets/image.py +++ b/pydm/widgets/image.py @@ -10,10 +10,12 @@ from .channel import PyDMChannel from .colormaps import cmaps, cmap_names, PyDMColorMap from .base import PyDMWidget +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes logger = logging.getLogger(__name__) +# works with pyside6 class ReadingOrder(Enum): """Class to build ReadingOrder ENUM property.""" @@ -21,6 +23,14 @@ class ReadingOrder(Enum): Clike = 1 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class ReadingOrder(object): # noqa F811 + Fortranlike = 0 + Clike = 1 + + +# works with pyside6 class DimensionOrder(Enum): """ Class to build DimensionOrder ENUM property. @@ -43,6 +53,13 @@ class DimensionOrder(Enum): WidthFirst = 1 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class DimensionOrder(object): # noqa F811 + HeightFirst = 0 + WidthFirst = 1 + + class ImageUpdateThread(QThread): updateSignal = Signal(list) @@ -100,7 +117,7 @@ def run(self): self.image_view.needs_redraw = False -class PyDMImageView( +class PyDMImageViewBase( ImageView, PyDMWidget, PyDMColorMap, @@ -753,3 +770,14 @@ def scaleYAxis(self): @scaleYAxis.setter def scaleYAxis(self, new_scale): self.getView().getAxis("left").setScale(new_scale) + + +# works with pyside6 +class PyDMImageView(PyDMImageViewBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMImageView(PyDMImageViewBase, ReadingOrder, DimensionOrder): # noqa F811 + pass diff --git a/pydm/widgets/label.py b/pydm/widgets/label.py index 7ccb5285d..ad43e4af8 100644 --- a/pydm/widgets/label.py +++ b/pydm/widgets/label.py @@ -6,17 +6,18 @@ from pydm import config from pydm.widgets.base import only_if_channel_set from PyQt5.QtCore import Q_ENUM +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes _labelRuleProperties = {"Text": ["value_changed", str]} -class PyDMLabel(QLabel, TextFormatter, PyDMWidget, new_properties=_labelRuleProperties): +class PyDMLabelBase(QLabel, TextFormatter, PyDMWidget, new_properties=_labelRuleProperties): """ A QLabel with support for setting the text via a PyDM Channel, or through the PyDM Rules system. .. note:: - If a PyDMLabel is configured to use a Channel, and also with a rule which changes the 'Text' property, + If a PyDMLabelBase is configured to use a Channel, and also with a rule which changes the 'Text' property, the behavior is undefined. Use either the Channel *or* a text rule, but not both. @@ -89,7 +90,7 @@ def value_changed(self, new_value): new_value : str, int, float, bool or np.ndarray The new value from the channel. The type depends on the channel. """ - super(PyDMLabel, self).value_changed(new_value) + super(PyDMLabelBase, self).value_changed(new_value) new_value = parse_value_for_display( value=new_value, precision=self.precision, @@ -127,3 +128,14 @@ def check_enable_state(self): if not self._connected: self.setText(self.channel) super().check_enable_state() + + +# works with pyside6 +class PyDMLabel(PyDMLabelBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMLabel(PyDMLabelBase, DisplayFormat): # noqa F811 + pass diff --git a/pydm/widgets/line_edit.py b/pydm/widgets/line_edit.py index 3b7e47a27..76b94a13b 100755 --- a/pydm/widgets/line_edit.py +++ b/pydm/widgets/line_edit.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) -class PyDMLineEdit(QLineEdit, TextFormatter, PyDMWritableWidget): +class PyDMLineEditBase(QLineEdit, TextFormatter, PyDMWritableWidget, DisplayFormat): """ A QLineEdit (writable text field) with support for Channels and more from PyDM. @@ -43,7 +43,7 @@ def __init__(self, parent=None, init_channel=None): self.returnPressed.connect(self.send_value) self.unitMenu = None - self._display_format_type = self.DisplayFormat.Default + self._display_format_type = DisplayFormat.Default self._string_encoding = "utf_8" self._user_set_read_only = False # Are we *really* read only? if utilities.is_pydm_app(): @@ -62,10 +62,10 @@ def displayFormat(self, new_type): def value_changed(self, new_val): """ - Receive and update the PyDMLineEdit for a new channel value + Receive and update the PyDMLineEditBase for a new channel value The actual value of the input is saved as well as the type received. - This also resets the PyDMLineEdit display text using + This also resets the PyDMLineEditBase display text using :meth:`.set_display` Parameters @@ -73,7 +73,7 @@ def value_changed(self, new_val): value: str, float or int The new value of the channel """ - super(PyDMLineEdit, self).value_changed(new_val) + super(PyDMLineEditBase, self).value_changed(new_val) self.set_display() def send_value(self): @@ -82,7 +82,7 @@ def send_value(self): The text is cleaned of all units, user-formatting and scale values before being sent back to the channel. This function is attached the - ReturnPressed signal of the PyDMLineEdit + ReturnPressed signal of the PyDMLineEditBase """ send_value = str(self.text()) # Clean text of unit string @@ -121,7 +121,7 @@ def send_value(self): self.send_value_signal[np.ndarray].emit(arr_value) elif self.channeltype == bool: try: - val = bool(PyDMLineEdit.strtobool(send_value)) + val = bool(PyDMLineEditBase.strtobool(send_value)) self.send_value_signal[bool].emit(val) # might want to add error to application screen except ValueError: @@ -142,15 +142,15 @@ def send_value(self): def setReadOnly(self, readOnly): self._user_set_read_only = readOnly - super(PyDMLineEdit, self).setReadOnly(True if self._user_set_read_only else not self._write_access) + super(PyDMLineEditBase, self).setReadOnly(True if self._user_set_read_only else not self._write_access) def write_access_changed(self, new_write_access): """ - Change the PyDMLineEdit to read only if write access is denied + Change the PyDMLineEditBase to read only if write access is denied """ - super(PyDMLineEdit, self).write_access_changed(new_write_access) + super(PyDMLineEditBase, self).write_access_changed(new_write_access) if not self._user_set_read_only: - super(PyDMLineEdit, self).setReadOnly(not new_write_access) + super(PyDMLineEditBase, self).setReadOnly(not new_write_access) def unit_changed(self, new_unit): """ @@ -160,7 +160,7 @@ def unit_changed(self, new_unit): attribute. Receiving a new value for the unit causes the display to reset. """ - super(PyDMLineEdit, self).unit_changed(new_unit) + super(PyDMLineEditBase, self).unit_changed(new_unit) self._scale = 1 def create_unit_options(self): @@ -168,7 +168,7 @@ def create_unit_options(self): Create the menu for displaying possible unit values The menu is filled with possible unit conversions based on the - current PyDMLineEdit. If either the unit is not found in the by + current PyDMLineEditBase. If either the unit is not found in the by the :func:`utilities.find_unit_options` function, or, the :attr:`.showUnits` attribute is set to False, the menu will tell the user that there are no available conversions @@ -234,12 +234,12 @@ def widget_ctx_menu(self): def set_display(self): """ - Set the text display of the PyDMLineEdit. + Set the text display of the PyDMLineEditBase. The original value given by the PV is converted to a text entry based on the current settings for scale value, precision, a user-defined format, and the current units. If the user is currently entering a - value in the PyDMLineEdit the text will not be changed. + value in the PyDMLineEditBase the text will not be changed. """ if self.value is None: return @@ -307,13 +307,13 @@ def focusInEvent(self, event: QFocusEvent) -> None: def focusOutEvent(self, event): """ - Overwrites the function called when a user leaves a PyDMLineEdit + Overwrites the function called when a user leaves a PyDMLineEditBase without pressing return. Resets the value of the text field to the current channel value. """ if self._display is not None: self.setText(self._display) - super(PyDMLineEdit, self).focusOutEvent(event) + super(PyDMLineEditBase, self).focusOutEvent(event) @staticmethod def strtobool(val): @@ -326,3 +326,14 @@ def strtobool(val): return 0 else: raise ValueError("invalid boolean input") + + +# works with pyside6 +class PyDMLineEdit(PyDMLineEditBase): + pass + + +if utilities.ACTIVE_QT_WRAPPER == utilities.QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMLineEdit(PyDMLineEditBase, DisplayFormat): # noqa F811 + pass diff --git a/pydm/widgets/logdisplay.py b/pydm/widgets/logdisplay.py index 6e564678f..90bcef9aa 100644 --- a/pydm/widgets/logdisplay.py +++ b/pydm/widgets/logdisplay.py @@ -18,6 +18,7 @@ QStyle, ) from qtpy.QtGui import QPainter +from ..utilities import ACTIVE_QT_WRAPPER, QtWrapperTypes logger = logging.getLogger(__name__) @@ -83,6 +84,7 @@ def emit(self, record): logger.debug("Handler was destroyed at the C++ level.") +# works with pyside6 class LogLevels(Enum): NOTSET = 0 DEBUG = 10 @@ -105,7 +107,35 @@ def as_dict(): return OrderedDict(sorted(entries, key=lambda x: x[1], reverse=False)) -class PyDMLogDisplay(QWidget): +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class LogLevels(object): # noqa F811 + NOTSET = 0 + DEBUG = 10 + INFO = 20 + WARNING = 30 + ERROR = 40 + CRITICAL = 50 + + @staticmethod + def as_dict(): + """ + Returns an ordered dict of LogLevels ordered by value. + Returns + ------- + OrderedDict + """ + # First let's remove the internals + entries = [ + (k, v) + for k, v in LogLevels.__dict__.items() + if not k.startswith("__") and not callable(v) and not isinstance(v, staticmethod) + ] + + return OrderedDict(sorted(entries, key=lambda x: x[1], reverse=False)) + + +class PyDMLogDisplayBase(QWidget): """ Standard display for Log Output @@ -255,3 +285,14 @@ def paintEvent(self, _): opt.initFrom(self) self.style().drawPrimitive(QStyle.PE_Widget, opt, painter, self) painter.setRenderHint(QPainter.Antialiasing) + + +# works with pyside6 +class PyDMLogDisplay(PyDMLogDisplayBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMLogDisplay(PyDMLogDisplayBase, LogLevels): # noqa F811 + pass diff --git a/pydm/widgets/template_repeater.py b/pydm/widgets/template_repeater.py index 99741b1d5..b548ee6d3 100644 --- a/pydm/widgets/template_repeater.py +++ b/pydm/widgets/template_repeater.py @@ -9,7 +9,7 @@ from .base import PyDMPrimitiveWidget from pydm.utilities import is_qt_designer import pydm.data_plugins -from ..utilities import find_file +from ..utilities import find_file, ACTIVE_QT_WRAPPER, QtWrapperTypes from ..display import load_file logger = logging.getLogger(__name__) @@ -113,18 +113,27 @@ def smart_spacing(self, pm): return parent.spacing() +# works with pyside6 class LayoutType(Enum): Vertical = 0 Horizontal = 1 Flow = 2 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class LayoutType(object): # noqa F811 + Vertical = 0 + Horizontal = 1 + Flow = 2 + + layout_class_for_type = (QVBoxLayout, QHBoxLayout, FlowLayout) -class PyDMTemplateRepeater(QFrame, PyDMPrimitiveWidget): +class PyDMTemplateRepeaterBase(QFrame, PyDMPrimitiveWidget): """ - PyDMTemplateRepeater takes a .ui file with macro variables as a template, and a JSON + PyDMTemplateRepeaterBase takes a .ui file with macro variables as a template, and a JSON file (or a list of dictionaries) with a list of values to use to fill in the macro variables, then creates a layout with one instance of the template for each item in the list. @@ -393,7 +402,8 @@ def rebuild(self): return self.setUpdatesEnabled(False) - layout_class = layout_class_for_type[self.layoutType.value] + layout_index = self.layoutType if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5 else self.layoutType.value + layout_class = layout_class_for_type[layout_index] if type(self.layout()) != layout_class: if self.layout() is not None: # Trick to remove the existing layout by re-parenting it in an empty widget. @@ -457,3 +467,14 @@ def data(self, new_data): """ self._data = new_data self.rebuild() + + +# works with pyside6 +class PyDMTemplateRepeater(PyDMTemplateRepeaterBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMTemplateRepeater(PyDMTemplateRepeaterBase, LayoutType): # noqa F811 + pass diff --git a/pydm/widgets/timeplot.py b/pydm/widgets/timeplot.py index 47adf83a1..8575cc483 100644 --- a/pydm/widgets/timeplot.py +++ b/pydm/widgets/timeplot.py @@ -9,7 +9,7 @@ from PyQt5.QtCore import Q_ENUM from .baseplot import BasePlot, BasePlotCurveItem from .channel import PyDMChannel -from ..utilities import remove_protocol +from ..utilities import remove_protocol, ACTIVE_QT_WRAPPER, QtWrapperTypes import logging from enum import Enum @@ -26,6 +26,7 @@ DEFAULT_UPDATE_INTERVAL = 1000 # Plot update rate for fixed rate mode in milliseconds +# works with pyside6 class updateMode(Enum): """updateMode as new type for plot update""" @@ -33,6 +34,15 @@ class updateMode(Enum): AtFixedRate = 2 +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + + class updateMode(object): # noqa F811 + """updateMode as new type for plot update""" + + OnValueChange = 1 + AtFixedRate = 2 + + class TimePlotCurveItem(BasePlotCurveItem): """ TimePlotCurveItem represents a single curve in a time plot. @@ -414,9 +424,9 @@ def channels(self): return [self.channel] -class PyDMTimePlot(BasePlot): +class PyDMTimePlotBase(BasePlot): """ - PyDMTimePlot is a widget to plot one or more channels vs. time. + PyDMTimePlotBase is a widget to plot one or more channels vs. time. Each curve can plot either a Y-axis waveform vs. its indices, or a Y-axis waveform against an X-axis waveform. @@ -477,7 +487,7 @@ def __init__( self.starting_epoch_time = time.time() self._bottom_axis = AxisItem("bottom") - super(PyDMTimePlot, self).__init__( + super(PyDMTimePlotBase, self).__init__( parent=parent, background=background, axisItems={"bottom": self._bottom_axis} ) @@ -499,7 +509,7 @@ def __init__( self.update_timer = QTimer(self) self.update_timer.setInterval(self._update_interval) - self._update_mode = PyDMTimePlot.OnValueChange + self._update_mode = PyDMTimePlotBase.OnValueChange self._needs_redraw = True self.labels = {"left": None, "right": None, "bottom": None} @@ -516,12 +526,12 @@ def to_dict(self) -> OrderedDict: """Adds attribute specific to TimePlot to add onto BasePlot's to_dict. This helps to recreate the Plot Config if we import a save file of it""" dic_ = OrderedDict([("refreshInterval", self.auto_scroll_timer.interval() / 1000)]) - dic_.update(super(PyDMTimePlot, self).to_dict()) + dic_.update(super(PyDMTimePlotBase, self).to_dict()) return dic_ def initialize_for_designer(self): # If we are in Qt Designer, don't update the plot continuously. - # This function gets called by PyDMTimePlot's designer plugin. + # This function gets called by PyDMTimePlotBase's designer plugin. self.redraw_timer.setSingleShot(True) def addYChannel( @@ -687,7 +697,7 @@ def updateXAxis(self, update_immediately=False): return if self._plot_by_timestamps: - if self._update_mode == PyDMTimePlot.OnValueChange: + if self._update_mode == PyDMTimePlotBase.OnValueChange: maxrange = max([curve.max_x() for curve in self._curves]) else: maxrange = time.time() @@ -709,7 +719,7 @@ def clearCurves(self): """ Remove all curves from the graph. """ - super(PyDMTimePlot, self).clear() + super(PyDMTimePlotBase, self).clear() def getCurves(self): """ @@ -920,7 +930,7 @@ def resetBufferSize(self): bufferSize = Property("int", getBufferSize, setBufferSize, resetBufferSize) def getUpdatesAsynchronously(self): - return self._update_mode == PyDMTimePlot.AtFixedRated + return self._update_mode == PyDMTimePlotBase.AtFixedRated def setUpdatesAsynchronously(self, value): for curve in self._curves: @@ -929,14 +939,14 @@ def setUpdatesAsynchronously(self, value): Check if value is from updatesAsynchronously(bool) or updateMode(int) """ if isinstance(value, int) and value == updateMode.AtFixedRate or isinstance(value, bool) and value is True: - self._update_mode = PyDMTimePlot.AtFixedRate + self._update_mode = PyDMTimePlotBase.AtFixedRate self.update_timer.start() else: - self._update_mode = PyDMTimePlot.OnValueChange + self._update_mode = PyDMTimePlotBase.OnValueChange self.update_timer.stop() def resetUpdatesAsynchronously(self): - self._update_mode = PyDMTimePlot.OnValueChange + self._update_mode = PyDMTimePlotBase.OnValueChange self.update_timer.stop() for curve in self._curves: curve.resetUpdatesAsynchronously() @@ -1058,14 +1068,14 @@ def resetUpdateInterval(self): def getAutoRangeX(self): if self._plot_by_timestamps: return False - return super(PyDMTimePlot, self).getAutoRangeX() + return super(PyDMTimePlotBase, self).getAutoRangeX() def setAutoRangeX(self, value): if self._plot_by_timestamps: self._auto_range_x = False self.plotItem.enableAutoRange(ViewBox.XAxis, enable=self._auto_range_x) else: - super(PyDMTimePlot, self).setAutoRangeX(value) + super(PyDMTimePlotBase, self).setAutoRangeX(value) def channels(self): return [curve.channel for curve in self._curves] @@ -1130,7 +1140,7 @@ def enableCrosshair( horizontal_movable : bool True if the user can move the horizontal line; False if not """ - super(PyDMTimePlot, self).enableCrosshair( + super(PyDMTimePlotBase, self).enableCrosshair( is_enabled, starting_x_pos, starting_y_pos, @@ -1141,6 +1151,17 @@ def enableCrosshair( ) +# works with pyside6 +class PyDMTimePlot(PyDMTimePlotBase): + pass + + +if ACTIVE_QT_WRAPPER == QtWrapperTypes.PYQT5: + # Overrides the previous class defintion + class PyDMTimePlot(PyDMTimePlotBase, updateMode): # noqa F811 + pass + + class TimeAxisItem(AxisItem): """ TimeAxisItem formats a unix time axis into a human-readable format.