extended search dialog and handle reset and recreation of dialog model

This commit is contained in:
Jakub Trllo 2022-02-12 15:08:48 +01:00
parent 12e7f75347
commit 61b07c356d
3 changed files with 157 additions and 77 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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