Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nstelter-slac committed Jan 10, 2025
1 parent fe794e0 commit 390cc65
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 55 deletions.
10 changes: 7 additions & 3 deletions pydm/tests/widgets/test_enum_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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(
Expand Down
29 changes: 28 additions & 1 deletion pydm/utilities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
41 changes: 36 additions & 5 deletions pydm/widgets/datetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
"""
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
13 changes: 13 additions & 0 deletions pydm/widgets/display_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand All @@ -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,
Expand Down
29 changes: 25 additions & 4 deletions pydm/widgets/enum_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -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
30 changes: 29 additions & 1 deletion pydm/widgets/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,27 @@
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."""

Fortranlike = 0
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.
Expand All @@ -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)

Expand Down Expand Up @@ -100,7 +117,7 @@ def run(self):
self.image_view.needs_redraw = False


class PyDMImageView(
class PyDMImageViewBase(
ImageView,
PyDMWidget,
PyDMColorMap,
Expand Down Expand Up @@ -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
18 changes: 15 additions & 3 deletions pydm/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Loading

0 comments on commit 390cc65

Please sign in to comment.