mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge pull request #2699 from BigRoy/settings_search
Settings: Implementation for search in settings
This commit is contained in:
commit
b43b304295
3 changed files with 242 additions and 0 deletions
|
|
@ -91,6 +91,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
state_changed = QtCore.Signal()
|
||||
saved = QtCore.Signal(QtWidgets.QWidget)
|
||||
restart_required_trigger = QtCore.Signal()
|
||||
reset_started = QtCore.Signal()
|
||||
reset_finished = QtCore.Signal()
|
||||
full_path_requested = QtCore.Signal(str, str)
|
||||
|
||||
require_restart_label_text = (
|
||||
|
|
@ -379,7 +381,12 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
"""Change path of widget based on category full path."""
|
||||
pass
|
||||
|
||||
def change_path(self, path):
|
||||
"""Change path and go to widget."""
|
||||
self.breadcrumbs_bar.change_path(path)
|
||||
|
||||
def set_path(self, path):
|
||||
"""Called from clicked widget."""
|
||||
self.breadcrumbs_bar.set_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout, footer_widget):
|
||||
|
|
@ -492,6 +499,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._update_labels_visibility()
|
||||
|
||||
def reset(self):
|
||||
self.reset_started.emit()
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
self._on_reset_start()
|
||||
|
|
@ -596,6 +604,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._on_reset_crash()
|
||||
else:
|
||||
self._on_reset_success()
|
||||
self.reset_finished.emit()
|
||||
|
||||
def _on_source_version_change(self, version):
|
||||
if self._updating_root:
|
||||
|
|
|
|||
186
openpype/tools/settings/settings/search_dialog.py
Normal file
186
openpype/tools/settings/settings/search_dialog.py
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
import re
|
||||
import collections
|
||||
|
||||
from Qt import QtCore, QtWidgets, QtGui
|
||||
|
||||
ENTITY_LABEL_ROLE = QtCore.Qt.UserRole + 1
|
||||
ENTITY_PATH_ROLE = QtCore.Qt.UserRole + 2
|
||||
|
||||
|
||||
def get_entity_children(entity):
|
||||
# TODO find better way how to go through all children
|
||||
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()
|
||||
compiled_regex = re.compile(pattern)
|
||||
source_model = self.sourceModel()
|
||||
|
||||
# Check current index itself in all columns
|
||||
source_index = source_model.index(row, 0, parent)
|
||||
if source_index.isValid():
|
||||
for role in (ENTITY_PATH_ROLE, ENTITY_LABEL_ROLE):
|
||||
value = source_model.data(source_index, role)
|
||||
if value and compiled_regex.search(value):
|
||||
return True
|
||||
return False
|
||||
|
||||
return super(
|
||||
RecursiveSortFilterProxyModel, self
|
||||
).filterAcceptsRow(row, parent)
|
||||
|
||||
|
||||
class SearchEntitiesDialog(QtWidgets.QDialog):
|
||||
path_clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent):
|
||||
super(SearchEntitiesDialog, self).__init__(parent=parent)
|
||||
|
||||
self.setWindowTitle("Search Settings")
|
||||
|
||||
filter_edit = QtWidgets.QLineEdit(self)
|
||||
filter_edit.setPlaceholderText("Search...")
|
||||
|
||||
model = EntityTreeModel()
|
||||
proxy = RecursiveSortFilterProxyModel()
|
||||
proxy.setSourceModel(model)
|
||||
proxy.setDynamicSortFilter(True)
|
||||
|
||||
view = QtWidgets.QTreeView(self)
|
||||
view.setAllColumnsShowFocus(True)
|
||||
view.setSortingEnabled(True)
|
||||
view.setModel(proxy)
|
||||
model.setColumnCount(3)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.addWidget(filter_edit)
|
||||
layout.addWidget(view)
|
||||
|
||||
filter_changed_timer = QtCore.QTimer()
|
||||
filter_changed_timer.setInterval(200)
|
||||
|
||||
view.selectionModel().selectionChanged.connect(
|
||||
self._on_selection_change
|
||||
)
|
||||
filter_changed_timer.timeout.connect(self._on_filter_timer)
|
||||
filter_edit.textChanged.connect(self._on_filter_changed)
|
||||
|
||||
self._filter_edit = filter_edit
|
||||
self._model = model
|
||||
self._proxy = proxy
|
||||
self._view = view
|
||||
self._filter_changed_timer = filter_changed_timer
|
||||
|
||||
self._first_show = True
|
||||
|
||||
def set_root_entity(self, entity):
|
||||
self._model.set_root_entity(entity)
|
||||
self._view.resizeColumnToContents(0)
|
||||
|
||||
def showEvent(self, event):
|
||||
super(SearchEntitiesDialog, self).showEvent(event)
|
||||
if self._first_show:
|
||||
self._first_show = False
|
||||
self.resize(700, 500)
|
||||
|
||||
def _on_filter_changed(self, txt):
|
||||
self._filter_changed_timer.start()
|
||||
|
||||
def _on_filter_timer(self):
|
||||
text = self._filter_edit.text()
|
||||
self._proxy.setFilterRegExp(text)
|
||||
|
||||
# WARNING This expanding and resizing is relatively slow.
|
||||
self._view.expandAll()
|
||||
self._view.resizeColumnToContents(0)
|
||||
|
||||
def _on_selection_change(self):
|
||||
current = self._view.currentIndex()
|
||||
path = current.data(ENTITY_PATH_ROLE)
|
||||
self.path_clicked.emit(path)
|
||||
|
||||
|
||||
class EntityTreeModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EntityTreeModel, self).__init__(*args, **kwargs)
|
||||
self.setColumnCount(3)
|
||||
|
||||
def data(self, index, role=None):
|
||||
if role is None:
|
||||
role = QtCore.Qt.DisplayRole
|
||||
|
||||
col = index.column()
|
||||
if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
|
||||
if col == 0:
|
||||
pass
|
||||
elif col == 1:
|
||||
role = ENTITY_LABEL_ROLE
|
||||
elif col == 2:
|
||||
role = ENTITY_PATH_ROLE
|
||||
|
||||
if col > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).data(index, role)
|
||||
|
||||
def flags(self, index):
|
||||
if index.column() > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).flags(index)
|
||||
|
||||
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if section == 0:
|
||||
return "Key"
|
||||
elif section == 1:
|
||||
return "Label"
|
||||
elif section == 2:
|
||||
return "Path"
|
||||
return ""
|
||||
return super(EntityTreeModel, self).headerData(
|
||||
section, orientation, role
|
||||
)
|
||||
|
||||
def set_root_entity(self, root_entity):
|
||||
parent = self.invisibleRootItem()
|
||||
parent.removeRows(0, parent.rowCount())
|
||||
if not root_entity:
|
||||
return
|
||||
|
||||
# We don't want to see the root entity so we directly add its children
|
||||
fill_queue = collections.deque()
|
||||
fill_queue.append((root_entity, parent))
|
||||
cols = self.columnCount()
|
||||
while fill_queue:
|
||||
parent_entity, parent_item = fill_queue.popleft()
|
||||
child_items = []
|
||||
for child in get_entity_children(parent_entity):
|
||||
label = child.label
|
||||
path = child.path
|
||||
key = path.split("/")[-1]
|
||||
item = QtGui.QStandardItem(key)
|
||||
item.setEditable(False)
|
||||
item.setData(label, ENTITY_LABEL_ROLE)
|
||||
item.setData(path, ENTITY_PATH_ROLE)
|
||||
item.setColumnCount(cols)
|
||||
child_items.append(item)
|
||||
fill_queue.append((child, item))
|
||||
|
||||
if child_items:
|
||||
parent_item.appendRows(child_items)
|
||||
|
|
@ -9,6 +9,7 @@ from .widgets import (
|
|||
RestartDialog,
|
||||
SettingsTabWidget
|
||||
)
|
||||
from .search_dialog import SearchEntitiesDialog
|
||||
from openpype import style
|
||||
|
||||
from openpype.lib import is_admin_password_required
|
||||
|
|
@ -58,15 +59,22 @@ class MainWidget(QtWidgets.QWidget):
|
|||
|
||||
self.setLayout(layout)
|
||||
|
||||
search_dialog = SearchEntitiesDialog(self)
|
||||
|
||||
self._shadow_widget = ShadowWidget("Working...", self)
|
||||
self._shadow_widget.setVisible(False)
|
||||
|
||||
header_tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||
search_dialog.path_clicked.connect(self._on_search_path_clicked)
|
||||
|
||||
for tab_widget in tab_widgets:
|
||||
tab_widget.saved.connect(self._on_tab_save)
|
||||
tab_widget.state_changed.connect(self._on_state_change)
|
||||
tab_widget.restart_required_trigger.connect(
|
||||
self._on_restart_required
|
||||
)
|
||||
tab_widget.reset_started.connect(self._on_reset_started)
|
||||
tab_widget.reset_started.connect(self._on_reset_finished)
|
||||
tab_widget.full_path_requested.connect(self._on_full_path_request)
|
||||
|
||||
header_tab_widget.context_menu_requested.connect(
|
||||
|
|
@ -75,6 +83,7 @@ class MainWidget(QtWidgets.QWidget):
|
|||
|
||||
self._header_tab_widget = header_tab_widget
|
||||
self.tab_widgets = tab_widgets
|
||||
self._search_dialog = search_dialog
|
||||
|
||||
def _on_tab_save(self, source_widget):
|
||||
for tab_widget in self.tab_widgets:
|
||||
|
|
@ -170,6 +179,21 @@ class MainWidget(QtWidgets.QWidget):
|
|||
for tab_widget in self.tab_widgets:
|
||||
tab_widget.reset()
|
||||
|
||||
def _update_search_dialog(self, clear=False):
|
||||
if self._search_dialog.isVisible():
|
||||
entity = None
|
||||
if not clear:
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
entity = widget.entity
|
||||
self._search_dialog.set_root_entity(entity)
|
||||
|
||||
def _on_tab_changed(self):
|
||||
self._update_search_dialog()
|
||||
|
||||
def _on_search_path_clicked(self, path):
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
widget.change_path(path)
|
||||
|
||||
def _on_restart_required(self):
|
||||
# Don't show dialog if there are not registered slots for
|
||||
# `trigger_restart` signal.
|
||||
|
|
@ -184,3 +208,26 @@ class MainWidget(QtWidgets.QWidget):
|
|||
result = dialog.exec_()
|
||||
if result == 1:
|
||||
self.trigger_restart.emit()
|
||||
|
||||
def _on_reset_started(self):
|
||||
widget = self.sender()
|
||||
current_widget = self._header_tab_widget.currentWidget()
|
||||
if current_widget is widget:
|
||||
self._update_search_dialog(True)
|
||||
|
||||
def _on_reset_finished(self):
|
||||
widget = self.sender()
|
||||
current_widget = self._header_tab_widget.currentWidget()
|
||||
if current_widget is widget:
|
||||
self._update_search_dialog()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.matches(QtGui.QKeySequence.Find):
|
||||
# todo: search in all widgets (or in active)?
|
||||
widget = self._header_tab_widget.currentWidget()
|
||||
self._search_dialog.show()
|
||||
self._search_dialog.set_root_entity(widget.entity)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
return super(MainWidget, self).keyPressEvent(event)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue