Skip to content

Commit

Permalink
Add Focus To Name feature using fuzzy search
Browse files Browse the repository at this point in the history
  • Loading branch information
MHendricks committed Mar 6, 2024
1 parent 908e3ff commit a443654
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 0 deletions.
Empty file.
93 changes: 93 additions & 0 deletions preditor/gui/fuzzy_search/fuzzy_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from __future__ import absolute_import

from functools import partial

from Qt.QtCore import QModelIndex, QPoint, Qt, Signal
from Qt.QtWidgets import QFrame, QLineEdit, QListView, QShortcut, QVBoxLayout

from ..group_tab_widget.grouped_tab_models import GroupTabFuzzyFilterProxyModel


class FuzzySearch(QFrame):
canceled = Signal("QModelIndex")
"""Passes the original QModelIndex for the tab that was selected when the
widget was first shown. This lets you reset back to the orignal state."""
highlighted = Signal("QModelIndex")
"""Emitted when the user navitages to the given index, but hasn't selected."""
selected = Signal("QModelIndex")
"""Emitted when the user selects a item."""

def __init__(self, model, parent=None, **kwargs):
super(FuzzySearch, self).__init__(parent=parent, **kwargs)
self.y_offset = 100
self.setMinimumSize(400, 200)
self.uiCloseSCT = QShortcut(
Qt.Key_Escape, self, context=Qt.WidgetWithChildrenShortcut
)
self.uiCloseSCT.activated.connect(self._canceled)

self.uiUpSCT = QShortcut(Qt.Key_Up, self, context=Qt.WidgetWithChildrenShortcut)
self.uiUpSCT.activated.connect(partial(self.increment_selection, -1))
self.uiDownSCT = QShortcut(
Qt.Key_Down, self, context=Qt.WidgetWithChildrenShortcut
)
self.uiDownSCT.activated.connect(partial(self.increment_selection, 1))

lyt = QVBoxLayout(self)
self.uiLineEDIT = QLineEdit(parent=self)
self.uiLineEDIT.textChanged.connect(self.update_completer)
self.uiLineEDIT.returnPressed.connect(self.activated)
lyt.addWidget(self.uiLineEDIT)
self.uiResultsLIST = QListView(self)
self.uiResultsLIST.activated.connect(self.activated)
self.proxy_model = GroupTabFuzzyFilterProxyModel(self)
self.proxy_model.setSourceModel(model)
self.uiResultsLIST.setModel(self.proxy_model)
lyt.addWidget(self.uiResultsLIST)

self.original_model_index = model.original_model_index

def activated(self):
current = self.uiResultsLIST.currentIndex()
self.selected.emit(current)
self.hide()

def increment_selection(self, direction):
current = self.uiResultsLIST.currentIndex()
col = 0
row = 0
if current.isValid():
col = current.column()
row = current.row() + direction
new = self.uiResultsLIST.model().index(row, col)
self.uiResultsLIST.setCurrentIndex(new)
self.highlighted.emit(new)

def update_completer(self, wildcard):
if wildcard:
if not self.uiResultsLIST.currentIndex().isValid():
new = self.uiResultsLIST.model().index(0, 0)
self.uiResultsLIST.setCurrentIndex(new)
else:
self.uiResultsLIST.clearSelection()
self.uiResultsLIST.setCurrentIndex(QModelIndex())
self.proxy_model.setFuzzySearch(wildcard)
self.highlighted.emit(self.uiResultsLIST.currentIndex())

def _canceled(self):
# Restore the original tab as the user didn't choose the new tab
self.canceled.emit(self.original_model_index)
self.hide()

def reposition(self):
pgeo = self.parent().geometry()
geo = self.geometry()
center = QPoint(pgeo.width() // 2, 0)
geo.moveCenter(center)
geo.setY(self.y_offset)
self.setGeometry(geo)

def popup(self):
self.show()
self.reposition()
self.uiLineEDIT.setFocus(Qt.PopupFocusReason)
108 changes: 108 additions & 0 deletions preditor/gui/group_tab_widget/grouped_tab_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from __future__ import absolute_import

import re

from Qt.QtCore import QSortFilterProxyModel, Qt
from Qt.QtGui import QStandardItem, QStandardItemModel


class GroupTabItemModel(QStandardItemModel):
GroupIndexRole = Qt.UserRole + 1
TabIndexRole = GroupIndexRole + 1

def __init__(self, manager, *args, **kwargs):
super(GroupTabItemModel, self).__init__(*args, **kwargs)
self.manager = manager

def workbox_indexes_from_model_index(self, index):
"""Returns the group_index and tab_index for the provided QModelIndex"""
return (
index.data(self.GroupIndexRole),
index.data(self.TabIndexRole),
)

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 GroupTabTreeItemModel(GroupTabItemModel):
def process(self):
root = self.invisibleRootItem()
current_group = self.manager.currentIndex()
current_tab = self.manager.currentWidget().currentIndex()

prev_group = -1
all_widgets = self.manager.all_widgets()
for _, group_name, tab_name, group_index, tab_index in all_widgets:
if prev_group != group_index:
group_item = QStandardItem(group_name)
group_item.setData(group_index, self.GroupIndexRole)
root.appendRow(group_item)
prev_group = group_index

tab_item = QStandardItem(tab_name)
tab_item.setData(group_index, self.GroupIndexRole)
tab_item.setData(tab_index, self.TabIndexRole)
group_item.appendRow(tab_item)
if group_index == current_group and tab_index == current_tab:
self.original_model_index = self.indexFromItem(tab_item)


class GroupTabListItemModel(GroupTabItemModel):
def flags(self, index):
return Qt.ItemIsEnabled | Qt.ItemIsSelectable

def process(self):
root = self.invisibleRootItem()
current_group = self.manager.currentIndex()
current_tab = self.manager.currentWidget().currentIndex()

all_widgets = self.manager.all_widgets()
for _, group_name, tab_name, group_index, tab_index in all_widgets:
tab_item = QStandardItem('/'.join((group_name, tab_name)))
tab_item.setData(group_index, self.GroupIndexRole)
tab_item.setData(tab_index, self.TabIndexRole)
root.appendRow(tab_item)
if group_index == current_group and tab_index == current_tab:
self.original_model_index = self.indexFromItem(tab_item)


class GroupTabFuzzyFilterProxyModel(QSortFilterProxyModel):
"""Implements a fuzzy search filter proxy model."""

def __init__(self, parent=None):
super(GroupTabFuzzyFilterProxyModel, self).__init__(parent=parent)
self._fuzzy_regex = None

def setFuzzySearch(self, search):
search = '.*'.join(search)
# search = '.*{}.*'.format(search)
self._fuzzy_regex = re.compile(search, re.I)
self.invalidateFilter()

def filterAcceptsRow(self, sourceRow, sourceParent):
if self.filterKeyColumn() == 0 and self._fuzzy_regex:

index = self.sourceModel().index(sourceRow, 0, sourceParent)
data = self.sourceModel().data(index)
ret = bool(self._fuzzy_regex.search(data))
return ret

return super(GroupTabFuzzyFilterProxyModel, self).filterAcceptsRow(
sourceRow, sourceParent
)

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])
20 changes: 20 additions & 0 deletions preditor/gui/loggerwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
)
from ..delayable_engine import DelayableEngine
from ..gui import Dialog, Window, loadUi
from ..gui.fuzzy_search.fuzzy_search import FuzzySearch
from ..gui.group_tab_widget.grouped_tab_models import GroupTabListItemModel
from ..logging_config import LoggingConfig
from ..utils import stylesheets
from .completer import CompleterMode
Expand Down Expand Up @@ -180,6 +182,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)
Expand Down Expand Up @@ -1013,6 +1017,22 @@ def showEvent(self, event):
def show_workbox_options(self):
self.uiWorkboxSTACK.setCurrentIndex(WorkboxPages.Options)

@Slot()
def show_focus_name(self):
model = GroupTabListItemModel(manager=self.uiWorkboxTAB)
model.process()

def update_tab(index):
group, tab = model.workbox_indexes_from_model_index(index)
if group is not None:
self.uiWorkboxTAB.set_current_groups_from_index(group, tab)

w = FuzzySearch(model, parent=self)
w.selected.connect(update_tab)
w.canceled.connect(update_tab)
w.highlighted.connect(update_tab)
w.popup()

def updateCopyIndentsAsSpaces(self):
for workbox, _, _, _, _ in self.uiWorkboxTAB.all_widgets():
workbox.__set_copy_indents_as_spaces__(
Expand Down
9 changes: 9 additions & 0 deletions preditor/gui/ui/loggerwindow.ui
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@
<addaction name="separator"/>
<addaction name="menuFocus_to_Group"/>
<addaction name="menuFocus_to_Tab"/>
<addaction name="uiFocusNameACT"/>
</widget>
<addaction name="uiScriptingMENU"/>
<addaction name="menuEdit"/>
Expand Down Expand Up @@ -931,6 +932,14 @@ at the indicated line in the specified text editor.
<string>Backup</string>
</property>
</action>
<action name="uiFocusNameACT">
<property name="text">
<string>Focus To Name</string>
</property>
<property name="shortcut">
<string>Ctrl+P</string>
</property>
</action>
<action name="uiRestartACT">
<property name="text">
<string>Restart PrEditor</string>
Expand Down

0 comments on commit a443654

Please sign in to comment.