-
Notifications
You must be signed in to change notification settings - Fork 27
Keyboard shortcuts #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Keyboard shortcuts #134
Changes from all commits
ffda7eb
f531047
1c205a4
c64a489
b618362
d0ffee5
e87e19c
741af7a
aaa8386
b087eec
f918010
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -107,6 +107,7 @@ | |
| from typing import Any, Dict, List, Optional, cast | ||
|
|
||
| from instrumentserver import QtCore, QtGui, QtWidgets | ||
| from instrumentserver.gui.shortcuts import KeyboardShortcutManager | ||
|
|
||
|
|
||
| class ItemBase(QtGui.QStandardItem): | ||
|
|
@@ -237,6 +238,8 @@ def _matches_any_pattern(name: str, patterns: List[str]) -> bool: | |
| :param patterns: List of glob patterns to match against (e.g., 'power_*', '*_frequency') | ||
| :return: True if name matches any pattern, False otherwise | ||
| """ | ||
| if not patterns: | ||
| return False | ||
| for pattern in patterns: | ||
| if fnmatch.fnmatch(name, pattern): | ||
| return True | ||
|
|
@@ -481,7 +484,7 @@ def filterAcceptsRow( | |
| item = parent.child(source_row, 0) | ||
|
|
||
| # The order in which things get constructed seems to impact this. | ||
| # When the application is first starting, the proxy model does not have the trash attribute. | ||
| # When the application is first starting, the proxy model does not have the trash attribute. | ||
| if hasattr(self, "trash"): | ||
| if self.trash: | ||
| # Assertion is there to satisfy mypy. item can be None, that is why we check before making the assertion | ||
|
|
@@ -769,6 +772,7 @@ class InstrumentDisplayBase(QtWidgets.QWidget): | |
| :param proxyModelType: The type of proxy model that should be used. | ||
| :param viewType: The type of view that should be used. | ||
| :param callSignals: If False, the constructor will not call the method connectSignals | ||
| :param shortcutManager: Manager shared across the application so actions can be registered to shortcuts | ||
| """ | ||
|
|
||
| def __init__( | ||
|
|
@@ -780,6 +784,7 @@ def __init__( | |
| proxyModelType: type = InstrumentSortFilterProxyModel, | ||
| viewType: type = InstrumentTreeViewBase, | ||
| callSignals: bool = True, | ||
| shortcutManager: Optional[KeyboardShortcutManager] = None, | ||
| parent: Optional[QtWidgets.QWidget] = None, | ||
| **modelKwargs: Any, | ||
| ) -> None: | ||
|
|
@@ -795,6 +800,12 @@ def __init__( | |
| self.proxyModel = proxyModelType(self.model) | ||
| self.view = viewType(self.proxyModel) | ||
|
|
||
| self.shortcutManager = ( | ||
| shortcutManager | ||
| if shortcutManager is not None | ||
| else KeyboardShortcutManager() | ||
| ) | ||
|
|
||
| self.layout_ = QtWidgets.QVBoxLayout() | ||
|
|
||
| self.lineEdit = QtWidgets.QLineEdit(self) | ||
|
|
@@ -830,6 +841,12 @@ def connectSignals(self) -> None: | |
| self.proxyModel.onSortingIndicatorChanged | ||
| ) | ||
|
|
||
| self.shortcutManager.register("jump_filter", self.lineEdit.setFocus, self) | ||
| self.shortcutManager.register("star_item", self._starCurrentItem, self) | ||
| self.shortcutManager.register("trash_item", self._trashCurrentItem, self) | ||
| self.shortcutManager.register("fit_column", self._fitCurrentColumn, self) | ||
| self.shortcutManager.register("sort_column", self._sortCurrentColumn, self) | ||
|
|
||
| def makeToolbar(self) -> QtWidgets.QToolBar: | ||
| """ | ||
| Creates the toolbar, override to add more buttons to the toolbar. | ||
|
|
@@ -842,6 +859,7 @@ def makeToolbar(self) -> QtWidgets.QToolBar: | |
| "refresh all items from the instrument", | ||
| ) | ||
| refreshAction.triggered.connect(lambda x: self.refreshAll()) # type: ignore[union-attr] | ||
| self.shortcutManager.apply_to_action("refresh_all", refreshAction) | ||
|
|
||
| toolbar.addSeparator() | ||
|
|
||
|
|
@@ -850,26 +868,30 @@ def makeToolbar(self) -> QtWidgets.QToolBar: | |
| "expand tree", | ||
| ) | ||
| expandAction.triggered.connect(lambda x: self.view.expandAll()) # type: ignore[union-attr] | ||
| self.shortcutManager.apply_to_action("expand_all", expandAction) | ||
|
|
||
| collapseAction = toolbar.addAction( | ||
| QtGui.QIcon(":/icons/collapse.svg"), | ||
| "collapse tree", | ||
| ) | ||
| collapseAction.triggered.connect(lambda x: self.view.collapseAll()) # type: ignore[union-attr] | ||
| self.shortcutManager.apply_to_action("collapse_all", collapseAction) | ||
|
|
||
| toolbar.addSeparator() | ||
|
|
||
| starAction = toolbar.addAction( | ||
| QtGui.QIcon(":/icons/star.svg"), "Move Starred items to the top" | ||
| ) | ||
| starAction.setCheckable(True) # type: ignore[union-attr] | ||
| starAction.triggered.connect(lambda x: self.promoteStar()) # type: ignore[union-attr] | ||
| starAction.triggered.connect(lambda x: self.proxyModel.onToggleStar()) # type: ignore[union-attr] | ||
| self.shortcutManager.apply_to_action("toggle_star", starAction) | ||
|
|
||
| trashAction = toolbar.addAction( | ||
| QtGui.QIcon(":/icons/trash-crossed.svg"), "Hide trashed items" | ||
| ) | ||
| trashAction.setCheckable(True) # type: ignore[union-attr] | ||
| trashAction.triggered.connect(lambda x: self.hideTrash()) # type: ignore[union-attr] | ||
| trashAction.triggered.connect(lambda x: self.proxyModel.onToggleTrash()) # type: ignore[union-attr] | ||
| self.shortcutManager.apply_to_action("toggle_trash", trashAction) | ||
|
|
||
| # Debugging tools keep commented for commits. | ||
| # printAction = toolbar.addAction( | ||
|
|
@@ -883,16 +905,53 @@ def makeToolbar(self) -> QtWidgets.QToolBar: | |
| return toolbar | ||
|
|
||
| @QtCore.Slot() | ||
| def hideTrash(self) -> None: | ||
| self.proxyModel.onToggleTrash() | ||
| def refreshAll(self) -> None: | ||
| self.model.refreshAll() | ||
|
|
||
| @QtCore.Slot() | ||
| def promoteStar(self) -> None: | ||
| self.proxyModel.onToggleStar() | ||
| def _starCurrentItem(self) -> None: | ||
| proxy_index = self.view.currentIndex() | ||
| if not proxy_index.isValid(): | ||
| return | ||
| source_index = self.proxyModel.mapToSource(proxy_index) | ||
| if source_index.column() != 0: | ||
| source_index = source_index.sibling(source_index.row(), 0) | ||
| item = self.model.itemFromIndex(source_index) | ||
| if isinstance(item, ItemBase): | ||
| self.view.lastSelectedItem = item | ||
| self.view.itemStarToggle.emit(item) | ||
|
|
||
| @QtCore.Slot() | ||
| def refreshAll(self) -> None: | ||
| self.model.refreshAll() | ||
| def _trashCurrentItem(self) -> None: | ||
| proxy_index = self.view.currentIndex() | ||
| if not proxy_index.isValid(): | ||
| return | ||
| source_index = self.proxyModel.mapToSource(proxy_index) | ||
| if source_index.column() != 0: | ||
| source_index = source_index.sibling(source_index.row(), 0) | ||
| item = self.model.itemFromIndex(source_index) | ||
| if isinstance(item, ItemBase): | ||
| self.view.lastSelectedItem = item | ||
| self.view.itemTrashToggle.emit(item) | ||
|
Comment on lines
911
to
+935
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are these 2 functions, instead of being the same one with a flag for star/trash? all the code is exactly the same except the last line |
||
|
|
||
| @QtCore.Slot() | ||
| def _fitCurrentColumn(self) -> None: | ||
| col = self.view.currentIndex().column() | ||
| self.view.resizeColumnToContents(col if col >= 0 else 0) | ||
|
|
||
| @QtCore.Slot() | ||
| def _sortCurrentColumn(self) -> None: | ||
| header = self.view.header() | ||
| col = self.view.currentIndex().column() | ||
| if col < 0: | ||
| col = header.sortIndicatorSection() | ||
| current_order = header.sortIndicatorOrder() | ||
| new_order = ( | ||
| QtCore.Qt.SortOrder.AscendingOrder | ||
| if current_order == QtCore.Qt.SortOrder.DescendingOrder | ||
| else QtCore.Qt.SortOrder.DescendingOrder | ||
| ) | ||
| header.setSortIndicator(col, new_order) | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need all these new slots?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the first three and refactored the code accordingly. As for the final four, they act as thin wrappers. They need to resolve what the currently selected item is to execute, something the right-click (for trash/star item) implicitly does
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why was the refactor needed though? |
||
| def debuggingMethod(self) -> None: | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patterns is a list of strings, how is
not []different than iterating over[]?