Skip to content

Commit

Permalink
Support entry_points for the MenuButton class
Browse files Browse the repository at this point in the history
- `hab_gui.widgets.menu_button.MenuButton` is now populated by entry points supporting sub-classes.
- Adds QAction subclasses to implement refreshing hab_gui and adding separators to menus.
- `AliasLaunchWindow.refresh_cache` now handles stopping/starting the `refresh_timer` internally.
- Correct `hab_gui_menu_button` entry_point name to `hab_gui.uri.menu.widget`.
- `hab_gui.uri.menu.widget` can now be omitted by setting to `null`.
- Fixed bug with how `allow_none` was handled by `AliasLaunchWindow.load_entry_point`.
  • Loading branch information
MHendricks committed Feb 19, 2024
1 parent 612c9fe commit 731f31e
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 33 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
Empty file added hab_gui/actions/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions hab_gui/actions/refresh_action.py
Original file line number Diff line number Diff line change
@@ -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)
16 changes: 16 additions & 0 deletions hab_gui/actions/separator_action.py
Original file line number Diff line number Diff line change
@@ -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)
48 changes: 32 additions & 16 deletions hab_gui/widgets/menu_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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()
53 changes: 37 additions & 16 deletions hab_gui/windows/alias_launch_window.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from functools import partial

import hab
from Qt import QtCore, QtWidgets
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -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."""
Expand All @@ -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):
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 731f31e

Please sign in to comment.