-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Focus To Name feature using fuzzy search
- Loading branch information
1 parent
ea773dc
commit 7047908
Showing
5 changed files
with
230 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters