Skip to content

Commit

Permalink
Merge pull request #1007 from jbellister-slac/help_files
Browse files Browse the repository at this point in the history
ENH: Add support for loading help files for displays
  • Loading branch information
YektaY authored Jul 10, 2023
2 parents c0d70ed + 2c7aaac commit 30ce4be
Show file tree
Hide file tree
Showing 12 changed files with 113 additions and 5 deletions.
Binary file added docs/source/_static/help_files.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions docs/source/help_files.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
=================
Adding Help Files
=================

If you are creating a display and would like to add some documentation on how it works, PyDM provides the ability
to do this with a minimum of extra effort. By placing a .txt or .html file in the same directory as your display,
PyDM will load this file and automatically add it to both the View menu of the top menu bar, as well as the right
click menu of widgets on the display.

In order for PyDM to associate the help file with your display, it must have the same name as your display file. For
example, let's say that we have a file called drawing_demo.ui. By adding a file called drawing_demo.txt to the same
location, PyDM will load that file along with the display.

.. figure:: /_static/help_files.gif
:scale: 100 %
:align: center
:alt: Help files

Where to find the automatically loaded help file
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ as well as a straightforward python framework to build complex applications.
:caption: User & API Documentation

stylesheets.rst
help_files.rst
widgets/index.rst
widgets/widget_rules/index.rst
add_data_plugins.rst
Expand Down
25 changes: 21 additions & 4 deletions pydm/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from qtpy import uic
from qtpy.QtWidgets import QApplication, QWidget

from .help_files import HelpWindow
from .utilities import import_module_by_filename, is_pydm_app, macro


Expand Down Expand Up @@ -63,14 +64,20 @@ def load_file(file, macros=None, args=None, target=ScreenTarget.NEW_PROCESS):
app.new_pydm_process(file, macros=macros, command_line_args=args)
return None

_, extension = os.path.splitext(file)
base, extension = os.path.splitext(file)
loader = _extension_to_loader.get(extension, load_py_file)
logger.debug("Loading %s file by way of %s...", file, loader.__name__)
w = loader(file, args=args, macros=macros)
loaded_display = loader(file, args=args, macros=macros)

if os.path.exists(base + '.txt'):
loaded_display.load_help_file(base + '.txt')
elif os.path.exists(base + '.html'):
loaded_display.load_help_file(base + '.html')

if target == ScreenTarget.DIALOG:
w.show()
loaded_display.show()

return w
return loaded_display


@lru_cache()
Expand Down Expand Up @@ -286,6 +293,7 @@ class Display(QWidget):
def __init__(self, parent=None, args=None, macros=None, ui_filename=None):
super(Display, self).__init__(parent=parent)
self.ui = None
self.help_window = None
self._ui_filename = ui_filename
self._loaded_file = None
self._args = args
Expand Down Expand Up @@ -355,6 +363,11 @@ def file_menu_items(self):
"""
return {}

def show_help(self) -> None:
""" Show the associated help file for this display """
if self.help_window is not None:
self.help_window.show()

def navigate_back(self):
pass

Expand Down Expand Up @@ -401,6 +414,10 @@ def load_ui_from_file(self, ui_file_path: str, macros: Optional[Dict[str, str]]
code_string, class_name = _compile_ui_file(ui_file_path)
_load_compiled_ui_into_display(code_string, class_name, self, macros)

def load_help_file(self, file_path: str) -> None:
""" Loads the input help file into a window for display """
self.help_window = HelpWindow(file_path)

def setStyleSheet(self, new_stylesheet):
# Handle the case where the widget's styleSheet property contains a filename, rather than a stylesheet.
possible_stylesheet_filename = os.path.expanduser(os.path.expandvars(new_stylesheet))
Expand Down
1 change: 1 addition & 0 deletions pydm/help_files/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .help_window import HelpWindow
33 changes: 33 additions & 0 deletions pydm/help_files/help_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pathlib import Path
from qtpy.QtWidgets import QTextBrowser, QVBoxLayout, QWidget
from qtpy.QtCore import Qt
from typing import Optional


class HelpWindow(QWidget):
"""
A window for displaying a help file for a PyDM display
Parameters
----------
help_file_path : str
The path to the help file to be displayed
"""
def __init__(self, help_file_path: str, parent: Optional[QWidget] = None):
super().__init__(parent, Qt.Window)
self.resize(500, 400)

path = Path(help_file_path)
self.setWindowTitle(f'Help for {path.stem}')

self.display_content = QTextBrowser()

with open(help_file_path) as file:
if path.suffix == '.txt':
self.display_content.setText(file.read())
else:
self.display_content.setHtml(file.read())

self.vBoxLayout = QVBoxLayout()
self.vBoxLayout.addWidget(self.display_content)
self.setLayout(self.vBoxLayout)
11 changes: 11 additions & 0 deletions pydm/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def __init__(self, parent=None, hide_nav_bar=False, hide_menu_bar=False, hide_st
self.ui.actionShow_Menu_Bar.triggered.connect(self.toggle_menu_bar)
self.ui.actionShow_Status_Bar.triggered.connect(self.toggle_status_bar)
self.ui.actionShow_Connections.triggered.connect(self.show_connections)
self.ui.actionShow_Help.triggered.connect(self.show_help)
self.ui.actionAbout_PyDM.triggered.connect(self.show_about_window)
self.ui.actionLoadTool.triggered.connect(self.load_tool)
self.ui.actionLoadTool.setIcon(self.iconFont.icon("rocket"))
Expand Down Expand Up @@ -486,6 +487,11 @@ def show_connections(self, checked):
c = ConnectionInspector(self)
c.show()

def show_help(self):
""" Show the associated help file for this window """
if self.display_widget() is not None:
self.display_widget().show_help()

@Slot(bool)
def show_about_window(self, checked):
a = AboutWindow(self)
Expand Down Expand Up @@ -530,6 +536,11 @@ def add_menu_items(self):
# create the custom menu with user given items
if not isinstance(self.display_widget(), Display):
return

# Only provide the view help menu option if an associated help file has been loaded
if self.display_widget().help_window is None:
self.ui.actionShow_Help.setVisible(False)

items = self.display_widget().menu_items()
if len(items) == 0:
self.ui.menuCustomActions.menuAction().setVisible(False)
Expand Down
1 change: 1 addition & 0 deletions pydm/pydm.ui
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<addaction name="actionShow_Menu_Bar"/>
<addaction name="actionShow_Status_Bar"/>
<addaction name="actionShow_Connections"/>
<addaction name="actionShow_Help"/>
</widget>
<widget class="QMenu" name="menuHistory">
<property name="title">
Expand Down
5 changes: 5 additions & 0 deletions pydm/pydm_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def setupUi(self, MainWindow):
self.actionShow_Connections = QtWidgets.QAction(MainWindow)
self.actionShow_Connections.setShortcutContext(QtCore.Qt.ApplicationShortcut)
self.actionShow_Connections.setObjectName("actionShow_Connections")
self.actionShow_Help = QtWidgets.QAction(MainWindow)
self.actionShow_Help.setShortcutContext(QtCore.Qt.ApplicationShortcut)
self.actionShow_Help.setObjectName("actionShow_Help")
self.actionLoadTool = QtWidgets.QAction(MainWindow)
self.actionLoadTool.setObjectName("actionLoadTool")
self.actionEnter_Fullscreen = QtWidgets.QAction(MainWindow)
Expand Down Expand Up @@ -135,6 +138,7 @@ def setupUi(self, MainWindow):
self.menuView.addAction(self.actionShow_Menu_Bar)
self.menuView.addAction(self.actionShow_Status_Bar)
self.menuView.addAction(self.actionShow_Connections)
self.menuView.addAction(self.actionShow_Help)
self.menuHistory.addAction(self.actionBack)
self.menuHistory.addAction(self.actionForward)
self.menuHistory.addAction(self.actionHome)
Expand Down Expand Up @@ -184,6 +188,7 @@ def retranslateUi(self, MainWindow):
self.actionShow_Menu_Bar.setShortcut(_translate("MainWindow", "Ctrl+M"))
self.actionShow_Status_Bar.setText(_translate("MainWindow", "Show Status Bar"))
self.actionShow_Connections.setText(_translate("MainWindow", "Show Connections..."))
self.actionShow_Help.setText(_translate("MainWindow", "View Help for this Display"))
self.actionLoadTool.setText(_translate("MainWindow", "Load..."))
self.actionEnter_Fullscreen.setText(_translate("MainWindow", "Enter Fullscreen"))
self.actionEnter_Fullscreen.setShortcut(_translate("MainWindow", "F11"))
Expand Down
1 change: 1 addition & 0 deletions pydm/tests/test_data/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a test help file for the test.ui display
13 changes: 12 additions & 1 deletion pydm/tests/test_display.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import pytest
from pydm import Display
from pydm.display import load_py_file, _compile_ui_file, _load_compiled_ui_into_display
from pydm.display import load_file, load_py_file, _compile_ui_file, _load_compiled_ui_into_display, ScreenTarget
from qtpy.QtWidgets import QLabel

# The path to the .ui file used in these tests
Expand Down Expand Up @@ -155,3 +155,14 @@ def setCommands(self, commands):

finally:
del QLabel.setCommands


def test_load_file_with_help_display(qtbot):
"""
Ensure that when a file containing help information is placed in the same directory as the display to load,
that help display is loaded and available to the user. This test depends on a test.txt or test.html file
being present in the same location as the file at test_ui_path.
"""
test_display = load_file(test_ui_path, target=ScreenTarget.HOME)
assert test_display.help_window is not None
assert test_display.help_window.display_content.toPlainText() == 'This is a test help file for the test.ui display\n'
8 changes: 8 additions & 0 deletions pydm/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,14 @@ def generate_context_menu(self):

kwargs = {'channels': self.channels_for_tools(), 'sender': self}
tools.assemble_tools_menu(menu, widget_only=True, widget=self, **kwargs)

# Add a view help action if the parent display has an associated help file
parent_display = self.find_parent_display()
if parent_display is not None and parent_display.help_window is not None:
if len(menu.actions()) > 0:
menu.addSeparator()
menu.addAction('View Help for this Display', parent_display.show_help)

return menu

def open_context_menu(self, ev):
Expand Down

0 comments on commit 30ce4be

Please sign in to comment.