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