diff --git a/examples/icons/icons.ui b/examples/icons/icons.ui new file mode 100644 index 000000000..ee9c038ea --- /dev/null +++ b/examples/icons/icons.ui @@ -0,0 +1,403 @@ + + + Form + + + + 0 + 0 + 510 + 400 + + + + + 0 + 0 + + + + + 500 + 400 + + + + Form + + + + + + <html><head/><body><p>The PyDMIcon property allows for specifying icons in Designer from provided cross-platform icon sets.</p><p>Icons can be used from both Qt's standard icon set (https://doc.qt.io/qt-6/qstyle.html#StandardPixmap-enum) and the &quot;Font Awesome&quot; icon set (https://fontawesome.com/icons?d=gallery).</p></body></html> + + + Qt::RichText + + + true + + + + + + + + + + + QFrame with support for alarms + This class inherits from QFrame and PyDMWidget. + + Parameters + ---------- + parent : QWidget + The parent widget for the Label + init_channel : str, optional + The channel to be used by the widget. + + + + true + + + ca://MTEST:Run + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + This button uses an icon from Qt's standard icon set. + + + + + + + + + + + + + + + + This button uses a standard icon. + + + false + + + false + + + + + + false + + + ca://MTEST:Run + + + SP_ArrowUp + + + + 170 + 0 + 127 + + + + false + + + + + + + + + false + + + + + + 1 + + + None + + + false + + + false + + + eye-slash + + + + + + + + + + + + + + QFrame with support for alarms + This class inherits from QFrame and PyDMWidget. + + Parameters + ---------- + parent : QWidget + The parent widget for the Label + init_channel : str, optional + The channel to be used by the widget. + + + + false + + + true + + + ca://MTEST:Run + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + This button uses an icon from the "Font Awesome" icon set. + + + + + + + + + + + + + This button uses a "Font Awesome" icon. + + + false + + + false + + + false + + + + + + false + + + ca://MTEST:Run + + + arrow-up + + + false + + + + + + + + + false + + + + + + 0 + + + None + + + false + + + false + + + + + + + + + + + + + + QFrame with support for alarms + This class inherits from QFrame and PyDMWidget. + + Parameters + ---------- + parent : QWidget + The parent widget for the Label + init_channel : str, optional + The channel to be used by the widget. + + + + true + + + ca://MTEST:Run + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + The color of icons from the "Font Awesome" set is configurable. (this can't be done with Qt standard icons since they are already multi-colored) + + + true + + + + + + + + + + + + + This button uses a colored "Font Awesome" icon. + + + false + + + false + + + + + + false + + + ca://MTEST:Run + + + arrow-up + + + + 255 + 0 + 0 + + + + false + + + + + + + + + false + + + + + + 0 + + + None + + + false + + + false + + + + + + + + + + + PyDMFrame + QFrame +
pydm.widgets.frame
+ 1 +
+ + PyDMPushButton + QPushButton +
pydm.widgets.pushbutton
+
+
+ + +
diff --git a/pydm/tests/widgets/test_pushbutton.py b/pydm/tests/widgets/test_pushbutton.py index df87c6a50..26ccdcb9c 100644 --- a/pydm/tests/widgets/test_pushbutton.py +++ b/pydm/tests/widgets/test_pushbutton.py @@ -102,6 +102,28 @@ def test_construct(qtbot, label, press_value, relative, init_channel, icon_font_ button_icon_pixmap = pydm_pushbutton.icon().pixmap(size) assert icon_pixmap.toImage() == button_icon_pixmap.toImage() + # verify that qt standard icons can be set through our custom property + style = pydm_pushbutton.style() + test_icon = style.standardIcon(style.SP_DesktopIcon) + test_icon_image = test_icon.pixmap(size).toImage() + + pydm_pushbutton.PyDMIcon = "SP_DesktopIcon" + push_btn_icon = pydm_pushbutton.icon() + push_btn_icon_image = push_btn_icon.pixmap(size).toImage() + + assert test_icon_image == push_btn_icon_image + + # verify that "Font Awesome" icons can be set through our custom property + icon_f = IconFont() + test_icon = icon_f.icon("eye-slash", color=None) + test_icon_image = test_icon.pixmap(size).toImage() + + pydm_pushbutton.PyDMIcon = "eye-slash" + push_btn_icon = pydm_pushbutton.icon() + push_btn_icon_image = push_btn_icon.pixmap(size).toImage() + + assert test_icon_image == push_btn_icon_image + assert pydm_pushbutton.showConfirmDialog is False assert pydm_pushbutton.confirmMessage == PyDMPushButton.DEFAULT_CONFIRM_MESSAGE assert pydm_pushbutton.passwordProtected is False diff --git a/pydm/tests/widgets/test_related_display_button.py b/pydm/tests/widgets/test_related_display_button.py index 4dfbc770d..6a33e688c 100644 --- a/pydm/tests/widgets/test_related_display_button.py +++ b/pydm/tests/widgets/test_related_display_button.py @@ -1,10 +1,11 @@ import os import pytest import sys -from qtpy.QtCore import Qt +from qtpy.QtCore import Qt, QSize from qtpy.QtWidgets import QApplication from ...utilities.stylesheet import global_style from ...widgets.related_display_button import PyDMRelatedDisplayButton +from ...utilities import IconFont test_ui_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test_data", "test.ui") test_ui_path_with_stylesheet = os.path.join( @@ -48,6 +49,40 @@ def test_press_with_filename(qtbot): button._rebuild_menu() qtbot.mouseRelease(button, Qt.LeftButton) + # verify default icon is set as expected + DEFAULT_ICON_NAME = "file" + DEFAULT_ICON_SIZE = QSize(16, 16) + + default_icon = IconFont().icon(DEFAULT_ICON_NAME) + + default_icon_pixmap = default_icon.pixmap(DEFAULT_ICON_SIZE) + related_display_button_icon_pixmap = button.icon().pixmap(DEFAULT_ICON_SIZE) + + assert related_display_button_icon_pixmap.toImage() == default_icon_pixmap.toImage() + assert button.cursor().pixmap().toImage() == default_icon_pixmap.toImage() + + # verify that qt standard icons can be set through our custom property + style = button.style() + test_icon = style.standardIcon(style.SP_DesktopIcon) + test_icon_image = test_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + button.PyDMIcon = "SP_DesktopIcon" + shell_cmd_icon = button.icon() + shell_cmd_icon_image = shell_cmd_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + assert test_icon_image == shell_cmd_icon_image + + # verify that "Font Awesome" icons can be set through our custom property + icon_f = IconFont() + test_icon = icon_f.icon("eye-slash", color=None) + test_icon_image = test_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + button.PyDMIcon = "eye-slash" + button_icon = button.icon() + push_btn_icon_image = button_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + assert test_icon_image == push_btn_icon_image + def check_title(): assert "Form" in QApplication.instance().main_window.windowTitle() diff --git a/pydm/tests/widgets/test_shell_command.py b/pydm/tests/widgets/test_shell_command.py index 27a1d73f6..4b1bd590e 100644 --- a/pydm/tests/widgets/test_shell_command.py +++ b/pydm/tests/widgets/test_shell_command.py @@ -73,6 +73,28 @@ def test_construct(qtbot, command, title): assert shell_cmd_icon_pixmap.toImage() == default_icon_pixmap.toImage() assert pydm_shell_command.cursor().pixmap().toImage() == default_icon_pixmap.toImage() + # verify that qt standard icons can be set through our custom property + style = pydm_shell_command.style() + test_icon = style.standardIcon(style.SP_DesktopIcon) + test_icon_image = test_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + pydm_shell_command.PyDMIcon = "SP_DesktopIcon" + shell_cmd_icon = pydm_shell_command.icon() + shell_cmd_icon_image = shell_cmd_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + assert test_icon_image == shell_cmd_icon_image + + # verify that "Font Awesome" icons can be set through our custom property + icon_f = IconFont() + test_icon = icon_f.icon("eye-slash", color=None) + test_icon_image = test_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + pydm_shell_command.PyDMIcon = "eye-slash" + shell_cmd_icon = pydm_shell_command.icon() + shell_cmd_icon_image = shell_cmd_icon.pixmap(DEFAULT_ICON_SIZE).toImage() + + assert test_icon_image == shell_cmd_icon_image + def test_deprecated_command_property_with_no_commands(qtbot): pydm_shell_command = PyDMShellCommand() diff --git a/pydm/widgets/pushbutton.py b/pydm/widgets/pushbutton.py index 37584c428..c8e44cd95 100644 --- a/pydm/widgets/pushbutton.py +++ b/pydm/widgets/pushbutton.py @@ -1,9 +1,10 @@ import hashlib -from qtpy.QtWidgets import QPushButton, QMessageBox, QInputDialog, QLineEdit +from qtpy.QtGui import QColor +from qtpy.QtWidgets import QPushButton, QMessageBox, QInputDialog, QLineEdit, QStyle from qtpy.QtCore import Slot, Property from .base import PyDMWritableWidget - +from ..utilities import IconFont import logging logger = logging.getLogger(__name__) @@ -72,6 +73,83 @@ def __init__( self._released = False self.clicked.connect(self.sendValue) + # Standard icons (which come with the qt install, and work cross-platform), + # and icons from the "Font Awesome" icon set (https://fontawesome.com/) + # can not be set with a widget's "icon" property in designer, only in python. + # so we provide our own property to specify standard icons and set them with python in the prop's setter. + self._pydm_icon_name = "" + # The color of "Font Awesome" icons can be set, + # but standard icons are already colored and can not be set. + self._pydm_icon_color = QColor(90, 90, 90) + + @Property(str) + def PyDMIcon(self) -> str: + """ + Name of icon to be set from Qt provided standard icons or from the fontawesome icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable fontawesome icons. + + Returns + ------- + str + """ + return self._pydm_icon_name + + @PyDMIcon.setter + def PyDMIcon(self, value: str) -> None: + """ + Name of icon to be set from Qt provided standard icons or from the "Font Awesome" icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable "Font Awesome" icons. + + Parameters + ---------- + value : str + """ + if self._pydm_icon_name == value: + return + + # We don't know if user is trying to use a standard icon or an icon from "Font Awesome", + # so 1st try to create a Font Awesome one, which hits exception if icon name is not valid. + try: + icon_f = IconFont() + i = icon_f.icon(value, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + icon = getattr(QStyle, value, None) + if icon: + self.setIcon(self.style().standardIcon(icon)) + + self._pydm_icon_name = value + + @Property(QColor) + def PyDMIconColor(self) -> QColor: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Returns + ------- + QColor + """ + return self._pydm_icon_color + + @PyDMIconColor.setter + def PyDMIconColor(self, state_color: QColor) -> None: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Parameters + ---------- + new_color : QColor + """ + if state_color != self._pydm_icon_color: + self._pydm_icon_color = state_color + # apply the new color + try: + icon_f = IconFont() + i = icon_f.icon(self._pydm_icon_name, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + return + @Property(bool) def passwordProtected(self): """ @@ -146,7 +224,7 @@ def protectedPassword(self, value): @Property(bool) def showConfirmDialog(self): """ - Wether or not to display a confirmation dialog. + Whether or not to display a confirmation dialog. Returns ------- @@ -157,7 +235,7 @@ def showConfirmDialog(self): @showConfirmDialog.setter def showConfirmDialog(self, value): """ - Wether or not to display a confirmation dialog. + Whether or not to display a confirmation dialog. Parameters ---------- @@ -482,7 +560,7 @@ def updatePressValue(self, value): @Property(bool) def writeWhenRelease(self): """ - Wether or not to write releaseValue on release + Whether or not to write releaseValue on release Returns ------- @@ -493,7 +571,7 @@ def writeWhenRelease(self): @writeWhenRelease.setter def writeWhenRelease(self, value): """ - Wether or not to write releaseValue on release + Whether or not to write releaseValue on release Parameters ---------- diff --git a/pydm/widgets/related_display_button.py b/pydm/widgets/related_display_button.py index abc653b55..6db1b80fe 100644 --- a/pydm/widgets/related_display_button.py +++ b/pydm/widgets/related_display_button.py @@ -4,8 +4,8 @@ import warnings from functools import partial import hashlib -from qtpy.QtWidgets import QPushButton, QMenu, QAction, QMessageBox, QInputDialog, QLineEdit, QWidget -from qtpy.QtGui import QCursor, QIcon, QMouseEvent +from qtpy.QtWidgets import QPushButton, QMenu, QAction, QMessageBox, QInputDialog, QLineEdit, QWidget, QStyle +from qtpy.QtGui import QCursor, QIcon, QMouseEvent, QColor from qtpy.QtCore import Slot, Property, Qt, QSize, QPoint from .base import PyDMWidget, only_if_channel_set from ..utilities import IconFont, find_file, is_pydm_app @@ -74,6 +74,15 @@ def __init__( self._follow_symlinks = False + # Standard icons (which come with the qt install, and work cross-platform), + # and icons from the "Font Awesome" icon set (https://fontawesome.com/) + # can not be set with a widget's "icon" property in designer, only in python. + # so we provide our own property to specify standard icons and set them with python in the prop's setter. + self._pydm_icon_name = "" + # The color of "Font Awesome" icons can be set, + # but standard icons are already colored and can not be set. + self._pydm_icon_color = QColor(90, 90, 90) + @only_if_channel_set def check_enable_state(self) -> None: """ @@ -91,6 +100,74 @@ def check_enable_state(self) -> None: self.setToolTip(tooltip) + @Property(str) + def PyDMIcon(self) -> str: + """ + Name of icon to be set from Qt provided standard icons or from the fontawesome icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable fontawesome icons. + + Returns + ------- + str + """ + return self._pydm_icon_name + + @PyDMIcon.setter + def PyDMIcon(self, value: str) -> None: + """ + Name of icon to be set from Qt provided standard icons or from the "Font Awesome" icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable "Font Awesome" icons. + + Parameters + ---------- + value : str + """ + if self._pydm_icon_name == value: + return + + # We don't know if user is trying to use a standard icon or an icon from "Font Awesome", + # so 1st try to create a Font Awesome one, which hits exception if icon name is not valid. + try: + icon_f = IconFont() + i = icon_f.icon(value, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + icon = getattr(QStyle, value, None) + if icon: + self.setIcon(self.style().standardIcon(icon)) + + self._pydm_icon_name = value + + @Property(QColor) + def PyDMIconColor(self) -> QColor: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Returns + ------- + QColor + """ + return self._pydm_icon_color + + @PyDMIconColor.setter + def PyDMIconColor(self, state_color: QColor) -> None: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Parameters + ---------- + new_color : QColor + """ + if state_color != self._pydm_icon_color: + self._pydm_icon_color = state_color + # apply the new color + try: + icon_f = IconFont() + i = icon_f.icon(self._pydm_icon_name, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + return + @Property("QStringList") def filenames(self) -> List[str]: return self._filenames diff --git a/pydm/widgets/shell_command.py b/pydm/widgets/shell_command.py index e20aa326c..9790a236c 100644 --- a/pydm/widgets/shell_command.py +++ b/pydm/widgets/shell_command.py @@ -7,8 +7,8 @@ import warnings import hashlib from ast import literal_eval -from qtpy.QtWidgets import QPushButton, QMenu, QMessageBox, QInputDialog, QLineEdit, QWidget -from qtpy.QtGui import QCursor, QIcon, QMouseEvent +from qtpy.QtWidgets import QPushButton, QMenu, QMessageBox, QInputDialog, QLineEdit, QWidget, QStyle +from qtpy.QtGui import QCursor, QIcon, QMouseEvent, QColor from qtpy.QtCore import Property, QSize, Qt, QTimer from .base import PyDMWidget, only_if_channel_set from ..utilities import IconFont @@ -76,6 +76,15 @@ def __init__( self._show_confirm_dialog = False self._confirm_message = PyDMShellCommand.DEFAULT_CONFIRM_MESSAGE + # Standard icons (which come with the qt install, and work cross-platform), + # and icons from the "Font Awesome" icon set (https://fontawesome.com/) + # can not be set with a widget's "icon" property in designer, only in python. + # so we provide our own property to specify standard icons and set them with python in the prop's setter. + self._pydm_icon_name = "" + # The color of "Font Awesome" icons can be set, + # but standard icons are already colored and can not be set. + self._pydm_icon_color = QColor(90, 90, 90) + def confirmDialog(self) -> bool: """ Show the confirmation dialog with the proper message in case @@ -102,10 +111,78 @@ def confirmDialog(self) -> bool: return True + @Property(str) + def PyDMIcon(self) -> str: + """ + Name of icon to be set from Qt provided standard icons or from the fontawesome icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable fontawesome icons. + + Returns + ------- + str + """ + return self._pydm_icon_name + + @PyDMIcon.setter + def PyDMIcon(self, value: str) -> None: + """ + Name of icon to be set from Qt provided standard icons or from the "Font Awesome" icon-set. + See "enum QStyle::StandardPixmap" in Qt's QStyle documentation for full list of usable standard icons. + See https://fontawesome.com/icons?d=gallery for list of usable "Font Awesome" icons. + + Parameters + ---------- + value : str + """ + if self._pydm_icon_name == value: + return + + # We don't know if user is trying to use a standard icon or an icon from "Font Awesome", + # so 1st try to create a Font Awesome one, which hits exception if icon name is not valid. + try: + icon_f = IconFont() + i = icon_f.icon(value, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + icon = getattr(QStyle, value, None) + if icon: + self.setIcon(self.style().standardIcon(icon)) + + self._pydm_icon_name = value + + @Property(QColor) + def PyDMIconColor(self) -> QColor: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Returns + ------- + QColor + """ + return self._pydm_icon_color + + @PyDMIconColor.setter + def PyDMIconColor(self, state_color: QColor) -> None: + """ + The color of the icon (color is only applied if using icon from the "Font Awesome" set) + Parameters + ---------- + new_color : QColor + """ + if state_color != self._pydm_icon_color: + self._pydm_icon_color = state_color + # apply the new color + try: + icon_f = IconFont() + i = icon_f.icon(self._pydm_icon_name, color=self._pydm_icon_color) + self.setIcon(i) + except Exception: + return + @Property(bool) def showConfirmDialog(self) -> bool: """ - Wether or not to display a confirmation dialog. + Whether or not to display a confirmation dialog. Returns ------- @@ -116,7 +193,7 @@ def showConfirmDialog(self) -> bool: @showConfirmDialog.setter def showConfirmDialog(self, value: bool) -> None: """ - Wether or not to display a confirmation dialog. + Whether or not to display a confirmation dialog. Parameters ---------- diff --git a/pydm/widgets/slider.py b/pydm/widgets/slider.py index 1c94d3b2e..6dca60461 100644 --- a/pydm/widgets/slider.py +++ b/pydm/widgets/slider.py @@ -530,7 +530,7 @@ def update_format_string(self): def set_enable_state(self): """ - Determines wether or not the widget must be enabled or not depending + Determines Whether or not the widget must be enabled or not depending on the write access, connection state and presence of limits information """ # Even though by documentation disabling parent QFrame (self), should disable internal @@ -718,7 +718,7 @@ def tickPosition(self, position): @Property(bool) def userDefinedLimits(self): """ - Wether or not to use limits defined by the user and not from the + Whether or not to use limits defined by the user and not from the channel Returns @@ -730,7 +730,7 @@ def userDefinedLimits(self): @userDefinedLimits.setter def userDefinedLimits(self, user_defined_limits): """ - Wether or not to use limits defined by the user and not from the + Whether or not to use limits defined by the user and not from the channel Parameters