From d91e6d07e2bca1d1426e0c63e1f3b1e8d18bde8c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:08:49 +0200 Subject: [PATCH 01/11] added basic implementation of filtering by ids --- .../ayon_core/tools/utils/folders_widget.py | 39 +++++++++++++++++-- client/ayon_core/tools/utils/tasks_widget.py | 36 ++++++++++++++++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index b3b5cebb0e..2e2c5b5d77 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -1,4 +1,5 @@ import collections +from typing import Optional from qtpy import QtWidgets, QtGui, QtCore @@ -33,7 +34,7 @@ class FoldersQtModel(QtGui.QStandardItemModel): refreshed = QtCore.Signal() def __init__(self, controller): - super(FoldersQtModel, self).__init__() + super().__init__() self._controller = controller self._items_by_id = {} @@ -334,6 +335,29 @@ class FoldersQtModel(QtGui.QStandardItemModel): self.refreshed.emit() +class FoldersProxyModel(RecursiveSortFilterProxyModel): + def __init__(self): + super().__init__() + + self._folder_ids_filter = None + + def set_folder_ids_filter(self, folder_ids: Optional[list[str]]): + if self._folder_ids_filter == folder_ids: + return + self._folder_ids_filter = folder_ids + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent_index): + if self._folder_ids_filter is not None: + if not self._folder_ids_filter: + return False + source_index = self.sourceModel().index(row, 0, parent_index) + folder_id = source_index.data(FOLDER_ID_ROLE) + if folder_id not in self._folder_ids_filter: + return False + return super().filterAcceptsRow(row, parent_index) + + class FoldersWidget(QtWidgets.QWidget): """Folders widget. @@ -369,13 +393,13 @@ class FoldersWidget(QtWidgets.QWidget): refreshed = QtCore.Signal() def __init__(self, controller, parent, handle_expected_selection=False): - super(FoldersWidget, self).__init__(parent) + super().__init__(parent) folders_view = TreeView(self) folders_view.setHeaderHidden(True) folders_model = FoldersQtModel(controller) - folders_proxy_model = RecursiveSortFilterProxyModel() + folders_proxy_model = FoldersProxyModel() folders_proxy_model.setSourceModel(folders_model) folders_proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) @@ -446,6 +470,15 @@ class FoldersWidget(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 refresh(self): """Refresh folders model. diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py index 30846e6cda..00960b6ba6 100644 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -1,3 +1,5 @@ +from typing import Optional + from qtpy import QtWidgets, QtGui, QtCore from ayon_core.style import ( @@ -343,6 +345,29 @@ class TasksQtModel(QtGui.QStandardItemModel): return self._has_content +class TasksProxyModel(QtCore.QSortFilterProxyModel): + def __init__(self): + super().__init__() + + self._task_ids_filter: Optional[set[str]] = None + + def set_task_ids_filter(self, task_ids: Optional[set[str]]): + if self._task_ids_filter == task_ids: + return + self._task_ids_filter = task_ids + self.invalidateFilter() + + def filterAcceptsRow(self, row, parent_index): + if self._task_ids_filter is not None: + if not self._task_ids_filter: + return False + source_index = self.sourceModel().index(row, 0, parent_index) + task_id = source_index.data(ITEM_ID_ROLE) + if task_id not in self._task_ids_filter: + return False + return super().filterAcceptsRow(row, parent_index) + + class TasksWidget(QtWidgets.QWidget): """Tasks widget. @@ -364,7 +389,7 @@ class TasksWidget(QtWidgets.QWidget): tasks_view.setIndentation(0) tasks_model = TasksQtModel(controller) - tasks_proxy_model = QtCore.QSortFilterProxyModel() + tasks_proxy_model = TasksProxyModel() tasks_proxy_model.setSourceModel(tasks_model) tasks_proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) @@ -490,6 +515,15 @@ class TasksWidget(QtWidgets.QWidget): ) return True + 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 _on_tasks_refresh_finished(self, event): """Tasks were refreshed in controller. From 37bf4a28c7fda385b86a44f2efb8330f5d6f5a5d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:09:42 +0200 Subject: [PATCH 02/11] added method to get task and folder ids for assignees --- .../tools/common_models/hierarchy.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/client/ayon_core/tools/common_models/hierarchy.py b/client/ayon_core/tools/common_models/hierarchy.py index edff8471b0..891eb80960 100644 --- a/client/ayon_core/tools/common_models/hierarchy.py +++ b/client/ayon_core/tools/common_models/hierarchy.py @@ -227,6 +227,9 @@ class HierarchyModel(object): self._tasks_by_id = NestedCacheItem( levels=2, default_factory=dict, lifetime=self.lifetime) + self._entity_ids_by_assignee = NestedCacheItem( + levels=2, default_factory=dict, lifetime=self.lifetime) + self._folders_refreshing = set() self._tasks_refreshing = set() self._controller = controller @@ -238,6 +241,8 @@ class HierarchyModel(object): self._task_items.reset() self._tasks_by_id.reset() + self._entity_ids_by_assignee.reset() + def refresh_project(self, project_name): """Force to refresh folder items for a project. @@ -461,6 +466,54 @@ class HierarchyModel(object): output = self.get_task_entities(project_name, {task_id}) return output[task_id] + def get_entity_ids_for_assignees( + self, project_name: str, assignees: list[str] + ): + folder_ids = set() + task_ids = set() + output = { + "folder_ids": folder_ids, + "task_ids": task_ids, + } + assignees = set(assignees) + for assignee in tuple(assignees): + cache = self._entity_ids_by_assignee[project_name][assignee] + if cache.is_valid: + assignees.discard(assignee) + assignee_data = cache.get_data() + folder_ids.update(assignee_data["folder_ids"]) + task_ids.update(assignee_data["task_ids"]) + + if not assignees: + return output + + tasks = ayon_api.get_tasks( + project_name, + assignees_all=assignees, + fields={"id", "folderId", "assignees"}, + ) + tasks_assignee = {} + for task in tasks: + folder_ids.add(task["folderId"]) + task_ids.add(task["id"]) + for assignee in task["assignees"]: + tasks_assignee.setdefault(assignee, []).append(task) + + for assignee, tasks in tasks_assignee.items(): + cache = self._entity_ids_by_assignee[project_name][assignee] + assignee_folder_ids = set() + assignee_task_ids = set() + assignee_data = { + "folder_ids": assignee_folder_ids, + "task_ids": assignee_task_ids, + } + for task in tasks: + assignee_folder_ids.add(task["folderId"]) + assignee_task_ids.add(task["id"]) + cache.update_data(assignee_data) + + return output + @contextlib.contextmanager def _folder_refresh_event_manager(self, project_name, sender): self._folders_refreshing.add(project_name) From d7b5e06077dd9ec99bf5e1e636338f33fc7eed40 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:10:06 +0200 Subject: [PATCH 03/11] basic implementation of setting filters in launcher --- client/ayon_core/tools/launcher/abstract.py | 42 ++++++++++++------- client/ayon_core/tools/launcher/control.py | 20 ++++++++- .../tools/launcher/ui/hierarchy_page.py | 34 +++++++++++++-- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/client/ayon_core/tools/launcher/abstract.py b/client/ayon_core/tools/launcher/abstract.py index 63ba4cd717..ea0842f24d 100644 --- a/client/ayon_core/tools/launcher/abstract.py +++ b/client/ayon_core/tools/launcher/abstract.py @@ -160,8 +160,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: list[FolderItem]: Minimum possible information needed for visualisation of folder hierarchy. - """ + """ pass @abstractmethod @@ -180,8 +180,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: list[TaskItem]: Minimum possible information needed for visualisation of tasks. - """ + """ pass @abstractmethod @@ -190,8 +190,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: Union[str, None]: Selected project name. - """ + """ pass @abstractmethod @@ -200,8 +200,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: Union[str, None]: Selected folder id. - """ + """ pass @abstractmethod @@ -210,8 +210,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: Union[str, None]: Selected task id. - """ + """ pass @abstractmethod @@ -220,8 +220,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: Union[str, None]: Selected task name. - """ + """ pass @abstractmethod @@ -238,8 +238,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: dict[str, Union[str, None]]: Selected context. - """ + """ pass @abstractmethod @@ -249,8 +249,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Args: project_name (Union[str, None]): Project nameor None if no project is selected. - """ + """ pass @abstractmethod @@ -260,8 +260,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Args: folder_id (Union[str, None]): Folder id or None if no folder is selected. - """ + """ pass @abstractmethod @@ -273,8 +273,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): is selected. task_name (Union[str, None]): Task name or None if no task is selected. - """ + """ pass # Actions @@ -290,8 +290,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Returns: list[ActionItem]: List of action items that should be shown for given context. - """ + """ pass @abstractmethod @@ -303,8 +303,8 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): folder_id (Union[str, None]): Folder id. task_id (Union[str, None]): Task id. action_id (str): Action identifier. - """ + """ pass @abstractmethod @@ -317,10 +317,10 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): project_name (Union[str, None]): Project name. folder_id (Union[str, None]): Folder id. task_id (Union[str, None]): Task id. - action_id (Iterable[str]): Action identifiers. + action_ids (Iterable[str]): Action identifiers. enabled (bool): New value of force not open workfile. - """ + """ pass @abstractmethod @@ -340,5 +340,17 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon): Triggers 'controller.refresh.actions.started' event at the beginning and 'controller.refresh.actions.finished' at the end. """ - + pass + + @abstractmethod + def get_my_tasks_entity_ids(self, project_name: str): + """Get entity ids for my tasks. + + Args: + project_name (str): Project name. + + Returns: + dict[str, Union[list[str]]]: Folder and task ids. + + """ pass diff --git a/client/ayon_core/tools/launcher/control.py b/client/ayon_core/tools/launcher/control.py index d60e4747e3..45cb2b7945 100644 --- a/client/ayon_core/tools/launcher/control.py +++ b/client/ayon_core/tools/launcher/control.py @@ -1,4 +1,4 @@ -from ayon_core.lib import Logger +from ayon_core.lib import Logger, get_ayon_username from ayon_core.lib.events import QueuedEventSystem from ayon_core.settings import get_project_settings from ayon_core.tools.common_models import ProjectsModel, HierarchyModel @@ -6,6 +6,8 @@ from ayon_core.tools.common_models import ProjectsModel, HierarchyModel from .abstract import AbstractLauncherFrontEnd, AbstractLauncherBackend from .models import LauncherSelectionModel, ActionsModel +NOT_SET = object() + class BaseLauncherController( AbstractLauncherFrontEnd, AbstractLauncherBackend @@ -15,6 +17,8 @@ class BaseLauncherController( self._event_system = None self._log = None + self._username = NOT_SET + self._selection_model = LauncherSelectionModel(self) self._projects_model = ProjectsModel(self) self._hierarchy_model = HierarchyModel(self) @@ -168,5 +172,19 @@ class BaseLauncherController( self._emit_event("controller.refresh.actions.finished") + def get_my_tasks_entity_ids(self, project_name: str): + username = self._get_my_username() + assignees = [] + if username: + assignees.append(username) + return self._hierarchy_model.get_entity_ids_for_assignees( + 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/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index ad48e8ac77..78ca5aaa1e 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -10,12 +10,13 @@ from ayon_core.tools.utils import ( ProjectsCombobox, FoldersWidget, TasksWidget, + NiceCheckbox, ) class HierarchyPage(QtWidgets.QWidget): def __init__(self, controller, parent): - super(HierarchyPage, self).__init__(parent) + super().__init__(parent) # Header header_widget = QtWidgets.QWidget(self) @@ -46,14 +47,23 @@ class HierarchyPage(QtWidgets.QWidget): # - Folders widget with filter folders_wrapper = QtWidgets.QWidget(content_body) - folders_filter_text = PlaceholderLineEdit(folders_wrapper) + filters_widget = QtWidgets.QWidget(folders_wrapper) + + folders_filter_text = PlaceholderLineEdit(filters_widget) folders_filter_text.setPlaceholderText("Filter folders...") + my_tasks_checkbox = NiceCheckbox(filters_widget) + + 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_checkbox, 0) + folders_widget = FoldersWidget(controller, folders_wrapper) folders_wrapper_layout = QtWidgets.QVBoxLayout(folders_wrapper) folders_wrapper_layout.setContentsMargins(0, 0, 0, 0) - folders_wrapper_layout.addWidget(folders_filter_text, 0) + folders_wrapper_layout.addWidget(filters_widget, 0) folders_wrapper_layout.addWidget(folders_widget, 1) # - Tasks widget @@ -72,6 +82,9 @@ 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( + self._on_my_tasks_checkbox_state_changed + ) self._is_visible = False self._controller = controller @@ -81,6 +94,8 @@ class HierarchyPage(QtWidgets.QWidget): self._folders_widget = folders_widget self._tasks_widget = tasks_widget + self._project_name = None + # Post init projects_combobox.set_listen_to_selection_change(self._is_visible) @@ -91,6 +106,7 @@ class HierarchyPage(QtWidgets.QWidget): self._projects_combobox.set_listen_to_selection_change(visible) if visible and project_name: self._projects_combobox.set_selection(project_name) + self._project_name = project_name def refresh(self): self._folders_widget.refresh() @@ -104,3 +120,15 @@ 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): + folder_ids = None + task_ids = None + if state == QtCore.Qt.Checked: + entity_ids = self._controller.get_my_tasks_entity_ids( + self._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) \ No newline at end of file From e7bae0f2606b6d9c2c37994fed3770090fa1f31d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 12:10:15 +0200 Subject: [PATCH 04/11] use py3 super calls --- client/ayon_core/tools/launcher/ui/window.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/window.py b/client/ayon_core/tools/launcher/ui/window.py index 2d52a73c38..aa336108ed 100644 --- a/client/ayon_core/tools/launcher/ui/window.py +++ b/client/ayon_core/tools/launcher/ui/window.py @@ -17,7 +17,7 @@ class LauncherWindow(QtWidgets.QWidget): page_side_anim_interval = 250 def __init__(self, controller=None, parent=None): - super(LauncherWindow, self).__init__(parent) + super().__init__(parent) if controller is None: controller = BaseLauncherController() @@ -153,14 +153,14 @@ class LauncherWindow(QtWidgets.QWidget): self.resize(520, 740) def showEvent(self, event): - super(LauncherWindow, self).showEvent(event) + super().showEvent(event) self._window_is_active = True if not self._actions_refresh_timer.isActive(): self._actions_refresh_timer.start() self._controller.refresh() def closeEvent(self, event): - super(LauncherWindow, self).closeEvent(event) + super().closeEvent(event) self._window_is_active = False self._actions_refresh_timer.stop() @@ -176,7 +176,7 @@ class LauncherWindow(QtWidgets.QWidget): self._on_actions_refresh_timeout() self._actions_refresh_timer.start() - super(LauncherWindow, self).changeEvent(event) + super().changeEvent(event) def _on_actions_refresh_timeout(self): # Stop timer if widget is not visible From 0f974c135225b5fce01ade11f6c945dda943c9d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:15:31 +0200 Subject: [PATCH 05/11] fix state change handling --- client/ayon_core/tools/launcher/ui/hierarchy_page.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 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 78ca5aaa1e..c3255b2d47 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -5,13 +5,12 @@ from ayon_core.tools.utils import ( PlaceholderLineEdit, SquareButton, RefreshButton, -) -from ayon_core.tools.utils import ( ProjectsCombobox, FoldersWidget, TasksWidget, NiceCheckbox, ) +from ayon_core.tools.utils.lib import checkstate_int_to_enum class HierarchyPage(QtWidgets.QWidget): @@ -52,11 +51,14 @@ class HierarchyPage(QtWidgets.QWidget): folders_filter_text = PlaceholderLineEdit(filters_widget) folders_filter_text.setPlaceholderText("Filter folders...") + my_tasks_label = QtWidgets.QLabel("My tasks", self) my_tasks_checkbox = NiceCheckbox(filters_widget) + my_tasks_checkbox.setChecked(False) 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) folders_widget = FoldersWidget(controller, folders_wrapper) @@ -91,6 +93,7 @@ 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 @@ -111,6 +114,9 @@ class HierarchyPage(QtWidgets.QWidget): def refresh(self): self._folders_widget.refresh() self._tasks_widget.refresh() + self._on_my_tasks_checkbox_state_changed( + self._on_my_tasks_checkbox.checkState() + ) def _on_back_clicked(self): self._controller.set_selected_project(None) @@ -124,6 +130,7 @@ class HierarchyPage(QtWidgets.QWidget): def _on_my_tasks_checkbox_state_changed(self, state): folder_ids = None task_ids = None + state = checkstate_int_to_enum(state) if state == QtCore.Qt.Checked: entity_ids = self._controller.get_my_tasks_entity_ids( self._project_name From c4c609432b0579e6d6528ba9217bdffa878a3849 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:15:45 +0200 Subject: [PATCH 06/11] skip filtering if items that are not tasks --- client/ayon_core/tools/utils/tasks_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py index 00960b6ba6..6037c47448 100644 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -363,7 +363,7 @@ class TasksProxyModel(QtCore.QSortFilterProxyModel): return False source_index = self.sourceModel().index(row, 0, parent_index) task_id = source_index.data(ITEM_ID_ROLE) - if task_id not in self._task_ids_filter: + if task_id is not None and task_id not in self._task_ids_filter: return False return super().filterAcceptsRow(row, parent_index) From ba31114aa38d093056fe9e454630c667fd05d354 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:55:42 +0200 Subject: [PATCH 07/11] move the checkbox next to project combo --- .../tools/launcher/ui/hierarchy_page.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index c3255b2d47..fbe1630474 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -26,12 +26,18 @@ class HierarchyPage(QtWidgets.QWidget): projects_combobox = ProjectsCombobox(controller, header_widget) + my_tasks_label = QtWidgets.QLabel("My tasks", header_widget) + my_tasks_checkbox = NiceCheckbox(header_widget) + my_tasks_checkbox.setChecked(False) + refresh_btn = RefreshButton(header_widget) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(btn_back, 0) header_layout.addWidget(projects_combobox, 1) + header_layout.addWidget(my_tasks_label, 0) + header_layout.addWidget(my_tasks_checkbox, 0) header_layout.addWidget(refresh_btn, 0) # Body - Folders + Tasks selection @@ -46,26 +52,14 @@ class HierarchyPage(QtWidgets.QWidget): # - Folders widget with filter folders_wrapper = QtWidgets.QWidget(content_body) - filters_widget = QtWidgets.QWidget(folders_wrapper) - - folders_filter_text = PlaceholderLineEdit(filters_widget) + folders_filter_text = PlaceholderLineEdit(folders_wrapper) folders_filter_text.setPlaceholderText("Filter folders...") - my_tasks_label = QtWidgets.QLabel("My tasks", self) - my_tasks_checkbox = NiceCheckbox(filters_widget) - my_tasks_checkbox.setChecked(False) - - 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) - folders_widget = FoldersWidget(controller, folders_wrapper) folders_wrapper_layout = QtWidgets.QVBoxLayout(folders_wrapper) folders_wrapper_layout.setContentsMargins(0, 0, 0, 0) - folders_wrapper_layout.addWidget(filters_widget, 0) + folders_wrapper_layout.addWidget(folders_filter_text, 0) folders_wrapper_layout.addWidget(folders_widget, 1) # - Tasks widget From 9eb652bd953c6b6e5d38459fa34c2b872edaabda Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:56:52 +0200 Subject: [PATCH 08/11] add missing line --- 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 fbe1630474..f8eab20ddc 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -132,4 +132,4 @@ class HierarchyPage(QtWidgets.QWidget): 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) \ No newline at end of file + self._tasks_widget.set_task_ids_filter(task_ids) From 81eff5ae6fb7658eab69579204fc7ff00ec5966c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 2 Apr 2025 09:47:46 +0200 Subject: [PATCH 09/11] fix attribute name --- 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 f8eab20ddc..835a963323 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -109,7 +109,7 @@ class HierarchyPage(QtWidgets.QWidget): self._folders_widget.refresh() self._tasks_widget.refresh() self._on_my_tasks_checkbox_state_changed( - self._on_my_tasks_checkbox.checkState() + self._my_tasks_checkbox.checkState() ) def _on_back_clicked(self): From 009348bca9bc89c93433568b99a575efe6182751 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:01:07 +0200 Subject: [PATCH 10/11] move widgets around --- .../tools/launcher/ui/hierarchy_page.py | 38 +++++++++++-------- .../ayon_core/tools/utils/folders_widget.py | 6 +++ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/client/ayon_core/tools/launcher/ui/hierarchy_page.py b/client/ayon_core/tools/launcher/ui/hierarchy_page.py index 835a963323..7c34989947 100644 --- a/client/ayon_core/tools/launcher/ui/hierarchy_page.py +++ b/client/ayon_core/tools/launcher/ui/hierarchy_page.py @@ -26,18 +26,12 @@ class HierarchyPage(QtWidgets.QWidget): projects_combobox = ProjectsCombobox(controller, header_widget) - my_tasks_label = QtWidgets.QLabel("My tasks", header_widget) - my_tasks_checkbox = NiceCheckbox(header_widget) - my_tasks_checkbox.setChecked(False) - refresh_btn = RefreshButton(header_widget) header_layout = QtWidgets.QHBoxLayout(header_widget) header_layout.setContentsMargins(0, 0, 0, 0) header_layout.addWidget(btn_back, 0) header_layout.addWidget(projects_combobox, 1) - header_layout.addWidget(my_tasks_label, 0) - header_layout.addWidget(my_tasks_checkbox, 0) header_layout.addWidget(refresh_btn, 0) # Body - Folders + Tasks selection @@ -49,23 +43,36 @@ class HierarchyPage(QtWidgets.QWidget): ) content_body.setOrientation(QtCore.Qt.Horizontal) - # - Folders widget with filter - folders_wrapper = QtWidgets.QWidget(content_body) + # - filters + filters_widget = QtWidgets.QWidget(self) - folders_filter_text = PlaceholderLineEdit(folders_wrapper) + folders_filter_text = PlaceholderLineEdit(filters_widget) folders_filter_text.setPlaceholderText("Filter folders...") - folders_widget = FoldersWidget(controller, folders_wrapper) + 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) - folders_wrapper_layout = QtWidgets.QVBoxLayout(folders_wrapper) - folders_wrapper_layout.setContentsMargins(0, 0, 0, 0) - folders_wrapper_layout.addWidget(folders_filter_text, 0) - folders_wrapper_layout.addWidget(folders_widget, 1) + 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) + + # - Folders widget + folders_widget = FoldersWidget(controller, content_body) + folders_widget.set_header_visible(True) # - Tasks widget tasks_widget = TasksWidget(controller, content_body) - content_body.addWidget(folders_wrapper) + content_body.addWidget(folders_widget) content_body.addWidget(tasks_widget) content_body.setStretchFactor(0, 100) content_body.setStretchFactor(1, 65) @@ -73,6 +80,7 @@ class HierarchyPage(QtWidgets.QWidget): main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(header_widget, 0) + main_layout.addWidget(filters_widget, 0) main_layout.addWidget(content_body, 1) btn_back.clicked.connect(self._on_back_clicked) diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index 2e2c5b5d77..7e3f2865e7 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -36,6 +36,9 @@ class FoldersQtModel(QtGui.QStandardItemModel): def __init__(self, controller): super().__init__() + self.setColumnCount(1) + self.setHeaderData(0, QtCore.Qt.Horizontal, "Folders") + self._controller = controller self._items_by_id = {} self._parent_id_by_id = {} @@ -479,6 +482,9 @@ class FoldersWidget(QtWidgets.QWidget): """ self._folders_proxy_model.set_folder_ids_filter(folder_ids) + def set_header_visible(self, visible: bool): + self._folders_view.setHeaderHidden(not visible) + def refresh(self): """Refresh folders model. From 9ebd2bbc640af6fc4feded1dd9416f54947c45a4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 2 Apr 2025 14:20:53 +0200 Subject: [PATCH 11/11] fix typehints for older pythons --- client/ayon_core/tools/utils/folders_widget.py | 1 + client/ayon_core/tools/utils/tasks_widget.py | 1 + 2 files changed, 2 insertions(+) diff --git a/client/ayon_core/tools/utils/folders_widget.py b/client/ayon_core/tools/utils/folders_widget.py index 7e3f2865e7..7b71dd087c 100644 --- a/client/ayon_core/tools/utils/folders_widget.py +++ b/client/ayon_core/tools/utils/folders_widget.py @@ -1,3 +1,4 @@ +from __future__ import annotations import collections from typing import Optional diff --git a/client/ayon_core/tools/utils/tasks_widget.py b/client/ayon_core/tools/utils/tasks_widget.py index 6037c47448..9118611c23 100644 --- a/client/ayon_core/tools/utils/tasks_widget.py +++ b/client/ayon_core/tools/utils/tasks_widget.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import Optional from qtpy import QtWidgets, QtGui, QtCore