From 3731cf26ef5c7a789690bd60fb63304dd042ed9e Mon Sep 17 00:00:00 2001 From: Mike Hendricks Date: Tue, 19 Dec 2023 15:34:06 -0600 Subject: [PATCH] WIP: Add Focus To Name feature and CommandPalette --- preditor/gui/command_palette/__init__.py | 0 .../gui/command_palette/command_palette.py | 57 ++++++++++++++++++ .../gui/command_palette/workbox_completer.py | 9 +++ .../gui/command_palette/workbox_item_model.py | 60 +++++++++++++++++++ preditor/gui/loggerwindow.py | 38 ++++++++++-- preditor/gui/ui/loggerwindow.ui | 9 +++ 6 files changed, 168 insertions(+), 5 deletions(-) create mode 100644 preditor/gui/command_palette/__init__.py create mode 100644 preditor/gui/command_palette/command_palette.py create mode 100644 preditor/gui/command_palette/workbox_completer.py create mode 100644 preditor/gui/command_palette/workbox_item_model.py diff --git a/preditor/gui/command_palette/__init__.py b/preditor/gui/command_palette/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/preditor/gui/command_palette/command_palette.py b/preditor/gui/command_palette/command_palette.py new file mode 100644 index 00000000..b5f2b625 --- /dev/null +++ b/preditor/gui/command_palette/command_palette.py @@ -0,0 +1,57 @@ +import six +from Qt.QtCore import QPoint, Qt +from Qt.QtWidgets import QFrame, QHBoxLayout, QLineEdit, QShortcut + +from .workbox_completer import WorkboxCompleter + + +class CommandPalette(QFrame): + def __init__(self, model, parent=None, **kwargs): + super(CommandPalette, self).__init__(parent=parent, **kwargs) + self.y_offset = 100 + lyt = QHBoxLayout(self) + self.uiLineEDIT = QLineEdit(parent=self) + lyt.addWidget(self.uiLineEDIT) + self.setMinimumSize(400, self.sizeHint().height()) + self.uiCloseSCT = QShortcut( + Qt.Key_Escape, self, context=Qt.WidgetWithChildrenShortcut + ) + self.uiCloseSCT.activated.connect(self.hide) + self.uiLineCOMPL = WorkboxCompleter() + self.uiLineCOMPL.setCaseSensitivity(False) + self.uiLineCOMPL.setModel(model) + self.uiLineEDIT.setCompleter(self.uiLineCOMPL) + self.uiLineCOMPL.activated.connect(self.completed) + self.uiLineCOMPL.highlighted.connect(self.completer_selected) + # self.uiLineCOMPL.popup().clicked.connect(self.completed) + self.current_name = parent.name_for_workbox(parent.current_workbox()) + + def completed(self, name): + if isinstance(name, six.string_types): + self.current_name = name + else: + self.current_name = self.uiLineCOMPL.pathFromIndex(name) + self.hide() + + def completer_selected(self, name): + self.parent().workbox_for_name(name.rstrip("/"), visible=True) + + def hide(self): + # Close the popup if its open + self.uiLineCOMPL.popup().hide() + # Restore the original tab as the user didn't choose the new tab + self.completer_selected(self.current_name) + super(CommandPalette, self).hide() + + def reposition(self): + pgeo = self.parent().geometry() + geo = self.geometry() + center = QPoint(pgeo.width() // 2, self.y_offset) + geo.moveCenter(center) + self.setGeometry(geo) + + def popup(self): + self.reposition() + self.uiLineEDIT.setFocus(Qt.PopupFocusReason) + self.show() + self.uiLineCOMPL.complete() diff --git a/preditor/gui/command_palette/workbox_completer.py b/preditor/gui/command_palette/workbox_completer.py new file mode 100644 index 00000000..dab9cb19 --- /dev/null +++ b/preditor/gui/command_palette/workbox_completer.py @@ -0,0 +1,9 @@ +from Qt.QtWidgets import QCompleter + + +class WorkboxCompleter(QCompleter): + def splitPath(self, path): + return path.split("/") + + def pathFromIndex(self, index): + return self.model().pathFromIndex(index) diff --git a/preditor/gui/command_palette/workbox_item_model.py b/preditor/gui/command_palette/workbox_item_model.py new file mode 100644 index 00000000..c45019fd --- /dev/null +++ b/preditor/gui/command_palette/workbox_item_model.py @@ -0,0 +1,60 @@ +from Qt.QtCore import Qt +from Qt.QtGui import QStandardItem, QStandardItemModel + + +def manager_items(manager): + for group_index in range(manager.count()): + group_name = manager.tabText(group_index) + + tab_widget = manager.widget(group_index) + for tab_index in range(tab_widget.count()): + tab_name = tab_widget.tabText(tab_index) + yield group_name, tab_name, group_index, tab_index + + +class WorkboxItemModel(QStandardItemModel): + def __init__(self, manager, *args, **kwargs): + super(WorkboxItemModel, self).__init__(*args, **kwargs) + self.manager = manager + + def pathFromIndex(self, index): + parts = [""] + while index.isValid(): + parts.append(self.data(index, Qt.DisplayRole)) + index = index.parent() + if len(parts) == 1: + return "" + return "/".join([x for x in parts[::-1] if x]) + + +class WorkboxTreeItemModel(WorkboxItemModel): + def process(self): + root = self.invisibleRootItem() + prev_group = -1 + for group_name, tab_name, group_index, _ in manager_items(self.manager): + if prev_group != group_index: + group_item = QStandardItem(group_name) + root.appendRow(group_item) + prev_group = group_index + + tab_item = QStandardItem(tab_name) + group_item.appendRow(tab_item) + + # for group in range(self.manager.count()): + # group_name = self.manager.tabText(group) + # group_item = QStandardItem(group_name) + # root.appendRow(group_item) + + # tab_widget = self.manager.widget(group) + # for index in range(tab_widget.count()): + # tab_name = tab_widget.tabText(index) + # tab_item = QStandardItem(tab_name) + # group_item.appendRow(tab_item) + + +class WorkboxListItemModel(WorkboxItemModel): + def process(self): + root = self.invisibleRootItem() + for group_name, tab_name, _, _ in manager_items(self.manager): + group_item = QStandardItem('/'.join((group_name, tab_name))) + root.appendRow(group_item) diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index b1ee8cc8..987edc30 100644 --- a/preditor/gui/loggerwindow.py +++ b/preditor/gui/loggerwindow.py @@ -37,6 +37,8 @@ ) from ..delayable_engine import DelayableEngine from ..gui import Dialog, Window, loadUi +from ..gui.command_palette.command_palette import CommandPalette +from ..gui.command_palette.workbox_item_model import WorkboxListItemModel from ..logging_config import LoggingConfig from ..utils import stylesheets from .completer import CompleterMode @@ -181,6 +183,8 @@ def __init__(self, parent, name=None, run_workbox=False, standalone=False): self.uiGroup8ACT.triggered.connect(partial(self.gotoGroupByIndex, 8)) self.uiGroupLastACT.triggered.connect(partial(self.gotoGroupByIndex, -1)) + self.uiFocusNameACT.triggered.connect(self.show_focus_name) + self.uiCommentToggleACT.triggered.connect(self.comment_toggle) self.uiSpellCheckEnabledACT.toggled.connect(self.setSpellCheckEnabled) @@ -312,7 +316,18 @@ def current_workbox(self): return self.uiWorkboxTAB.current_groups_widget() @classmethod - def workbox_for_name(cls, name, show=False): + def name_for_workbox(cls, workbox): + ret = [] + logger = cls.instance() + index = logger.uiWorkboxTAB.currentIndex() + ret.append(logger.uiWorkboxTAB.tabText(index)) + group_widget = logger.uiWorkboxTAB.currentWidget() + index = group_widget.currentIndex() + ret.append(group_widget.tabText(index)) + return "/".join(ret) + + @classmethod + def workbox_for_name(cls, name, show=False, visible=False): """Used to find a workbox for a given name. It accepts a string matching the "{group}/{workbox}" format, or if True, the current workbox. @@ -332,13 +347,19 @@ def workbox_for_name(cls, name, show=False): # If name is a string, find first tab with that name elif isinstance(name, six.string_types): - group, editor = name.split('/', 1) - index = logger.uiWorkboxTAB.index_for_text(group) - if index != -1: - tab_widget = logger.uiWorkboxTAB.widget(index) + split = name.split('/', 1) + if len(split) < 2: + return None + group, editor = split + group_index = logger.uiWorkboxTAB.index_for_text(group) + if group_index != -1: + tab_widget = logger.uiWorkboxTAB.widget(group_index) index = tab_widget.index_for_text(editor) if index != -1: workbox = tab_widget.widget(index) + if visible: + tab_widget.setCurrentIndex(index) + logger.uiWorkboxTAB.setCurrentIndex(group_index) if show and workbox: workbox.__show__() @@ -1001,6 +1022,13 @@ def showEvent(self, event): def show_workbox_options(self): self.uiWorkboxSTACK.setCurrentIndex(WorkboxPages.Options) + @Slot() + def show_focus_name(self): + model = WorkboxListItemModel(manager=self.uiWorkboxTAB) + model.process() + w = CommandPalette(model, parent=self) + w.popup() + def updateCopyIndentsAsSpaces(self): for workbox in self.uiWorkboxTAB.all_widgets(): workbox.__set_copy_indents_as_spaces__( diff --git a/preditor/gui/ui/loggerwindow.ui b/preditor/gui/ui/loggerwindow.ui index b19f3c28..4fc142c3 100644 --- a/preditor/gui/ui/loggerwindow.ui +++ b/preditor/gui/ui/loggerwindow.ui @@ -322,6 +322,7 @@ + @@ -931,6 +932,14 @@ at the indicated line in the specified text editor. Backup + + + Focus To Name + + + Ctrl+P + + Restart PrEditor