From 6dbd3b0df3cfc3c47344ecf3325b54ffb3740020 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 11 Aug 2021 17:42:16 +0200 Subject: [PATCH 01/41] initial version of breadcrumbs widget --- .../settings/settings/breadcrumb_widget.py | 387 ++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 openpype/tools/settings/settings/breadcrumb_widget.py diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py new file mode 100644 index 00000000000..306ac142182 --- /dev/null +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -0,0 +1,387 @@ +import os +import sys +sys.path.append(r"C:\Users\jakub.trllo\Desktop\pype\pype3_2\.venv\Lib\site-packages") + +from Qt import QtWidgets, QtGui, QtCore + +# px, size of generated semi-transparent icons +TRANSP_ICON_SIZE = 40, 40 + +PREFIX_ROLE = QtCore.Qt.UserRole + 1 +LAST_SEGMENT_ROLE = QtCore.Qt.UserRole + 2 + + +class BreadcrumbItem(QtGui.QStandardItem): + def __init__(self, *args, **kwargs): + self._display_value = None + self._edit_value = None + super(BreadcrumbItem, self).__init__(*args, **kwargs) + + def data(self, role=None): + if role == QtCore.Qt.DisplayRole: + return self._display_value + + if role == QtCore.Qt.EditRole: + return self._edit_value + + if role is None: + args = tuple() + else: + args = (role, ) + return super(BreadcrumbItem, self).data(*args) + + def setData(self, value, role): + if role == QtCore.Qt.DisplayRole: + self._display_value = value + return True + + if role == QtCore.Qt.EditRole: + self._edit_value = value + return True + + if role is None: + args = (value, ) + else: + args = (value, role) + return super(BreadcrumbItem, self).setData(*args) + + +class BreadcrumbsModel(QtGui.QStandardItemModel): + def __init__(self): + super(BreadcrumbsModel, self).__init__() + self.current_path = "" + + self.reset() + + def reset(self): + root_item = self.invisibleRootItem() + rows = root_item.rowCount() + if rows > 0: + root_item.removeRows(0, rows) + + paths = [ + "project_settings", + "project_settings/blabla", + "project_settings/blabla2", + "project_settings/blabla2/dada" + ] + items = [] + for path in paths: + if not path: + continue + path_items = path.split("/") + value = path + label = path_items.pop(-1) + prefix = "/".join(path_items) + if prefix: + prefix += "/" + + item = QtGui.QStandardItem(value) + item.setData(label, LAST_SEGMENT_ROLE) + item.setData(prefix, PREFIX_ROLE) + + items.append(item) + + root_item.appendRows(items) + + +class BreadcrumbsProxy(QtCore.QSortFilterProxyModel): + def __init__(self, *args, **kwargs): + super(BreadcrumbsProxy, self).__init__(*args, **kwargs) + + self._current_path = "" + + def set_path_prefix(self, prefix): + path = prefix + if not prefix.endswith("/"): + path_items = path.split("/") + if len(path_items) == 1: + path = "" + else: + path_items.pop(-1) + path = "/".join(path_items) + "/" + + if path == self._current_path: + return + + self._current_path = prefix + + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent): + index = self.sourceModel().index(row, 0, parent) + prefix_path = index.data(PREFIX_ROLE) + return prefix_path == self._current_path + + +class BreadcrumbsHintMenu(QtWidgets.QMenu): + def __init__(self, model, path_prefix, parent): + super(BreadcrumbsHintMenu, self).__init__(parent) + + self._path_prefix = path_prefix + self._model = model + + def showEvent(self, event): + self.clear() + + self._model.set_path_prefix(self._path_prefix) + + row_count = self._model.rowCount() + if row_count == 0: + action = self.addAction("* Nothing") + action.setData(".") + else: + for row in range(self._model.rowCount()): + index = self._model.index(row, 0) + label = index.data(LAST_SEGMENT_ROLE) + value = index.data(QtCore.Qt.EditRole) + action = self.addAction(label) + action.setData(value) + + super(BreadcrumbsHintMenu, self).showEvent(event) + + +class ClickableWidget(QtWidgets.QWidget): + clicked = QtCore.Signal() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class BreadcrumbsPathInput(QtWidgets.QLineEdit): + cancelled = QtCore.Signal() + confirmed = QtCore.Signal() + + def __init__(self, model, parent): + super(BreadcrumbsPathInput, self).__init__(parent) + + self.setFrame(False) + + completer = QtWidgets.QCompleter(self) + completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + completer.setModel(model) + + popup = completer.popup() + popup.setUniformItemSizes(True) + popup.setLayoutMode(QtWidgets.QListView.Batched) + + self.setCompleter(completer) + + completer.activated.connect(self._on_completer_activated) + self.textEdited.connect(self._on_text_change) + + self._completer = completer + self._model = model + + self._context_menu_visible = False + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Escape: + self.cancelled.emit() + elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + self.confirmed.emit() + else: + super(BreadcrumbsPathInput, self).keyPressEvent(event) + + def focusOutEvent(self, event): + if not self._context_menu_visible: + self.cancelled.emit() + + self._context_menu_visible = False + super(BreadcrumbsPathInput, self).focusOutEvent(event) + + def contextMenuEvent(self, event): + self._context_menu_visible = True + super(BreadcrumbsPathInput, self).contextMenuEvent(event) + + def _on_completer_activated(self, path): + self.confirmed.emit(path) + + def _on_text_change(self, path): + self._model.set_path_prefix(path) + + +class BreadcrumbsButton(QtWidgets.QToolButton): + path_selected = QtCore.Signal(str) + + def __init__(self, path, model, parent): + super(BreadcrumbsButton, self).__init__(parent) + + path_prefix = path + "/" + + self.setAutoRaise(True) + self.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + + self.setMouseTracking(True) + + self.setText(path.split("/")[-1]) + + menu = BreadcrumbsHintMenu(model, path_prefix, self) + + self.setMenu(menu) + + # fixed size breadcrumbs + self.setMinimumSize(self.minimumSizeHint()) + size_policy = self.sizePolicy() + size_policy.setVerticalPolicy(size_policy.Minimum) + self.setSizePolicy(size_policy) + + menu.triggered.connect(self._on_menu_click) + self.clicked.connect(self._on_click) + + self._path = path + self._path_prefix = path_prefix + self._model = model + self._menu = menu + + def _on_click(self): + self.path_selected.emit(self._path) + + def _on_menu_click(self, action): + item = action.data() + self.path_selected.emit(item) + + +class BreadcrumbsAddressBar(QtWidgets.QFrame): + "Windows Explorer-like address bar" + path_selected = QtCore.Signal(str) + + def __init__(self, parent=None): + super(BreadcrumbsAddressBar, self).__init__(parent) + + model = BreadcrumbsModel() + proxy_model = BreadcrumbsProxy() + proxy_model.setSourceModel(model) + + self.setAutoFillBackground(True) + self.setFrameShape(self.StyledPanel) + + # Edit presented path textually + path_input = BreadcrumbsPathInput(proxy_model, self) + path_input.setVisible(False) + + path_input.cancelled.connect(self._on_input_cancel) + path_input.confirmed.connect(self._on_input_confirm) + + # Container for `crumbs_panel` + crumbs_container = QtWidgets.QWidget(self) + + # Container for breadcrumbs + crumbs_panel = QtWidgets.QWidget(crumbs_container) + + crumbs_layout = QtWidgets.QHBoxLayout() + crumbs_layout.setContentsMargins(0, 0, 0, 0) + crumbs_layout.setSpacing(0) + + crumbs_cont_layout = QtWidgets.QHBoxLayout(crumbs_container) + crumbs_cont_layout.setContentsMargins(0, 0, 0, 0) + crumbs_cont_layout.setSpacing(0) + crumbs_cont_layout.addWidget(crumbs_panel) + + # Clicking on empty space to the right puts the bar into edit mode + switch_space = ClickableWidget(self) + + crumb_panel_layout = QtWidgets.QHBoxLayout(crumbs_panel) + crumb_panel_layout.setContentsMargins(0, 0, 0, 0) + crumb_panel_layout.setSpacing(0) + crumb_panel_layout.addLayout(crumbs_layout, 0) + crumb_panel_layout.addWidget(switch_space, 1) + + switch_space.clicked.connect(self.switch_space_mouse_up) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(path_input) + layout.addWidget(crumbs_container) + + self.setMaximumHeight(path_input.height()) + + self.crumbs_layout = crumbs_layout + self.crumbs_panel = crumbs_panel + self.switch_space = switch_space + self.path_input = path_input + self.crumbs_container = crumbs_container + + self.model = model + self.proxy_model = proxy_model + + self._current_path = None + + self.set_path("project_settings") + + def _on_input_confirm(self): + self.set_path(self.path_input.text()) + self._show_address_field(False) + + def _on_input_cancel(self): + self._cancel_edit() + + def _clear_crumbs(self): + while self.crumbs_layout.count(): + widget = self.crumbs_layout.takeAt(0).widget() + if widget: + widget.deleteLater() + + def _insert_crumb(self, path): + btn = BreadcrumbsButton(path, self.proxy_model, self.crumbs_panel) + + self.crumbs_layout.insertWidget(0, btn) + + btn.path_selected.connect(self._on_crumb_clicked) + + def _on_crumb_clicked(self, path): + "Breadcrumb was clicked" + self.set_path(path) + + def set_path(self, path): + if path is None or path == ".": + path = self._current_path + + # exit edit mode + self._cancel_edit() + + self._clear_crumbs() + self._current_path = path + self.path_input.setText(path) + path_items = [ + item + for item in path.split("/") + if item + ] + while path_items: + item = "/".join(path_items) + self._insert_crumb(item) + path_items.pop(-1) + + self.path_selected.emit(self._current_path) + + def _cancel_edit(self): + "Set edit line text back to current path and switch to view mode" + # revert path + self.path_input.setText(self.path()) + # switch back to breadcrumbs view + self._show_address_field(False) + + def path(self): + "Get path displayed in this BreadcrumbsAddressBar" + return self._current_path + + def switch_space_mouse_up(self): + "EVENT: switch_space mouse clicked" + self._show_address_field(True) + + def _show_address_field(self, show=True): + "Show text address field" + self.crumbs_container.setVisible(not show) + self.path_input.setVisible(show) + if show: + self.path_input.setFocus() + self.path_input.selectAll() + + def minimumSizeHint(self): + result = super(BreadcrumbsAddressBar, self).minimumSizeHint() + result.setHeight(self.path_input.height()) + return result From 30e131685ece0a01622048a786502b0812c3c338 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 11 Aug 2021 17:42:31 +0200 Subject: [PATCH 02/41] use breadcrumbs widget in category --- openpype/tools/settings/settings/categories.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 8be3eddfa83..fa00c8a814c 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -31,6 +31,7 @@ from openpype.settings import SaveWarningExc from .widgets import ProjectListWidget +from .breadcrumb_widget import BreadcrumbsAddressBar from .base import GUIWidget from .list_item_widget import ListWidget @@ -175,6 +176,7 @@ def create_ui(self): scroll_widget = QtWidgets.QScrollArea(self) scroll_widget.setObjectName("GroupWidget") content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) content_layout.setSpacing(5) @@ -197,6 +199,8 @@ def create_ui(self): if self.user_role == "developer": self._add_developer_ui(footer_layout) + breadcrumbs_widget = BreadcrumbsAddressBar(content_widget) + save_btn = QtWidgets.QPushButton("Save", footer_widget) require_restart_label = QtWidgets.QLabel(footer_widget) require_restart_label.setAlignment(QtCore.Qt.AlignCenter) @@ -207,6 +211,7 @@ def create_ui(self): configurations_layout.setContentsMargins(0, 0, 0, 0) configurations_layout.setSpacing(0) + configurations_layout.addWidget(breadcrumbs_widget, 0) configurations_layout.addWidget(scroll_widget, 1) configurations_layout.addWidget(footer_widget, 0) From f236acdc08a69d81b678a3e8c2f3d630021486bb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 11 Aug 2021 17:44:50 +0200 Subject: [PATCH 03/41] fixed height issues --- openpype/tools/settings/settings/breadcrumb_widget.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index 306ac142182..05f7b983c2d 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -383,5 +383,8 @@ def _show_address_field(self, show=True): def minimumSizeHint(self): result = super(BreadcrumbsAddressBar, self).minimumSizeHint() - result.setHeight(self.path_input.height()) + result.setHeight(max( + self.path_input.height(), + self.crumbs_container.height() + )) return result From 048da648e967c26ad7d44223c706c8fe6395016a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 11:34:24 +0200 Subject: [PATCH 04/41] diverse between path changed and path edited --- .../tools/settings/settings/breadcrumb_widget.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index 05f7b983c2d..95841864197 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -246,7 +246,8 @@ def _on_menu_click(self, action): class BreadcrumbsAddressBar(QtWidgets.QFrame): "Windows Explorer-like address bar" - path_selected = QtCore.Signal(str) + path_changed = QtCore.Signal(str) + path_edited = QtCore.Signal(str) def __init__(self, parent=None): super(BreadcrumbsAddressBar, self).__init__(parent) @@ -310,10 +311,9 @@ def __init__(self, parent=None): self._current_path = None - self.set_path("project_settings") def _on_input_confirm(self): - self.set_path(self.path_input.text()) + self.change_path(self.path_input.text()) self._show_address_field(False) def _on_input_cancel(self): @@ -334,7 +334,11 @@ def _insert_crumb(self, path): def _on_crumb_clicked(self, path): "Breadcrumb was clicked" + self.change_path(path) + + def change_path(self, path): self.set_path(path) + self.path_edited.emit(path) def set_path(self, path): if path is None or path == ".": @@ -356,7 +360,7 @@ def set_path(self, path): self._insert_crumb(item) path_items.pop(-1) - self.path_selected.emit(self._current_path) + self.path_changed.emit(self._current_path) def _cancel_edit(self): "Set edit line text back to current path and switch to view mode" From a4ccb3eb6deec734e84806eefb5daf3611327412 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 11:34:40 +0200 Subject: [PATCH 05/41] model is not set by default --- openpype/tools/settings/settings/breadcrumb_widget.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index 95841864197..76f08e5f8e9 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -252,9 +252,7 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame): def __init__(self, parent=None): super(BreadcrumbsAddressBar, self).__init__(parent) - model = BreadcrumbsModel() proxy_model = BreadcrumbsProxy() - proxy_model.setSourceModel(model) self.setAutoFillBackground(True) self.setFrameShape(self.StyledPanel) @@ -306,11 +304,14 @@ def __init__(self, parent=None): self.path_input = path_input self.crumbs_container = crumbs_container - self.model = model - self.proxy_model = proxy_model + self._model = None + self._proxy_model = proxy_model self._current_path = None + def set_model(self, model): + self._model = model + self._proxy_model.setSourceModel(model) def _on_input_confirm(self): self.change_path(self.path_input.text()) @@ -326,7 +327,7 @@ def _clear_crumbs(self): widget.deleteLater() def _insert_crumb(self, path): - btn = BreadcrumbsButton(path, self.proxy_model, self.crumbs_panel) + btn = BreadcrumbsButton(path, self._proxy_model, self.crumbs_panel) self.crumbs_layout.insertWidget(0, btn) From ca1c04f14210439022d9cb52dfc7a3f026c3fc85 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 11:34:54 +0200 Subject: [PATCH 06/41] added base concept of settings models --- .../settings/settings/breadcrumb_widget.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index 76f08e5f8e9..d63c8bb980d 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -53,18 +53,32 @@ def __init__(self): self.reset() + def reset(self): + return + + +class SettingsBreadcrumbs(BreadcrumbsModel): + def __init__(self): + self.entity = None + + super(SettingsBreadcrumbs, self).__init__() + + def set_entity(self, entity): + self.entity = entity + self.reset() + + +class SystemSettingsBreadcrumbs(SettingsBreadcrumbs): def reset(self): root_item = self.invisibleRootItem() rows = root_item.rowCount() if rows > 0: root_item.removeRows(0, rows) - paths = [ - "project_settings", - "project_settings/blabla", - "project_settings/blabla2", - "project_settings/blabla2/dada" - ] + if self.entity is None: + return + + paths = [] items = [] for path in paths: if not path: @@ -85,6 +99,10 @@ def reset(self): root_item.appendRows(items) +class ProjectSettingsBreadcrumbs(SettingsBreadcrumbs): + pass + + class BreadcrumbsProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): super(BreadcrumbsProxy, self).__init__(*args, **kwargs) From e04cedb2d15aeb0fe5f33fbc3e30b3a2d66dd29f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 11:35:44 +0200 Subject: [PATCH 07/41] added base logic of breadcrumbs in category widget --- .../tools/settings/settings/categories.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index fa00c8a814c..55528a3d0db 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -31,7 +31,11 @@ from openpype.settings import SaveWarningExc from .widgets import ProjectListWidget -from .breadcrumb_widget import BreadcrumbsAddressBar +from .breadcrumb_widget import ( + BreadcrumbsAddressBar, + SystemSettingsBreadcrumbs, + ProjectSettingsBreadcrumbs +) from .base import GUIWidget from .list_item_widget import ListWidget @@ -222,6 +226,7 @@ def create_ui(self): save_btn.clicked.connect(self._save) refresh_btn.clicked.connect(self._on_refresh) + breadcrumbs_widget.path_edited.connect(self._on_path_edit) self.save_btn = save_btn self.refresh_btn = refresh_btn @@ -230,6 +235,8 @@ def create_ui(self): self.content_layout = content_layout self.content_widget = content_widget self.configurations_widget = configurations_widget + self.breadcrumbs_widget = breadcrumbs_widget + self.breadcrumbs_model = None self.main_layout = main_layout self.ui_tweaks() @@ -237,6 +244,9 @@ def create_ui(self): def ui_tweaks(self): return + def _on_path_edit(self, path): + print("Path edited:", path) + def _add_developer_ui(self, footer_layout): modify_defaults_widget = QtWidgets.QWidget() modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget) @@ -432,10 +442,16 @@ def reset(self): def _on_reset_crash(self): self.save_btn.setEnabled(False) + if self.breadcrumbs_model is not None: + self.breadcrumbs_model.set_entity(None) + def _on_reset_success(self): if not self.save_btn.isEnabled(): self.save_btn.setEnabled(True) + if self.breadcrumbs_model is not None: + self.breadcrumbs_model.set_entity(self.entity) + def add_children_gui(self): for child_obj in self.entity.children: item = self.create_ui_for_entity(self, child_obj, self) @@ -526,6 +542,10 @@ def _create_root_entity(self): self.modify_defaults_checkbox.setChecked(True) self.modify_defaults_checkbox.setEnabled(False) + def ui_tweaks(self): + self.breadcrumbs_model = SystemSettingsBreadcrumbs() + self.breadcrumbs_widget.set_model(self.breadcrumbs_model) + def _on_modify_defaults(self): if self.modify_defaults_checkbox.isChecked(): if not self.entity.is_in_defaults_state(): @@ -540,6 +560,9 @@ def initialize_attributes(self): self.project_name = None def ui_tweaks(self): + self.breadcrumbs_model = ProjectSettingsBreadcrumbs() + self.breadcrumbs_widget.set_model(self.breadcrumbs_model) + project_list_widget = ProjectListWidget(self) self.main_layout.insertWidget(0, project_list_widget, 0) From 2ba972a4824944132a561127d264bb305b1a4e8b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 12:32:26 +0200 Subject: [PATCH 08/41] fix signal emit --- openpype/tools/settings/settings/breadcrumb_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index d63c8bb980d..2de23163d38 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -215,7 +215,7 @@ def contextMenuEvent(self, event): super(BreadcrumbsPathInput, self).contextMenuEvent(event) def _on_completer_activated(self, path): - self.confirmed.emit(path) + self.confirmed.emit() def _on_text_change(self, path): self._model.set_path_prefix(path) From 91eaa205ce643ce02e5ba84578fc513426485f5c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 12:33:15 +0200 Subject: [PATCH 09/41] added ability to collect static entities by path --- openpype/settings/entities/base_entity.py | 8 ++++++++ openpype/settings/entities/dict_conditional.py | 5 +++++ .../entities/dict_immutable_keys_entity.py | 12 ++++++++++++ openpype/settings/entities/input_entities.py | 5 +++++ openpype/settings/entities/item_entities.py | 15 +++++++++++++++ openpype/settings/entities/root_entities.py | 8 ++++++++ 6 files changed, 53 insertions(+) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index b4ebe885f59..851684520b1 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -174,6 +174,14 @@ def __init__(self, schema_data): roles = [roles] self.roles = roles + @abstractmethod + def collect_static_entities_by_path(self): + """Collect all paths of all static path entities. + + Static path is entity which is not dynamic or under dynamic entity. + """ + pass + @property def require_restart_on_change(self): return self._require_restart_on_change diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d275d8ac3dc..b8985883222 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -318,6 +318,11 @@ def _add_children(self): self.non_gui_children[item_key][child_obj.key] = child_obj + def collect_static_entities_by_path(self): + if self.is_dynamic_item or self.is_in_dynamic_item: + return {} + return {self.path: self} + def get_child_path(self, child_obj): """Get hierarchical path of child entity. diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index bde53047870..73b08f101a9 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -203,6 +203,18 @@ def _item_initalization(self): ) self.show_borders = self.schema_data.get("show_borders", True) + def collect_static_entities_by_path(self): + output = {} + if self.is_dynamic_item or self.is_in_dynamic_item: + return output + + output[self.path] = self + for children in self.non_gui_children.values(): + result = children.collect_static_entities_by_path() + if result: + output.update(result) + return output + def get_child_path(self, child_obj): """Get hierarchical path of child entity. diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 69525299636..336d1f5c1e1 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -53,6 +53,11 @@ def schema_validations(self): def _settings_value(self): pass + def collect_static_entities_by_path(self): + if self.is_dynamic_item or self.is_in_dynamic_item: + return {} + return {self.path: self} + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 7e84f8c801e..ecce7a22c0f 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -106,6 +106,9 @@ def _item_initalization(self): self.valid_value_types = valid_value_types self.child_obj = self.create_schema_object(item_schema, self) + def collect_static_entities_by_path(self): + return self.child_obj.collect_static_entities_by_path() + def get_child_path(self, _child_obj): return self.path @@ -222,6 +225,18 @@ def schema_validations(self): super(ListStrictEntity, self).schema_validations() + def collect_static_entities_by_path(self): + output = {} + if self.is_dynamic_item or self.is_in_dynamic_item: + return output + + output[self.path] = self + for child_obj in self.children: + result = child_obj.collect_static_entities_by_path() + if result: + output.update(result) + return output + def get_child_path(self, child_obj): result_idx = None for idx, _child_obj in enumerate(self.children): diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 00677480e8c..4a06d2d5916 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -242,6 +242,14 @@ def on_child_change(self, _child_entity): """Whan any children has changed.""" self.on_change() + def collect_static_entities_by_path(self): + output = {} + for child_obj in self.non_gui_children.values(): + result = child_obj.collect_static_entities_by_path() + if result: + output.update(result) + return output + def get_child_path(self, child_entity): """Return path of children entity""" for key, _child_entity in self.non_gui_children.items(): From f174aff062d4d598c2a448765b9af75921f2657b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 12:33:33 +0200 Subject: [PATCH 10/41] implemented base of model --- .../settings/settings/breadcrumb_widget.py | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index 2de23163d38..d8a35daa5aa 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -61,9 +61,14 @@ class SettingsBreadcrumbs(BreadcrumbsModel): def __init__(self): self.entity = None + self.entities_by_path = {} + self.dynamic_paths = set() + super(SettingsBreadcrumbs, self).__init__() def set_entity(self, entity): + self.entities_by_path = {} + self.dynamic_paths = set() self.entity = entity self.reset() @@ -78,9 +83,10 @@ def reset(self): if self.entity is None: return - paths = [] + entities_by_path = self.entity.collect_static_entities_by_path() + self.entities_by_path = entities_by_path items = [] - for path in paths: + for path in entities_by_path.keys(): if not path: continue path_items = path.split("/") @@ -100,7 +106,35 @@ def reset(self): class ProjectSettingsBreadcrumbs(SettingsBreadcrumbs): - pass + def reset(self): + root_item = self.invisibleRootItem() + rows = root_item.rowCount() + if rows > 0: + root_item.removeRows(0, rows) + + if self.entity is None: + return + + entities_by_path = self.entity.collect_static_entities_by_path() + self.entities_by_path = entities_by_path + items = [] + for path in entities_by_path.keys(): + if not path: + continue + path_items = path.split("/") + value = path + label = path_items.pop(-1) + prefix = "/".join(path_items) + if prefix: + prefix += "/" + + item = QtGui.QStandardItem(value) + item.setData(label, LAST_SEGMENT_ROLE) + item.setData(prefix, PREFIX_ROLE) + + items.append(item) + + root_item.appendRows(items) class BreadcrumbsProxy(QtCore.QSortFilterProxyModel): @@ -270,12 +304,11 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame): def __init__(self, parent=None): super(BreadcrumbsAddressBar, self).__init__(parent) - proxy_model = BreadcrumbsProxy() - self.setAutoFillBackground(True) self.setFrameShape(self.StyledPanel) # Edit presented path textually + proxy_model = BreadcrumbsProxy() path_input = BreadcrumbsPathInput(proxy_model, self) path_input.setVisible(False) From 19c6a4a72e5054bdfb8a4839ad47b22efd609ff5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 12:33:46 +0200 Subject: [PATCH 11/41] fixed layouts --- .../tools/settings/settings/categories.py | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 55528a3d0db..9edd22ed89e 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -181,6 +181,15 @@ def create_ui(self): scroll_widget.setObjectName("GroupWidget") content_widget = QtWidgets.QWidget(scroll_widget) + breadcrumbs_label = QtWidgets.QLabel("Path:", content_widget) + breadcrumbs_widget = BreadcrumbsAddressBar(content_widget) + + breadcrumbs_layout = QtWidgets.QHBoxLayout() + breadcrumbs_layout.setContentsMargins(5, 0, 5, 0) + breadcrumbs_layout.setSpacing(5) + breadcrumbs_layout.addWidget(breadcrumbs_label) + breadcrumbs_layout.addWidget(breadcrumbs_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(3, 3, 3, 3) content_layout.setSpacing(5) @@ -189,40 +198,39 @@ def create_ui(self): scroll_widget.setWidgetResizable(True) scroll_widget.setWidget(content_widget) - configurations_widget = QtWidgets.QWidget(self) - - footer_widget = QtWidgets.QWidget(configurations_widget) - footer_layout = QtWidgets.QHBoxLayout(footer_widget) - refresh_icon = qtawesome.icon("fa.refresh", color="white") - refresh_btn = QtWidgets.QPushButton(footer_widget) + refresh_btn = QtWidgets.QPushButton(self) refresh_btn.setIcon(refresh_icon) - footer_layout.addWidget(refresh_btn, 0) - + footer_layout = QtWidgets.QHBoxLayout() if self.user_role == "developer": self._add_developer_ui(footer_layout) - breadcrumbs_widget = BreadcrumbsAddressBar(content_widget) - - save_btn = QtWidgets.QPushButton("Save", footer_widget) - require_restart_label = QtWidgets.QLabel(footer_widget) + save_btn = QtWidgets.QPushButton("Save", self) + require_restart_label = QtWidgets.QLabel(self) require_restart_label.setAlignment(QtCore.Qt.AlignCenter) + + footer_layout.addWidget(refresh_btn, 0) footer_layout.addWidget(require_restart_label, 1) footer_layout.addWidget(save_btn, 0) - configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) + configurations_layout = QtWidgets.QVBoxLayout() configurations_layout.setContentsMargins(0, 0, 0, 0) configurations_layout.setSpacing(0) - configurations_layout.addWidget(breadcrumbs_widget, 0) configurations_layout.addWidget(scroll_widget, 1) - configurations_layout.addWidget(footer_widget, 0) + configurations_layout.addLayout(footer_layout, 0) + + conf_wrapper_layout = QtWidgets.QHBoxLayout() + conf_wrapper_layout.setContentsMargins(0, 0, 0, 0) + conf_wrapper_layout.setSpacing(0) + conf_wrapper_layout.addLayout(configurations_layout, 1) - main_layout = QtWidgets.QHBoxLayout(self) + main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) - main_layout.addWidget(configurations_widget, 1) + main_layout.addLayout(breadcrumbs_layout, 0) + main_layout.addLayout(conf_wrapper_layout, 1) save_btn.clicked.connect(self._save) refresh_btn.clicked.connect(self._on_refresh) @@ -234,9 +242,9 @@ def create_ui(self): self.scroll_widget = scroll_widget self.content_layout = content_layout self.content_widget = content_widget - self.configurations_widget = configurations_widget self.breadcrumbs_widget = breadcrumbs_widget self.breadcrumbs_model = None + self.conf_wrapper_layout = conf_wrapper_layout self.main_layout = main_layout self.ui_tweaks() @@ -565,7 +573,7 @@ def ui_tweaks(self): project_list_widget = ProjectListWidget(self) - self.main_layout.insertWidget(0, project_list_widget, 0) + self.conf_wrapper_layout.insertWidget(0, project_list_widget, 0) project_list_widget.project_changed.connect(self._on_project_change) From 9cf06f1673e09dfdb4c8d03b2b1fdb25f9a98931 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 15:09:12 +0200 Subject: [PATCH 12/41] added ability to set focus and scroll to a widget --- openpype/tools/settings/settings/base.py | 24 +++++++++++++++++++ .../tools/settings/settings/categories.py | 9 ++++++- .../settings/settings/dict_conditional.py | 19 +++++++++++++++ .../settings/settings/dict_mutable_widget.py | 22 +++++++++++++++++ .../tools/settings/settings/item_widgets.py | 22 +++++++++++++++++ .../settings/settings/list_item_widget.py | 22 +++++++++++++++++ .../settings/settings/list_strict_widget.py | 13 ++++++++++ openpype/tools/settings/settings/widgets.py | 3 +++ 8 files changed, 133 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index eb5f82ab9a2..6775e8fc828 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -25,6 +25,21 @@ def __init__(self, category_widget, entity, entity_widget): self.label_widget = None self.create_ui() + def scroll_to(self, widget): + self.category_widget.scroll_to(widget) + + def set_focus(self, scroll_to=False): + if scroll_to: + self.scroll_to(self) + self.setFocus() + + def make_sure_is_visible(self, path, scroll_to): + raise NotImplementedError( + "{} not implemented `make_sure_is_visible`".format( + self.__class__.__name__ + ) + ) + def trigger_hierarchical_style_update(self): self.category_widget.hierarchical_style_update() @@ -337,6 +352,12 @@ def _add_inputs_to_layout(self): ) ) + def make_sure_is_visible(self, path, scroll_to): + if path: + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + def update_style(self): has_unsaved_changes = self.entity.has_unsaved_changes if not has_unsaved_changes and self.entity.group_item: @@ -427,6 +448,9 @@ def set_entity_value(self): def hierarchical_style_update(self): pass + def make_sure_is_visible(self, *args, **kwargs): + pass + def get_invalid(self): return [] diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 9edd22ed89e..c7917866095 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -253,7 +253,14 @@ def ui_tweaks(self): return def _on_path_edit(self, path): - print("Path edited:", path) + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, True) + + def scroll_to(self, widget): + if not widget: + return + + self.scroll_widget.ensureWidgetVisible(widget) def _add_developer_ui(self, footer_layout): modify_defaults_widget = QtWidgets.QWidget() diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 31a4fa9fab0..222cca03f9e 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -213,6 +213,25 @@ def _ui_label_wrap(self): else: body_widget.hide_toolbox(hide_content=False) + def make_sure_is_visible(self, path, scroll_to): + if not path: + return + + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + return + + if not path.startswith(entity_path): + return + + if self.body_widget and not self.body_widget.is_expanded(): + self.body_widget.toggle_content(True) + QtWidgets.QApplication.processEvents() + + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, scroll_to) + def add_widget_to_layout(self, widget, label=None): if not widget.entity: map_id = widget.id diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index df6525e86aa..596923fee4c 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -319,6 +319,9 @@ def key_label_input_focused_out(event): self.category_widget, self.entity, self ) + def make_sure_is_visible(self, *args, **kwargs): + self.input_field.make_sure_is_visible(*args, **kwargs) + def get_style_state(self): if self.is_invalid: return "invalid" @@ -846,6 +849,25 @@ def _on_entity_change(self): if changed: self.on_shuffle() + def make_sure_is_visible(self, path, scroll_to): + if not path: + return + + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + return + + if not path.startswith(entity_path): + return + + if self.body_widget and not self.body_widget.is_expanded(): + self.body_widget.toggle_content(True) + QtWidgets.QApplication.processEvents() + + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, scroll_to) + def set_entity_value(self): while self.input_fields: self.remove_row(self.input_fields[0]) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 82afbb0a13f..6724b913d41 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -154,6 +154,25 @@ def _ui_label_wrap(self): else: body_widget.hide_toolbox(hide_content=False) + def make_sure_is_visible(self, path, scroll_to): + if not path: + return + + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + return + + if not path.startswith(entity_path): + return + + if self.body_widget and not self.body_widget.is_expanded(): + self.body_widget.toggle_content(True) + QtWidgets.QApplication.processEvents() + + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, scroll_to) + def add_widget_to_layout(self, widget, label=None): if self.checkbox_child and widget.entity is self.checkbox_child: self.body_widget.add_widget_before_label(widget) @@ -562,6 +581,9 @@ def add_widget_to_layout(self, widget, label=None): def set_entity_value(self): self.input_field.set_entity_value() + def make_sure_is_visible(self, *args, **kwargs): + self.input_field.make_sure_is_visible(*args, **kwargs) + def hierarchical_style_update(self): self.update_style() self.input_field.hierarchical_style_update() diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index c9df5caf011..5abd9b0b202 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -129,6 +129,9 @@ def create_ui_for_entity(self, *args, **kwargs): *args, **kwargs ) + def make_sure_is_visible(self, *args, **kwargs): + self.input_field.make_sure_is_visible(*args, **kwargs) + @property def is_invalid(self): return self.input_field.is_invalid @@ -275,6 +278,25 @@ def get_invalid(self): invalid.extend(input_field.get_invalid()) return invalid + def make_sure_is_visible(self, path, scroll_to): + if not path: + return + + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + return + + if not path.startswith(entity_path): + return + + if self.body_widget and not self.body_widget.is_expanded(): + self.body_widget.toggle_content(True) + QtWidgets.QApplication.processEvents() + + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, scroll_to) + def _on_entity_change(self): # TODO do less inefficient childen_order = [] diff --git a/openpype/tools/settings/settings/list_strict_widget.py b/openpype/tools/settings/settings/list_strict_widget.py index 340db2e8c67..94a6ff53e26 100644 --- a/openpype/tools/settings/settings/list_strict_widget.py +++ b/openpype/tools/settings/settings/list_strict_widget.py @@ -65,6 +65,19 @@ def get_invalid(self): invalid.extend(input_field.get_invalid()) return invalid + def make_sure_is_visible(self, path, scroll_to): + if not path: + return + + entity_path = self.entity.path + if entity_path == path: + self.set_focus(scroll_to) + return + + if path.startswith(entity_path): + for input_field in self.input_fields: + input_field.make_sure_is_visible(path, scroll_to) + def add_widget_to_layout(self, widget, label=None): # Horizontally added children if self.entity.is_horizontal: diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index b20ce5ed66b..2f9b504525b 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -215,6 +215,9 @@ def set_content_widget(self, content_widget): self.main_layout.addWidget(content_widget) self.content_widget = content_widget + def is_expanded(self): + return self.button_toggle.isChecked() + def _btn_clicked(self): self.toggle_content(self.button_toggle.isChecked()) From ad52e0f1400157fafc6d238826c5b3826236a04a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 15:25:21 +0200 Subject: [PATCH 13/41] change path based on focus --- openpype/tools/settings/settings/base.py | 17 ++++++++++++++++- openpype/tools/settings/settings/categories.py | 7 ++++--- openpype/tools/settings/settings/widgets.py | 2 ++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 6775e8fc828..b10ec58142c 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -28,6 +28,9 @@ def __init__(self, category_widget, entity, entity_widget): def scroll_to(self, widget): self.category_widget.scroll_to(widget) + def set_path(self, path): + self.category_widget.set_path(path) + def set_focus(self, scroll_to=False): if scroll_to: self.scroll_to(self) @@ -292,11 +295,23 @@ def show_actions_menu(self, event=None): if to_run: to_run() + def focused_in(self): + if self.entity is not None: + self.set_path(self.entity.path) + def mouseReleaseEvent(self, event): if self.allow_actions and event.button() == QtCore.Qt.RightButton: return self.show_actions_menu() - return super(BaseWidget, self).mouseReleaseEvent(event) + focused_in = False + if event.button() == QtCore.Qt.LeftButton: + focused_in = True + self.focused_in() + + result = super(BaseWidget, self).mouseReleaseEvent(event) + if focused_in and not event.isAccepted(): + event.accept() + return result class InputWidget(BaseWidget): diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index c7917866095..1506e5bbfda 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -257,10 +257,11 @@ def _on_path_edit(self, path): input_field.make_sure_is_visible(path, True) def scroll_to(self, widget): - if not widget: - return + if widget: + self.scroll_widget.ensureWidgetVisible(widget) - self.scroll_widget.ensureWidgetVisible(widget) + def set_path(self, path): + self.breadcrumbs_widget.set_path(path) def _add_developer_ui(self, footer_layout): modify_defaults_widget = QtWidgets.QWidget() diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index 2f9b504525b..ac5870eabca 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -383,6 +383,8 @@ def setProperty(self, name, value): def mouseReleaseEvent(self, event): if self.input_field: + if event and event.button() == QtCore.Qt.LeftButton: + self.input_field.focused_in() return self.input_field.show_actions_menu(event) return super(GridLabelWidget, self).mouseReleaseEvent(event) From d4b8c7be863d173048f0180e54eab47228d5e53b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 15:48:47 +0200 Subject: [PATCH 14/41] got rid of spacer widgets --- .../settings/settings/dict_mutable_widget.py | 4 +-- .../tools/settings/settings/item_widgets.py | 5 +-- .../settings/settings/list_item_widget.py | 13 +------- openpype/tools/settings/settings/widgets.py | 31 ++++++------------- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index 596923fee4c..b733727e4eb 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -61,7 +61,6 @@ def _on_add_clicked(self): def create_addible_ui(self): add_btn = create_add_btn(self) remove_btn = create_remove_btn(self) - spacer_widget = SpacerWidget(self) remove_btn.setEnabled(False) @@ -70,13 +69,12 @@ def create_addible_ui(self): layout.setSpacing(3) layout.addWidget(add_btn, 0) layout.addWidget(remove_btn, 0) - layout.addWidget(spacer_widget, 1) + layout.addStretch(1) add_btn.clicked.connect(self._on_add_clicked) self.add_btn = add_btn self.remove_btn = remove_btn - self.spacer_widget = spacer_widget def _on_focus_lose(self): if self.key_input.hasFocus() or self.key_label_input.hasFocus(): diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 6724b913d41..57631f45302 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -289,11 +289,8 @@ def _add_inputs_to_layout(self): height=checkbox_height, parent=self.content_widget ) - spacer = QtWidgets.QWidget(self.content_widget) - spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) - self.content_layout.addWidget(self.input_field, 0) - self.content_layout.addWidget(spacer, 1) + self.content_layout.addStretch(1) self.setFocusProxy(self.input_field) diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 5abd9b0b202..52b96a658f0 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -18,8 +18,6 @@ def __init__(self, entity_widget, parent): add_btn = QtWidgets.QPushButton("+", self) remove_btn = QtWidgets.QPushButton("-", self) - spacer_widget = QtWidgets.QWidget(self) - spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) remove_btn.setEnabled(False) @@ -35,13 +33,12 @@ def __init__(self, entity_widget, parent): layout.setSpacing(3) layout.addWidget(add_btn, 0) layout.addWidget(remove_btn, 0) - layout.addWidget(spacer_widget, 1) + layout.addStretch(1) add_btn.clicked.connect(self._on_add_clicked) self.add_btn = add_btn self.remove_btn = remove_btn - self.spacer_widget = spacer_widget def _on_add_clicked(self): self.entity_widget.add_new_item() @@ -101,12 +98,6 @@ def __init__(self, entity, entity_widget): self.category_widget, self.entity, self ) - spacer_widget = QtWidgets.QWidget(self) - spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - spacer_widget.setVisible(False) - - layout.addWidget(spacer_widget, 1) - layout.addWidget(up_btn, 0) layout.addWidget(down_btn, 0) @@ -115,8 +106,6 @@ def __init__(self, entity, entity_widget): self.up_btn = up_btn self.down_btn = down_btn - self.spacer_widget = spacer_widget - self._row = -1 self._is_last = False diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index ac5870eabca..17eab5c89f6 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -160,15 +160,13 @@ def __init__(self, label, parent): after_label_layout = QtWidgets.QHBoxLayout(after_label_widget) after_label_layout.setContentsMargins(0, 0, 0, 0) - spacer_widget = QtWidgets.QWidget(side_line_widget) - side_line_layout = QtWidgets.QHBoxLayout(side_line_widget) side_line_layout.setContentsMargins(5, 10, 0, 10) side_line_layout.addWidget(button_toggle) side_line_layout.addWidget(before_label_widget) side_line_layout.addWidget(label_widget) side_line_layout.addWidget(after_label_widget) - side_line_layout.addWidget(spacer_widget, 1) + side_line_layout.addStretch(1) top_part_layout = QtWidgets.QHBoxLayout(top_part) top_part_layout.setContentsMargins(0, 0, 0, 0) @@ -176,7 +174,6 @@ def __init__(self, label, parent): before_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.setAttribute(QtCore.Qt.WA_TranslucentBackground) @@ -344,31 +341,21 @@ def __init__(self, label, parent=None): self.properties = {} - layout = QtWidgets.QVBoxLayout(self) - layout.setContentsMargins(0, 2, 0, 0) - layout.setSpacing(0) - - label_proxy = QtWidgets.QWidget(self) + label_widget = QtWidgets.QLabel(label, self) - label_proxy_layout = QtWidgets.QHBoxLayout(label_proxy) + label_proxy_layout = QtWidgets.QHBoxLayout() label_proxy_layout.setContentsMargins(0, 0, 0, 0) label_proxy_layout.setSpacing(0) - label_widget = QtWidgets.QLabel(label, label_proxy) - spacer_widget_h = SpacerWidget(label_proxy) - label_proxy_layout.addWidget( - spacer_widget_h, 0, alignment=QtCore.Qt.AlignRight - ) - label_proxy_layout.addWidget( - label_widget, 0, alignment=QtCore.Qt.AlignRight - ) + label_proxy_layout.addWidget(label_widget, 0, QtCore.Qt.AlignRight) - spacer_widget_v = SpacerWidget(self) + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 2, 0, 0) + layout.setSpacing(0) - layout.addWidget(label_proxy, 0) - layout.addWidget(spacer_widget_v, 1) + layout.addLayout(label_proxy_layout, 0) + layout.addStretch(1) - label_proxy.setAttribute(QtCore.Qt.WA_TranslucentBackground) label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) self.label_widget = label_widget From 3ebc4b34af41232953aeea9596b7f1c9c97e5bd4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 15:55:13 +0200 Subject: [PATCH 15/41] handle text input focus --- .../tools/settings/settings/item_widgets.py | 26 ++++++++++++++----- openpype/tools/settings/settings/widgets.py | 16 ++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 57631f45302..c98c3ba0b83 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -7,7 +7,9 @@ NumberSpinBox, GridLabelWidget, ComboBox, - NiceCheckbox + NiceCheckbox, + SettingsPlainTextEdit, + SettingsLineEdit ) from .multiselection_combobox import MultiSelectionComboBox from .wrapper_widgets import ( @@ -313,9 +315,9 @@ class TextWidget(InputWidget): def _add_inputs_to_layout(self): multiline = self.entity.multiline if multiline: - self.input_field = QtWidgets.QPlainTextEdit(self.content_widget) + self.input_field = SettingsPlainTextEdit(self.content_widget) else: - self.input_field = QtWidgets.QLineEdit(self.content_widget) + self.input_field = SettingsLineEdit(self.content_widget) placeholder_text = self.entity.placeholder_text if placeholder_text: @@ -329,8 +331,12 @@ def _add_inputs_to_layout(self): self.content_layout.addWidget(self.input_field, 1, **layout_kwargs) + self.input_field.focused_in.connect(self._on_input_focus) self.input_field.textChanged.connect(self._on_value_change) + def _on_input_focus(self): + self.focused_in() + def _on_entity_change(self): if self.entity.value != self.input_value(): self.set_entity_value() @@ -382,7 +388,7 @@ def _on_value_change(self): self.entity.set(self.input_field.value()) -class RawJsonInput(QtWidgets.QPlainTextEdit): +class RawJsonInput(SettingsPlainTextEdit): tab_length = 4 def __init__(self, valid_type, *args, **kwargs): @@ -444,15 +450,18 @@ def _add_inputs_to_layout(self): QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding ) - self.setFocusProxy(self.input_field) self.content_layout.addWidget( self.input_field, 1, alignment=QtCore.Qt.AlignTop ) + self.input_field.focused_in.connect(self._on_input_focus) self.input_field.textChanged.connect(self._on_value_change) + def _on_input_focus(self): + self.focused_in() + def set_entity_value(self): self.input_field.set_value(self.entity.value) self._is_invalid = self.input_field.has_invalid_value() @@ -651,14 +660,19 @@ def get_invalid(self): class PathInputWidget(InputWidget): def _add_inputs_to_layout(self): - self.input_field = QtWidgets.QLineEdit(self.content_widget) + self.input_field = SettingsLineEdit(self.content_widget) placeholder = self.entity.placeholder_text if placeholder: self.input_field.setPlaceholderText(placeholder) self.setFocusProxy(self.input_field) self.content_layout.addWidget(self.input_field) + self.input_field.textChanged.connect(self._on_value_change) + self.input_field.focused_in.connect(self._on_input_focus) + + def _on_input_focus(self): + self.focused_in() def _on_entity_change(self): if self.entity.value != self.input_value(): diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index 17eab5c89f6..c727b221690 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -9,6 +9,22 @@ from openpype.settings.lib import get_system_settings +class SettingsLineEdit(QtWidgets.QLineEdit): + focused_in = QtCore.Signal() + + def focusInEvent(self, event): + super(SettingsLineEdit, self).focusInEvent(event) + self.focused_in.emit() + + +class SettingsPlainTextEdit(QtWidgets.QPlainTextEdit): + focused_in = QtCore.Signal() + + def focusInEvent(self, event): + super(SettingsPlainTextEdit, self).focusInEvent(event) + self.focused_in.emit() + + class ShadowWidget(QtWidgets.QWidget): def __init__(self, message, parent): super(ShadowWidget, self).__init__(parent) From 8491018e68927c94afb9a7ab198536a000f2b444 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 16:02:36 +0200 Subject: [PATCH 16/41] added focus in signals to comboboxes --- openpype/tools/settings/settings/item_widgets.py | 8 ++++++-- .../settings/settings/multiselection_combobox.py | 6 ++++++ openpype/tools/settings/settings/widgets.py | 11 ++++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index c98c3ba0b83..97a63c78384 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -6,7 +6,7 @@ ExpandingWidget, NumberSpinBox, GridLabelWidget, - ComboBox, + SettingsComboBox, NiceCheckbox, SettingsPlainTextEdit, SettingsLineEdit @@ -495,7 +495,7 @@ def _add_inputs_to_layout(self): ) else: - self.input_field = ComboBox(self.content_widget) + self.input_field = SettingsComboBox(self.content_widget) for enum_item in self.entity.enum_items: for value, label in enum_item.items(): @@ -505,8 +505,12 @@ def _add_inputs_to_layout(self): self.setFocusProxy(self.input_field) + self.input_field.focused_in.connect(self._on_input_focus) self.input_field.value_changed.connect(self._on_value_change) + def _on_input_focus(self): + self.focused_in() + def _on_entity_change(self): if self.entity.value != self.input_field.value(): self.set_entity_value() diff --git a/openpype/tools/settings/settings/multiselection_combobox.py b/openpype/tools/settings/settings/multiselection_combobox.py index 30ecb7b84bd..2e625faa2d5 100644 --- a/openpype/tools/settings/settings/multiselection_combobox.py +++ b/openpype/tools/settings/settings/multiselection_combobox.py @@ -21,6 +21,8 @@ def paint(self, painter, option, index): class MultiSelectionComboBox(QtWidgets.QComboBox): value_changed = QtCore.Signal() + focused_in = QtCore.Signal() + ignored_keys = { QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, @@ -56,6 +58,10 @@ def __init__( self.lines = {} self.item_height = None + def focusInEvent(self, event): + self.focused_in.emit() + return super(SettingsComboBox, self).focusInEvent(event) + def mousePressEvent(self, event): """Reimplemented.""" self._popup_is_shown = False diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index c727b221690..f7c379eb3e9 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -109,18 +109,23 @@ def value(self): return output -class ComboBox(QtWidgets.QComboBox): +class SettingsComboBox(QtWidgets.QComboBox): value_changed = QtCore.Signal() + focused_in = QtCore.Signal() def __init__(self, *args, **kwargs): - super(ComboBox, self).__init__(*args, **kwargs) + super(SettingsComboBox, self).__init__(*args, **kwargs) self.currentIndexChanged.connect(self._on_change) self.setFocusPolicy(QtCore.Qt.StrongFocus) def wheelEvent(self, event): if self.hasFocus(): - return super(ComboBox, self).wheelEvent(event) + return super(SettingsComboBox, self).wheelEvent(event) + + def focusInEvent(self, event): + self.focused_in.emit() + return super(SettingsComboBox, self).focusInEvent(event) def _on_change(self, *args, **kwargs): self.value_changed.emit() From fd138b53be2f487a23e670dd19f16ebb8bfcdadc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 16:07:37 +0200 Subject: [PATCH 17/41] set names of breadcrumb widgets to be able style them --- openpype/tools/settings/settings/breadcrumb_widget.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumb_widget.py index d8a35daa5aa..ccd55b7bdad 100644 --- a/openpype/tools/settings/settings/breadcrumb_widget.py +++ b/openpype/tools/settings/settings/breadcrumb_widget.py @@ -209,6 +209,8 @@ class BreadcrumbsPathInput(QtWidgets.QLineEdit): def __init__(self, model, parent): super(BreadcrumbsPathInput, self).__init__(parent) + self.setObjectName("BreadcrumbsPathInput") + self.setFrame(False) completer = QtWidgets.QCompleter(self) @@ -261,6 +263,8 @@ class BreadcrumbsButton(QtWidgets.QToolButton): def __init__(self, path, model, parent): super(BreadcrumbsButton, self).__init__(parent) + self.setObjectName("BreadcrumbsButton") + path_prefix = path + "/" self.setAutoRaise(True) @@ -304,6 +308,8 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame): def __init__(self, parent=None): super(BreadcrumbsAddressBar, self).__init__(parent) + self.setObjectName("BreadcrumbsAddressBar") + self.setAutoFillBackground(True) self.setFrameShape(self.StyledPanel) From a9e41a704c27060a218c7c1b70250a494c5f7462 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 16:08:02 +0200 Subject: [PATCH 18/41] renamed file with breadcrumbs content --- .../settings/{breadcrumb_widget.py => breadcrumbs_widget.py} | 0 openpype/tools/settings/settings/categories.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename openpype/tools/settings/settings/{breadcrumb_widget.py => breadcrumbs_widget.py} (100%) diff --git a/openpype/tools/settings/settings/breadcrumb_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py similarity index 100% rename from openpype/tools/settings/settings/breadcrumb_widget.py rename to openpype/tools/settings/settings/breadcrumbs_widget.py diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 1506e5bbfda..3f332378ebf 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -31,7 +31,7 @@ from openpype.settings import SaveWarningExc from .widgets import ProjectListWidget -from .breadcrumb_widget import ( +from .breadcrumbs_widget import ( BreadcrumbsAddressBar, SystemSettingsBreadcrumbs, ProjectSettingsBreadcrumbs From c8eb053cb732455384a378e25d695b1b6f57c2b0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:13:18 +0200 Subject: [PATCH 19/41] insert empty item on init --- openpype/tools/settings/settings/breadcrumbs_widget.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index ccd55b7bdad..8f010de1364 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -265,14 +265,19 @@ def __init__(self, path, model, parent): self.setObjectName("BreadcrumbsButton") - path_prefix = path + "/" + path_prefix = path + if path: + path_prefix += "/" self.setAutoRaise(True) self.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) self.setMouseTracking(True) - self.setText(path.split("/")[-1]) + if path: + self.setText(path.split("/")[-1]) + else: + self.setProperty("empty", "1") menu = BreadcrumbsHintMenu(model, path_prefix, self) @@ -417,6 +422,7 @@ def set_path(self, path): item = "/".join(path_items) self._insert_crumb(item) path_items.pop(-1) + self._insert_crumb("") self.path_changed.emit(self._current_path) From 6064471b71f5b3e2cefc212c35af8ab7f93fdc0f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:17:06 +0200 Subject: [PATCH 20/41] added styles --- .../settings/settings/breadcrumbs_widget.py | 3 +- .../tools/settings/settings/style/style.css | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index 8f010de1364..085034a1f81 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -313,8 +313,6 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame): def __init__(self, parent=None): super(BreadcrumbsAddressBar, self).__init__(parent) - self.setObjectName("BreadcrumbsAddressBar") - self.setAutoFillBackground(True) self.setFrameShape(self.StyledPanel) @@ -331,6 +329,7 @@ def __init__(self, parent=None): # Container for breadcrumbs crumbs_panel = QtWidgets.QWidget(crumbs_container) + crumbs_panel.setObjectName("BreadcrumbsPanel") crumbs_layout = QtWidgets.QHBoxLayout() crumbs_layout.setContentsMargins(0, 0, 0, 0) diff --git a/openpype/tools/settings/settings/style/style.css b/openpype/tools/settings/settings/style/style.css index 3ce9837a8bc..6a6eb7209a5 100644 --- a/openpype/tools/settings/settings/style/style.css +++ b/openpype/tools/settings/settings/style/style.css @@ -388,4 +388,31 @@ QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { background: #3d8ec9; -} \ No newline at end of file +} + +#BreadcrumbsPathInput { + padding: 2px; +} + +#BreadcrumbsButton { + padding-right: 12px; + font-size: 9pt; +} + +#BreadcrumbsButton[empty="1"] { + padding-right: 0px; +} + +#BreadcrumbsButton::menu-button { + width: 12px; + background: rgba(127, 127, 127, 60); +} +#BreadcrumbsButton::menu-button:hover { + background: rgba(127, 127, 127, 90); +} + +#BreadcrumbsPanel { + border: 1px solid #4e5254; + border-radius: 5px; + background: #21252B;; +} From d1191ab7e0742850fd5b90bb84b6a2a5069bcf8a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:17:17 +0200 Subject: [PATCH 21/41] fixed GUIWIdget --- openpype/tools/settings/settings/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index b10ec58142c..0541c36ffeb 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -458,7 +458,7 @@ def _create_separator_ui(self): layout.addWidget(splitter_item) def set_entity_value(self): - return + pass def hierarchical_style_update(self): pass @@ -466,6 +466,9 @@ def hierarchical_style_update(self): def make_sure_is_visible(self, *args, **kwargs): pass + def set_path(self, *args, **kwargs): + pass + def get_invalid(self): return [] From d17d6d801bbfa15f38b3a5bd32ead1c3341c7665 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:17:42 +0200 Subject: [PATCH 22/41] use only path input reference --- openpype/tools/settings/settings/breadcrumbs_widget.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index 085034a1f81..ce3d06dbc3b 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -450,8 +450,5 @@ def _show_address_field(self, show=True): def minimumSizeHint(self): result = super(BreadcrumbsAddressBar, self).minimumSizeHint() - result.setHeight(max( - self.path_input.height(), - self.crumbs_container.height() - )) + result.setHeight(self.path_input.minimumSizeHint().height()) return result From 37b3af142ef153d6f1fb11ec80f5fa4318777727 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:18:35 +0200 Subject: [PATCH 23/41] set font size for path input --- openpype/tools/settings/settings/style/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/settings/settings/style/style.css b/openpype/tools/settings/settings/style/style.css index 6a6eb7209a5..250c15063f3 100644 --- a/openpype/tools/settings/settings/style/style.css +++ b/openpype/tools/settings/settings/style/style.css @@ -392,6 +392,7 @@ QTableView::item:selected:active, QTreeView::item:selected:active, QListView::it #BreadcrumbsPathInput { padding: 2px; + font-size: 9pt; } #BreadcrumbsButton { From 58983c21793939bdeea818951919afb9a1654eea Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:18:44 +0200 Subject: [PATCH 24/41] reset path after refresh --- openpype/tools/settings/settings/categories.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 3f332378ebf..88e987e7935 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -466,7 +466,10 @@ def _on_reset_success(self): self.save_btn.setEnabled(True) if self.breadcrumbs_model is not None: + path = self.breadcrumbs_widget.path() + self.breadcrumbs_widget.set_path("") self.breadcrumbs_model.set_entity(self.entity) + self.breadcrumbs_widget.change_path(path) def add_children_gui(self): for child_obj in self.entity.children: From f5712bb99130688b93a6c82251a23173329a8757 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:21:37 +0200 Subject: [PATCH 25/41] path is also changed for number inputs --- openpype/tools/settings/settings/item_widgets.py | 4 ++++ openpype/tools/settings/settings/widgets.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 97a63c78384..4b60ef8d1ca 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -374,6 +374,10 @@ def _add_inputs_to_layout(self): self.content_layout.addWidget(self.input_field, 1) self.input_field.valueChanged.connect(self._on_value_change) + self.input_field.focused_in.connect(self._on_input_focus) + + def _on_input_focus(self): + self.focused_in() def _on_entity_change(self): if self.entity.value != self.input_field.value(): diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py index f7c379eb3e9..34b222dd8e5 100644 --- a/openpype/tools/settings/settings/widgets.py +++ b/openpype/tools/settings/settings/widgets.py @@ -86,6 +86,8 @@ def leaveEvent(self, event): class NumberSpinBox(QtWidgets.QDoubleSpinBox): + focused_in = QtCore.Signal() + def __init__(self, *args, **kwargs): min_value = kwargs.pop("minimum", -99999) max_value = kwargs.pop("maximum", 99999) @@ -96,6 +98,10 @@ def __init__(self, *args, **kwargs): self.setMinimum(min_value) self.setMaximum(max_value) + def focusInEvent(self, event): + super(NumberSpinBox, self).focusInEvent(event) + self.focused_in.emit() + def wheelEvent(self, event): if self.hasFocus(): super(NumberSpinBox, self).wheelEvent(event) From bda4d311c5ec4175db9f73da690299a9ac803046 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 17:54:42 +0200 Subject: [PATCH 26/41] enhanced filling of path --- .../settings/settings/breadcrumbs_widget.py | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index ce3d06dbc3b..2a9eb2dce9a 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -72,6 +72,12 @@ def set_entity(self, entity): self.entity = entity self.reset() + def has_children(self, path): + for key in self.entities_by_path.keys(): + if key.startswith(path): + return True + return False + class SystemSettingsBreadcrumbs(SettingsBreadcrumbs): def reset(self): @@ -206,7 +212,7 @@ class BreadcrumbsPathInput(QtWidgets.QLineEdit): cancelled = QtCore.Signal() confirmed = QtCore.Signal() - def __init__(self, model, parent): + def __init__(self, model, proxy_model, parent): super(BreadcrumbsPathInput, self).__init__(parent) self.setObjectName("BreadcrumbsPathInput") @@ -215,7 +221,7 @@ def __init__(self, model, parent): completer = QtWidgets.QCompleter(self) completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) - completer.setModel(model) + completer.setModel(proxy_model) popup = completer.popup() popup.setUniformItemSizes(True) @@ -228,16 +234,39 @@ def __init__(self, model, parent): self._completer = completer self._model = model + self._proxy_model = proxy_model self._context_menu_visible = False + def set_model(self, model): + self._model = model + + def event(self, event): + if ( + event.type() == QtCore.QEvent.KeyPress + and event.key() == QtCore.Qt.Key_Tab + ): + if self._model: + find_value = self.text() + "/" + if self._model.has_children(find_value): + self.insert("/") + else: + self._completer.popup().hide() + event.accept() + return True + + return super(BreadcrumbsPathInput, self).event(event) + def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Escape: self.cancelled.emit() - elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + return + + if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): self.confirmed.emit() - else: - super(BreadcrumbsPathInput, self).keyPressEvent(event) + return + + super(BreadcrumbsPathInput, self).keyPressEvent(event) def focusOutEvent(self, event): if not self._context_menu_visible: @@ -254,7 +283,7 @@ def _on_completer_activated(self, path): self.confirmed.emit() def _on_text_change(self, path): - self._model.set_path_prefix(path) + self._proxy_model.set_path_prefix(path) class BreadcrumbsButton(QtWidgets.QToolButton): @@ -318,7 +347,7 @@ def __init__(self, parent=None): # Edit presented path textually proxy_model = BreadcrumbsProxy() - path_input = BreadcrumbsPathInput(proxy_model, self) + path_input = BreadcrumbsPathInput(None, proxy_model, self) path_input.setVisible(False) path_input.cancelled.connect(self._on_input_cancel) @@ -372,6 +401,7 @@ def __init__(self, parent=None): def set_model(self, model): self._model = model + self.path_input.set_model(model) self._proxy_model.setSourceModel(model) def _on_input_confirm(self): From a5a29d8895bab7a332b18c748654b709792e87a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 18:10:21 +0200 Subject: [PATCH 27/41] list items have implemented getters --- openpype/settings/entities/item_entities.py | 13 +++++++++++++ openpype/settings/entities/list_entity.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index ecce7a22c0f..efba6dbb61b 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -195,6 +195,19 @@ def reset_callbacks(self): class ListStrictEntity(ItemEntity): schema_types = ["list-strict"] + def __getitem__(self, idx): + if not isinstance(idx, int): + idx = int(idx) + return self.children[idx] + + def get(self, idx, default=None): + if not isinstance(idx, int): + idx = int(idx) + + if idx < len(self.children): + return self.children[idx] + return default + def _item_initalization(self): self.valid_value_types = (list, ) self.require_key = True diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index b07441251a6..bcfacbf1448 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -45,6 +45,19 @@ def __contains__(self, item): return True return False + def __getitem__(self, idx): + if not isinstance(idx, int): + idx = int(idx) + return self.children[idx] + + def get(self, idx, default=None): + if not isinstance(idx, int): + idx = int(idx) + + if idx < len(self.children): + return self.children[idx] + return default + def index(self, item): if isinstance(item, BaseEntity): for idx, child_entity in enumerate(self.children): From 689e480cd5c16713d1aad8e5b0504c959e5676e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 18:10:56 +0200 Subject: [PATCH 28/41] validate path before confirming it --- .../settings/settings/breadcrumbs_widget.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index 2a9eb2dce9a..90105a4b4ee 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -78,6 +78,19 @@ def has_children(self, path): return True return False + def is_valid_path(self, path): + if not path: + return True + + path_items = path.split("/") + try: + entity = self.entity + for item in path_items: + entity = entity[item] + except Exception: + return False + return True + class SystemSettingsBreadcrumbs(SettingsBreadcrumbs): def reset(self): @@ -406,7 +419,6 @@ def set_model(self, model): def _on_input_confirm(self): self.change_path(self.path_input.text()) - self._show_address_field(False) def _on_input_cancel(self): self._cancel_edit() @@ -429,8 +441,11 @@ def _on_crumb_clicked(self, path): self.change_path(path) def change_path(self, path): - self.set_path(path) - self.path_edited.emit(path) + if self._model and not self._model.is_valid_path(path): + self._show_address_field() + else: + self.set_path(path) + self.path_edited.emit(path) def set_path(self, path): if path is None or path == ".": From ea0aff363eea0685e9e2f445543d567c5709f765 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 12 Aug 2021 18:14:19 +0200 Subject: [PATCH 29/41] list have also setters --- openpype/settings/entities/item_entities.py | 5 +++++ openpype/settings/entities/list_entity.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index efba6dbb61b..ac6b3e76ddf 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -200,6 +200,11 @@ def __getitem__(self, idx): idx = int(idx) return self.children[idx] + def __setitem__(self, idx, value): + if not isinstance(idx, int): + idx = int(idx) + self.children[idx].set(value) + def get(self, idx, default=None): if not isinstance(idx, int): idx = int(idx) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index bcfacbf1448..b06f4d7a2e5 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -50,6 +50,11 @@ def __getitem__(self, idx): idx = int(idx) return self.children[idx] + def __setitem__(self, idx, value): + if not isinstance(idx, int): + idx = int(idx) + self.children[idx].set(value) + def get(self, idx, default=None): if not isinstance(idx, int): idx = int(idx) From ec13c2228095a9791732a3ace68dc5aafc887814 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:22:40 +0200 Subject: [PATCH 30/41] removed unused parts --- openpype/tools/settings/settings/breadcrumbs_widget.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index 90105a4b4ee..b625a7bb072 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -1,12 +1,5 @@ -import os -import sys -sys.path.append(r"C:\Users\jakub.trllo\Desktop\pype\pype3_2\.venv\Lib\site-packages") - from Qt import QtWidgets, QtGui, QtCore -# px, size of generated semi-transparent icons -TRANSP_ICON_SIZE = 40, 40 - PREFIX_ROLE = QtCore.Qt.UserRole + 1 LAST_SEGMENT_ROLE = QtCore.Qt.UserRole + 2 From 2691d294c4236ec257ce1d54470938c9f71a1517 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:22:48 +0200 Subject: [PATCH 31/41] added margins to path widget --- openpype/tools/settings/settings/categories.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 88e987e7935..d524462600b 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -185,7 +185,7 @@ def create_ui(self): breadcrumbs_widget = BreadcrumbsAddressBar(content_widget) breadcrumbs_layout = QtWidgets.QHBoxLayout() - breadcrumbs_layout.setContentsMargins(5, 0, 5, 0) + breadcrumbs_layout.setContentsMargins(5, 5, 5, 5) breadcrumbs_layout.setSpacing(5) breadcrumbs_layout.addWidget(breadcrumbs_label) breadcrumbs_layout.addWidget(breadcrumbs_widget) From c39ea2b65409c9ee2a5adcf7d39c3bfd0e798435 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:22:54 +0200 Subject: [PATCH 32/41] fixed super --- openpype/tools/settings/settings/multiselection_combobox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/multiselection_combobox.py b/openpype/tools/settings/settings/multiselection_combobox.py index 2e625faa2d5..176f4cab8cb 100644 --- a/openpype/tools/settings/settings/multiselection_combobox.py +++ b/openpype/tools/settings/settings/multiselection_combobox.py @@ -60,7 +60,7 @@ def __init__( def focusInEvent(self, event): self.focused_in.emit() - return super(SettingsComboBox, self).focusInEvent(event) + return super(MultiSelectionComboBox, self).focusInEvent(event) def mousePressEvent(self, event): """Reimplemented.""" From 65cf4559d278b68b0d9bea699ced3a14ad073ff9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:50:02 +0200 Subject: [PATCH 33/41] dict stores all direct children --- openpype/tools/settings/settings/item_widgets.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 4b60ef8d1ca..4d344cb8fc2 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -48,6 +48,7 @@ def create_ui(self): self._ui_item_base() label = self.entity.label + self._direct_children_widgets = [] self._parent_widget_by_entity_id = {} self._added_wrapper_ids = set() self._prepare_entity_layouts( @@ -193,6 +194,8 @@ def add_widget_to_layout(self, widget, label=None): self._added_wrapper_ids.add(wrapper.id) return + self._direct_children_widgets.append(widget) + row = self.content_layout.rowCount() if not label or isinstance(widget, WrapperWidget): self.content_layout.addWidget(widget, row, 0, 1, 2) From 4a7995e9adeaa45f39e00a2bbe608175e247b122 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:51:03 +0200 Subject: [PATCH 34/41] make_sure_is_visible expect result --- openpype/tools/settings/settings/base.py | 5 +++++ .../tools/settings/settings/dict_conditional.py | 10 ++++++---- .../tools/settings/settings/dict_mutable_widget.py | 10 ++++++---- openpype/tools/settings/settings/item_widgets.py | 14 ++++++++------ .../tools/settings/settings/list_item_widget.py | 12 +++++++----- .../tools/settings/settings/list_strict_widget.py | 8 +++++--- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 0541c36ffeb..915a408f4e3 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -372,6 +372,8 @@ def make_sure_is_visible(self, path, scroll_to): entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) + return True + return False def update_style(self): has_unsaved_changes = self.entity.has_unsaved_changes @@ -464,6 +466,9 @@ def hierarchical_style_update(self): pass def make_sure_is_visible(self, *args, **kwargs): + return False + + def focused_in(self): pass def set_path(self, *args, **kwargs): diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 222cca03f9e..c30159195c3 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -215,22 +215,24 @@ def _ui_label_wrap(self): def make_sure_is_visible(self, path, scroll_to): if not path: - return + return False entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) - return + return True if not path.startswith(entity_path): - return + return False if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) QtWidgets.QApplication.processEvents() for input_field in self.input_fields: - input_field.make_sure_is_visible(path, scroll_to) + if input_field.make_sure_is_visible(path, scroll_to): + return True + return False def add_widget_to_layout(self, widget, label=None): if not widget.entity: diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index b733727e4eb..5bab74fabf3 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -318,7 +318,7 @@ def key_label_input_focused_out(event): ) def make_sure_is_visible(self, *args, **kwargs): - self.input_field.make_sure_is_visible(*args, **kwargs) + return self.input_field.make_sure_is_visible(*args, **kwargs) def get_style_state(self): if self.is_invalid: @@ -854,17 +854,19 @@ def make_sure_is_visible(self, path, scroll_to): entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) - return + return True if not path.startswith(entity_path): - return + return False if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) QtWidgets.QApplication.processEvents() for input_field in self.input_fields: - input_field.make_sure_is_visible(path, scroll_to) + if input_field.make_sure_is_visible(path, scroll_to): + return True + return False def set_entity_value(self): while self.input_fields: diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 4d344cb8fc2..9c69ee6705b 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -159,22 +159,24 @@ def _ui_label_wrap(self): def make_sure_is_visible(self, path, scroll_to): if not path: - return + return False entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) - return + return True if not path.startswith(entity_path): - return + return False if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) QtWidgets.QApplication.processEvents() - for input_field in self.input_fields: - input_field.make_sure_is_visible(path, scroll_to) + for direct_child in self._direct_children_widgets: + if direct_child.make_sure_is_visible(path, scroll_to): + return True + return False def add_widget_to_layout(self, widget, label=None): if self.checkbox_child and widget.entity is self.checkbox_child: @@ -599,7 +601,7 @@ def set_entity_value(self): self.input_field.set_entity_value() def make_sure_is_visible(self, *args, **kwargs): - self.input_field.make_sure_is_visible(*args, **kwargs) + return self.input_field.make_sure_is_visible(*args, **kwargs) def hierarchical_style_update(self): self.update_style() diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 52b96a658f0..50630986d67 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -119,7 +119,7 @@ def create_ui_for_entity(self, *args, **kwargs): ) def make_sure_is_visible(self, *args, **kwargs): - self.input_field.make_sure_is_visible(*args, **kwargs) + return self.input_field.make_sure_is_visible(*args, **kwargs) @property def is_invalid(self): @@ -269,22 +269,24 @@ def get_invalid(self): def make_sure_is_visible(self, path, scroll_to): if not path: - return + return False entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) - return + return True if not path.startswith(entity_path): - return + return False if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) QtWidgets.QApplication.processEvents() for input_field in self.input_fields: - input_field.make_sure_is_visible(path, scroll_to) + if input_field.make_sure_is_visible(path, scroll_to): + return True + return False def _on_entity_change(self): # TODO do less inefficient diff --git a/openpype/tools/settings/settings/list_strict_widget.py b/openpype/tools/settings/settings/list_strict_widget.py index 94a6ff53e26..046b6992f6b 100644 --- a/openpype/tools/settings/settings/list_strict_widget.py +++ b/openpype/tools/settings/settings/list_strict_widget.py @@ -67,16 +67,18 @@ def get_invalid(self): def make_sure_is_visible(self, path, scroll_to): if not path: - return + return False entity_path = self.entity.path if entity_path == path: self.set_focus(scroll_to) - return + return True if path.startswith(entity_path): for input_field in self.input_fields: - input_field.make_sure_is_visible(path, scroll_to) + if input_field.make_sure_is_visible(path, scroll_to): + return True + return False def add_widget_to_layout(self, widget, label=None): # Horizontally added children From b859ff99ddcec420427532813ca3a5ac16a9ab51 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:51:23 +0200 Subject: [PATCH 35/41] wrappers use the result to be able expand themselves --- .../tools/settings/settings/wrapper_widgets.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/tools/settings/settings/wrapper_widgets.py b/openpype/tools/settings/settings/wrapper_widgets.py index 915a2cf8754..b14a226912f 100644 --- a/openpype/tools/settings/settings/wrapper_widgets.py +++ b/openpype/tools/settings/settings/wrapper_widgets.py @@ -19,6 +19,14 @@ def __init__(self, schema_data, parent=None): self.create_ui() + def make_sure_is_visible(self, *args, **kwargs): + changed = False + for input_field in self.input_fields: + if input_field.make_sure_is_visible(*args, **kwargs): + changed = True + break + return changed + def create_ui(self): raise NotImplementedError( "{} does not have implemented `create_ui`.".format( @@ -89,6 +97,14 @@ def create_ui(self): else: body_widget.hide_toolbox(hide_content=False) + def make_sure_is_visible(self, *args, **kwargs): + result = super(CollapsibleWrapper, self).make_sure_is_visible( + *args, **kwargs + ) + if result: + self.body_widget.toggle_content(True) + return result + def add_widget_to_layout(self, widget, label=None): self.input_fields.append(widget) From a706fecda4cef15627288c32b32e14675b2999eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:56:29 +0200 Subject: [PATCH 36/41] process app events before scrolling instead of each widget --- openpype/tools/settings/settings/categories.py | 5 +++++ openpype/tools/settings/settings/dict_conditional.py | 1 - openpype/tools/settings/settings/dict_mutable_widget.py | 1 - openpype/tools/settings/settings/item_widgets.py | 1 - openpype/tools/settings/settings/list_item_widget.py | 1 - 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index d524462600b..6d8a5aa6b26 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -258,6 +258,11 @@ def _on_path_edit(self, path): def scroll_to(self, widget): if widget: + # Process events which happened before ensurence + # - that is because some widgets could be not visible before + # this method was called and have incorrect size + QtWidgets.QApplication.processEvents() + # Scroll to widget self.scroll_widget.ensureWidgetVisible(widget) def set_path(self, path): diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index c30159195c3..3e3270cac94 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -227,7 +227,6 @@ def make_sure_is_visible(self, path, scroll_to): if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) - QtWidgets.QApplication.processEvents() for input_field in self.input_fields: if input_field.make_sure_is_visible(path, scroll_to): diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index 5bab74fabf3..25908f5a60d 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -861,7 +861,6 @@ def make_sure_is_visible(self, path, scroll_to): if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) - QtWidgets.QApplication.processEvents() for input_field in self.input_fields: if input_field.make_sure_is_visible(path, scroll_to): diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 9c69ee6705b..fed924e0bf7 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -171,7 +171,6 @@ def make_sure_is_visible(self, path, scroll_to): if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) - QtWidgets.QApplication.processEvents() for direct_child in self._direct_children_widgets: if direct_child.make_sure_is_visible(path, scroll_to): diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 50630986d67..17412a30b90 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -281,7 +281,6 @@ def make_sure_is_visible(self, path, scroll_to): if self.body_widget and not self.body_widget.is_expanded(): self.body_widget.toggle_content(True) - QtWidgets.QApplication.processEvents() for input_field in self.input_fields: if input_field.make_sure_is_visible(path, scroll_to): From c08bf6b4ad4b5b3d8e55ffd4f14cfbb511d5c6dc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 09:57:11 +0200 Subject: [PATCH 37/41] added few docstrings --- openpype/tools/settings/settings/base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index 915a408f4e3..8235cf86426 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -32,11 +32,25 @@ def set_path(self, path): self.category_widget.set_path(path) def set_focus(self, scroll_to=False): + """Set focus of a widget. + + Args: + scroll_to(bool): Also scroll to widget in category widget. + """ if scroll_to: self.scroll_to(self) self.setFocus() def make_sure_is_visible(self, path, scroll_to): + """Make a widget of entity visible by it's path. + + Args: + path(str): Path to entity. + scroll_to(bool): Should be scrolled to entity. + + Returns: + bool: Entity with path was found. + """ raise NotImplementedError( "{} not implemented `make_sure_is_visible`".format( self.__class__.__name__ From b7d8520bff58def12579be3813de8a1e376ed87f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 14:49:42 +0200 Subject: [PATCH 38/41] skip visibility when done --- openpype/tools/settings/settings/categories.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 6d8a5aa6b26..d1babd7fdb2 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -254,7 +254,8 @@ def ui_tweaks(self): def _on_path_edit(self, path): for input_field in self.input_fields: - input_field.make_sure_is_visible(path, True) + if input_field.make_sure_is_visible(path, True): + break def scroll_to(self, widget): if widget: From 6f1636ec8cb947086be01b61a4a25348ab511a37 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 14:49:56 +0200 Subject: [PATCH 39/41] add checbkox widget to direct children --- openpype/tools/settings/settings/item_widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index fed924e0bf7..7bf2ffa59b4 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -180,6 +180,7 @@ def make_sure_is_visible(self, path, scroll_to): def add_widget_to_layout(self, widget, label=None): if self.checkbox_child and widget.entity is self.checkbox_child: self.body_widget.add_widget_before_label(widget) + self._direct_children_widgets.append(widget) return if not widget.entity: From 3537202caf37c58bde43f0a279f6d9d038f779be Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 14:50:04 +0200 Subject: [PATCH 40/41] fixed return value --- openpype/tools/settings/settings/dict_mutable_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dict_mutable_widget.py b/openpype/tools/settings/settings/dict_mutable_widget.py index 25908f5a60d..14314b8ab3a 100644 --- a/openpype/tools/settings/settings/dict_mutable_widget.py +++ b/openpype/tools/settings/settings/dict_mutable_widget.py @@ -849,7 +849,7 @@ def _on_entity_change(self): def make_sure_is_visible(self, path, scroll_to): if not path: - return + return False entity_path = self.entity.path if entity_path == path: From f2d59de930ec9dd91bd46c695e371a240be7eff4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 13 Aug 2021 14:50:13 +0200 Subject: [PATCH 41/41] handle checkbox widget properly --- .../tools/settings/settings/item_widgets.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index 7bf2ffa59b4..d29fa6f42b0 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -169,13 +169,24 @@ def make_sure_is_visible(self, path, scroll_to): if not path.startswith(entity_path): return False - if self.body_widget and not self.body_widget.is_expanded(): - self.body_widget.toggle_content(True) - + is_checkbox_child = False + changed = False for direct_child in self._direct_children_widgets: if direct_child.make_sure_is_visible(path, scroll_to): - return True - return False + changed = True + if direct_child.entity is self.checkbox_child: + is_checkbox_child = True + break + + # Change scroll to this widget + if is_checkbox_child: + self.scroll_to(self) + + elif self.body_widget and not self.body_widget.is_expanded(): + # Expand widget if is callapsible + self.body_widget.toggle_content(True) + + return changed def add_widget_to_layout(self, widget, label=None): if self.checkbox_child and widget.entity is self.checkbox_child: