From b54c95b9ae578f08dbd1fd6c244e3bcdb584602d 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 | 59 +++++++++++++++++++ .../gui/command_palette/workbox_completer.py | 11 ++++ .../gui/command_palette/workbox_item_model.py | 41 +++++++++++++ preditor/gui/group_tab_widget/__init__.py | 19 ++++-- preditor/gui/loggerwindow.py | 48 +++++++++++---- preditor/gui/ui/loggerwindow.ui | 9 +++ 7 files changed, 172 insertions(+), 15 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..da9b9c25 --- /dev/null +++ b/preditor/gui/command_palette/command_palette.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import + +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..2f279e21 --- /dev/null +++ b/preditor/gui/command_palette/workbox_completer.py @@ -0,0 +1,11 @@ +from __future__ import absolute_import + +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..48bed9af --- /dev/null +++ b/preditor/gui/command_palette/workbox_item_model.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import + +from Qt.QtCore import Qt +from Qt.QtGui import QStandardItem, QStandardItemModel + + +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 self.manager.all_widgets(): + 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) + + +class WorkboxListItemModel(WorkboxItemModel): + def process(self): + root = self.invisibleRootItem() + for _, group_name, tab_name, _, _ in self.manager.all_widgets(): + group_item = QStandardItem('/'.join((group_name, tab_name))) + root.appendRow(group_item) diff --git a/preditor/gui/group_tab_widget/__init__.py b/preditor/gui/group_tab_widget/__init__.py index 0d9814fb..4bae065b 100644 --- a/preditor/gui/group_tab_widget/__init__.py +++ b/preditor/gui/group_tab_widget/__init__.py @@ -114,11 +114,20 @@ def add_new_tab(self, group, title="Workbox"): return parent, editor def all_widgets(self): - """Returns every widget under every group.""" - for i in range(self.count()): - tab_widget = self.widget(i) - for j in range(tab_widget.count()): - yield tab_widget.widget(j) + """A generator yielding information about every widget under every group. + + Yields: + group tab name, widget tab name, group tab index, widget tab index, widget + """ + for group_index in range(self.count()): + group_name = self.tabText(group_index) + + tab_widget = self.widget(group_index) + for tab_index in range(tab_widget.count()): + tab_name = tab_widget.tabText(tab_index) + yield tab_widget.widget( + tab_index + ), group_name, tab_name, group_index, tab_index def close_current_tab(self): """Convenient method to close the currently open editor tab prompting diff --git a/preditor/gui/loggerwindow.py b/preditor/gui/loggerwindow.py index b1ee8cc8..dbc9c0c5 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__() @@ -580,7 +601,7 @@ def closeEvent(self, event): self.uiConsoleTOOLBAR.hide() # Handle any cleanup each workbox tab may need to do before closing - for editor in self.uiWorkboxTAB.all_widgets(): + for editor, _, _, _, _ in self.uiWorkboxTAB.all_widgets(): editor.__close__() def closeLogger(self): @@ -788,7 +809,7 @@ def restoreToolbars(self, pref=None): def setAutoCompleteEnabled(self, state): self.uiConsoleTXT.completer().setEnabled(state) - for workbox in self.uiWorkboxTAB.all_widgets(): + for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets(): workbox.__set_auto_complete_enabled__(state) def setSpellCheckEnabled(self, state): @@ -956,7 +977,7 @@ def setFont(self, font, report=False): super(LoggerWindow, self).setFont(font) self.console().setConsoleFont(font) - for workbox in self.uiWorkboxTAB.all_widgets(): + for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets(): workbox.__set_margins_font__(font) workbox.__set_font__(font) @@ -1001,14 +1022,21 @@ 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(): + for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets(): workbox.__set_copy_indents_as_spaces__( self.uiCopyTabsToSpacesACT.isChecked() ) def updateIndentationsUseTabs(self): - for workbox in self.uiWorkboxTAB.all_widgets(): + for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets(): workbox.__set_indentations_use_tabs__( self.uiIndentationsTabsACT.isChecked() ) 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