diff --git a/docs/images/config_section.png b/docs/images/config_section.png index 93225570..27be5c66 100644 Binary files a/docs/images/config_section.png and b/docs/images/config_section.png differ diff --git a/docs/images/elog_entry_form.png b/docs/images/elog_entry_form.png index 3a420cbf..510f855a 100644 Binary files a/docs/images/elog_entry_form.png and b/docs/images/elog_entry_form.png differ diff --git a/docs/images/footer.png b/docs/images/footer.png index a34ac856..9b08dcc9 100644 Binary files a/docs/images/footer.png and b/docs/images/footer.png differ diff --git a/docs/images/plot_section.png b/docs/images/plot_section.png index 8bebb1a8..ad9bb694 100644 Binary files a/docs/images/plot_section.png and b/docs/images/plot_section.png differ diff --git a/docs/images/sections.png b/docs/images/sections.png index 8cae2aee..b1ff5715 100644 Binary files a/docs/images/sections.png and b/docs/images/sections.png differ diff --git a/docs/images/trace_screenshot.png b/docs/images/trace_screenshot.png index 31c6e39d..a7b54bf0 100644 Binary files a/docs/images/trace_screenshot.png and b/docs/images/trace_screenshot.png differ diff --git a/docs/overview/plot_section.md b/docs/overview/plot_section.md index 4c47f39a..d16d93ff 100644 --- a/docs/overview/plot_section.md +++ b/docs/overview/plot_section.md @@ -20,6 +20,16 @@ A box will be drawn between where the mouse button was pressed and where it was +## Feedback Form & Documentation Buttons + +Above the plotting section are a couple of buttons to assist users in using Trace. + +The leftmost button marked with a :material-chat-processing-outline: icon will open a feedback form where users can request features, report bugs, or provide general feedback. The button just to the right with a :octicons-question-16: icon can be used to open the documentation page for Trace. + +Both of these actions are also available under the Help menu in the menu bar at the top of the application. + + + ## Time Span Buttons Above the plotting section are a few buttons for quickly toggling between common time spans (30s, 1m, 1h, 1w, 1M). diff --git a/docs/traces.md b/docs/traces.md index 441266d8..8048486f 100644 --- a/docs/traces.md +++ b/docs/traces.md @@ -22,11 +22,6 @@ Traces can also be added using the [PV Search tool]. [PV Search tool]: tools/search.md -When adding new traces, they will be attached to the last y-axis in the configuration section. -If no axes exist, a new one will be created for the new trace. - - -### :material-wrench: Planned for Future Development When adding new traces, they will be attached to a y-axis with the same unit. If no such axis exists, a new one will be created for the channel's unit. diff --git a/trace/config.json b/trace/config.json index e54b5154..3ab7d6d2 100644 --- a/trace/config.json +++ b/trace/config.json @@ -1,6 +1,8 @@ { "save_file_dir": "$PHYSICS_DATA/Trace/", "datetime_pv": "SIOC:SYS0:AL00:TOD", + "documentation_url": "https://slaclab.github.io/trace/", + "feedback_form_url": "https://forms.office.com/r/7EtmhWZTfJ", "color_palettes": { "default": [ "#008CF9", "#006E00", "#B80058", @@ -15,8 +17,8 @@ "#17becf" ], "light": [ - "#8dd3c7", "#bebada", - "#fb8072", "#80b1d3", "#fdb462", + "#8dd3c7", "#bebada", + "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5", "#d9d9d9", "#bc80bd", "#ccebc5", "#ffed6f" ], diff --git a/trace/config.py b/trace/config.py index 9dbd5dbe..854d12b8 100644 --- a/trace/config.py +++ b/trace/config.py @@ -7,7 +7,7 @@ config_file = Path(__file__).parent / "config.json" with config_file.open() as f: - loaded_json = load(f) + loaded_json: dict = load(f) light_stylesheet = Path(__file__).parent / "stylesheets/light_mode.qss" dark_stylesheet = Path(__file__).parent / "stylesheets/dark_mode.qss" @@ -16,6 +16,9 @@ datetime_pv = loaded_json["datetime_pv"] +DOCUMENTATION_URL = loaded_json.get("documentation_url") +FEEDBACK_FORM_URL = loaded_json.get("feedback_form_url") + # Set default save file directory # If the directory does not exist, set it to the home directory save_file_dir = Path(os.path.expandvars(loaded_json["save_file_dir"])) diff --git a/trace/main.py b/trace/main.py index 94230892..813b9995 100644 --- a/trace/main.py +++ b/trace/main.py @@ -6,8 +6,8 @@ from pathlib import Path from datetime import datetime -from qtpy.QtGui import QFont, QColor, QImage, QKeySequence -from qtpy.QtCore import Qt, Slot, QSize, Signal, QBuffer, QIODevice, QSettings +from qtpy.QtGui import QFont, QColor, QImage, QKeySequence, QDesktopServices +from qtpy.QtCore import Qt, QUrl, Slot, QSize, Signal, QBuffer, QIODevice, QSettings from qtpy.QtWidgets import ( QMenu, QLabel, @@ -33,7 +33,7 @@ from pydm.widgets import PyDMLabel, PyDMArchiverTimePlot from pydm.utilities.macro import parse_macro_string -from config import logger, datetime_pv +from config import DOCUMENTATION_URL, FEEDBACK_FORM_URL, logger, datetime_pv from file_io import PathAction, TraceFileHandler from widgets import ControlPanel, ElogPostModal, DataInsightTool, PlotSettingsModal from services import Theme, IconColors, ThemeManager, get_user, post_entry @@ -226,6 +226,28 @@ def build_toolbar(self, parent: QWidget) -> QWidget: tool_layout.setContentsMargins(0, 0, 0, 0) toolbar_widget.setLayout(tool_layout) + if FEEDBACK_FORM_URL: + feedback_btn = QPushButton() + icon = self.theme_manager.create_icon("ph.chat-circle-text", IconColors.PRIMARY) + feedback_btn.setIcon(icon) + feedback_btn.setIconSize(QSize(25, 25)) + feedback_btn.setFixedSize(QSize(25, 25)) + feedback_btn.setFlat(True) + feedback_btn.setToolTip("Provide Feedback / Report Bugs") + feedback_btn.clicked.connect(self.open_feedback_page) + tool_layout.addWidget(feedback_btn) + + if DOCUMENTATION_URL: + documentation_btn = QPushButton() + icon = self.theme_manager.create_icon("ph.question", IconColors.PRIMARY) + documentation_btn.setIcon(icon) + documentation_btn.setIconSize(QSize(25, 25)) + documentation_btn.setFixedSize(QSize(25, 25)) + documentation_btn.setFlat(True) + documentation_btn.setToolTip("Open Documentation") + documentation_btn.clicked.connect(self.open_documentation_page) + tool_layout.addWidget(documentation_btn) + tool_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) tool_layout.addSpacerItem(tool_spacer) @@ -487,6 +509,9 @@ def configure_app(self, app: QApplication) -> None: trace_menu = self.construct_trace_menu(menu_bar) menu_bar.insertMenu(first_menu, trace_menu) + help_menu = self.construct_help_menu(menu_bar) + menu_bar.addMenu(help_menu) + def construct_trace_menu(self, parent: QMenuBar) -> QMenu: """Create the menu for the application. This includes actions for file IO, saving to the E-Log, opening tools, and setting the app theme. @@ -532,6 +557,27 @@ def construct_trace_menu(self, parent: QMenuBar) -> QMenu: return menu + def construct_help_menu(self, parent: QMenuBar) -> QMenu: + """Create the help menu for the application. This includes actions + for opening the documentation and feedback pages. + + Parameters + ---------- + parent : QMenuBar + The menu bar that the Help menu will be a part of. + + Returns + ------- + QMenu + The Help menu consisting of actions for accessing help resources. + """ + menu = QMenu("Help", parent) + + menu.addAction("Open Documentation...", self.open_documentation_page) + menu.addAction("Provide Feedback / Report Bugs...", self.open_feedback_page) + + return menu + def toggle_theme(self): """Toggle between dark and light mode. @@ -775,6 +821,39 @@ def autoScroll(self, enable: bool, timespan: float = None): refresh_interval = self.plot_settings.auto_scroll_interval self.plot.setAutoScroll(enable, timespan, refresh_rate=refresh_interval) + @Slot() + def open_feedback_page(self) -> None: + """Open the form for providing feedback or reporting bugs in the + default browser. + """ + feedback_url = QUrl(FEEDBACK_FORM_URL) + site_name = "Trace feedback page" + self.open_url_in_browser(feedback_url, site_name) + + @Slot() + def open_documentation_page(self) -> None: + """Open the Trace documentation webpage in the default browser.""" + doc_url = QUrl(DOCUMENTATION_URL) + site_name = "Trace documentation page" + self.open_url_in_browser(doc_url, site_name) + + def open_url_in_browser(self, url: QUrl, site_name: str = "website") -> None: + """Open the given URL in the default web browser. + + Parameters + ---------- + url : QUrl + The URL to open. + site_name : str, optional + The name of the site for error messages, by default "website". + """ + if not QDesktopServices.openUrl(url): + QMessageBox.warning( + self, + "Error", + f"Unable to open the {site_name}. Please visit:\n{url.url()}", + ) + @staticmethod def git_version(): """Get the current git tag for the project.