From 445dd4ec5bd79732736eaae89d4c6d337ef9c339 Mon Sep 17 00:00:00 2001 From: jm22dogs Date: Fri, 4 Apr 2025 12:32:15 +0100 Subject: [PATCH 01/31] add card sublabel with folder and task name --- .../tools/publisher/widgets/card_view_widgets.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 2f633b3149..65c5d1d4ef 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -492,10 +492,22 @@ class InstanceCardWidget(CardWidget): self._icon_widget.setVisible(valid) self._context_warning.setVisible(not valid) + @staticmethod + def get_card_widget_sub_label(folder_name, task_name=None): + sublabel = "
" + sublabel += "{}".format(folder_name) + if task_name: + sublabel += " - {}".format(task_name) + sublabel += "" + return sublabel + def _update_product_name(self): variant = self.instance.variant product_name = self.instance.product_name label = self.instance.label + folder_name = self.instance.get_folder_path().split("/")[-1] + task_name = self.instance.get_task_name() + if ( variant == self._last_variant and product_name == self._last_product_name @@ -513,6 +525,7 @@ class InstanceCardWidget(CardWidget): for part in found_parts: replacement = "{}".format(part) label = label.replace(part, replacement) + label += self.get_card_widget_sub_label(folder_name, task_name) self._label_widget.setText(label) # HTML text will cause that label start catch mouse clicks From 1e3aaa887db0a9e7d6e823404911782530c308e2 Mon Sep 17 00:00:00 2001 From: Juan M <166030421+jm22dogs@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:41:39 +0100 Subject: [PATCH 02/31] Update client/ayon_core/tools/publisher/widgets/card_view_widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../publisher/widgets/card_view_widgets.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 65c5d1d4ef..8a8b81d615 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -493,13 +493,17 @@ class InstanceCardWidget(CardWidget): self._context_warning.setVisible(not valid) @staticmethod - def get_card_widget_sub_label(folder_name, task_name=None): - sublabel = "
" - sublabel += "{}".format(folder_name) - if task_name: - sublabel += " - {}".format(task_name) - sublabel += "" - return sublabel + def _get_card_widget_sub_label(folder_path, task_name): + parts = [] + if folder_path: + folder_name = folder_path.split("/")[-1] + parts.append(f"{folder_name}") + if task_name: + parts.append(folder_name) + if not parts: + return None + sublabel = " - ".join(parts) + return f"{sublabel}" def _update_product_name(self): variant = self.instance.variant From 66ecc40a80f084bfccfc28ffe424ef0f3fa8cb1a Mon Sep 17 00:00:00 2001 From: Juan M <166030421+jm22dogs@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:42:45 +0100 Subject: [PATCH 03/31] Update client/ayon_core/tools/publisher/widgets/card_view_widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 8a8b81d615..e847b2e970 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -529,7 +529,9 @@ class InstanceCardWidget(CardWidget): for part in found_parts: replacement = "{}".format(part) label = label.replace(part, replacement) - label += self.get_card_widget_sub_label(folder_name, task_name) + sublabel = self._get_card_widget_sub_label(folder_path, task_name) + if sublabel: + label += f"
{sublabel}" self._label_widget.setText(label) # HTML text will cause that label start catch mouse clicks From b6296423f5c7103da44e8e103f106d26b9e34030 Mon Sep 17 00:00:00 2001 From: Juan M <166030421+jm22dogs@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:42:55 +0100 Subject: [PATCH 04/31] Update client/ayon_core/tools/publisher/widgets/card_view_widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index e847b2e970..cbafa6fe81 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -509,7 +509,7 @@ class InstanceCardWidget(CardWidget): variant = self.instance.variant product_name = self.instance.product_name label = self.instance.label - folder_name = self.instance.get_folder_path().split("/")[-1] + folder_path = self.instance.get_folder_path() task_name = self.instance.get_task_name() if ( From 08aee24a484986a4e8d3943ff762fa926d3cbc55 Mon Sep 17 00:00:00 2001 From: Juan M <166030421+jm22dogs@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:47:51 +0100 Subject: [PATCH 05/31] Update client/ayon_core/tools/publisher/widgets/card_view_widgets.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- .../ayon_core/tools/publisher/widgets/card_view_widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index cbafa6fe81..958542d264 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -529,9 +529,9 @@ class InstanceCardWidget(CardWidget): for part in found_parts: replacement = "{}".format(part) label = label.replace(part, replacement) - sublabel = self._get_card_widget_sub_label(folder_path, task_name) - if sublabel: - label += f"
{sublabel}" + sublabel = self._get_card_widget_sub_label(folder_path, task_name) + if sublabel: + label += f"
{sublabel}" self._label_widget.setText(label) # HTML text will cause that label start catch mouse clicks From 0f480ee410e99cd72d7cfcd576df4c44af9211e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:20:38 +0100 Subject: [PATCH 06/31] fix updates of the label --- .../publisher/widgets/card_view_widgets.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 8df16f24c2..fdb05e1268 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -288,6 +288,8 @@ class InstanceCardWidget(CardWidget): self._last_product_name = None self._last_variant = None self._last_label = None + self._last_folder_path = None + self._last_task_name = None icon_widget = IconValuePixmapLabel(group_icon, self) icon_widget.setObjectName("ProductTypeIconLabel") @@ -384,15 +386,18 @@ class InstanceCardWidget(CardWidget): self._context_warning.setVisible(not valid) @staticmethod - def _get_card_widget_sub_label(folder_path, task_name): + def _get_card_widget_sub_label( + folder_path: Optional[str], + task_name: Optional[str], + ) -> str: parts = [] if folder_path: folder_name = folder_path.split("/")[-1] parts.append(f"{folder_name}") if task_name: - parts.append(folder_name) + parts.append(task_name) if not parts: - return None + return "" sublabel = " - ".join(parts) return f"{sublabel}" @@ -400,25 +405,30 @@ class InstanceCardWidget(CardWidget): variant = self.instance.variant product_name = self.instance.product_name label = self.instance.label - folder_path = self.instance.get_folder_path() - task_name = self.instance.get_task_name() + folder_path = self.instance.folder_path + task_name = self.instance.task_name if ( variant == self._last_variant and product_name == self._last_product_name and label == self._last_label + and folder_path == self._last_folder_path + and task_name == self._last_task_name ): return self._last_variant = variant self._last_product_name = product_name self._last_label = label + self._last_folder_path = folder_path + self._last_task_name = task_name + # Make `variant` bold label = html_escape(self.instance.label) found_parts = set(re.findall(variant, label, re.IGNORECASE)) if found_parts: for part in found_parts: - replacement = "{}".format(part) + replacement = f"{part}" label = label.replace(part, replacement) sublabel = self._get_card_widget_sub_label(folder_path, task_name) if sublabel: From 606fc39ee35c6a4f7b7b3b8622fa33398dc98659 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:32:29 +0100 Subject: [PATCH 07/31] use string concatenation --- .../tools/publisher/widgets/card_view_widgets.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index fdb05e1268..8f2c1ce0f8 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -390,15 +390,14 @@ class InstanceCardWidget(CardWidget): folder_path: Optional[str], task_name: Optional[str], ) -> str: - parts = [] + sublabel = "" if folder_path: folder_name = folder_path.split("/")[-1] - parts.append(f"{folder_name}") + sublabel = f"- {folder_name}" if task_name: - parts.append(task_name) - if not parts: - return "" - sublabel = " - ".join(parts) + sublabel += f" - {task_name}" + if not sublabel: + return sublabel return f"{sublabel}" def _update_product_name(self): From 447c0f45e5d6c3795589f948bee4f86aa09e3350 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:34:50 +0100 Subject: [PATCH 08/31] use cursive for task --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 8f2c1ce0f8..6023676913 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -395,7 +395,7 @@ class InstanceCardWidget(CardWidget): folder_name = folder_path.split("/")[-1] sublabel = f"- {folder_name}" if task_name: - sublabel += f" - {task_name}" + sublabel += f" - {task_name}" if not sublabel: return sublabel return f"{sublabel}" From b8714b386461e4b0c8e9a8ae5e1370ed41310572 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:54:15 +0100 Subject: [PATCH 09/31] faster split Co-authored-by: Roy Nieterau --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 6023676913..0ee11bfc67 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -392,7 +392,7 @@ class InstanceCardWidget(CardWidget): ) -> str: sublabel = "" if folder_path: - folder_name = folder_path.split("/")[-1] + folder_name = folder_path.rsplit("/", 1)[-1] sublabel = f"- {folder_name}" if task_name: sublabel += f" - {task_name}" From ad3c4c9317fa5bd7c81310106e017bc3460f623a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:52:45 +0100 Subject: [PATCH 10/31] remove first dash Co-authored-by: Roy Nieterau --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 0ee11bfc67..9c8288b415 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -393,7 +393,7 @@ class InstanceCardWidget(CardWidget): sublabel = "" if folder_path: folder_name = folder_path.rsplit("/", 1)[-1] - sublabel = f"- {folder_name}" + sublabel = f"{folder_name}" if task_name: sublabel += f" - {task_name}" if not sublabel: From 527b1f979570b55ac2bebcf3bf0665fdec6c373a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 17:53:57 +0100 Subject: [PATCH 11/31] deselect entities when previous selection widget is focused --- .../tools/launcher/ui/hierarchy_page.py | 44 +++++++++++++++++-- .../tools/launcher/ui/workfiles_page.py | 8 +++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index 47388d9685..ff1ba92976 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -1,5 +1,5 @@ import qtawesome -from qtpy import QtWidgets, QtCore +from qtpy import QtWidgets, QtCore, QtGui from ayon_core.tools.utils import ( PlaceholderLineEdit, @@ -15,6 +15,36 @@ from ayon_core.tools.utils.lib import checkstate_int_to_enum from .workfiles_page import WorkfilesPage +class LauncherFoldersWidget(FoldersWidget): + focused_in = QtCore.Signal() + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._folders_view.installEventFilter(self) + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.FocusIn: + self.focused_in.emit() + return False + + +class LauncherTasksWidget(TasksWidget): + focused_in = QtCore.Signal() + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self._tasks_view.installEventFilter(self) + + def deselect(self): + sel_model = self._tasks_view.selectionModel() + sel_model.clearSelection() + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.FocusIn: + self.focused_in.emit() + return False + + class HierarchyPage(QtWidgets.QWidget): def __init__(self, controller, parent): super().__init__(parent) @@ -68,12 +98,12 @@ class HierarchyPage(QtWidgets.QWidget): filters_layout.addWidget(my_tasks_checkbox, 0) # - Folders widget - folders_widget = FoldersWidget(controller, content_body) + folders_widget = LauncherFoldersWidget(controller, content_body) folders_widget.set_header_visible(True) folders_widget.set_deselectable(True) # - Tasks widget - tasks_widget = TasksWidget(controller, content_body) + tasks_widget = LauncherTasksWidget(controller, content_body) # - Third page - Workfiles workfiles_page = WorkfilesPage(controller, content_body) @@ -97,6 +127,8 @@ class HierarchyPage(QtWidgets.QWidget): my_tasks_checkbox.stateChanged.connect( self._on_my_tasks_checkbox_state_changed ) + folders_widget.focused_in.connect(self._on_folders_focus) + tasks_widget.focused_in.connect(self._on_tasks_focus) self._is_visible = False self._controller = controller @@ -151,3 +183,9 @@ class HierarchyPage(QtWidgets.QWidget): task_ids = entity_ids["task_ids"] self._folders_widget.set_folder_ids_filter(folder_ids) self._tasks_widget.set_task_ids_filter(task_ids) + + def _on_folders_focus(self): + self._tasks_widget.deselect() + + def _on_tasks_focus(self): + self._workfiles_page.deselect() diff --git a/client/ayon_core/tools/launcher/ui/workfiles_page.py b/client/ayon_core/tools/launcher/ui/workfiles_page.py index 1ea223031e..d81221f38d 100644 --- a/client/ayon_core/tools/launcher/ui/workfiles_page.py +++ b/client/ayon_core/tools/launcher/ui/workfiles_page.py @@ -3,7 +3,7 @@ from typing import Optional import ayon_api from qtpy import QtCore, QtWidgets, QtGui -from ayon_core.tools.utils import get_qt_icon +from ayon_core.tools.utils import get_qt_icon, DeselectableTreeView from ayon_core.tools.launcher.abstract import AbstractLauncherFrontEnd VERSION_ROLE = QtCore.Qt.UserRole + 1 @@ -127,7 +127,7 @@ class WorkfilesModel(QtGui.QStandardItemModel): return icon -class WorkfilesView(QtWidgets.QTreeView): +class WorkfilesView(DeselectableTreeView): def drawBranches(self, painter, rect, index): return @@ -165,6 +165,10 @@ class WorkfilesPage(QtWidgets.QWidget): def refresh(self) -> None: self._workfiles_model.refresh() + def deselect(self): + sel_model = self._workfiles_view.selectionModel() + sel_model.clearSelection() + def _on_refresh(self) -> None: self._workfiles_proxy.sort(0, QtCore.Qt.DescendingOrder) From 503e627fb5805d23ab5e505bc25b1a9a63f6817f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:05:26 +0100 Subject: [PATCH 12/31] remove unused import --- client/ayon_core/tools/launcher/ui/hierarchy_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index ff1ba92976..faebf14ab6 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -1,5 +1,5 @@ import qtawesome -from qtpy import QtWidgets, QtCore, QtGui +from qtpy import QtWidgets, QtCore from ayon_core.tools.utils import ( PlaceholderLineEdit, From c1d0510fd34fbab644f03652cecfbb6f76d65214 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:05:07 +0100 Subject: [PATCH 13/31] deselect only workfiles --- client/ayon_core/tools/launcher/ui/hierarchy_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index faebf14ab6..dc535db5fc 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -185,7 +185,7 @@ class HierarchyPage(QtWidgets.QWidget): self._tasks_widget.set_task_ids_filter(task_ids) def _on_folders_focus(self): - self._tasks_widget.deselect() + self._workfiles_page.deselect() def _on_tasks_focus(self): self._workfiles_page.deselect() From feba551e99d6217f6d8b033fdffc2c67873c79dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:46:08 +0100 Subject: [PATCH 14/31] remove dash between folder and task --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 9c8288b415..80c253da01 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -395,7 +395,7 @@ class InstanceCardWidget(CardWidget): folder_name = folder_path.rsplit("/", 1)[-1] sublabel = f"{folder_name}" if task_name: - sublabel += f" - {task_name}" + sublabel += f" {task_name}" if not sublabel: return sublabel return f"{sublabel}" From 9dbd46d86663a127840d6dfb40589dedd1ca826d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:46:16 +0100 Subject: [PATCH 15/31] indent context a little --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 80c253da01..f40730fdf9 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -393,7 +393,7 @@ class InstanceCardWidget(CardWidget): sublabel = "" if folder_path: folder_name = folder_path.rsplit("/", 1)[-1] - sublabel = f"{folder_name}" + sublabel = f"  {folder_name}" if task_name: sublabel += f" {task_name}" if not sublabel: From a07fc4bfaad5c472facc00ff6e0772951dc99084 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 10 Nov 2025 17:55:14 +0800 Subject: [PATCH 16/31] make sure the confirm message box on top after creating hero version --- client/ayon_core/plugins/load/create_hero_version.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/load/create_hero_version.py b/client/ayon_core/plugins/load/create_hero_version.py index aef0cf8863..d01a97e2ff 100644 --- a/client/ayon_core/plugins/load/create_hero_version.py +++ b/client/ayon_core/plugins/load/create_hero_version.py @@ -75,6 +75,7 @@ class CreateHeroVersion(load.ProductLoaderPlugin): msgBox.setStyleSheet(style.load_stylesheet()) msgBox.setWindowFlags( msgBox.windowFlags() | QtCore.Qt.WindowType.FramelessWindowHint + | QtCore.Qt.WindowType.WindowStaysOnTopHint ) msgBox.exec_() From 770b94bde52a4541b16e56a254228f22ff83c056 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:08:35 +0100 Subject: [PATCH 17/31] show context only if is not same as current context --- .../publisher/widgets/card_view_widgets.py | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index f40730fdf9..9ae545c213 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -23,6 +23,7 @@ from __future__ import annotations import re import collections +from dataclasses import dataclass from typing import Optional from qtpy import QtWidgets, QtCore @@ -58,6 +59,13 @@ class SelectionTypes: extend_to = "extend_to" +@dataclass +class _SharedInfo: + """Shared information for multiple widgets.""" + current_folder_path: Optional[str] = None + current_task_name: Optional[str] = None + + class BaseGroupWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str, str) removed_selected = QtCore.Signal() @@ -202,11 +210,12 @@ class ContextCardWidget(CardWidget): Is not visually under group widget and is always at the top of card view. """ - def __init__(self, parent): + def __init__(self, shared_info: _SharedInfo, parent: QtWidgets.QWidget): super().__init__(parent) self._id = CONTEXT_ID self._group_identifier = CONTEXT_GROUP + self._shared_info = shared_info icon_widget = PublishPixmapLabel(None, self) icon_widget.setObjectName("ProductTypeIconLabel") @@ -273,9 +282,12 @@ class InstanceCardWidget(CardWidget): is_parent_active: bool, group_icon, parent: BaseGroupWidget, + shared_info: _SharedInfo, ): super().__init__(parent) + self._shared_info = shared_info + self.instance = instance self._is_active = instance.is_active @@ -389,8 +401,15 @@ class InstanceCardWidget(CardWidget): def _get_card_widget_sub_label( folder_path: Optional[str], task_name: Optional[str], + shared_info: _SharedInfo, ) -> str: sublabel = "" + if ( + shared_info.current_folder_path == folder_path + and shared_info.current_task_name == task_name + ): + return sublabel + if folder_path: folder_name = folder_path.rsplit("/", 1)[-1] sublabel = f"  {folder_name}" @@ -429,7 +448,9 @@ class InstanceCardWidget(CardWidget): for part in found_parts: replacement = f"{part}" label = label.replace(part, replacement) - sublabel = self._get_card_widget_sub_label(folder_path, task_name) + sublabel = self._get_card_widget_sub_label( + folder_path, task_name, self._shared_info + ) if sublabel: label += f"
{sublabel}" @@ -514,6 +535,7 @@ class InstanceCardView(AbstractInstanceView): super().__init__(parent) self._controller: AbstractPublisherFrontend = controller + self._shared_info: _SharedInfo = _SharedInfo() scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -729,11 +751,16 @@ class InstanceCardView(AbstractInstanceView): def refresh(self): """Refresh instances in view based on CreatedContext.""" + self._shared_info.current_folder_path = ( + self._controller.get_current_folder_path() + ) + self._shared_info.current_task_name = ( + self._controller.get_current_task_name() + ) self._make_sure_context_widget_exists() self._update_convertors_group() - context_info_by_id = self._controller.get_instances_context_info() # Prepare instances by group and identifiers by group @@ -841,6 +868,8 @@ class InstanceCardView(AbstractInstanceView): widget.setVisible(False) widget.deleteLater() + sorted_group_names.insert(0, CONTEXT_GROUP) + self._parent_id_by_id = parent_id_by_id self._instance_ids_by_parent_id = instance_ids_by_parent_id self._group_name_by_instance_id = group_by_instance_id @@ -908,7 +937,8 @@ class InstanceCardView(AbstractInstanceView): context_info, is_parent_active, group_icon, - group_widget + group_widget, + self._shared_info, ) widget.selected.connect(self._on_widget_selection) widget.active_changed.connect(self._on_active_changed) @@ -927,7 +957,7 @@ class InstanceCardView(AbstractInstanceView): if self._context_widget is not None: return - widget = ContextCardWidget(self._content_widget) + widget = ContextCardWidget(self._shared_info, self._content_widget) widget.selected.connect(self._on_widget_selection) widget.double_clicked.connect(self.double_clicked) From 9a70ecdd7e0d6388dd60eaa3bace6c33f2bf7b5c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 11:50:16 +0100 Subject: [PATCH 18/31] revert the last changes --- .../publisher/widgets/card_view_widgets.py | 47 ++++--------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 9ae545c213..68b206c262 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -59,13 +59,6 @@ class SelectionTypes: extend_to = "extend_to" -@dataclass -class _SharedInfo: - """Shared information for multiple widgets.""" - current_folder_path: Optional[str] = None - current_task_name: Optional[str] = None - - class BaseGroupWidget(QtWidgets.QWidget): selected = QtCore.Signal(str, str, str) removed_selected = QtCore.Signal() @@ -210,12 +203,11 @@ class ContextCardWidget(CardWidget): Is not visually under group widget and is always at the top of card view. """ - def __init__(self, shared_info: _SharedInfo, parent: QtWidgets.QWidget): + def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) self._id = CONTEXT_ID self._group_identifier = CONTEXT_GROUP - self._shared_info = shared_info icon_widget = PublishPixmapLabel(None, self) icon_widget.setObjectName("ProductTypeIconLabel") @@ -282,12 +274,9 @@ class InstanceCardWidget(CardWidget): is_parent_active: bool, group_icon, parent: BaseGroupWidget, - shared_info: _SharedInfo, ): super().__init__(parent) - self._shared_info = shared_info - self.instance = instance self._is_active = instance.is_active @@ -401,23 +390,14 @@ class InstanceCardWidget(CardWidget): def _get_card_widget_sub_label( folder_path: Optional[str], task_name: Optional[str], - shared_info: _SharedInfo, ) -> str: sublabel = "" - if ( - shared_info.current_folder_path == folder_path - and shared_info.current_task_name == task_name - ): - return sublabel - if folder_path: folder_name = folder_path.rsplit("/", 1)[-1] - sublabel = f"  {folder_name}" + sublabel = f"{folder_name}" if task_name: - sublabel += f" {task_name}" - if not sublabel: - return sublabel - return f"{sublabel}" + sublabel += f" - {task_name}" + return sublabel def _update_product_name(self): variant = self.instance.variant @@ -448,11 +428,11 @@ class InstanceCardWidget(CardWidget): for part in found_parts: replacement = f"{part}" label = label.replace(part, replacement) - sublabel = self._get_card_widget_sub_label( - folder_path, task_name, self._shared_info - ) + + label = f"{label}" + sublabel = self._get_card_widget_sub_label(folder_path, task_name) if sublabel: - label += f"
{sublabel}" + label += f"
{sublabel}" self._label_widget.setText(label) # HTML text will cause that label start catch mouse clicks @@ -535,7 +515,6 @@ class InstanceCardView(AbstractInstanceView): super().__init__(parent) self._controller: AbstractPublisherFrontend = controller - self._shared_info: _SharedInfo = _SharedInfo() scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) @@ -751,13 +730,6 @@ class InstanceCardView(AbstractInstanceView): def refresh(self): """Refresh instances in view based on CreatedContext.""" - self._shared_info.current_folder_path = ( - self._controller.get_current_folder_path() - ) - self._shared_info.current_task_name = ( - self._controller.get_current_task_name() - ) - self._make_sure_context_widget_exists() self._update_convertors_group() @@ -938,7 +910,6 @@ class InstanceCardView(AbstractInstanceView): is_parent_active, group_icon, group_widget, - self._shared_info, ) widget.selected.connect(self._on_widget_selection) widget.active_changed.connect(self._on_active_changed) @@ -957,7 +928,7 @@ class InstanceCardView(AbstractInstanceView): if self._context_widget is not None: return - widget = ContextCardWidget(self._shared_info, self._content_widget) + widget = ContextCardWidget(self._content_widget) widget.selected.connect(self._on_widget_selection) widget.double_clicked.connect(self.double_clicked) From 9be4493a9ea888062f3476bee7e1d883c1062581 Mon Sep 17 00:00:00 2001 From: Mustafa Zaky Jafar Date: Mon, 10 Nov 2025 12:56:51 +0200 Subject: [PATCH 19/31] remove unused import --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 68b206c262..aef3f85e0c 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -23,7 +23,6 @@ from __future__ import annotations import re import collections -from dataclasses import dataclass from typing import Optional from qtpy import QtWidgets, QtCore From 0f8339ac922f204cd5bf7c5f8396c6111e00d915 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:45:22 +0100 Subject: [PATCH 20/31] use span for context label too --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index aef3f85e0c..ca95b1ff1a 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -211,7 +211,7 @@ class ContextCardWidget(CardWidget): icon_widget = PublishPixmapLabel(None, self) icon_widget.setObjectName("ProductTypeIconLabel") - label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) + label_widget = QtWidgets.QLabel(f"{CONTEXT_LABEL}", self) icon_layout = QtWidgets.QHBoxLayout() icon_layout.setContentsMargins(5, 5, 5, 5) From d1ef11defa7f193bede9cbeebfe03771085fee02 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:06:56 +0100 Subject: [PATCH 21/31] define helper widget for folders filtering --- client/ayon_core/tools/utils/__init__.py | 2 + .../ayon_core/tools/utils/folders_widget.py | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/client/ayon_core/tools/utils/__init__.py b/client/ayon_core/tools/utils/__init__.py index 111b7c614b..56989927ee 100644 --- a/client/ayon_core/tools/utils/__init__.py +++ b/client/ayon_core/tools/utils/__init__.py @@ -76,6 +76,7 @@ from .folders_widget import ( FoldersQtModel, FOLDERS_MODEL_SENDER_NAME, SimpleFoldersWidget, + FoldersFiltersWidget, ) from .tasks_widget import ( @@ -160,6 +161,7 @@ __all__ = ( "FoldersQtModel", "FOLDERS_MODEL_SENDER_NAME", "SimpleFoldersWidget", + "FoldersFiltersWidget", "TasksWidget", "TasksQtModel", diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index 7b71dd087c..12f4bebdae 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -15,6 +15,8 @@ from ayon_core.tools.common_models import ( from .models import RecursiveSortFilterProxyModel from .views import TreeView from .lib import RefreshThread, get_qt_icon +from .widgets import PlaceholderLineEdit +from .nice_checkbox import NiceCheckbox FOLDERS_MODEL_SENDER_NAME = "qt_folders_model" @@ -794,3 +796,41 @@ class SimpleFoldersWidget(FoldersWidget): event (Event): Triggered event. """ pass + + +class FoldersFiltersWidget(QtWidgets.QWidget): + """Helper widget for most commonly used filters in context selection.""" + text_changed = QtCore.Signal(str) + my_tasks_changed = QtCore.Signal(bool) + + def __init__(self, parent: QtWidgets.QWidget) -> None: + super().__init__(parent) + + folders_filter_input = PlaceholderLineEdit(self) + folders_filter_input.setPlaceholderText("Folder name filter...") + + my_tasks_tooltip = ( + "Filter folders and task to only those you are assigned to." + ) + my_tasks_label = QtWidgets.QLabel("My tasks", self) + my_tasks_label.setToolTip(my_tasks_tooltip) + + my_tasks_checkbox = NiceCheckbox(self) + my_tasks_checkbox.setChecked(False) + my_tasks_checkbox.setToolTip(my_tasks_tooltip) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(folders_filter_input, 1) + layout.addWidget(my_tasks_label, 0) + layout.addWidget(my_tasks_checkbox, 0) + + folders_filter_input.textChanged.connect(self.text_changed) + my_tasks_checkbox.stateChanged.connect(self._on_my_tasks_change) + + self._folders_filter_input = folders_filter_input + self._my_tasks_checkbox = my_tasks_checkbox + + def _on_my_tasks_change(self, _state: int) -> None: + self.my_tasks_changed.emit(self._my_tasks_checkbox.isChecked()) From cef3bc229a4508e0f19c2dd5fadcb2b0f96c2233 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:07:12 +0100 Subject: [PATCH 22/31] disable case sensitivity for folders proxy --- client/ayon_core/tools/utils/folders_widget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index 12f4bebdae..126363086b 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -345,6 +345,8 @@ class FoldersProxyModel(RecursiveSortFilterProxyModel): def __init__(self): super().__init__() + self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) + self._folder_ids_filter = None def set_folder_ids_filter(self, folder_ids: Optional[list[str]]): From 0dd47211c54850066d6440599b6fb540b388949d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:13:24 +0100 Subject: [PATCH 23/31] add 'get_current_username' to UsersModel --- client/ayon_core/tools/common_models/users.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/common_models/users.py b/client/ayon_core/tools/common_models/users.py index f7939e5cd3..42a76d8d7d 100644 --- a/client/ayon_core/tools/common_models/users.py +++ b/client/ayon_core/tools/common_models/users.py @@ -1,10 +1,13 @@ import json import collections +from typing import Optional import ayon_api from ayon_api.graphql import FIELD_VALUE, GraphQlQuery, fields_to_dict -from ayon_core.lib import NestedCacheItem +from ayon_core.lib import NestedCacheItem, get_ayon_username + +NOT_SET = object() # --- Implementation that should be in ayon-python-api --- @@ -105,9 +108,18 @@ class UserItem: class UsersModel: def __init__(self, controller): + self._current_username = NOT_SET self._controller = controller self._users_cache = NestedCacheItem(default_factory=list) + def get_current_username(self) -> Optional[str]: + if self._current_username is NOT_SET: + self._current_username = get_ayon_username() + return self._current_username + + def reset(self) -> None: + self._users_cache.reset() + def get_user_items(self, project_name): """Get user items. From ad83d827e2a1dcb092d191360a1756086813f9cf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:14:32 +0100 Subject: [PATCH 24/31] move private methods below public one --- client/ayon_core/tools/loader/control.py | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 9f159bfb21..9ec3e580d9 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -476,20 +476,6 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): def is_standard_projects_filter_enabled(self): return self._host is not None - def _get_project_anatomy(self, project_name): - if not project_name: - return None - cache = self._project_anatomy_cache[project_name] - if not cache.is_valid: - cache.update_data(Anatomy(project_name)) - return cache.get_data() - - def _create_event_system(self): - return QueuedEventSystem() - - def _emit_event(self, topic, data=None): - self._event_system.emit(topic, data or {}, "controller") - def get_product_types_filter(self): output = ProductTypesFilter( is_allow_list=False, @@ -545,3 +531,17 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): product_types=profile["filter_product_types"] ) return output + + def _create_event_system(self): + return QueuedEventSystem() + + def _emit_event(self, topic, data=None): + self._event_system.emit(topic, data or {}, "controller") + + def _get_project_anatomy(self, project_name): + if not project_name: + return None + cache = self._project_anatomy_cache[project_name] + if not cache.is_valid: + cache.update_data(Anatomy(project_name)) + return cache.get_data() From 91d44a833b7915b9bbbccb23ad81fd45df295db2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:22:44 +0100 Subject: [PATCH 25/31] implemented my tasks filter to browser --- client/ayon_core/tools/loader/abstract.py | 15 +++++++++ client/ayon_core/tools/loader/control.py | 20 +++++++++++- .../tools/loader/ui/folders_widget.py | 19 ++++++++--- .../ayon_core/tools/loader/ui/tasks_widget.py | 21 ++++++++++-- client/ayon_core/tools/loader/ui/window.py | 32 ++++++++++++++----- 5 files changed, 91 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index 9c7934d2db..089d298b2c 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -666,6 +666,21 @@ class FrontendLoaderController(_BaseLoaderController): """ pass + @abstractmethod + def get_my_tasks_entity_ids( + self, project_name: str + ) -> dict[str, list[str]]: + """Get entity ids for my tasks. + + Args: + project_name (str): Project name. + + Returns: + dict[str, list[str]]: Folder and task ids. + + """ + pass + @abstractmethod def get_available_tags_by_entity_type( self, project_name: str diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 9ec3e580d9..2a86a50b6d 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -8,7 +8,11 @@ import ayon_api from ayon_core.settings import get_project_settings from ayon_core.pipeline import get_current_host_name -from ayon_core.lib import NestedCacheItem, CacheItem, filter_profiles +from ayon_core.lib import ( + NestedCacheItem, + CacheItem, + filter_profiles, +) from ayon_core.lib.events import QueuedEventSystem from ayon_core.pipeline import Anatomy, get_current_context from ayon_core.host import ILoadHost @@ -18,6 +22,7 @@ from ayon_core.tools.common_models import ( ThumbnailsModel, TagItem, ProductTypeIconMapping, + UsersModel, ) from .abstract import ( @@ -32,6 +37,8 @@ from .models import ( SiteSyncModel ) +NOT_SET = object() + class ExpectedSelection: def __init__(self, controller): @@ -124,6 +131,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._loader_actions_model = LoaderActionsModel(self) self._thumbnails_model = ThumbnailsModel() self._sitesync_model = SiteSyncModel(self) + self._users_model = UsersModel(self) @property def log(self): @@ -160,6 +168,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): self._projects_model.reset() self._thumbnails_model.reset() self._sitesync_model.reset() + self._users_model.reset() self._projects_model.refresh() @@ -235,6 +244,15 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): output[folder_id] = label return output + def get_my_tasks_entity_ids(self, project_name: str): + username = self._users_model.get_current_username() + assignees = [] + if username: + assignees.append(username) + return self._hierarchy_model.get_entity_ids_for_assignees( + project_name, assignees + ) + def get_available_tags_by_entity_type( self, project_name: str ) -> dict[str, list[str]]: diff --git a/client/ayon_core/tools/loader/ui/folders_widget.py b/client/ayon_core/tools/loader/ui/folders_widget.py index f238eabcef..6de0b17ea2 100644 --- a/client/ayon_core/tools/loader/ui/folders_widget.py +++ b/client/ayon_core/tools/loader/ui/folders_widget.py @@ -1,11 +1,11 @@ +from typing import Optional + import qtpy from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.tools.utils import ( - RecursiveSortFilterProxyModel, - DeselectableTreeView, -) from ayon_core.style import get_objected_colors +from ayon_core.tools.utils import DeselectableTreeView +from ayon_core.tools.utils.folders_widget import FoldersProxyModel from ayon_core.tools.utils import ( FoldersQtModel, @@ -260,7 +260,7 @@ class LoaderFoldersWidget(QtWidgets.QWidget): QtWidgets.QAbstractItemView.ExtendedSelection) folders_model = LoaderFoldersModel(controller) - folders_proxy_model = RecursiveSortFilterProxyModel() + folders_proxy_model = FoldersProxyModel() folders_proxy_model.setSourceModel(folders_model) folders_proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) @@ -314,6 +314,15 @@ class LoaderFoldersWidget(QtWidgets.QWidget): if name: self._folders_view.expandAll() + def set_folder_ids_filter(self, folder_ids: Optional[list[str]]): + """Set filter of folder ids. + + Args: + folder_ids (list[str]): The list of folder ids. + + """ + self._folders_proxy_model.set_folder_ids_filter(folder_ids) + def set_merged_products_selection(self, items): """ diff --git a/client/ayon_core/tools/loader/ui/tasks_widget.py b/client/ayon_core/tools/loader/ui/tasks_widget.py index cc7e2e9c95..3a38739cf0 100644 --- a/client/ayon_core/tools/loader/ui/tasks_widget.py +++ b/client/ayon_core/tools/loader/ui/tasks_widget.py @@ -1,11 +1,11 @@ import collections import hashlib +from typing import Optional from qtpy import QtWidgets, QtCore, QtGui from ayon_core.style import get_default_entity_icon_color from ayon_core.tools.utils import ( - RecursiveSortFilterProxyModel, DeselectableTreeView, TasksQtModel, TASKS_MODEL_SENDER_NAME, @@ -15,9 +15,11 @@ from ayon_core.tools.utils.tasks_widget import ( ITEM_NAME_ROLE, PARENT_ID_ROLE, TASK_TYPE_ROLE, + TasksProxyModel, ) from ayon_core.tools.utils.lib import RefreshThread, get_qt_icon + # Role that can't clash with default 'tasks_widget' roles FOLDER_LABEL_ROLE = QtCore.Qt.UserRole + 100 NO_TASKS_ID = "--no-task--" @@ -295,7 +297,7 @@ class LoaderTasksQtModel(TasksQtModel): return super().data(index, role) -class LoaderTasksProxyModel(RecursiveSortFilterProxyModel): +class LoaderTasksProxyModel(TasksProxyModel): def lessThan(self, left, right): if left.data(ITEM_ID_ROLE) == NO_TASKS_ID: return False @@ -303,6 +305,12 @@ class LoaderTasksProxyModel(RecursiveSortFilterProxyModel): return True return super().lessThan(left, right) + def filterAcceptsRow(self, row, parent_index): + source_index = self.sourceModel().index(row, 0, parent_index) + if source_index.data(ITEM_ID_ROLE) == NO_TASKS_ID: + return True + return super().filterAcceptsRow(row, parent_index) + class LoaderTasksWidget(QtWidgets.QWidget): refreshed = QtCore.Signal() @@ -363,6 +371,15 @@ class LoaderTasksWidget(QtWidgets.QWidget): if name: self._tasks_view.expandAll() + def set_task_ids_filter(self, task_ids: Optional[list[str]]): + """Set filter of folder ids. + + Args: + task_ids (list[str]): The list of folder ids. + + """ + self._tasks_proxy_model.set_task_ids_filter(task_ids) + def refresh(self): self._tasks_model.refresh() diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index df5beb708f..d1d1222f51 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -5,14 +5,14 @@ from qtpy import QtWidgets, QtCore, QtGui from ayon_core.resources import get_ayon_icon_filepath from ayon_core.style import load_stylesheet from ayon_core.tools.utils import ( - PlaceholderLineEdit, ErrorMessageBox, ThumbnailPainterWidget, RefreshButton, GoToCurrentButton, + FoldersFiltersWidget, ) from ayon_core.tools.utils.lib import center_window -from ayon_core.tools.utils import ProjectsCombobox +from ayon_core.tools.utils import ProjectsCombobox, NiceCheckbox from ayon_core.tools.common_models import StatusItem from ayon_core.tools.loader.abstract import ProductTypeItem from ayon_core.tools.loader.control import LoaderController @@ -170,15 +170,14 @@ class LoaderWindow(QtWidgets.QWidget): context_top_layout.addWidget(go_to_current_btn, 0) context_top_layout.addWidget(refresh_btn, 0) - folders_filter_input = PlaceholderLineEdit(context_widget) - folders_filter_input.setPlaceholderText("Folder name filter...") + filters_widget = FoldersFiltersWidget(context_widget) folders_widget = LoaderFoldersWidget(controller, context_widget) context_layout = QtWidgets.QVBoxLayout(context_widget) context_layout.setContentsMargins(0, 0, 0, 0) context_layout.addWidget(context_top_widget, 0) - context_layout.addWidget(folders_filter_input, 0) + context_layout.addWidget(filters_widget, 0) context_layout.addWidget(folders_widget, 1) tasks_widget = LoaderTasksWidget(controller, context_widget) @@ -247,9 +246,12 @@ class LoaderWindow(QtWidgets.QWidget): projects_combobox.refreshed.connect(self._on_projects_refresh) folders_widget.refreshed.connect(self._on_folders_refresh) products_widget.refreshed.connect(self._on_products_refresh) - folders_filter_input.textChanged.connect( + filters_widget.text_changed.connect( self._on_folder_filter_change ) + filters_widget.my_tasks_changed.connect( + self._on_my_tasks_checkbox_state_changed + ) search_bar.filter_changed.connect(self._on_filter_change) product_group_checkbox.stateChanged.connect( self._on_product_group_change @@ -303,7 +305,7 @@ class LoaderWindow(QtWidgets.QWidget): self._refresh_btn = refresh_btn self._projects_combobox = projects_combobox - self._folders_filter_input = folders_filter_input + self._filters_widget = filters_widget self._folders_widget = folders_widget self._tasks_widget = tasks_widget @@ -421,9 +423,23 @@ class LoaderWindow(QtWidgets.QWidget): self._group_dialog.set_product_ids(project_name, product_ids) self._group_dialog.show() - def _on_folder_filter_change(self, text): + def _on_folder_filter_change(self, text: str) -> None: self._folders_widget.set_name_filter(text) + def _on_my_tasks_checkbox_state_changed(self, enabled: bool) -> None: + # self._folders_widget + folder_ids = None + task_ids = None + if enabled: + entity_ids = self._controller.get_my_tasks_entity_ids( + self._selected_project_name + ) + folder_ids = entity_ids["folder_ids"] + task_ids = entity_ids["task_ids"] + self._folders_widget.set_folder_ids_filter(folder_ids) + self._tasks_widget.set_task_ids_filter(task_ids) + + def _on_product_group_change(self): self._products_widget.set_enable_grouping( self._product_group_checkbox.isChecked() From e6325fa2e81580555cb07847e6e78cfac2c6376e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:27:49 +0100 Subject: [PATCH 26/31] use 'FoldersFiltersWidget' in launcher --- .../tools/launcher/ui/hierarchy_page.py | 37 +++---------------- 1 file changed, 6 insertions(+), 31 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index dc535db5fc..8554a5af8c 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -2,14 +2,13 @@ import qtawesome from qtpy import QtWidgets, QtCore from ayon_core.tools.utils import ( - PlaceholderLineEdit, SquareButton, RefreshButton, ProjectsCombobox, FoldersWidget, TasksWidget, - NiceCheckbox, ) +from ayon_core.tools.utils.folders_widget import FoldersFiltersWidget from ayon_core.tools.utils.lib import checkstate_int_to_enum from .workfiles_page import WorkfilesPage @@ -76,26 +75,7 @@ class HierarchyPage(QtWidgets.QWidget): content_body.setOrientation(QtCore.Qt.Horizontal) # - filters - filters_widget = QtWidgets.QWidget(self) - - folders_filter_text = PlaceholderLineEdit(filters_widget) - folders_filter_text.setPlaceholderText("Filter folders...") - - my_tasks_tooltip = ( - "Filter folders and task to only those you are assigned to." - ) - my_tasks_label = QtWidgets.QLabel("My tasks", filters_widget) - my_tasks_label.setToolTip(my_tasks_tooltip) - - my_tasks_checkbox = NiceCheckbox(filters_widget) - my_tasks_checkbox.setChecked(False) - my_tasks_checkbox.setToolTip(my_tasks_tooltip) - - filters_layout = QtWidgets.QHBoxLayout(filters_widget) - filters_layout.setContentsMargins(0, 0, 0, 0) - filters_layout.addWidget(folders_filter_text, 1) - filters_layout.addWidget(my_tasks_label, 0) - filters_layout.addWidget(my_tasks_checkbox, 0) + filters_widget = FoldersFiltersWidget(self) # - Folders widget folders_widget = LauncherFoldersWidget(controller, content_body) @@ -123,8 +103,8 @@ class HierarchyPage(QtWidgets.QWidget): btn_back.clicked.connect(self._on_back_clicked) refresh_btn.clicked.connect(self._on_refresh_clicked) - folders_filter_text.textChanged.connect(self._on_filter_text_changed) - my_tasks_checkbox.stateChanged.connect( + filters_widget.text_changed.connect(self._on_filter_text_changed) + filters_widget.my_tasks_changed.connect( self._on_my_tasks_checkbox_state_changed ) folders_widget.focused_in.connect(self._on_folders_focus) @@ -135,7 +115,6 @@ class HierarchyPage(QtWidgets.QWidget): self._btn_back = btn_back self._projects_combobox = projects_combobox - self._my_tasks_checkbox = my_tasks_checkbox self._folders_widget = folders_widget self._tasks_widget = tasks_widget self._workfiles_page = workfiles_page @@ -158,9 +137,6 @@ class HierarchyPage(QtWidgets.QWidget): self._folders_widget.refresh() self._tasks_widget.refresh() self._workfiles_page.refresh() - self._on_my_tasks_checkbox_state_changed( - self._my_tasks_checkbox.checkState() - ) def _on_back_clicked(self): self._controller.set_selected_project(None) @@ -171,11 +147,10 @@ class HierarchyPage(QtWidgets.QWidget): def _on_filter_text_changed(self, text): self._folders_widget.set_name_filter(text) - def _on_my_tasks_checkbox_state_changed(self, state): + def _on_my_tasks_checkbox_state_changed(self, enabled: bool) -> None: folder_ids = None task_ids = None - state = checkstate_int_to_enum(state) - if state == QtCore.Qt.Checked: + if enabled: entity_ids = self._controller.get_my_tasks_entity_ids( self._project_name ) From 9c3dec09c9382aa210f59583081e7224b0e8ec22 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:53:36 +0100 Subject: [PATCH 27/31] small cleanup --- client/ayon_core/tools/launcher/control.py | 23 +++++++++++----------- client/ayon_core/tools/loader/control.py | 4 +++- client/ayon_core/tools/loader/ui/window.py | 2 -- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/tools/launcher/control.py b/client/ayon_core/tools/launcher/control.py index 85b362f9d7..f4656de787 100644 --- a/client/ayon_core/tools/launcher/control.py +++ b/client/ayon_core/tools/launcher/control.py @@ -1,10 +1,14 @@ from typing import Optional -from ayon_core.lib import Logger, get_ayon_username +from ayon_core.lib import Logger from ayon_core.lib.events import QueuedEventSystem from ayon_core.addon import AddonsManager from ayon_core.settings import get_project_settings, get_studio_settings -from ayon_core.tools.common_models import ProjectsModel, HierarchyModel +from ayon_core.tools.common_models import ( + ProjectsModel, + HierarchyModel, + UsersModel, +) from .abstract import ( AbstractLauncherFrontEnd, @@ -30,13 +34,12 @@ class BaseLauncherController( self._addons_manager = None - self._username = NOT_SET - self._selection_model = LauncherSelectionModel(self) self._projects_model = ProjectsModel(self) self._hierarchy_model = HierarchyModel(self) self._actions_model = ActionsModel(self) self._workfiles_model = WorkfilesModel(self) + self._users_model = UsersModel(self) @property def log(self): @@ -209,6 +212,7 @@ class BaseLauncherController( self._projects_model.reset() self._hierarchy_model.reset() + self._users_model.reset() self._actions_model.refresh() self._projects_model.refresh() @@ -229,8 +233,10 @@ class BaseLauncherController( self._emit_event("controller.refresh.actions.finished") - def get_my_tasks_entity_ids(self, project_name: str): - username = self._get_my_username() + def get_my_tasks_entity_ids( + self, project_name: str + ) -> dict[str, list[str]]: + username = self._users_model.get_current_username() assignees = [] if username: assignees.append(username) @@ -238,10 +244,5 @@ class BaseLauncherController( project_name, assignees ) - def _get_my_username(self): - if self._username is NOT_SET: - self._username = get_ayon_username() - return self._username - def _emit_event(self, topic, data=None): self.emit_event(topic, data, "controller") diff --git a/client/ayon_core/tools/loader/control.py b/client/ayon_core/tools/loader/control.py index 2a86a50b6d..d0cc9db2f5 100644 --- a/client/ayon_core/tools/loader/control.py +++ b/client/ayon_core/tools/loader/control.py @@ -244,7 +244,9 @@ class LoaderController(BackendLoaderController, FrontendLoaderController): output[folder_id] = label return output - def get_my_tasks_entity_ids(self, project_name: str): + def get_my_tasks_entity_ids( + self, project_name: str + ) -> dict[str, list[str]]: username = self._users_model.get_current_username() assignees = [] if username: diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index d1d1222f51..48704e7481 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -427,7 +427,6 @@ class LoaderWindow(QtWidgets.QWidget): self._folders_widget.set_name_filter(text) def _on_my_tasks_checkbox_state_changed(self, enabled: bool) -> None: - # self._folders_widget folder_ids = None task_ids = None if enabled: @@ -439,7 +438,6 @@ class LoaderWindow(QtWidgets.QWidget): self._folders_widget.set_folder_ids_filter(folder_ids) self._tasks_widget.set_task_ids_filter(task_ids) - def _on_product_group_change(self): self._products_widget.set_enable_grouping( self._product_group_checkbox.isChecked() From 3a6ee43f22e94d9c3f304787820433b3277e3761 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:22:20 +0100 Subject: [PATCH 28/31] added doption to change filters --- client/ayon_core/tools/utils/folders_widget.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index 126363086b..f506af5352 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -834,5 +834,11 @@ class FoldersFiltersWidget(QtWidgets.QWidget): self._folders_filter_input = folders_filter_input self._my_tasks_checkbox = my_tasks_checkbox + def set_text(self, text: str) -> None: + self._folders_filter_input.setText(text) + + def set_my_tasks_checked(self, checked: bool) -> None: + self._my_tasks_checkbox.setChecked(checked) + def _on_my_tasks_change(self, _state: int) -> None: self.my_tasks_changed.emit(self._my_tasks_checkbox.isChecked()) From f9f55b48b03fefa20cb2c361e676858afba1c78e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 16:23:19 +0100 Subject: [PATCH 29/31] added my tasks filtering to publisher --- client/ayon_core/tools/publisher/abstract.py | 15 +++ client/ayon_core/tools/publisher/control.py | 19 +++- .../widgets/create_context_widgets.py | 33 ++++-- .../tools/publisher/widgets/create_widget.py | 12 +- .../tools/publisher/widgets/folders_dialog.py | 107 ++++++++++-------- 5 files changed, 126 insertions(+), 60 deletions(-) diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index 14da15793d..bfd0948519 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -295,6 +295,21 @@ class AbstractPublisherFrontend(AbstractPublisherCommon): """Get folder id from folder path.""" pass + @abstractmethod + def get_my_tasks_entity_ids( + self, project_name: str + ) -> dict[str, list[str]]: + """Get entity ids for my tasks. + + Args: + project_name (str): Project name. + + Returns: + dict[str, list[str]]: Folder and task ids. + + """ + pass + # --- Create --- @abstractmethod def get_creator_items(self) -> Dict[str, "CreatorItem"]: diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index 038816c6fc..3d11131dc3 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -11,7 +11,11 @@ from ayon_core.pipeline import ( registered_host, get_process_id, ) -from ayon_core.tools.common_models import ProjectsModel, HierarchyModel +from ayon_core.tools.common_models import ( + ProjectsModel, + HierarchyModel, + UsersModel, +) from .models import ( PublishModel, @@ -101,6 +105,7 @@ class PublisherController( # Cacher of avalon documents self._projects_model = ProjectsModel(self) self._hierarchy_model = HierarchyModel(self) + self._users_model = UsersModel(self) @property def log(self): @@ -317,6 +322,17 @@ class PublisherController( return False return True + def get_my_tasks_entity_ids( + self, project_name: str + ) -> dict[str, list[str]]: + username = self._users_model.get_current_username() + assignees = [] + if username: + assignees.append(username) + return self._hierarchy_model.get_entity_ids_for_assignees( + project_name, assignees + ) + # --- Publish specific callbacks --- def get_context_title(self): """Get context title for artist shown at the top of main window.""" @@ -359,6 +375,7 @@ class PublisherController( self._emit_event("controller.reset.started") self._hierarchy_model.reset() + self._users_model.reset() # Publish part must be reset after plugins self._create_model.reset() diff --git a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py index faf2248181..49d236353f 100644 --- a/client/ayon_core/tools/publisher/widgets/create_context_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/create_context_widgets.py @@ -1,10 +1,14 @@ from qtpy import QtWidgets, QtCore from ayon_core.lib.events import QueuedEventSystem -from ayon_core.tools.utils import PlaceholderLineEdit, GoToCurrentButton from ayon_core.tools.common_models import HierarchyExpectedSelection -from ayon_core.tools.utils import FoldersWidget, TasksWidget +from ayon_core.tools.utils import ( + FoldersWidget, + TasksWidget, + FoldersFiltersWidget, + GoToCurrentButton, +) from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend @@ -180,8 +184,7 @@ class CreateContextWidget(QtWidgets.QWidget): headers_widget = QtWidgets.QWidget(self) - folder_filter_input = PlaceholderLineEdit(headers_widget) - folder_filter_input.setPlaceholderText("Filter folders..") + filters_widget = FoldersFiltersWidget(headers_widget) current_context_btn = GoToCurrentButton(headers_widget) current_context_btn.setToolTip("Go to current context") @@ -189,7 +192,8 @@ class CreateContextWidget(QtWidgets.QWidget): headers_layout = QtWidgets.QHBoxLayout(headers_widget) headers_layout.setContentsMargins(0, 0, 0, 0) - headers_layout.addWidget(folder_filter_input, 1) + headers_layout.setSpacing(5) + headers_layout.addWidget(filters_widget, 1) headers_layout.addWidget(current_context_btn, 0) hierarchy_controller = CreateHierarchyController(controller) @@ -207,15 +211,16 @@ class CreateContextWidget(QtWidgets.QWidget): main_layout.setContentsMargins(0, 0, 0, 0) main_layout.setSpacing(0) main_layout.addWidget(headers_widget, 0) + main_layout.addSpacing(5) main_layout.addWidget(folders_widget, 2) main_layout.addWidget(tasks_widget, 1) folders_widget.selection_changed.connect(self._on_folder_change) tasks_widget.selection_changed.connect(self._on_task_change) current_context_btn.clicked.connect(self._on_current_context_click) - folder_filter_input.textChanged.connect(self._on_folder_filter_change) + filters_widget.text_changed.connect(self._on_folder_filter_change) + filters_widget.my_tasks_changed.connect(self._on_my_tasks_change) - self._folder_filter_input = folder_filter_input self._current_context_btn = current_context_btn self._folders_widget = folders_widget self._tasks_widget = tasks_widget @@ -303,5 +308,17 @@ class CreateContextWidget(QtWidgets.QWidget): self._last_project_name, folder_id, task_name ) - def _on_folder_filter_change(self, text): + def _on_folder_filter_change(self, text: str) -> None: self._folders_widget.set_name_filter(text) + + def _on_my_tasks_change(self, enabled: bool) -> None: + folder_ids = None + task_ids = None + if enabled: + entity_ids = self._controller.get_my_tasks_entity_ids( + self._last_project_name + ) + folder_ids = entity_ids["folder_ids"] + task_ids = entity_ids["task_ids"] + self._folders_widget.set_folder_ids_filter(folder_ids) + self._tasks_widget.set_task_ids_filter(task_ids) diff --git a/client/ayon_core/tools/publisher/widgets/create_widget.py b/client/ayon_core/tools/publisher/widgets/create_widget.py index b9b3afd895..d98bc95eb2 100644 --- a/client/ayon_core/tools/publisher/widgets/create_widget.py +++ b/client/ayon_core/tools/publisher/widgets/create_widget.py @@ -710,11 +710,13 @@ class CreateWidget(QtWidgets.QWidget): def _on_first_show(self): width = self.width() - part = int(width / 4) - rem_width = width - part - self._main_splitter_widget.setSizes([part, rem_width]) - rem_width = rem_width - part - self._creators_splitter.setSizes([part, rem_width]) + part = int(width / 9) + context_width = part * 3 + create_sel_width = part * 2 + rem_width = width - context_width + self._main_splitter_widget.setSizes([context_width, rem_width]) + rem_width -= create_sel_width + self._creators_splitter.setSizes([create_sel_width, rem_width]) def showEvent(self, event): super().showEvent(event) diff --git a/client/ayon_core/tools/publisher/widgets/folders_dialog.py b/client/ayon_core/tools/publisher/widgets/folders_dialog.py index d2eb68310e..e0d9c098d8 100644 --- a/client/ayon_core/tools/publisher/widgets/folders_dialog.py +++ b/client/ayon_core/tools/publisher/widgets/folders_dialog.py @@ -1,7 +1,10 @@ from qtpy import QtWidgets from ayon_core.lib.events import QueuedEventSystem -from ayon_core.tools.utils import PlaceholderLineEdit, FoldersWidget +from ayon_core.tools.utils import ( + FoldersWidget, + FoldersFiltersWidget, +) from ayon_core.tools.publisher.abstract import AbstractPublisherFrontend @@ -43,8 +46,7 @@ class FoldersDialog(QtWidgets.QDialog): super().__init__(parent) self.setWindowTitle("Select folder") - filter_input = PlaceholderLineEdit(self) - filter_input.setPlaceholderText("Filter folders..") + filters_widget = FoldersFiltersWidget(self) folders_controller = FoldersDialogController(controller) folders_widget = FoldersWidget(folders_controller, self) @@ -59,7 +61,8 @@ class FoldersDialog(QtWidgets.QDialog): btns_layout.addWidget(cancel_btn) layout = QtWidgets.QVBoxLayout(self) - layout.addWidget(filter_input, 0) + layout.setSpacing(5) + layout.addWidget(filters_widget, 0) layout.addWidget(folders_widget, 1) layout.addLayout(btns_layout, 0) @@ -68,12 +71,13 @@ class FoldersDialog(QtWidgets.QDialog): ) folders_widget.double_clicked.connect(self._on_ok_clicked) - filter_input.textChanged.connect(self._on_filter_change) + filters_widget.text_changed.connect(self._on_filter_change) + filters_widget.my_tasks_changed.connect(self._on_my_tasks_change) ok_btn.clicked.connect(self._on_ok_clicked) cancel_btn.clicked.connect(self._on_cancel_clicked) self._controller = controller - self._filter_input = filter_input + self._filters_widget = filters_widget self._ok_btn = ok_btn self._cancel_btn = cancel_btn @@ -88,6 +92,49 @@ class FoldersDialog(QtWidgets.QDialog): self._first_show = True self._default_height = 500 + self._project_name = None + + def showEvent(self, event): + """Refresh folders widget on show.""" + super().showEvent(event) + if self._first_show: + self._first_show = False + self._on_first_show() + # Refresh on show + self.reset(False) + + def reset(self, force=True): + """Reset widget.""" + if not force and not self._soft_reset_enabled: + return + + self._project_name = self._controller.get_current_project_name() + if self._soft_reset_enabled: + self._soft_reset_enabled = False + + self._folders_widget.set_project_name(self._project_name) + + def get_selected_folder_path(self): + """Get selected folder path.""" + return self._selected_folder_path + + def set_selected_folders(self, folder_paths: list[str]) -> None: + """Change preselected folder before showing the dialog. + + This also resets model and clean filter. + """ + self.reset(False) + self._filters_widget.set_text("") + self._filters_widget.set_my_tasks_checked(False) + + folder_id = None + for folder_path in folder_paths: + folder_id = self._controller.get_folder_id_from_path(folder_path) + if folder_id: + break + if folder_id: + self._folders_widget.set_selected_folder(folder_id) + def _on_first_show(self): center = self.rect().center() size = self.size() @@ -103,27 +150,6 @@ class FoldersDialog(QtWidgets.QDialog): # Change reset enabled so model is reset on show event self._soft_reset_enabled = True - def showEvent(self, event): - """Refresh folders widget on show.""" - super().showEvent(event) - if self._first_show: - self._first_show = False - self._on_first_show() - # Refresh on show - self.reset(False) - - def reset(self, force=True): - """Reset widget.""" - if not force and not self._soft_reset_enabled: - return - - if self._soft_reset_enabled: - self._soft_reset_enabled = False - - self._folders_widget.set_project_name( - self._controller.get_current_project_name() - ) - def _on_filter_change(self, text): """Trigger change of filter of folders.""" self._folders_widget.set_name_filter(text) @@ -137,22 +163,11 @@ class FoldersDialog(QtWidgets.QDialog): ) self.done(1) - def set_selected_folders(self, folder_paths): - """Change preselected folder before showing the dialog. - - This also resets model and clean filter. - """ - self.reset(False) - self._filter_input.setText("") - - folder_id = None - for folder_path in folder_paths: - folder_id = self._controller.get_folder_id_from_path(folder_path) - if folder_id: - break - if folder_id: - self._folders_widget.set_selected_folder(folder_id) - - def get_selected_folder_path(self): - """Get selected folder path.""" - return self._selected_folder_path + def _on_my_tasks_change(self, enabled: bool) -> None: + folder_ids = None + if enabled: + entity_ids = self._controller.get_my_tasks_entity_ids( + self._project_name + ) + folder_ids = entity_ids["folder_ids"] + self._folders_widget.set_folder_ids_filter(folder_ids) From ba4ecc6f80b0894e3c9345924106b0eeae6a109b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:04:29 +0100 Subject: [PATCH 30/31] use filters widget in workfiles tool --- .../tools/workfiles/widgets/window.py | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 00362ea866..811fe602d1 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -6,12 +6,11 @@ from ayon_core.tools.utils import ( FoldersWidget, GoToCurrentButton, MessageOverlayObject, - NiceCheckbox, PlaceholderLineEdit, RefreshButton, TasksWidget, + FoldersFiltersWidget, ) -from ayon_core.tools.utils.lib import checkstate_int_to_enum from ayon_core.tools.workfiles.control import BaseWorkfileController from .files_widget import FilesWidget @@ -69,7 +68,6 @@ class WorkfilesToolWindow(QtWidgets.QWidget): self._default_window_flags = flags self._folders_widget = None - self._folder_filter_input = None self._files_widget = None @@ -178,48 +176,33 @@ class WorkfilesToolWindow(QtWidgets.QWidget): col_widget = QtWidgets.QWidget(parent) header_widget = QtWidgets.QWidget(col_widget) - folder_filter_input = PlaceholderLineEdit(header_widget) - folder_filter_input.setPlaceholderText("Filter folders..") + filters_widget = FoldersFiltersWidget(header_widget) go_to_current_btn = GoToCurrentButton(header_widget) refresh_btn = RefreshButton(header_widget) + header_layout = QtWidgets.QHBoxLayout(header_widget) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.addWidget(filters_widget, 1) + header_layout.addWidget(go_to_current_btn, 0) + header_layout.addWidget(refresh_btn, 0) + folder_widget = FoldersWidget( controller, col_widget, handle_expected_selection=True ) - my_tasks_tooltip = ( - "Filter folders and task to only those you are assigned to." - ) - - my_tasks_label = QtWidgets.QLabel("My tasks") - my_tasks_label.setToolTip(my_tasks_tooltip) - - my_tasks_checkbox = NiceCheckbox(folder_widget) - my_tasks_checkbox.setChecked(False) - my_tasks_checkbox.setToolTip(my_tasks_tooltip) - - header_layout = QtWidgets.QHBoxLayout(header_widget) - header_layout.setContentsMargins(0, 0, 0, 0) - header_layout.addWidget(folder_filter_input, 1) - header_layout.addWidget(go_to_current_btn, 0) - header_layout.addWidget(refresh_btn, 0) - header_layout.addWidget(my_tasks_label, 0) - header_layout.addWidget(my_tasks_checkbox, 0) - col_layout = QtWidgets.QVBoxLayout(col_widget) col_layout.setContentsMargins(0, 0, 0, 0) col_layout.addWidget(header_widget, 0) col_layout.addWidget(folder_widget, 1) - folder_filter_input.textChanged.connect(self._on_folder_filter_change) - go_to_current_btn.clicked.connect(self._on_go_to_current_clicked) - refresh_btn.clicked.connect(self._on_refresh_clicked) - my_tasks_checkbox.stateChanged.connect( + filters_widget.text_changed.connect(self._on_folder_filter_change) + filters_widget.my_tasks_changed.connect( self._on_my_tasks_checkbox_state_changed ) + go_to_current_btn.clicked.connect(self._on_go_to_current_clicked) + refresh_btn.clicked.connect(self._on_refresh_clicked) - self._folder_filter_input = folder_filter_input self._folders_widget = folder_widget return col_widget @@ -403,11 +386,10 @@ class WorkfilesToolWindow(QtWidgets.QWidget): else: self.close() - def _on_my_tasks_checkbox_state_changed(self, state): + def _on_my_tasks_checkbox_state_changed(self, enabled: bool) -> None: folder_ids = None task_ids = None - state = checkstate_int_to_enum(state) - if state == QtCore.Qt.Checked: + if enabled: entity_ids = self._controller.get_my_tasks_entity_ids( self._project_name ) From dc7f1556750fdcece3b6391ae83fd8bb7e95eeac Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:07:24 +0100 Subject: [PATCH 31/31] remove unused imports --- client/ayon_core/tools/launcher/ui/hierarchy_page.py | 1 - client/ayon_core/tools/loader/ui/window.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index 8554a5af8c..3c8be4679e 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -9,7 +9,6 @@ from ayon_core.tools.utils import ( TasksWidget, ) from ayon_core.tools.utils.folders_widget import FoldersFiltersWidget -from ayon_core.tools.utils.lib import checkstate_int_to_enum from .workfiles_page import WorkfilesPage diff --git a/client/ayon_core/tools/loader/ui/window.py b/client/ayon_core/tools/loader/ui/window.py index 48704e7481..27e416b495 100644 --- a/client/ayon_core/tools/loader/ui/window.py +++ b/client/ayon_core/tools/loader/ui/window.py @@ -12,7 +12,7 @@ from ayon_core.tools.utils import ( FoldersFiltersWidget, ) from ayon_core.tools.utils.lib import center_window -from ayon_core.tools.utils import ProjectsCombobox, NiceCheckbox +from ayon_core.tools.utils import ProjectsCombobox from ayon_core.tools.common_models import StatusItem from ayon_core.tools.loader.abstract import ProductTypeItem from ayon_core.tools.loader.control import LoaderController