diff --git a/README.md b/README.md index ad6d626..0b3ad27 100644 --- a/README.md +++ b/README.md @@ -85,13 +85,16 @@ you can implement your own widgets extending or completely re-implementing them. [tt-group]: ## "The hab-gui feature this entry_point is being used for." [tt-multi]: ## "How having multiple entry_points for this group is handled." [tt-multi-first]: ## "Only the first entry_point for this group is used, the rest are discarded." +[tt-multi-all]: ## "All uniquely named entry point names for this group are run." | [Group][tt-group] | Description | Used by | [Multiple][tt-multi] | |---|---|---|---| | hab_gui.alias.widget | Widget used to display and launch a specific alias for the current URI. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | | hab_gui.aliases.widget | Class used to display the `hab_gui.alias.widget`'s. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | | hab_gui.init | Used to customize the init of hab gui's launched from the command line. By default this installs a `sys.excepthook` that captures any python exceptions and shows them in a QMessageBox dialog. See [hab-gui-init.json](tests/site/hab-gui-init.json). | [hab_gui.cli](hab_gui/cli.py) when starting a QApplication instance. | [First][tt-multi-first] | -| hab_gui.uri.pin.widget | Class used to allow the user to pinned commonly used URIs. Pinning can be disabled by the site file, or setting this entry_point to `null`. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | +| hab_gui.uri.menu.actions | Used to customize the menu shown by `hab_gui.uri.menu.widget`. This should reference `QAction` subclasses conforming to [hab_gui.actions.refresh_action.RefreshAction](hab_gui/actions/refresh_action.py). | [MenuButton](hab_gui/widgets/menu_button.py) | [All][tt-multi-all] | +| hab_gui.uri.menu.widget | Class used to show a menu interface on the right of `hab_gui.uri.widget`. This can be omitted by setting this entry_point to `null`. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | +| hab_gui.uri.pin.widget | Class used to allow the user to pin commonly used URIs. Pinning can be disabled by the site file, or setting this entry_point to `null`. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | | hab_gui.uri.widget | Class used by the user to choose the current URI they want to launch aliases from. This class can be customized to provide the user with URI's generated from a DB that are not explicitly defined by configs. | [AliasLaunchWindow](hab_gui/windows/alias_launch_window.py) | [First][tt-multi-first] | - See [hab-gui.json](tests/site/hab-gui.json) for an example of adding the `gui` sub-command to `hab`. diff --git a/hab_gui/actions/__init__.py b/hab_gui/actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hab_gui/actions/refresh_action.py b/hab_gui/actions/refresh_action.py new file mode 100644 index 0000000..96e2e54 --- /dev/null +++ b/hab_gui/actions/refresh_action.py @@ -0,0 +1,27 @@ +from Qt import QtWidgets + +from .. import utils + + +class RefreshAction(QtWidgets.QAction): + """A QAction that causes the hab_widget to refresh the resolved hab setup and UI. + + Args: + resolver (hab.Resolver): The resolver used for settings. + hab_widget (QWidget): The URI widget menu operations are performed on. + verbosity (int, optional): The current verbosity setting. + parent (Qt.QtWidgets.QWidget, optional): Define a parent for this widget. + """ + + def __init__(self, resolver, hab_widget, verbosity=0, parent=None): + super().__init__( + utils.Paths.icon("refresh.svg"), + "Refresh Hab Config", + parent, + ) + self.hab_widget = hab_widget + self.resolver = resolver + self.verbosity = verbosity + self.setObjectName("refresh_hab_cfg") + + self.triggered.connect(self.hab_widget.refresh_cache) diff --git a/hab_gui/actions/separator_action.py b/hab_gui/actions/separator_action.py new file mode 100644 index 0000000..5842cac --- /dev/null +++ b/hab_gui/actions/separator_action.py @@ -0,0 +1,16 @@ +from Qt import QtWidgets + + +class SeparatorAction(QtWidgets.QAction): + """A entry point used to add a separator to a menu using entry_points. + + Args: + resolver (hab.Resolver): Ignored for this class. + hab_widget (QWidget): Ignored for this class. + verbosity (int, optional): Ignored for this class. + parent (Qt.QtWidgets.QWidget, optional): Define a parent for this widget. + """ + + def __init__(self, resolver, hab_widget, verbosity=0, parent=None): + super().__init__(parent) + self.setSeparator(True) diff --git a/hab_gui/widgets/menu_button.py b/hab_gui/widgets/menu_button.py index 946769b..ed83bee 100644 --- a/hab_gui/widgets/menu_button.py +++ b/hab_gui/widgets/menu_button.py @@ -10,6 +10,14 @@ class MenuButton(QtWidgets.QToolButton): """A button that gives the user access to a menu for the hab launcher. + The menu is defined by a entry_point specification matching the + `entry_point_name` property. If this entry point is not specified it will + default to the value returned by `entry_point_default`. + + Each named entry_point will be added to the menu. See `hab_gui.actions` for + some pre-built QActions. This is a dictionary, so if you want to re-use + actions like `SeparatorAction`, make sure they all have unique names. + Args: resolver (hab.Resolver): The resolver used for settings. hab_widget (QWidget): The URI widget menu operations are performed on. @@ -29,12 +37,31 @@ def __init__(self, resolver, hab_widget, verbosity=0, parent=None): self.setPopupMode(self.InstantPopup) self.refresh() + @property + def entry_point_default(self): + """The default entry point values used if self.entry_point_name is not + defined in the site's entry_points. + """ + return { + "refresh": "hab_gui.actions.refresh_action:RefreshAction", + } + + @property + def entry_point_name(self): + """The name of the entry point that defines what QActions are added to + the menu. + """ + return "hab_gui.uri.menu.actions" + def populate_menu(self, menu): - """Builds the menu by adding QActions with connected signals.""" - act = menu.addAction("Refresh Hab Config") - act.setObjectName("refresh_hab_cfg") - act.setIcon(utils.Paths.icon("refresh.svg")) - act.triggered.connect(self.refresh_hab_config) + """Builds the menu by adding QActions defined by the entry_points.""" + eps = self.resolver.site.entry_points_for_group( + self.entry_point_name, default=self.entry_point_default + ) + for ep in eps: + cls = ep.load() + act = cls(resolver=self.resolver, hab_widget=self.hab_widget, parent=self) + menu.addAction(act) def refresh(self): """Rebuilds the menu shown when a user clicks on the button. @@ -46,14 +73,3 @@ def refresh(self): self.populate_menu(menu) self.setMenu(menu) - - def refresh_hab_config(self): - """Reset the refresh_timer if active and calls refresh_cache.""" - running = self.hab_widget.refresh_timer.isActive() - if running: - self.hab_widget.refresh_timer.stop() - - self.hab_widget.refresh_cache() - - if running: - self.hab_widget.refresh_timer.start() diff --git a/hab_gui/windows/alias_launch_window.py b/hab_gui/windows/alias_launch_window.py index b728819..cc54626 100644 --- a/hab_gui/windows/alias_launch_window.py +++ b/hab_gui/windows/alias_launch_window.py @@ -1,4 +1,5 @@ import logging +from functools import partial import hab from Qt import QtCore, QtWidgets @@ -56,7 +57,7 @@ def __init__( refresh_time = self.resolver.site.get("hab_gui_refresh_inverval", ["00:30:00"]) refresh_time = refresh_time[0] if refresh_time: - self.refresh_timer.timeout.connect(self.refresh_cache) + self.refresh_timer.timeout.connect(partial(self.refresh_cache, False)) refresh_time = utils.interval(refresh_time) logger.debug(f"Setting auto-refresh interval to {refresh_time} seconds") self.refresh_timer.start(refresh_time * 1000) @@ -66,7 +67,8 @@ def apply_layout(self): column_uri_widget = 1 if self.prefs_enabled else 0 self.setCentralWidget(self.main_widget) self.layout.addWidget(self.uri_widget, 0, column_uri_widget) - self.layout.addWidget(self.menu_button, 0, column_uri_widget + 1) + if self._cls_menu_button: + self.layout.addWidget(self.menu_button, 0, column_uri_widget + 1) self.layout.addWidget(self.alias_buttons, 1, 0, 1, -1) self.main_widget.setLayout(self.layout) @@ -75,9 +77,12 @@ def apply_layout(self): # creating it first and that breaks the default tab ordering. if self.prefs_enabled: self.setTabOrder(self.pinned_uris, self.uri_widget) - self.setTabOrder(self.uri_widget, self.menu_button) - self.setTabOrder(self.menu_button, self.alias_buttons) - self.setTabOrder(self.alias_buttons, self.pinned_uris) + if self._cls_menu_button: + self.setTabOrder(self.uri_widget, self.menu_button) + self.setTabOrder(self.menu_button, self.alias_buttons) + self.setTabOrder(self.alias_buttons, self.pinned_uris) + else: + self.setTabOrder(self.uri_widget, self.pinned_uris) def closeEvent(self, event): # noqa: N802 """Saves the currently selected URI on close if prefs are enabled.""" @@ -89,10 +94,10 @@ def load_entry_point(self, name, default, allow_none=False): default = {"default": default} eps = self.resolver.site.entry_points_for_group(name, default=default) + if allow_none and (not eps or eps[0].value is None): + return None if not eps: raise ValueError(f"A valid entry_point for {name} must be defined") - if allow_none and eps[0].value is None: - return None return eps[0].load() def process_entry_points(self): @@ -110,8 +115,9 @@ def process_entry_points(self): ) # Allows the user to refresh hab configuration in case it has changed. self._cls_menu_button = self.load_entry_point( - "hab_gui_menu_button", + "hab_gui.uri.menu.widget", "hab_gui.widgets.menu_button:MenuButton", + allow_none=True, ) # Allows the user to pin commonly used URI's self._cls_uri_pin_widget = self.load_entry_point( @@ -141,9 +147,10 @@ def init_gui(self, uri=None): self.prefs_enabled = False # Create a refresh button - self.menu_button = self._cls_menu_button( - self.resolver, verbosity=self.verbosity, hab_widget=self - ) + if self._cls_menu_button: + self.menu_button = self._cls_menu_button( + self.resolver, verbosity=self.verbosity, hab_widget=self + ) if self.prefs_enabled: self.pinned_uris = self._cls_uri_pin_widget( @@ -177,11 +184,25 @@ def init_gui(self, uri=None): self.uri_widget.setFocus() @utils.cursor_override() - def refresh_cache(self): - logger.debug("Refresh cache") - self.uri_widget.refresh() - self.resolver.clear_caches() - self.alias_buttons.refresh() + def refresh_cache(self, reset_timer=True): + """Refresh the resolved hab and re-display. + + Args: + reset_timer (bool, optional): Stop and restart the refresh_timer if + its currently active. + """ + logger.debug(f"Refreshing cache with reset_timer: {reset_timer}") + running = self.refresh_timer.isActive() + try: + if reset_timer and running: + self.refresh_timer.stop() + + self.uri_widget.refresh() + self.resolver.clear_caches() + self.alias_buttons.refresh() + finally: + if reset_timer and running: + self.refresh_timer.start() def uri_changed(self, uri): self.alias_buttons.uri = uri