mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
extended search dialog and handle reset and recreation of dialog model
This commit is contained in:
parent
12e7f75347
commit
61b07c356d
3 changed files with 157 additions and 77 deletions
|
|
@ -86,6 +86,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
state_changed = QtCore.Signal()
|
||||
saved = QtCore.Signal(QtWidgets.QWidget)
|
||||
restart_required_trigger = QtCore.Signal()
|
||||
restart_started = QtCore.Signal()
|
||||
restart_finished = QtCore.Signal()
|
||||
full_path_requested = QtCore.Signal(str, str)
|
||||
|
||||
def __init__(self, user_role, parent=None):
|
||||
|
|
@ -307,7 +309,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_widget.change_path(path)
|
||||
|
||||
def set_path(self, path):
|
||||
"""Called from clicked widget."""
|
||||
self.breadcrumbs_widget.set_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout):
|
||||
|
|
@ -429,6 +436,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self.require_restart_label.setText(value)
|
||||
|
||||
def reset(self):
|
||||
self.restart_started.emit()
|
||||
self.set_state(CategoryState.Working)
|
||||
|
||||
self._on_reset_start()
|
||||
|
|
@ -509,6 +517,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self._on_reset_crash()
|
||||
else:
|
||||
self._on_reset_success()
|
||||
self.restart_finished.emit()
|
||||
|
||||
def _on_reset_crash(self):
|
||||
self.save_btn.setEnabled(False)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
from functools import partial
|
||||
import re
|
||||
import collections
|
||||
|
||||
from Qt import QtCore, QtWidgets
|
||||
from openpype.tools.utils.models import TreeModel, Item
|
||||
from openpype.tools.utils.lib import schedule
|
||||
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 []
|
||||
|
|
@ -23,115 +24,163 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
|
|||
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
|
||||
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
|
||||
|
||||
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)
|
||||
return super(
|
||||
RecursiveSortFilterProxyModel, self
|
||||
).filterAcceptsRow(row, parent)
|
||||
|
||||
|
||||
class SearchEntitiesDialog(QtWidgets.QDialog):
|
||||
|
||||
path_clicked = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, entity, parent=None):
|
||||
def __init__(self, parent):
|
||||
super(SearchEntitiesDialog, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
self.setWindowTitle("Search Settings")
|
||||
|
||||
filter_edit = QtWidgets.QLineEdit()
|
||||
filter_edit.setPlaceholderText("Search..")
|
||||
filter_edit = QtWidgets.QLineEdit(self)
|
||||
filter_edit.setPlaceholderText("Search...")
|
||||
|
||||
model = EntityTreeModel()
|
||||
proxy = RecursiveSortFilterProxyModel()
|
||||
proxy.setSourceModel(model)
|
||||
proxy.setDynamicSortFilter(True)
|
||||
view = QtWidgets.QTreeView()
|
||||
view.setModel(proxy)
|
||||
|
||||
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)
|
||||
view.selectionModel().selectionChanged.connect(self.on_select)
|
||||
|
||||
view.setAllColumnsShowFocus(True)
|
||||
view.setSortingEnabled(True)
|
||||
view.sortByColumn(1, QtCore.Qt.AscendingOrder)
|
||||
|
||||
self._filter_edit = filter_edit
|
||||
self._model = model
|
||||
self._proxy = proxy
|
||||
self._view = view
|
||||
self._filter_changed_timer = filter_changed_timer
|
||||
|
||||
# Refresh to the passed entity
|
||||
model.set_root(entity)
|
||||
self._first_show = True
|
||||
|
||||
view.resizeColumnToContents(0)
|
||||
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):
|
||||
# Provide slight delay to filtering so user can type quickly
|
||||
schedule(partial(self.on_filter_changed, txt), 250, channel="search")
|
||||
self._filter_changed_timer.start()
|
||||
|
||||
def on_filter_changed(self, txt):
|
||||
self._proxy.setFilterRegExp(txt)
|
||||
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_select(self):
|
||||
def _on_selection_change(self):
|
||||
current = self._view.currentIndex()
|
||||
item = current.data(EntityTreeModel.ItemRole)
|
||||
self.path_clicked.emit(item["path"])
|
||||
path = current.data(ENTITY_PATH_ROLE)
|
||||
self.path_clicked.emit(path)
|
||||
|
||||
|
||||
class EntityTreeModel(TreeModel):
|
||||
class EntityTreeModel(QtGui.QStandardItemModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EntityTreeModel, self).__init__(*args, **kwargs)
|
||||
self.setColumnCount(3)
|
||||
|
||||
Columns = ["trail", "label", "key", "path"]
|
||||
def data(self, index, role=None):
|
||||
if role is None:
|
||||
role = QtCore.Qt.DisplayRole
|
||||
|
||||
def add_entity(self, entity, parent=None):
|
||||
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
|
||||
|
||||
item = Item()
|
||||
if col > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).data(index, role)
|
||||
|
||||
# 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
|
||||
def flags(self, index):
|
||||
if index.column() > 0:
|
||||
index = self.index(index.row(), 0, index.parent())
|
||||
return super(EntityTreeModel, self).flags(index)
|
||||
|
||||
parent.add_child(item)
|
||||
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
|
||||
)
|
||||
|
||||
for child in get_entity_children(entity):
|
||||
self.add_entity(child, parent=item)
|
||||
|
||||
def set_root(self, root_entity):
|
||||
self.clear()
|
||||
self.beginResetModel()
|
||||
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
|
||||
for child in get_entity_children(root_entity):
|
||||
self.add_entity(child, parent=self._root_item)
|
||||
self.endResetModel()
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -55,19 +55,27 @@ 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.restart_started.connect(self._on_restart_started)
|
||||
tab_widget.restart_finished.connect(self._on_restart_finished)
|
||||
tab_widget.full_path_requested.connect(self._on_full_path_request)
|
||||
|
||||
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:
|
||||
|
|
@ -151,6 +159,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.
|
||||
|
|
@ -166,25 +189,24 @@ class MainWidget(QtWidgets.QWidget):
|
|||
if result == 1:
|
||||
self.trigger_restart.emit()
|
||||
|
||||
def _on_restart_started(self):
|
||||
widget = self.sender()
|
||||
current_widget = self._header_tab_widget.currentWidget()
|
||||
if current_widget is widget:
|
||||
self._update_search_dialog(True)
|
||||
|
||||
def _on_restart_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):
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
self._search_dialog.show()
|
||||
self._search_dialog.set_root_entity(widget.entity)
|
||||
event.accept()
|
||||
return
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue