diff --git a/docs/source/_static/help_files.gif b/docs/source/_static/help_files.gif
new file mode 100644
index 000000000..b05962cc9
Binary files /dev/null and b/docs/source/_static/help_files.gif differ
diff --git a/docs/source/help_files.rst b/docs/source/help_files.rst
new file mode 100644
index 000000000..ecec0fe75
--- /dev/null
+++ b/docs/source/help_files.rst
@@ -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
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 97173a346..67af6f283 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -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
diff --git a/pydm/display.py b/pydm/display.py
index 45150d835..91dcd35ca 100644
--- a/pydm/display.py
+++ b/pydm/display.py
@@ -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
@@ -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()
@@ -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
@@ -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
@@ -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))
diff --git a/pydm/help_files/__init__.py b/pydm/help_files/__init__.py
new file mode 100644
index 000000000..43c626665
--- /dev/null
+++ b/pydm/help_files/__init__.py
@@ -0,0 +1 @@
+from .help_window import HelpWindow
diff --git a/pydm/help_files/help_window.py b/pydm/help_files/help_window.py
new file mode 100644
index 000000000..d5eb43c02
--- /dev/null
+++ b/pydm/help_files/help_window.py
@@ -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)
diff --git a/pydm/main_window.py b/pydm/main_window.py
index 83fccb125..87dc9d65f 100644
--- a/pydm/main_window.py
+++ b/pydm/main_window.py
@@ -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"))
@@ -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)
@@ -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)
diff --git a/pydm/pydm.ui b/pydm/pydm.ui
index f1943559e..af3f8385e 100644
--- a/pydm/pydm.ui
+++ b/pydm/pydm.ui
@@ -62,6 +62,7 @@
+