From d0d0d359806599e46cfa3ac504a37fd1a6179c3d Mon Sep 17 00:00:00 2001 From: craftablescience Date: Tue, 1 Apr 2025 15:03:28 -0700 Subject: [PATCH 1/4] ENH: add PyDMWindow widget to apply custom attributes to Display, allow it to hide certain window components on first load --- pydm/main_window.py | 34 ++++++++---- pydm/widgets/frame.py | 2 +- pydm/widgets/qtplugins.py | 6 +++ pydm/widgets/window.py | 106 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 pydm/widgets/window.py diff --git a/pydm/main_window.py b/pydm/main_window.py index 0249e99a7..5eb48fcf2 100644 --- a/pydm/main_window.py +++ b/pydm/main_window.py @@ -39,6 +39,7 @@ def __init__( self.iconFont = IconFont() self._display_widget = None self._showing_file_path_in_title_bar = False + self._display_widget_has_been_shown = False # style sheet change flag self.isSS_Changed = False @@ -91,16 +92,7 @@ def __init__( self.showMacros.triggered.connect(self.show_macro_window) self.ui.actionQuit.triggered.connect(self.quit_main_window) - if hide_nav_bar: - self.toggle_nav_bar(False) - self.ui.actionShow_Navigation_Bar.setChecked(False) - if hide_menu_bar: - # Toggle the menu bar via the QAction so that the menu item - # stays in sync with menu visibility. - self.ui.actionShow_Menu_Bar.activate(QAction.Trigger) - if hide_status_bar: - self.toggle_status_bar(False) - self.ui.actionShow_Status_Bar.setChecked(False) + self.hide_window_components(hide_nav_bar, hide_menu_bar, hide_status_bar) # Try to find the designer binary. self.ui.actionEdit_in_Designer.setEnabled(False) @@ -140,6 +132,16 @@ def set_display_widget(self, new_widget): self.enable_disable_navigation() self.update_window_title() self.add_menu_items() + + # We want to respect the user's choices after the first display has loaded + if not self._display_widget_has_been_shown: + self.hide_window_components( + self._display_widget.property("hideNavBar"), + self._display_widget.property("hideMenuBar"), + self._display_widget.property("hideStatusBar"), + ) + self._display_widget_has_been_shown = True + # Resizing to the new widget's dimensions needs to be # done on the event loop for some reason - you can't # just do it here. @@ -264,6 +266,18 @@ def update_window_title(self): title += " [Read Only Mode]" self.setWindowTitle(title) + def hide_window_components(self, hide_nav_bar, hide_menu_bar, hide_status_bar): + if hide_nav_bar: + self.toggle_nav_bar(False) + self.ui.actionShow_Navigation_Bar.setChecked(False) + if hide_menu_bar: + # Toggle the menu bar via the QAction so that the menu item + # stays in sync with menu visibility. + self.ui.actionShow_Menu_Bar.activate(QAction.Trigger) + if hide_status_bar: + self.toggle_status_bar(False) + self.ui.actionShow_Status_Bar.setChecked(False) + @property def showing_file_path_in_title_bar(self): return self._showing_file_path_in_title_bar diff --git a/pydm/widgets/frame.py b/pydm/widgets/frame.py index 8210ed432..e9d4ac492 100644 --- a/pydm/widgets/frame.py +++ b/pydm/widgets/frame.py @@ -11,7 +11,7 @@ class PyDMFrame(QFrame, PyDMWidget): Parameters ---------- parent : QWidget - The parent widget for the Label + The parent widget for the Frame init_channel : str, optional The channel to be used by the widget. """ diff --git a/pydm/widgets/qtplugins.py b/pydm/widgets/qtplugins.py index 28806c288..c18c91ab5 100644 --- a/pydm/widgets/qtplugins.py +++ b/pydm/widgets/qtplugins.py @@ -25,6 +25,7 @@ from .enum_button import PyDMEnumButton from .enum_combo_box import PyDMEnumComboBox from .frame import PyDMFrame +from .window import PyDMWindow from .image import PyDMImageView from .label import PyDMLabel from .line_edit import PyDMLineEdit @@ -201,6 +202,11 @@ PyDMFrame, group=WidgetCategory.CONTAINER, is_container=True, extensions=BASE_EXTENSIONS, icon=ifont.icon("expand") ) +# Window plugin +PyDMWindowPlugin = qtplugin_factory( + PyDMWindow, group=WidgetCategory.CONTAINER, is_container=True, extensions=BASE_EXTENSIONS, icon=ifont.icon("expand") +) + # Image plugin PyDMImageViewPlugin = qtplugin_factory( PyDMImageView, group=WidgetCategory.DISPLAY, extensions=BASE_EXTENSIONS, icon=ifont.icon("camera") diff --git a/pydm/widgets/window.py b/pydm/widgets/window.py new file mode 100644 index 000000000..fce71c5b5 --- /dev/null +++ b/pydm/widgets/window.py @@ -0,0 +1,106 @@ +import warnings +from qtpy.QtWidgets import QWidget +from qtpy.QtCore import Property +from typing import Optional +from .base import is_qt_designer + + +class PyDMWindow(QWidget): + """ + QWidget with support for some custom PyDM properties. Right now it only + supports disabling the menu bar, nav bar, and status bar by default. This + widget will only function if it is at the root of the UI hierarchy. + This class inherits from QWidget. It is NOT a PyDMWidget. + + Parameters + ---------- + parent : QWidget + The parent widget for the Window. Should ideally be None + """ + + def __init__(self, parent: Optional[QWidget] = None): + if parent is not None and not is_qt_designer(): + warnings.warn("PyDMWindow must be at the root of the UI hierarchy, or it will not function properly!") + + super().__init__(parent) + self._hide_menu_bar = False + self._hide_nav_bar = False + self._hide_status_bar = False + + @Property(bool) + def hideMenuBar(self): + """ + Whether or not the widget should automatically disable the + menu bar when the display is loaded. + + Returns + ------- + hide_menu_bar : bool + The configured value + """ + return self._hide_menu_bar + + @hideMenuBar.setter + def hideMenuBar(self, new_val): + """ + Whether or not the widget should automatically disable the + menu bar when the display is loaded. + + Parameters + ---------- + new_val : bool + The new configuration to use + """ + self._hide_menu_bar = new_val + + @Property(bool) + def hideNavBar(self): + """ + Whether or not the widget should automatically disable the + nav bar when the display is loaded. + + Returns + ------- + hide_nav_bar : bool + The configured value + """ + return self._hide_nav_bar + + @hideNavBar.setter + def hideNavBar(self, new_val): + """ + Whether or not the widget should automatically disable the + nav bar when the display is loaded. + + Parameters + ---------- + new_val : bool + The new configuration to use + """ + self._hide_nav_bar = new_val + + @Property(bool) + def hideStatusBar(self): + """ + Whether or not the widget should automatically disable the + status bar when the display is loaded. + + Returns + ------- + hide_status_bar : bool + The configured value + """ + return self._hide_status_bar + + @hideStatusBar.setter + def hideStatusBar(self, new_val): + """ + Whether or not the widget should automatically disable the + status bar when the display is loaded. + + Parameters + ---------- + new_val : bool + The new configuration to use + """ + self._hide_status_bar = new_val From 6f91ecc08d6dc29cff8dcbcff057276429f3e7c0 Mon Sep 17 00:00:00 2001 From: craftablescience Date: Tue, 1 Apr 2025 15:13:45 -0700 Subject: [PATCH 2/4] TST: add tests for PyDMWindow widget --- pydm/tests/test_plugins_import.py | 7 +++++++ pydm/tests/widgets/test_window.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 pydm/tests/widgets/test_window.py diff --git a/pydm/tests/test_plugins_import.py b/pydm/tests/test_plugins_import.py index 3dfaeaa10..78abbf801 100644 --- a/pydm/tests/test_plugins_import.py +++ b/pydm/tests/test_plugins_import.py @@ -60,6 +60,13 @@ def test_import_frame_plugin(): qtplugin_factory(PyDMFrame, is_container=True) +def test_import_window_plugin(): + # Window plugin + from ..widgets.window import PyDMWindow + + qtplugin_factory(PyDMWindow, is_container=True) + + def test_import_enum_button_plugin(): # Enum Button plugin from ..widgets.enum_button import PyDMEnumButton diff --git a/pydm/tests/widgets/test_window.py b/pydm/tests/widgets/test_window.py new file mode 100644 index 000000000..c8d03bbdc --- /dev/null +++ b/pydm/tests/widgets/test_window.py @@ -0,0 +1,28 @@ +# Unit Tests for the Window Widget + +from ...widgets.window import PyDMWindow + + +# -------------------- +# POSITIVE TEST CASES +# -------------------- + + +def test_construct(qtbot): + """ + Test the construction of the widget. + + Expectations: + The correct default values are assigned to the widget's attributes. + + Parameters + ---------- + qtbot : fixture + pytest-qt window for widget test + """ + pydm_window = PyDMWindow() + qtbot.addWidget(pydm_window) + + assert pydm_window._hide_menu_bar is False + assert pydm_window._hide_nav_bar is False + assert pydm_window._hide_status_bar is False From 8bcd3bce41878df4aecea1a80ab6aec4988900ab Mon Sep 17 00:00:00 2001 From: craftablescience Date: Mon, 7 Apr 2025 14:24:16 -0700 Subject: [PATCH 3/4] DOC: add example and documentation for PyDMWindow --- docs/source/widgets/index.rst | 1 + docs/source/widgets/window.rst | 32 +++++++++++++++++++++ examples/window/window.ui | 52 ++++++++++++++++++++++++++++++++++ pydm/widgets/__init__.py | 2 ++ 4 files changed, 87 insertions(+) create mode 100644 docs/source/widgets/window.rst create mode 100644 examples/window/window.ui diff --git a/docs/source/widgets/index.rst b/docs/source/widgets/index.rst index 8287502bb..caf71468a 100644 --- a/docs/source/widgets/index.rst +++ b/docs/source/widgets/index.rst @@ -58,6 +58,7 @@ Container Widgets frame.rst tab_widget.rst template_repeater.rst + window.rst Drawing Widgets --------------- diff --git a/docs/source/widgets/window.rst b/docs/source/widgets/window.rst new file mode 100644 index 000000000..a163bfe2a --- /dev/null +++ b/docs/source/widgets/window.rst @@ -0,0 +1,32 @@ +####################### +PyDMWindow +####################### + +The PyDM Window Widget is a container widget that allows the display creator to set certain global display +properties. It is currently used to hide specific UI elements when the display is the first loaded display +in the running PyDM instance. + +Using the PyDM Window Widget in Designer +======================================== + +In designer, when creating a new display, select PyDMWindow as the base widget. + + +Widget Properties +================= + +============= ==== =========== +Property Type Description +============= ==== =========== +hideMenuBar bool Hide the menu bar if this is the first loaded display. +hideNavBar bool Hide the nav bar if this is the first loaded display. +hideStatusBar bool Hide the status bar if this is the first loaded display. +============= ==== =========== + + +API Documentation +================= + +.. autoclass:: pydm.widgets.window.PyDMWindow + :members: + :show-inheritance: diff --git a/examples/window/window.ui b/examples/window/window.ui new file mode 100644 index 000000000..f3bc6fa1c --- /dev/null +++ b/examples/window/window.ui @@ -0,0 +1,52 @@ + + + Display + + + + 0 + 0 + 400 + 300 + + + + PyDMWindow + + + true + + + true + + + true + + + + + 10 + 10 + 380 + 280 + + + + <html><head/><body><p>This display is using a PyDMWindow widget as the root widget. This allows it to customize some otherwise unavailable properties.</p><p><br/></p><p>Currently it is used for hiding specific parts of the PyDM interface, including the menu bar, nav bar, and status bar. Disabling these elements can make your popup displays look nicer!</p></body></html> + + + true + + + + + + PyDMWindow + QWidget +
pydm.widgets.window
+ 1 +
+
+ + +
diff --git a/pydm/widgets/__init__.py b/pydm/widgets/__init__.py index 199ff295e..ee0a321fc 100644 --- a/pydm/widgets/__init__.py +++ b/pydm/widgets/__init__.py @@ -35,6 +35,7 @@ "PyDMTabWidget", "PyDMTemplateRepeater", "PyDMNTTable", + "PyDMWindow", ] from .channel import PyDMChannel @@ -75,3 +76,4 @@ from .tab_bar import PyDMTabWidget from .template_repeater import PyDMTemplateRepeater from .nt_table import PyDMNTTable +from .window import PyDMWindow From 016671d94b207f27db4a5a8adbe3b82700c77d0e Mon Sep 17 00:00:00 2001 From: craftablescience Date: Thu, 10 Apr 2025 11:11:35 -0700 Subject: [PATCH 4/4] DOC: fix PyDMChannel import path --- docs/source/channel.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/channel.rst b/docs/source/channel.rst index 575012129..62cdb69f5 100644 --- a/docs/source/channel.rst +++ b/docs/source/channel.rst @@ -2,5 +2,5 @@ Channel ======================== -.. autoclass:: channel.PyDMChannel +.. autoclass:: pydm.widgets.channel.PyDMChannel :members: