From 4af3b1b1f997dd24e5d48391cbf2337602be403d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 11 Feb 2022 17:25:57 +0100 Subject: [PATCH] Draft implementation for search in settings --- openpype/tools/settings/settings/search.py | 137 +++++++++++++++++++++ openpype/tools/settings/settings/window.py | 25 ++++ 2 files changed, 162 insertions(+) create mode 100644 openpype/tools/settings/settings/search.py diff --git a/openpype/tools/settings/settings/search.py b/openpype/tools/settings/settings/search.py new file mode 100644 index 0000000000..3d61dd33fe --- /dev/null +++ b/openpype/tools/settings/settings/search.py @@ -0,0 +1,137 @@ +from functools import partial +import re + +from Qt import QtCore, QtWidgets +from openpype.tools.utils.models import TreeModel, Item +from openpype.tools.utils.lib import schedule + + +def get_entity_children(entity): + + if hasattr(entity, "values"): + return entity.values() + return [] + + +class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): + """Filters recursively to regex in all columns""" + + def __init__(self): + super(RecursiveSortFilterProxyModel, self).__init__() + + # Note: Recursive filtering was introduced in Qt 5.10. + self.setRecursiveFilteringEnabled(True) + + def filterAcceptsRow(self, row, parent): + + if not parent.isValid(): + return False + + regex = self.filterRegExp() + if not regex.isEmpty() and regex.isValid(): + pattern = regex.pattern() + source_model = self.sourceModel() + + # Check current index itself in all columns + for column in range(source_model.columnCount(parent)): + source_index = source_model.index(row, column, parent) + if not source_index.isValid(): + continue + + key = source_model.data(source_index, self.filterRole()) + if not key: + continue + + if re.search(pattern, key, re.IGNORECASE): + return True + + return False + + return super(RecursiveSortFilterProxyModel, + self).filterAcceptsRow(row, parent) + + +class SearchEntitiesDialog(QtWidgets.QDialog): + + path_clicked = QtCore.Signal(str) + + def __init__(self, entity, parent=None): + super(SearchEntitiesDialog, self).__init__(parent=parent) + + layout = QtWidgets.QVBoxLayout(self) + + filter_edit = QtWidgets.QLineEdit() + filter_edit.setPlaceholderText("Search..") + + model = EntityTreeModel() + proxy = RecursiveSortFilterProxyModel() + proxy.setSourceModel(model) + proxy.setDynamicSortFilter(True) + view = QtWidgets.QTreeView() + view.setModel(proxy) + + layout.addWidget(filter_edit) + layout.addWidget(view) + + filter_edit.textChanged.connect(self._on_filter_changed) + view.selectionModel().selectionChanged.connect(self.on_select) + + view.setAllColumnsShowFocus(True) + view.setSortingEnabled(True) + view.sortByColumn(1, QtCore.Qt.AscendingOrder) + + self._model = model + self._proxy = proxy + self._view = view + + # Refresh to the passed entity + model.set_root(entity) + + view.resizeColumnToContents(0) + + def _on_filter_changed(self, txt): + # Provide slight delay to filtering so user can type quickly + schedule(partial(self.on_filter_changed, txt), 250, channel="search") + + def on_filter_changed(self, txt): + self._proxy.setFilterRegExp(txt) + + # WARNING This expanding and resizing is relatively slow. + self._view.expandAll() + self._view.resizeColumnToContents(0) + + def on_select(self): + current = self._view.currentIndex() + item = current.data(EntityTreeModel.ItemRole) + self.path_clicked.emit(item["path"]) + + +class EntityTreeModel(TreeModel): + + Columns = ["trail", "label", "key", "path"] + + def add_entity(self, entity, parent=None): + + item = Item() + + # Label and key can sometimes be emtpy so we use the trail from path + # in the most left column since it's never empty + item["trail"] = entity.path.rsplit("/", 1)[-1] + item["label"] = entity.label + item["key"] = entity.key + item["path"] = entity.path + + parent.add_child(item) + + for child in get_entity_children(entity): + self.add_entity(child, parent=item) + + def set_root(self, root_entity): + self.clear() + self.beginResetModel() + + # We don't want to see the root entity so we directly add its children + for child in get_entity_children(root_entity): + self.add_entity(child, parent=self._root_item) + self.endResetModel() + diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index 411e7b5e7f..bbfba088ba 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -164,3 +164,28 @@ class MainWidget(QtWidgets.QWidget): result = dialog.exec_() if result == 1: self.trigger_restart.emit() + + def keyPressEvent(self, event): + + # todo: This might not be the cleanest place but works for prototype + if event.matches(QtGui.QKeySequence.Find): + print("Search!") + + # todo: search in all widgets (or in active)? + widget = self._header_tab_widget.currentWidget() + root_entity = widget.entity + + from .search import SearchEntitiesDialog + search = SearchEntitiesDialog(root_entity, parent=self) + search.resize(700, 500) + search.setWindowTitle("Search") + search.show() + + def on_path(path): + widget.breadcrumbs_widget.change_path(path) + + search.path_clicked.connect(on_path) + event.accept() + return + + return super(MainWidget, self).keyPressEvent(event)