mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into bugfix/implement_backward_compatibility_for_version_up_workfile
This commit is contained in:
commit
c5e8e7451b
38 changed files with 8795 additions and 147 deletions
|
|
@ -5,7 +5,6 @@ from abc import ABCMeta, abstractmethod
|
|||
import ayon_api
|
||||
import six
|
||||
|
||||
from ayon_core.style import get_default_entity_icon_color
|
||||
from ayon_core.lib import NestedCacheItem
|
||||
|
||||
HIERARCHY_MODEL_SENDER = "hierarchy.model"
|
||||
|
|
@ -31,11 +30,10 @@ class FolderItem:
|
|||
path (str): Folder path.
|
||||
folder_type (str): Type of folder.
|
||||
label (Union[str, None]): Folder label.
|
||||
icon (Union[dict[str, Any], None]): Icon definition.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, entity_id, parent_id, name, path, folder_type, label, icon
|
||||
self, entity_id, parent_id, name, path, folder_type, label
|
||||
):
|
||||
self.entity_id = entity_id
|
||||
self.parent_id = parent_id
|
||||
|
|
@ -43,13 +41,6 @@ class FolderItem:
|
|||
self.path = path
|
||||
self.folder_type = folder_type
|
||||
self.label = label or name
|
||||
if not icon:
|
||||
icon = {
|
||||
"type": "awesome-font",
|
||||
"name": "fa.folder",
|
||||
"color": get_default_entity_icon_color()
|
||||
}
|
||||
self.icon = icon
|
||||
|
||||
def to_data(self):
|
||||
"""Converts folder item to data.
|
||||
|
|
@ -65,7 +56,6 @@ class FolderItem:
|
|||
"path": self.path,
|
||||
"folder_type": self.folder_type,
|
||||
"label": self.label,
|
||||
"icon": self.icon,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -95,23 +85,15 @@ class TaskItem:
|
|||
name (str): Name of task.
|
||||
task_type (str): Type of task.
|
||||
parent_id (str): Parent folder id.
|
||||
icon (Union[dict[str, Any], None]): Icon definitions.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, task_id, name, task_type, parent_id, icon
|
||||
self, task_id, name, task_type, parent_id
|
||||
):
|
||||
self.task_id = task_id
|
||||
self.name = name
|
||||
self.task_type = task_type
|
||||
self.parent_id = parent_id
|
||||
if icon is None:
|
||||
icon = {
|
||||
"type": "awesome-font",
|
||||
"name": "fa.male",
|
||||
"color": get_default_entity_icon_color()
|
||||
}
|
||||
self.icon = icon
|
||||
|
||||
self._label = None
|
||||
|
||||
|
|
@ -149,7 +131,6 @@ class TaskItem:
|
|||
"name": self.name,
|
||||
"parent_id": self.parent_id,
|
||||
"task_type": self.task_type,
|
||||
"icon": self.icon,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
|
@ -180,8 +161,7 @@ def _get_task_items_from_tasks(tasks):
|
|||
task["id"],
|
||||
task["name"],
|
||||
task["type"],
|
||||
folder_id,
|
||||
None
|
||||
folder_id
|
||||
))
|
||||
return output
|
||||
|
||||
|
|
@ -197,8 +177,7 @@ def _get_folder_item_from_hierarchy_item(item):
|
|||
name,
|
||||
path,
|
||||
item["folderType"],
|
||||
item["label"],
|
||||
None,
|
||||
item["label"]
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -210,8 +189,7 @@ def _get_folder_item_from_entity(entity):
|
|||
name,
|
||||
entity["path"],
|
||||
entity["folderType"],
|
||||
entity["label"] or name,
|
||||
None,
|
||||
entity["label"] or name
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,74 @@ class StatusItem:
|
|||
)
|
||||
|
||||
|
||||
class FolderTypeItem:
|
||||
"""Item representing folder type of project.
|
||||
|
||||
Args:
|
||||
name (str): Folder type name ("Shot").
|
||||
short (str): Short folder type name ("sh").
|
||||
icon (str): Icon name in MaterialIcons ("fiber_new").
|
||||
|
||||
"""
|
||||
def __init__(self, name, short, icon):
|
||||
self.name = name
|
||||
self.short = short
|
||||
self.icon = icon
|
||||
|
||||
def to_data(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"short": self.short,
|
||||
"icon": self.icon,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
return cls(**data)
|
||||
|
||||
@classmethod
|
||||
def from_project_item(cls, folder_type_data):
|
||||
return cls(
|
||||
name=folder_type_data["name"],
|
||||
short=folder_type_data["shortName"],
|
||||
icon=folder_type_data["icon"],
|
||||
)
|
||||
|
||||
|
||||
class TaskTypeItem:
|
||||
"""Item representing task type of project.
|
||||
|
||||
Args:
|
||||
name (str): Task type name ("Shot").
|
||||
short (str): Short task type name ("sh").
|
||||
icon (str): Icon name in MaterialIcons ("fiber_new").
|
||||
|
||||
"""
|
||||
def __init__(self, name, short, icon):
|
||||
self.name = name
|
||||
self.short = short
|
||||
self.icon = icon
|
||||
|
||||
def to_data(self):
|
||||
return {
|
||||
"name": self.name,
|
||||
"short": self.short,
|
||||
"icon": self.icon,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data):
|
||||
return cls(**data)
|
||||
|
||||
@classmethod
|
||||
def from_project_item(cls, task_type_data):
|
||||
return cls(
|
||||
name=task_type_data["name"],
|
||||
short=task_type_data["shortName"],
|
||||
icon=task_type_data["icon"],
|
||||
)
|
||||
|
||||
|
||||
class ProjectItem:
|
||||
"""Item representing folder entity on a server.
|
||||
|
||||
|
|
@ -147,19 +215,21 @@ def _get_project_items_from_entitiy(projects):
|
|||
class ProjectsModel(object):
|
||||
def __init__(self, controller):
|
||||
self._projects_cache = CacheItem(default_factory=list)
|
||||
self._project_statuses_cache = NestedCacheItem(
|
||||
levels=1, default_factory=list
|
||||
)
|
||||
self._projects_by_name = NestedCacheItem(
|
||||
levels=1, default_factory=list
|
||||
)
|
||||
self._project_statuses_cache = {}
|
||||
self._folder_types_cache = {}
|
||||
self._task_types_cache = {}
|
||||
|
||||
self._is_refreshing = False
|
||||
self._controller = controller
|
||||
|
||||
def reset(self):
|
||||
self._project_statuses_cache = {}
|
||||
self._folder_types_cache = {}
|
||||
self._task_types_cache = {}
|
||||
self._projects_cache.reset()
|
||||
self._project_statuses_cache.reset()
|
||||
self._projects_by_name.reset()
|
||||
|
||||
def refresh(self):
|
||||
|
|
@ -217,22 +287,87 @@ class ProjectsModel(object):
|
|||
list[StatusItem]: Status items for project.
|
||||
|
||||
"""
|
||||
statuses_cache = self._project_statuses_cache[project_name]
|
||||
if not statuses_cache.is_valid:
|
||||
with self._project_statuses_refresh_event_manager(
|
||||
sender, project_name
|
||||
if project_name is None:
|
||||
return []
|
||||
|
||||
statuses_cache = self._project_statuses_cache.get(project_name)
|
||||
if (
|
||||
statuses_cache is not None
|
||||
and not self._projects_cache.is_valid
|
||||
):
|
||||
statuses_cache = None
|
||||
|
||||
if statuses_cache is None:
|
||||
with self._project_items_refresh_event_manager(
|
||||
sender, project_name, "statuses"
|
||||
):
|
||||
project_entity = None
|
||||
if project_name:
|
||||
project_entity = self.get_project_entity(project_name)
|
||||
project_entity = self.get_project_entity(project_name)
|
||||
statuses = []
|
||||
if project_entity:
|
||||
statuses = [
|
||||
StatusItem.from_project_item(status)
|
||||
for status in project_entity["statuses"]
|
||||
]
|
||||
statuses_cache.update_data(statuses)
|
||||
return statuses_cache.get_data()
|
||||
statuses_cache = statuses
|
||||
self._project_statuses_cache[project_name] = statuses_cache
|
||||
return list(statuses_cache)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender):
|
||||
"""Get project status items.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (Union[str, None]): Name of sender who asked for items.
|
||||
|
||||
Returns:
|
||||
list[FolderType]: Folder type items for project.
|
||||
|
||||
"""
|
||||
return self._get_project_items(
|
||||
project_name,
|
||||
sender,
|
||||
"folder_types",
|
||||
self._folder_types_cache,
|
||||
self._folder_type_items_getter,
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender):
|
||||
"""Get project task type items.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (Union[str, None]): Name of sender who asked for items.
|
||||
|
||||
Returns:
|
||||
list[TaskTypeItem]: Task type items for project.
|
||||
|
||||
"""
|
||||
return self._get_project_items(
|
||||
project_name,
|
||||
sender,
|
||||
"task_types",
|
||||
self._task_types_cache,
|
||||
self._task_type_items_getter,
|
||||
)
|
||||
|
||||
def _get_project_items(
|
||||
self, project_name, sender, item_type, cache_obj, getter
|
||||
):
|
||||
if (
|
||||
project_name in cache_obj
|
||||
and (
|
||||
project_name is None
|
||||
or self._projects_by_name[project_name].is_valid
|
||||
)
|
||||
):
|
||||
return cache_obj[project_name]
|
||||
|
||||
with self._project_items_refresh_event_manager(
|
||||
sender, project_name, item_type
|
||||
):
|
||||
cache_value = getter(self.get_project_entity(project_name))
|
||||
cache_obj[project_name] = cache_value
|
||||
return cache_value
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _project_refresh_event_manager(self, sender):
|
||||
|
|
@ -254,9 +389,11 @@ class ProjectsModel(object):
|
|||
self._is_refreshing = False
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _project_statuses_refresh_event_manager(self, sender, project_name):
|
||||
def _project_items_refresh_event_manager(
|
||||
self, sender, project_name, item_type
|
||||
):
|
||||
self._controller.emit_event(
|
||||
"projects.statuses.refresh.started",
|
||||
f"projects.{item_type}.refresh.started",
|
||||
{"sender": sender, "project_name": project_name},
|
||||
PROJECTS_MODEL_SENDER
|
||||
)
|
||||
|
|
@ -265,7 +402,7 @@ class ProjectsModel(object):
|
|||
|
||||
finally:
|
||||
self._controller.emit_event(
|
||||
"projects.statuses.refresh.finished",
|
||||
f"projects.{item_type}.refresh.finished",
|
||||
{"sender": sender, "project_name": project_name},
|
||||
PROJECTS_MODEL_SENDER
|
||||
)
|
||||
|
|
@ -282,3 +419,27 @@ class ProjectsModel(object):
|
|||
def _query_projects(self):
|
||||
projects = ayon_api.get_projects(fields=["name", "active", "library"])
|
||||
return _get_project_items_from_entitiy(projects)
|
||||
|
||||
def _status_items_getter(self, project_entity):
|
||||
if not project_entity:
|
||||
return []
|
||||
return [
|
||||
StatusItem.from_project_item(status)
|
||||
for status in project_entity["statuses"]
|
||||
]
|
||||
|
||||
def _folder_type_items_getter(self, project_entity):
|
||||
if not project_entity:
|
||||
return []
|
||||
return [
|
||||
FolderTypeItem.from_project_item(folder_type)
|
||||
for folder_type in project_entity["folderTypes"]
|
||||
]
|
||||
|
||||
def _task_type_items_getter(self, project_entity):
|
||||
if not project_entity:
|
||||
return []
|
||||
return [
|
||||
TaskTypeItem.from_project_item(task_type)
|
||||
for task_type in project_entity["taskTypes"]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -227,6 +227,16 @@ class ContextDialogController:
|
|||
def get_project_items(self, sender=None):
|
||||
return self._projects_model.get_project_items(sender)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_task_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_folder_items(self, project_name, sender=None):
|
||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||
|
||||
|
|
|
|||
|
|
@ -104,8 +104,48 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon):
|
|||
Returns:
|
||||
list[ProjectItem]: Minimum possible information needed
|
||||
for visualisation of folder hierarchy.
|
||||
"""
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
"""Folder type items for a project.
|
||||
|
||||
This function may trigger events with topics
|
||||
'projects.folder_types.refresh.started' and
|
||||
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||
value in data.
|
||||
That may help to avoid re-refresh of items in UI elements.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (str): Who requested folder type items.
|
||||
|
||||
Returns:
|
||||
list[FolderTypeItem]: Folder type information.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
"""Task type items for a project.
|
||||
|
||||
This function may trigger events with topics
|
||||
'projects.task_types.refresh.started' and
|
||||
'projects.task_types.refresh.finished' which will contain 'sender'
|
||||
value in data.
|
||||
That may help to avoid re-refresh of items in UI elements.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (str): Who requested task type items.
|
||||
|
||||
Returns:
|
||||
list[TaskTypeItem]: Task type information.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
|
|
|
|||
|
|
@ -59,12 +59,23 @@ class BaseLauncherController(
|
|||
def get_project_items(self, sender=None):
|
||||
return self._projects_model.get_project_items(sender)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_task_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_folder_items(self, project_name, sender=None):
|
||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||
|
||||
def get_task_items(self, project_name, folder_id, sender=None):
|
||||
return self._hierarchy_model.get_task_items(
|
||||
project_name, folder_id, sender)
|
||||
project_name, folder_id, sender
|
||||
)
|
||||
|
||||
# Project settings for applications actions
|
||||
def get_project_settings(self, project_name):
|
||||
|
|
|
|||
|
|
@ -510,6 +510,26 @@ class FrontendLoaderController(_BaseLoaderController):
|
|||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
"""Folder type items for a project.
|
||||
|
||||
This function may trigger events with topics
|
||||
'projects.folder_types.refresh.started' and
|
||||
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||
value in data.
|
||||
That may help to avoid re-refresh of items in UI elements.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (str): Who requested folder type items.
|
||||
|
||||
Returns:
|
||||
list[FolderTypeItem]: Folder type information.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_project_status_items(self, project_name, sender=None):
|
||||
"""Items for all projects available on server.
|
||||
|
|
|
|||
|
|
@ -180,6 +180,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
|
|||
def get_project_items(self, sender=None):
|
||||
return self._projects_model.get_project_items(sender)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_project_status_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_project_status_items(
|
||||
project_name, sender
|
||||
|
|
|
|||
|
|
@ -188,16 +188,6 @@ class LoaderFoldersModel(FoldersQtModel):
|
|||
|
||||
self._colored_items = set()
|
||||
|
||||
def _fill_item_data(self, item, folder_item):
|
||||
"""
|
||||
|
||||
Args:
|
||||
item (QtGui.QStandardItem): Item to fill data.
|
||||
folder_item (FolderItem): Folder item.
|
||||
"""
|
||||
|
||||
super(LoaderFoldersModel, self)._fill_item_data(item, folder_item)
|
||||
|
||||
def set_merged_products_selection(self, items):
|
||||
changes = {
|
||||
folder_id: None
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ class ProductsModel(QtGui.QStandardItemModel):
|
|||
self._last_project_name = None
|
||||
self._last_folder_ids = []
|
||||
self._last_project_statuses = {}
|
||||
self._last_status_icons_by_name = {}
|
||||
|
||||
def get_product_item_indexes(self):
|
||||
return [
|
||||
|
|
@ -181,6 +182,13 @@ class ProductsModel(QtGui.QStandardItemModel):
|
|||
return status_item.color
|
||||
|
||||
col = index.column()
|
||||
if col == self.status_col and role == QtCore.Qt.DecorationRole:
|
||||
role = VERSION_STATUS_ICON_ROLE
|
||||
|
||||
if role == VERSION_STATUS_ICON_ROLE:
|
||||
status_name = self.data(index, VERSION_STATUS_NAME_ROLE)
|
||||
return self._get_status_icon(status_name)
|
||||
|
||||
if col == 0:
|
||||
return super(ProductsModel, self).data(index, role)
|
||||
|
||||
|
|
@ -260,6 +268,25 @@ class ProductsModel(QtGui.QStandardItemModel):
|
|||
break
|
||||
yield color
|
||||
|
||||
def _get_status_icon(self, status_name):
|
||||
icon = self._last_status_icons_by_name.get(status_name)
|
||||
if icon is not None:
|
||||
return icon
|
||||
|
||||
status_item = self._last_project_statuses.get(status_name)
|
||||
if status_item is not None:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": status_item.icon,
|
||||
"color": status_item.color,
|
||||
})
|
||||
|
||||
if icon is None:
|
||||
icon = QtGui.QIcon()
|
||||
|
||||
self._last_status_icons_by_name[status_name] = icon
|
||||
return icon
|
||||
|
||||
def _clear(self):
|
||||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRows(0, root_item.rowCount())
|
||||
|
|
@ -419,6 +446,7 @@ class ProductsModel(QtGui.QStandardItemModel):
|
|||
status_item.name: status_item
|
||||
for status_item in status_items
|
||||
}
|
||||
self._last_status_icons_by_name = {}
|
||||
|
||||
active_site_icon_def = self._controller.get_active_site_icon_def(
|
||||
project_name
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from ayon_core.pipeline.create.context import (
|
|||
ConvertorsOperationFailed,
|
||||
)
|
||||
from ayon_core.pipeline.publish import get_publish_instance_label
|
||||
from ayon_core.tools.common_models import HierarchyModel
|
||||
from ayon_core.tools.common_models import ProjectsModel, HierarchyModel
|
||||
from ayon_core.lib.profiles_filtering import filter_profiles
|
||||
|
||||
# Define constant for plugin orders offset
|
||||
|
|
@ -1632,6 +1632,7 @@ class PublisherController(BasePublisherController):
|
|||
self._resetting_instances = False
|
||||
|
||||
# Cacher of avalon documents
|
||||
self._projects_model = ProjectsModel(self)
|
||||
self._hierarchy_model = HierarchyModel(self)
|
||||
|
||||
@property
|
||||
|
|
@ -1697,6 +1698,16 @@ class PublisherController(BasePublisherController):
|
|||
|
||||
return self._create_context.get_current_project_settings()
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_task_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
# Hierarchy model
|
||||
def get_folder_items(self, project_name, sender=None):
|
||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||
|
|
@ -1725,14 +1736,14 @@ class PublisherController(BasePublisherController):
|
|||
return folder_item.entity_id
|
||||
return None
|
||||
|
||||
def get_task_names_by_folder_paths(self, folder_paths):
|
||||
def get_task_items_by_folder_paths(self, folder_paths):
|
||||
if not folder_paths:
|
||||
return {}
|
||||
folder_items = self._hierarchy_model.get_folder_items_by_paths(
|
||||
self.project_name, folder_paths
|
||||
)
|
||||
output = {
|
||||
folder_path: set()
|
||||
folder_path: []
|
||||
for folder_path in folder_paths
|
||||
}
|
||||
project_name = self.project_name
|
||||
|
|
@ -1740,10 +1751,7 @@ class PublisherController(BasePublisherController):
|
|||
task_items = self._hierarchy_model.get_task_items(
|
||||
project_name, folder_item.entity_id, None
|
||||
)
|
||||
output[folder_item.path] = {
|
||||
task_item.name
|
||||
for task_item in task_items
|
||||
}
|
||||
output[folder_item.path] = task_items
|
||||
|
||||
return output
|
||||
|
||||
|
|
|
|||
|
|
@ -128,6 +128,16 @@ class CreateHierarchyController:
|
|||
project_name, folder_id, sender
|
||||
)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._controller.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
return self._controller.get_task_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
# Selection model
|
||||
def set_selected_project(self, project_name):
|
||||
self._selection_model.set_selected_project(project_name)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ class FoldersDialogController:
|
|||
def get_folder_items(self, project_name, sender=None):
|
||||
return self._controller.get_folder_items(project_name, sender)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._controller.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def set_selected_folder(self, folder_id):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -99,12 +99,16 @@ class TasksModel(QtGui.QStandardItemModel):
|
|||
root_item.removeRows(0, self.rowCount())
|
||||
return
|
||||
|
||||
task_names_by_folder_path = (
|
||||
self._controller.get_task_names_by_folder_paths(
|
||||
task_items_by_folder_path = (
|
||||
self._controller.get_task_items_by_folder_paths(
|
||||
self._folder_paths
|
||||
)
|
||||
)
|
||||
|
||||
task_names_by_folder_path = {
|
||||
folder_path: {item.name for item in task_items}
|
||||
for folder_path, task_items in task_items_by_folder_path.items()
|
||||
}
|
||||
self._task_names_by_folder_path = task_names_by_folder_path
|
||||
|
||||
new_task_names = self.get_intersection_of_tasks(
|
||||
|
|
@ -122,22 +126,54 @@ class TasksModel(QtGui.QStandardItemModel):
|
|||
item = self._items_by_name.pop(task_name)
|
||||
root_item.removeRow(item.row())
|
||||
|
||||
icon = get_qt_icon({
|
||||
default_icon = get_qt_icon({
|
||||
"type": "awesome-font",
|
||||
"name": "fa.male",
|
||||
"color": get_default_entity_icon_color(),
|
||||
})
|
||||
new_items = []
|
||||
task_type_items = {
|
||||
task_type_item.name: task_type_item
|
||||
for task_type_item in self._controller.get_task_type_items(
|
||||
self._controller.project_name
|
||||
)
|
||||
}
|
||||
icon_name_by_task_name = {}
|
||||
for task_items in task_items_by_folder_path.values():
|
||||
for task_item in task_items:
|
||||
task_name = task_item.name
|
||||
if (
|
||||
task_name not in new_task_names
|
||||
or task_name in icon_name_by_task_name
|
||||
):
|
||||
continue
|
||||
task_type_name = task_item.task_type
|
||||
task_type_item = task_type_items.get(task_type_name)
|
||||
if task_type_item:
|
||||
icon_name_by_task_name[task_name] = task_type_item.icon
|
||||
|
||||
for task_name in new_task_names:
|
||||
if task_name in self._items_by_name:
|
||||
item = self._items_by_name.get(task_name)
|
||||
if item is None:
|
||||
item = QtGui.QStandardItem(task_name)
|
||||
item.setData(task_name, TASK_NAME_ROLE)
|
||||
self._items_by_name[task_name] = item
|
||||
new_items.append(item)
|
||||
|
||||
if not task_name:
|
||||
continue
|
||||
|
||||
item = QtGui.QStandardItem(task_name)
|
||||
item.setData(task_name, TASK_NAME_ROLE)
|
||||
if task_name:
|
||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||
self._items_by_name[task_name] = item
|
||||
new_items.append(item)
|
||||
icon_name = icon_name_by_task_name.get(task_name)
|
||||
icon = None
|
||||
if icon_name:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": icon_name,
|
||||
"color": get_default_entity_icon_color(),
|
||||
})
|
||||
if icon is None:
|
||||
icon = default_icon
|
||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||
|
||||
if new_items:
|
||||
root_item.appendRows(new_items)
|
||||
|
|
|
|||
|
|
@ -116,6 +116,9 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
|
||||
self._default_icon_color = get_default_entity_icon_color()
|
||||
|
||||
self._last_project_statuses = {}
|
||||
self._last_status_icons_by_name = {}
|
||||
|
||||
def outdated(self, item):
|
||||
return item.get("isOutdated", True)
|
||||
|
||||
|
|
@ -159,10 +162,11 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
self._controller.get_site_provider_icons().items()
|
||||
)
|
||||
}
|
||||
status_items_by_name = {
|
||||
self._last_project_statuses = {
|
||||
status_item.name: status_item
|
||||
for status_item in self._controller.get_project_status_items()
|
||||
}
|
||||
self._last_status_icons_by_name = {}
|
||||
|
||||
group_item_icon = qtawesome.icon(
|
||||
"fa.folder", color=self._default_icon_color
|
||||
|
|
@ -186,7 +190,6 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
remote_site_icon = site_icons.get(sites_info["remote_site_provider"])
|
||||
|
||||
root_item = self.invisibleRootItem()
|
||||
|
||||
group_items = []
|
||||
for repre_id, container_items in items_by_repre_id.items():
|
||||
repre_info = repre_info_by_id[repre_id]
|
||||
|
|
@ -195,8 +198,6 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
is_latest = False
|
||||
is_hero = False
|
||||
status_name = None
|
||||
status_color = None
|
||||
status_short = None
|
||||
if not repre_info.is_valid:
|
||||
group_name = "< Entity N/A >"
|
||||
item_icon = invalid_item_icon
|
||||
|
|
@ -219,10 +220,10 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
if not is_latest:
|
||||
version_color = self.OUTDATED_COLOR
|
||||
status_name = version_item.status
|
||||
status_item = status_items_by_name.get(status_name)
|
||||
if status_item:
|
||||
status_short = status_item.short
|
||||
status_color = status_item.color
|
||||
|
||||
status_color, status_short, status_icon = self._get_status_data(
|
||||
status_name
|
||||
)
|
||||
|
||||
container_model_items = []
|
||||
for container_item in container_items:
|
||||
|
|
@ -273,6 +274,7 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
group_item.setData(status_name, STATUS_NAME_ROLE)
|
||||
group_item.setData(status_short, STATUS_SHORT_ROLE)
|
||||
group_item.setData(status_color, STATUS_COLOR_ROLE)
|
||||
group_item.setData(status_icon, STATUS_ICON_ROLE)
|
||||
|
||||
group_item.setData(
|
||||
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
|
||||
|
|
@ -355,6 +357,32 @@ class InventoryModel(QtGui.QStandardItemModel):
|
|||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRows(0, root_item.rowCount())
|
||||
|
||||
def _get_status_data(self, status_name):
|
||||
status_item = self._last_project_statuses.get(status_name)
|
||||
status_icon = self._get_status_icon(status_name, status_item)
|
||||
status_color = status_short = None
|
||||
if status_item is not None:
|
||||
status_color = status_item.color
|
||||
status_short = status_item.short
|
||||
return status_color, status_short, status_icon
|
||||
|
||||
def _get_status_icon(self, status_name, status_item):
|
||||
icon = self._last_status_icons_by_name.get(status_name)
|
||||
if icon is not None:
|
||||
return icon
|
||||
|
||||
icon = None
|
||||
if status_item is not None:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": status_item.icon,
|
||||
"color": status_item.color,
|
||||
})
|
||||
if icon is None:
|
||||
icon = QtGui.QIcon()
|
||||
self._last_status_icons_by_name[status_name] = icon
|
||||
return icon
|
||||
|
||||
|
||||
class FilterProxyModel(QtCore.QSortFilterProxyModel):
|
||||
"""Filter model to where key column's value is in the filtered tags"""
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import uuid
|
|||
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
|
||||
from ayon_core.tools.utils import get_qt_icon
|
||||
from ayon_core.tools.utils.delegates import StatusDelegate
|
||||
|
||||
from .model import (
|
||||
|
|
@ -20,13 +21,15 @@ class VersionOption:
|
|||
label,
|
||||
status_name,
|
||||
status_short,
|
||||
status_color
|
||||
status_color,
|
||||
status_icon,
|
||||
):
|
||||
self.version = version
|
||||
self.label = label
|
||||
self.status_name = status_name
|
||||
self.status_short = status_short
|
||||
self.status_color = status_color
|
||||
self.status_icon = status_icon
|
||||
|
||||
|
||||
class SelectVersionModel(QtGui.QStandardItemModel):
|
||||
|
|
@ -84,27 +87,52 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
|||
return
|
||||
|
||||
painter.save()
|
||||
text_field_rect = self.style().subControlRect(
|
||||
|
||||
status_icon = self.itemData(idx, STATUS_ICON_ROLE)
|
||||
content_field_rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.CC_ComboBox,
|
||||
option,
|
||||
QtWidgets.QStyle.SC_ComboBoxEditField
|
||||
)
|
||||
adj_rect = text_field_rect.adjusted(1, 0, -1, 0)
|
||||
).adjusted(1, 0, -1, 0)
|
||||
|
||||
metrics = option.fontMetrics
|
||||
version_text_width = metrics.width(option.currentText) + 2
|
||||
version_text_rect = QtCore.QRect(content_field_rect)
|
||||
version_text_rect.setWidth(version_text_width)
|
||||
|
||||
painter.drawText(
|
||||
adj_rect,
|
||||
version_text_rect,
|
||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||
option.currentText
|
||||
)
|
||||
metrics = QtGui.QFontMetrics(self.font())
|
||||
text_width = metrics.width(option.currentText)
|
||||
x_offset = text_width + 2
|
||||
diff_width = adj_rect.width() - x_offset
|
||||
if diff_width <= 0:
|
||||
|
||||
status_text_rect = QtCore.QRect(content_field_rect)
|
||||
status_text_rect.setLeft(version_text_rect.right() + 2)
|
||||
if status_icon is not None and not status_icon.isNull():
|
||||
icon_rect = QtCore.QRect(status_text_rect)
|
||||
diff = icon_rect.height() - metrics.height()
|
||||
if diff < 0:
|
||||
diff = 0
|
||||
top_offset = diff // 2
|
||||
bottom_offset = diff - top_offset
|
||||
icon_rect.adjust(0, top_offset, 0, -bottom_offset)
|
||||
icon_rect.setWidth(metrics.height())
|
||||
status_icon.paint(
|
||||
painter,
|
||||
icon_rect,
|
||||
QtCore.Qt.AlignCenter,
|
||||
QtGui.QIcon.Normal,
|
||||
QtGui.QIcon.On
|
||||
)
|
||||
status_text_rect.setLeft(icon_rect.right() + 2)
|
||||
|
||||
if status_text_rect.width() <= 0:
|
||||
return
|
||||
|
||||
status_rect = adj_rect.adjusted(x_offset + 2, 0, 0, 0)
|
||||
if diff_width < metrics.width(status_name):
|
||||
if status_text_rect.width() < metrics.width(status_name):
|
||||
status_name = self.itemData(idx, STATUS_SHORT_ROLE)
|
||||
if status_text_rect.width() < metrics.width(status_name):
|
||||
status_name = ""
|
||||
|
||||
color = QtGui.QColor(self.itemData(idx, STATUS_COLOR_ROLE))
|
||||
|
||||
|
|
@ -112,7 +140,7 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
|||
pen.setColor(color)
|
||||
painter.setPen(pen)
|
||||
painter.drawText(
|
||||
status_rect,
|
||||
status_text_rect,
|
||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||
status_name
|
||||
)
|
||||
|
|
@ -140,7 +168,17 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
|||
root_item.removeRows(0, root_item.rowCount())
|
||||
|
||||
new_items = []
|
||||
icons_by_name = {}
|
||||
for version_option in version_options:
|
||||
icon = icons_by_name.get(version_option.status_icon)
|
||||
if icon is None:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": version_option.status_icon,
|
||||
"color": version_option.status_color
|
||||
})
|
||||
icons_by_name[version_option.status_icon] = icon
|
||||
|
||||
item_id = uuid.uuid4().hex
|
||||
item = QtGui.QStandardItem(version_option.label)
|
||||
item.setColumnCount(root_item.columnCount())
|
||||
|
|
@ -153,6 +191,7 @@ class SelectVersionComboBox(QtWidgets.QComboBox):
|
|||
item.setData(
|
||||
version_option.status_color, STATUS_COLOR_ROLE
|
||||
)
|
||||
item.setData(icon, STATUS_ICON_ROLE)
|
||||
item.setData(item_id, ITEM_ID_ROLE)
|
||||
|
||||
new_items.append(item)
|
||||
|
|
|
|||
|
|
@ -740,10 +740,12 @@ class SceneInventoryView(QtWidgets.QTreeView):
|
|||
status_name = version_item.status
|
||||
status_short = None
|
||||
status_color = None
|
||||
status_icon = None
|
||||
status_item = status_items_by_name.get(status_name)
|
||||
if status_item:
|
||||
status_short = status_item.short
|
||||
status_color = status_item.color
|
||||
status_icon = status_item.icon
|
||||
version_options.append(
|
||||
VersionOption(
|
||||
version,
|
||||
|
|
@ -751,6 +753,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
|
|||
status_name,
|
||||
status_short,
|
||||
status_color,
|
||||
status_icon,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import time
|
|||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from qtpy import QtWidgets, QtGui
|
||||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -130,32 +130,65 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
|||
else:
|
||||
style = QtWidgets.QApplication.style()
|
||||
|
||||
style.drawControl(
|
||||
QtWidgets.QCommonStyle.CE_ItemViewItem,
|
||||
self.initStyleOption(option, index)
|
||||
|
||||
mode = QtGui.QIcon.Normal
|
||||
if not (option.state & QtWidgets.QStyle.State_Enabled):
|
||||
mode = QtGui.QIcon.Disabled
|
||||
elif option.state & QtWidgets.QStyle.State_Selected:
|
||||
mode = QtGui.QIcon.Selected
|
||||
state = QtGui.QIcon.Off
|
||||
if option.state & QtWidgets.QStyle.State_Open:
|
||||
state = QtGui.QIcon.On
|
||||
icon = self._get_status_icon(index)
|
||||
option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
|
||||
option.icon = icon
|
||||
act_size = icon.actualSize(option.decorationSize, mode, state)
|
||||
option.decorationSize = QtCore.QSize(
|
||||
min(option.decorationSize.width(), act_size.width()),
|
||||
min(option.decorationSize.height(), act_size.height())
|
||||
)
|
||||
|
||||
text = self._get_status_name(index)
|
||||
if text:
|
||||
option.features |= QtWidgets.QStyleOptionViewItem.HasDisplay
|
||||
option.text = text
|
||||
|
||||
painter.save()
|
||||
painter.setClipRect(option.rect)
|
||||
|
||||
icon_rect = style.subElementRect(
|
||||
QtWidgets.QCommonStyle.SE_ItemViewItemDecoration,
|
||||
option,
|
||||
option.widget
|
||||
)
|
||||
text_rect = style.subElementRect(
|
||||
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
||||
option,
|
||||
option.widget
|
||||
)
|
||||
|
||||
# Draw background
|
||||
style.drawPrimitive(
|
||||
QtWidgets.QCommonStyle.PE_PanelItemViewItem,
|
||||
option,
|
||||
painter,
|
||||
option.widget
|
||||
)
|
||||
|
||||
painter.save()
|
||||
|
||||
text_rect = style.subElementRect(
|
||||
QtWidgets.QCommonStyle.SE_ItemViewItemText,
|
||||
option
|
||||
# Draw icon
|
||||
option.icon.paint(
|
||||
painter,
|
||||
icon_rect,
|
||||
option.decorationAlignment,
|
||||
mode,
|
||||
state
|
||||
)
|
||||
text_margin = style.proxy().pixelMetric(
|
||||
QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
|
||||
option,
|
||||
option.widget
|
||||
) + 1
|
||||
padded_text_rect = text_rect.adjusted(
|
||||
text_margin, 0, - text_margin, 0
|
||||
)
|
||||
|
||||
fm = QtGui.QFontMetrics(option.font)
|
||||
text = self._get_status_name(index)
|
||||
if padded_text_rect.width() < fm.width(text):
|
||||
if text_rect.width() < fm.width(text):
|
||||
text = self._get_status_short_name(index)
|
||||
if text_rect.width() < fm.width(text):
|
||||
text = ""
|
||||
|
||||
fg_color = self._get_status_color(index)
|
||||
pen = painter.pen()
|
||||
|
|
@ -163,11 +196,47 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
|||
painter.setPen(pen)
|
||||
|
||||
painter.drawText(
|
||||
padded_text_rect,
|
||||
text_rect,
|
||||
option.displayAlignment,
|
||||
text
|
||||
)
|
||||
|
||||
if option.state & QtWidgets.QStyle.State_HasFocus:
|
||||
focus_opt = QtWidgets.QStyleOptionFocusRect()
|
||||
focus_opt.state = option.state
|
||||
focus_opt.direction = option.direction
|
||||
focus_opt.rect = option.rect
|
||||
focus_opt.fontMetrics = option.fontMetrics
|
||||
focus_opt.palette = option.palette
|
||||
|
||||
focus_opt.rect = style.subElementRect(
|
||||
QtWidgets.QCommonStyle.SE_ItemViewItemFocusRect,
|
||||
option,
|
||||
option.widget
|
||||
)
|
||||
focus_opt.state |= (
|
||||
QtWidgets.QStyle.State_KeyboardFocusChange
|
||||
| QtWidgets.QStyle.State_Item
|
||||
)
|
||||
focus_opt.backgroundColor = option.palette.color(
|
||||
(
|
||||
QtGui.QPalette.Normal
|
||||
if option.state & QtWidgets.QStyle.State_Enabled
|
||||
else QtGui.QPalette.Disabled
|
||||
),
|
||||
(
|
||||
QtGui.QPalette.Highlight
|
||||
if option.state & QtWidgets.QStyle.State_Selected
|
||||
else QtGui.QPalette.Window
|
||||
)
|
||||
)
|
||||
style.drawPrimitive(
|
||||
QtWidgets.QCommonStyle.PE_FrameFocusRect,
|
||||
focus_opt,
|
||||
painter,
|
||||
option.widget
|
||||
)
|
||||
|
||||
painter.restore()
|
||||
|
||||
def _get_status_name(self, index):
|
||||
|
|
@ -180,6 +249,9 @@ class StatusDelegate(QtWidgets.QStyledItemDelegate):
|
|||
return QtGui.QColor(index.data(self.status_color_role))
|
||||
|
||||
def _get_status_icon(self, index):
|
||||
icon = None
|
||||
if self.status_icon_role is not None:
|
||||
return index.data(self.status_icon_role)
|
||||
return None
|
||||
icon = index.data(self.status_icon_role)
|
||||
if icon is None:
|
||||
return QtGui.QIcon()
|
||||
return icon
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import collections
|
|||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
|
||||
from ayon_core.lib.events import QueuedEventSystem
|
||||
from ayon_core.style import get_default_entity_icon_color
|
||||
from ayon_core.tools.common_models import (
|
||||
ProjectsModel,
|
||||
HierarchyModel,
|
||||
HierarchyExpectedSelection,
|
||||
)
|
||||
|
|
@ -25,8 +27,9 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
|
||||
Args:
|
||||
controller (AbstractWorkfilesFrontend): The control object.
|
||||
"""
|
||||
|
||||
"""
|
||||
_default_folder_icon = None
|
||||
refreshed = QtCore.Signal()
|
||||
|
||||
def __init__(self, controller):
|
||||
|
|
@ -71,13 +74,6 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
|
||||
self.set_project_name(self._last_project_name)
|
||||
|
||||
def _clear_items(self):
|
||||
self._items_by_id = {}
|
||||
self._parent_id_by_id = {}
|
||||
self._has_content = False
|
||||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRows(0, root_item.rowCount())
|
||||
|
||||
def get_index_by_id(self, item_id):
|
||||
"""Get index by folder id.
|
||||
|
||||
|
|
@ -123,7 +119,7 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
|
||||
if not project_name:
|
||||
self._last_project_name = project_name
|
||||
self._fill_items({})
|
||||
self._fill_items({}, {})
|
||||
self._current_refresh_thread = None
|
||||
return
|
||||
|
||||
|
|
@ -140,15 +136,42 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
|
||||
thread = RefreshThread(
|
||||
project_name,
|
||||
self._controller.get_folder_items,
|
||||
project_name,
|
||||
FOLDERS_MODEL_SENDER_NAME
|
||||
self._thread_getter,
|
||||
project_name
|
||||
)
|
||||
self._current_refresh_thread = thread
|
||||
self._refresh_threads[thread.id] = thread
|
||||
thread.refresh_finished.connect(self._on_refresh_thread)
|
||||
thread.start()
|
||||
|
||||
@classmethod
|
||||
def _get_default_folder_icon(cls):
|
||||
if cls._default_folder_icon is None:
|
||||
cls._default_folder_icon = get_qt_icon({
|
||||
"type": "awesome-font",
|
||||
"name": "fa.folder",
|
||||
"color": get_default_entity_icon_color()
|
||||
})
|
||||
return cls._default_folder_icon
|
||||
|
||||
def _clear_items(self):
|
||||
self._items_by_id = {}
|
||||
self._parent_id_by_id = {}
|
||||
self._has_content = False
|
||||
root_item = self.invisibleRootItem()
|
||||
root_item.removeRows(0, root_item.rowCount())
|
||||
|
||||
def _thread_getter(self, project_name):
|
||||
folder_items = self._controller.get_folder_items(
|
||||
project_name, FOLDERS_MODEL_SENDER_NAME
|
||||
)
|
||||
folder_type_items = {}
|
||||
if hasattr(self._controller, "get_folder_type_items"):
|
||||
folder_type_items = self._controller.get_folder_type_items(
|
||||
project_name, FOLDERS_MODEL_SENDER_NAME
|
||||
)
|
||||
return folder_items, folder_type_items
|
||||
|
||||
def _on_refresh_thread(self, thread_id):
|
||||
"""Callback when refresh thread is finished.
|
||||
|
||||
|
|
@ -169,19 +192,55 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
or thread_id != self._current_refresh_thread.id
|
||||
):
|
||||
return
|
||||
|
||||
self._fill_items(thread.get_result())
|
||||
folder_items, folder_type_items = thread.get_result()
|
||||
self._fill_items(folder_items, folder_type_items)
|
||||
self._current_refresh_thread = None
|
||||
|
||||
def _fill_item_data(self, item, folder_item):
|
||||
def _get_folder_item_icon(
|
||||
self,
|
||||
folder_item,
|
||||
folder_type_item_by_name,
|
||||
folder_type_icon_cache
|
||||
):
|
||||
icon = folder_type_icon_cache.get(folder_item.folder_type)
|
||||
if icon is not None:
|
||||
return icon
|
||||
|
||||
folder_type_item = folder_type_item_by_name.get(
|
||||
folder_item.folder_type
|
||||
)
|
||||
icon = None
|
||||
if folder_type_item is not None:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": folder_type_item.icon,
|
||||
"color": get_default_entity_icon_color()
|
||||
})
|
||||
|
||||
if icon is None:
|
||||
icon = self._get_default_folder_icon()
|
||||
folder_type_icon_cache[folder_item.folder_type] = icon
|
||||
return icon
|
||||
|
||||
def _fill_item_data(
|
||||
self,
|
||||
item,
|
||||
folder_item,
|
||||
folder_type_item_by_name,
|
||||
folder_type_icon_cache
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
item (QtGui.QStandardItem): Item to fill data.
|
||||
folder_item (FolderItem): Folder item.
|
||||
"""
|
||||
|
||||
icon = get_qt_icon(folder_item.icon)
|
||||
"""
|
||||
icon = self._get_folder_item_icon(
|
||||
folder_item,
|
||||
folder_type_item_by_name,
|
||||
folder_type_icon_cache
|
||||
)
|
||||
item.setData(folder_item.entity_id, FOLDER_ID_ROLE)
|
||||
item.setData(folder_item.name, FOLDER_NAME_ROLE)
|
||||
item.setData(folder_item.path, FOLDER_PATH_ROLE)
|
||||
|
|
@ -189,7 +248,7 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
item.setData(folder_item.label, QtCore.Qt.DisplayRole)
|
||||
item.setData(icon, QtCore.Qt.DecorationRole)
|
||||
|
||||
def _fill_items(self, folder_items_by_id):
|
||||
def _fill_items(self, folder_items_by_id, folder_type_items):
|
||||
if not folder_items_by_id:
|
||||
if folder_items_by_id is not None:
|
||||
self._clear_items()
|
||||
|
|
@ -197,6 +256,11 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
self.refreshed.emit()
|
||||
return
|
||||
|
||||
folder_type_item_by_name = {
|
||||
folder_type.name: folder_type
|
||||
for folder_type in folder_type_items
|
||||
}
|
||||
folder_type_icon_cache = {}
|
||||
self._has_content = True
|
||||
|
||||
folder_ids = set(folder_items_by_id)
|
||||
|
|
@ -242,7 +306,12 @@ class FoldersQtModel(QtGui.QStandardItemModel):
|
|||
else:
|
||||
is_new = self._parent_id_by_id[item_id] != parent_id
|
||||
|
||||
self._fill_item_data(item, folder_item)
|
||||
self._fill_item_data(
|
||||
item,
|
||||
folder_item,
|
||||
folder_type_item_by_name,
|
||||
folder_type_icon_cache
|
||||
)
|
||||
if is_new:
|
||||
new_items.append(item)
|
||||
self._items_by_id[item_id] = item
|
||||
|
|
@ -619,6 +688,7 @@ class SimpleSelectionModel(object):
|
|||
class SimpleFoldersController(object):
|
||||
def __init__(self):
|
||||
self._event_system = self._create_event_system()
|
||||
self._projects_model = ProjectsModel(self)
|
||||
self._hierarchy_model = HierarchyModel(self)
|
||||
self._selection_model = SimpleSelectionModel(self)
|
||||
self._expected_selection = HierarchyExpectedSelection(
|
||||
|
|
@ -639,6 +709,11 @@ class SimpleFoldersController(object):
|
|||
def get_folder_items(self, project_name, sender=None):
|
||||
return self._hierarchy_model.get_folder_items(project_name, sender)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def set_selected_project(self, project_name):
|
||||
self._selection_model.set_selected_project(project_name)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from functools import partial
|
|||
|
||||
from qtpy import QtWidgets, QtCore, QtGui
|
||||
import qtawesome
|
||||
import qtmaterialsymbols
|
||||
|
||||
from ayon_core.style import (
|
||||
get_objected_colors,
|
||||
|
|
@ -468,7 +469,7 @@ class _IconsCache:
|
|||
if icon_type == "path":
|
||||
parts = [icon_type, icon_def["path"]]
|
||||
|
||||
elif icon_type == "awesome-font":
|
||||
elif icon_type in {"awesome-font", "material-symbols"}:
|
||||
parts = [icon_type, icon_def["name"], icon_def["color"]]
|
||||
return "|".join(parts)
|
||||
|
||||
|
|
@ -495,6 +496,13 @@ class _IconsCache:
|
|||
if icon is None:
|
||||
icon = cls.get_qta_icon_by_name_and_color(
|
||||
"fa.{}".format(icon_name), icon_color)
|
||||
|
||||
elif icon_type == "material-symbols":
|
||||
icon_name = icon_def["name"]
|
||||
icon_color = icon_def["color"]
|
||||
if qtmaterialsymbols.get_icon_name_char(icon_name) is not None:
|
||||
icon = qtmaterialsymbols.get_icon(icon_name, icon_color)
|
||||
|
||||
if icon is None:
|
||||
icon = cls.get_default()
|
||||
cls._cache[cache_key] = icon
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
|
||||
from ayon_core.style import get_disabled_entity_icon_color
|
||||
from ayon_core.style import (
|
||||
get_disabled_entity_icon_color,
|
||||
get_default_entity_icon_color,
|
||||
)
|
||||
|
||||
from .views import DeselectableTreeView
|
||||
from .lib import RefreshThread, get_qt_icon
|
||||
|
|
@ -17,8 +20,9 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
|||
|
||||
Args:
|
||||
controller (AbstractWorkfilesFrontend): The control object.
|
||||
"""
|
||||
|
||||
"""
|
||||
_default_task_icon = None
|
||||
refreshed = QtCore.Signal()
|
||||
|
||||
def __init__(self, controller):
|
||||
|
|
@ -176,7 +180,7 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
|||
return
|
||||
thread = RefreshThread(
|
||||
folder_id,
|
||||
self._controller.get_task_items,
|
||||
self._thread_getter,
|
||||
project_name,
|
||||
folder_id
|
||||
)
|
||||
|
|
@ -185,8 +189,55 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
|||
thread.refresh_finished.connect(self._on_refresh_thread)
|
||||
thread.start()
|
||||
|
||||
def _thread_getter(self, project_name, folder_id):
|
||||
task_items = self._controller.get_task_items(
|
||||
project_name, folder_id
|
||||
)
|
||||
task_type_items = {}
|
||||
if hasattr(self._controller, "get_task_type_items"):
|
||||
task_type_items = self._controller.get_task_type_items(
|
||||
project_name
|
||||
)
|
||||
return task_items, task_type_items
|
||||
|
||||
@classmethod
|
||||
def _get_default_task_icon(cls):
|
||||
if cls._default_task_icon is None:
|
||||
cls._default_task_icon = get_qt_icon({
|
||||
"type": "awesome-font",
|
||||
"name": "fa.male",
|
||||
"color": get_default_entity_icon_color()
|
||||
})
|
||||
return cls._default_task_icon
|
||||
|
||||
def _get_task_item_icon(
|
||||
self,
|
||||
task_item,
|
||||
task_type_item_by_name,
|
||||
task_type_icon_cache
|
||||
):
|
||||
icon = task_type_icon_cache.get(task_item.task_type)
|
||||
if icon is not None:
|
||||
return icon
|
||||
|
||||
task_type_item = task_type_item_by_name.get(
|
||||
task_item.task_type
|
||||
)
|
||||
icon = None
|
||||
if task_type_item is not None:
|
||||
icon = get_qt_icon({
|
||||
"type": "material-symbols",
|
||||
"name": task_type_item.icon,
|
||||
"color": get_default_entity_icon_color()
|
||||
})
|
||||
|
||||
if icon is None:
|
||||
icon = self._get_default_task_icon()
|
||||
task_type_icon_cache[task_item.task_type] = icon
|
||||
return icon
|
||||
|
||||
def _fill_data_from_thread(self, thread):
|
||||
task_items = thread.get_result()
|
||||
task_items, task_type_items = thread.get_result()
|
||||
# Task items are refreshed
|
||||
if task_items is None:
|
||||
return
|
||||
|
|
@ -197,6 +248,11 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
|||
return
|
||||
self._remove_invalid_items()
|
||||
|
||||
task_type_item_by_name = {
|
||||
task_type_item.name: task_type_item
|
||||
for task_type_item in task_type_items
|
||||
}
|
||||
task_type_icon_cache = {}
|
||||
new_items = []
|
||||
new_names = set()
|
||||
for task_item in task_items:
|
||||
|
|
@ -209,8 +265,11 @@ class TasksQtModel(QtGui.QStandardItemModel):
|
|||
new_items.append(item)
|
||||
self._items_by_name[name] = item
|
||||
|
||||
# TODO cache locally
|
||||
icon = get_qt_icon(task_item.icon)
|
||||
icon = self._get_task_item_icon(
|
||||
task_item,
|
||||
task_type_item_by_name,
|
||||
task_type_icon_cache
|
||||
)
|
||||
item.setData(task_item.label, QtCore.Qt.DisplayRole)
|
||||
item.setData(name, ITEM_NAME_ROLE)
|
||||
item.setData(task_item.id, ITEM_ID_ROLE)
|
||||
|
|
|
|||
|
|
@ -546,6 +546,46 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon):
|
|||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
"""Folder type items for a project.
|
||||
|
||||
This function may trigger events with topics
|
||||
'projects.folder_types.refresh.started' and
|
||||
'projects.folder_types.refresh.finished' which will contain 'sender'
|
||||
value in data.
|
||||
That may help to avoid re-refresh of items in UI elements.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (str): Who requested folder type items.
|
||||
|
||||
Returns:
|
||||
list[FolderTypeItem]: Folder type information.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
"""Task type items for a project.
|
||||
|
||||
This function may trigger events with topics
|
||||
'projects.task_types.refresh.started' and
|
||||
'projects.task_types.refresh.finished' which will contain 'sender'
|
||||
value in data.
|
||||
That may help to avoid re-refresh of items in UI elements.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
sender (str): Who requested task type items.
|
||||
|
||||
Returns:
|
||||
list[TaskTypeItem]: Task type information.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
# Host information
|
||||
@abstractmethod
|
||||
def get_workfile_extensions(self):
|
||||
|
|
|
|||
|
|
@ -231,6 +231,16 @@ class BaseWorkfileController(
|
|||
return self._projects_model.get_project_entity(
|
||||
project_name)
|
||||
|
||||
def get_folder_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_folder_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_task_type_items(self, project_name, sender=None):
|
||||
return self._projects_model.get_task_type_items(
|
||||
project_name, sender
|
||||
)
|
||||
|
||||
def get_folder_entity(self, project_name, folder_id):
|
||||
return self._hierarchy_model.get_folder_entity(
|
||||
project_name, folder_id)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from ayon_core.tools.utils.delegates import PrettyTimeDelegate
|
|||
|
||||
from .utils import BaseOverlayFrame
|
||||
|
||||
|
||||
REPRE_ID_ROLE = QtCore.Qt.UserRole + 1
|
||||
FILEPATH_ROLE = QtCore.Qt.UserRole + 2
|
||||
AUTHOR_ROLE = QtCore.Qt.UserRole + 3
|
||||
|
|
@ -249,7 +248,7 @@ class PublishedFilesModel(QtGui.QStandardItemModel):
|
|||
|
||||
# Handle roles for first column
|
||||
col = index.column()
|
||||
if col != 1:
|
||||
if col == 0:
|
||||
return super().data(index, role)
|
||||
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
|
|
|
|||
18
client/ayon_core/vendor/python/qtmaterialsymbols/__init__.py
vendored
Normal file
18
client/ayon_core/vendor/python/qtmaterialsymbols/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from .version import __version__
|
||||
from .utils import get_icon_name_char
|
||||
from .iconic_font import (
|
||||
IconicFont,
|
||||
get_instance,
|
||||
get_icon,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"__version__",
|
||||
|
||||
"get_icon_name_char",
|
||||
|
||||
"IconicFont",
|
||||
"get_instance",
|
||||
"get_icon",
|
||||
)
|
||||
209
client/ayon_core/vendor/python/qtmaterialsymbols/browser.py
vendored
Normal file
209
client/ayon_core/vendor/python/qtmaterialsymbols/browser.py
vendored
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
import sys
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .iconic_font import get_instance
|
||||
|
||||
|
||||
# TODO: Set icon colour and copy code with color kwarg
|
||||
|
||||
VIEW_COLUMNS = 5
|
||||
AUTO_SEARCH_TIMEOUT = 500
|
||||
ALL_COLLECTIONS = 'All'
|
||||
|
||||
|
||||
class IconBrowser(QtWidgets.QMainWindow):
|
||||
"""
|
||||
A small browser window that allows the user to search through all icons from
|
||||
the available version of QtAwesome. You can also copy the name and python
|
||||
code for the currently selected icon.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(IconBrowser, self).__init__()
|
||||
self.setMinimumSize(400, 300)
|
||||
self.setWindowTitle("Material Icon Browser")
|
||||
|
||||
instance = get_instance()
|
||||
fontMaps = instance.get_charmap()
|
||||
|
||||
iconNames = list(fontMaps)
|
||||
|
||||
self._filterTimer = QtCore.QTimer(self)
|
||||
self._filterTimer.setSingleShot(True)
|
||||
self._filterTimer.setInterval(AUTO_SEARCH_TIMEOUT)
|
||||
self._filterTimer.timeout.connect(self._updateFilter)
|
||||
|
||||
model = IconModel(self.palette().color(QtGui.QPalette.Text))
|
||||
model.setStringList(sorted(iconNames))
|
||||
|
||||
self._proxyModel = QtCore.QSortFilterProxyModel()
|
||||
self._proxyModel.setSourceModel(model)
|
||||
self._proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
|
||||
self._listView = IconListView(self)
|
||||
self._listView.setUniformItemSizes(True)
|
||||
self._listView.setViewMode(QtWidgets.QListView.IconMode)
|
||||
self._listView.setModel(self._proxyModel)
|
||||
self._listView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self._listView.doubleClicked.connect(self._copyIconText)
|
||||
|
||||
self._lineEdit = QtWidgets.QLineEdit(self)
|
||||
self._lineEdit.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self._lineEdit.textChanged.connect(self._triggerDelayedUpdate)
|
||||
self._lineEdit.returnPressed.connect(self._triggerImmediateUpdate)
|
||||
|
||||
self._comboBox = QtWidgets.QComboBox(self)
|
||||
self._comboBox.setMinimumWidth(75)
|
||||
self._comboBox.currentIndexChanged.connect(self._triggerImmediateUpdate)
|
||||
self._comboBox.addItems([ALL_COLLECTIONS] + sorted(fontMaps.keys()))
|
||||
|
||||
lyt = QtWidgets.QHBoxLayout()
|
||||
lyt.setContentsMargins(0, 0, 0, 0)
|
||||
lyt.addWidget(self._comboBox)
|
||||
lyt.addWidget(self._lineEdit)
|
||||
|
||||
searchBarFrame = QtWidgets.QFrame(self)
|
||||
searchBarFrame.setLayout(lyt)
|
||||
|
||||
self._copyButton = QtWidgets.QPushButton('Copy Name', self)
|
||||
self._copyButton.clicked.connect(self._copyIconText)
|
||||
|
||||
lyt = QtWidgets.QVBoxLayout()
|
||||
lyt.addWidget(searchBarFrame)
|
||||
lyt.addWidget(self._listView)
|
||||
lyt.addWidget(self._copyButton)
|
||||
|
||||
frame = QtWidgets.QFrame(self)
|
||||
frame.setLayout(lyt)
|
||||
|
||||
self.setCentralWidget(frame)
|
||||
|
||||
QtWidgets.QShortcut(
|
||||
QtGui.QKeySequence(QtCore.Qt.Key_Return),
|
||||
self,
|
||||
self._copyIconText,
|
||||
)
|
||||
|
||||
self._lineEdit.setFocus()
|
||||
|
||||
# geo = self.geometry()
|
||||
# desktop = QtWidgets.QApplication.desktop()
|
||||
# screen = desktop.screenNumber(desktop.cursor().pos())
|
||||
# centerPoint = desktop.screenGeometry(screen).center()
|
||||
# geo.moveCenter(centerPoint)
|
||||
# self.setGeometry(geo)
|
||||
|
||||
def _updateFilter(self):
|
||||
"""
|
||||
Update the string used for filtering in the proxy model with the
|
||||
current text from the line edit.
|
||||
"""
|
||||
reString = ""
|
||||
|
||||
group = self._comboBox.currentText()
|
||||
if group != ALL_COLLECTIONS:
|
||||
reString += r"^%s\." % group
|
||||
|
||||
searchTerm = self._lineEdit.text()
|
||||
if searchTerm:
|
||||
reString += ".*%s.*$" % searchTerm
|
||||
|
||||
self._proxyModel.setFilterRegularExpression(reString)
|
||||
|
||||
def _triggerDelayedUpdate(self):
|
||||
"""
|
||||
Reset the timer used for committing the search term to the proxy model.
|
||||
"""
|
||||
self._filterTimer.stop()
|
||||
self._filterTimer.start()
|
||||
|
||||
def _triggerImmediateUpdate(self):
|
||||
"""
|
||||
Stop the timer used for committing the search term and update the
|
||||
proxy model immediately.
|
||||
"""
|
||||
self._filterTimer.stop()
|
||||
self._updateFilter()
|
||||
|
||||
def _copyIconText(self):
|
||||
"""
|
||||
Copy the name of the currently selected icon to the clipboard.
|
||||
"""
|
||||
indexes = self._listView.selectedIndexes()
|
||||
if not indexes:
|
||||
return
|
||||
|
||||
clipboard = QtWidgets.QApplication.instance().clipboard()
|
||||
clipboard.setText(indexes[0].data())
|
||||
|
||||
|
||||
class IconListView(QtWidgets.QListView):
|
||||
"""
|
||||
A QListView that scales it's grid size to ensure the same number of
|
||||
columns are always drawn.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(IconListView, self).__init__(parent)
|
||||
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
"""
|
||||
Re-implemented to re-calculate the grid size to provide scaling icons
|
||||
|
||||
Parameters
|
||||
----------
|
||||
event : QtCore.QEvent
|
||||
"""
|
||||
width = self.viewport().width() - 30
|
||||
# The minus 30 above ensures we don't end up with an item width that
|
||||
# can't be drawn the expected number of times across the view without
|
||||
# being wrapped. Without this, the view can flicker during resize
|
||||
tileWidth = width / VIEW_COLUMNS
|
||||
iconWidth = int(tileWidth * 0.8)
|
||||
|
||||
self.setGridSize(QtCore.QSize(tileWidth, tileWidth))
|
||||
self.setIconSize(QtCore.QSize(iconWidth, iconWidth))
|
||||
|
||||
return super(IconListView, self).resizeEvent(event)
|
||||
|
||||
|
||||
class IconModel(QtCore.QStringListModel):
|
||||
def __init__(self, iconColor):
|
||||
super(IconModel, self).__init__()
|
||||
self._iconColor = iconColor
|
||||
|
||||
def flags(self, index):
|
||||
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
|
||||
|
||||
def data(self, index, role):
|
||||
"""
|
||||
Re-implemented to return the icon for the current index.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index : QtCore.QModelIndex
|
||||
role : int
|
||||
|
||||
Returns
|
||||
-------
|
||||
Any
|
||||
"""
|
||||
if role == QtCore.Qt.DecorationRole:
|
||||
iconString = self.data(index, role=QtCore.Qt.DisplayRole)
|
||||
instance = get_instance()
|
||||
return instance.get_icon(iconString, color=self._iconColor)
|
||||
return super(IconModel, self).data(index, role)
|
||||
|
||||
|
||||
def run():
|
||||
"""
|
||||
Start the IconBrowser and block until the process exits.
|
||||
"""
|
||||
app = QtWidgets.QApplication([])
|
||||
|
||||
browser = IconBrowser()
|
||||
browser.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
8
client/ayon_core/vendor/python/qtmaterialsymbols/exceptions.py
vendored
Normal file
8
client/ayon_core/vendor/python/qtmaterialsymbols/exceptions.py
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
class ApplicationNotRunning(Exception):
|
||||
"""Raised when the QApplication is not running."""
|
||||
pass
|
||||
|
||||
|
||||
class FontError(Exception):
|
||||
"""Raised when there is an issue with font."""
|
||||
pass
|
||||
254
client/ayon_core/vendor/python/qtmaterialsymbols/iconic_font.py
vendored
Normal file
254
client/ayon_core/vendor/python/qtmaterialsymbols/iconic_font.py
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
r"""
|
||||
|
||||
Iconic Font
|
||||
===========
|
||||
|
||||
A lightweight module handling iconic fonts.
|
||||
|
||||
It is designed to provide a simple way for creating QIcons from glyphs.
|
||||
|
||||
From a user's viewpoint, the main entry point is the ``IconicFont`` class which
|
||||
contains methods for loading new iconic fonts with their character map and
|
||||
methods returning instances of ``QIcon``.
|
||||
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from typing import Dict, Optional, Union
|
||||
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
from .structures import IconOptions, Position
|
||||
from .utils import get_char_mapping, get_icon_name_char, _get_font_name
|
||||
|
||||
|
||||
class _Cache:
|
||||
instance = None
|
||||
|
||||
|
||||
def get_instance():
|
||||
if _Cache.instance is None:
|
||||
_Cache.instance = IconicFont()
|
||||
return _Cache.instance
|
||||
|
||||
|
||||
def get_icon(*args, **kwargs):
|
||||
return get_instance().get_icon(*args, **kwargs)
|
||||
|
||||
|
||||
class CharIconPainter:
|
||||
"""Char icon painter."""
|
||||
def paint(self, iconic, painter, rect, mode, state, options):
|
||||
"""Main paint method."""
|
||||
self._paint_icon(iconic, painter, rect, mode, state, options)
|
||||
|
||||
def _paint_icon(self, iconic, painter, rect, mode, state, options):
|
||||
"""Paint a single icon."""
|
||||
painter.save()
|
||||
|
||||
color = options.get_color_for_state(state, mode)
|
||||
char = options.get_char_for_state(state, mode)
|
||||
|
||||
painter.setPen(QtGui.QColor(color))
|
||||
|
||||
# A 16 pixel-high icon yields a font size of 14, which is pixel perfect
|
||||
# for font-awesome. 16 * 0.875 = 14
|
||||
# The reason why the glyph size is smaller than the icon size is to
|
||||
# account for font bearing.
|
||||
# draw_size = round(0.875 * rect.height() * options.scale_factor)
|
||||
|
||||
draw_size = round(rect.height() * options.scale_factor)
|
||||
|
||||
painter.setFont(iconic.get_font(draw_size))
|
||||
if options.offset is not None:
|
||||
rect = QtCore.QRect(rect)
|
||||
rect.translate(
|
||||
round(options.offset.x * rect.width()),
|
||||
round(options.offset.y * rect.height())
|
||||
)
|
||||
|
||||
scale_x = -1 if options.hflip else 1
|
||||
scale_y = -1 if options.vflip else 1
|
||||
|
||||
if options.vflip or options.hflip or options.rotate:
|
||||
x_center = rect.width() * 0.5
|
||||
y_center = rect.height() * 0.5
|
||||
painter.translate(x_center, y_center)
|
||||
|
||||
transfrom = QtGui.QTransform()
|
||||
transfrom.scale(scale_x, scale_y)
|
||||
painter.setTransform(transfrom, True)
|
||||
|
||||
if options.rotate:
|
||||
painter.rotate(options.rotate)
|
||||
painter.translate(-x_center, -y_center)
|
||||
|
||||
painter.setOpacity(options.opacity)
|
||||
|
||||
painter.drawText(rect, QtCore.Qt.AlignCenter, char)
|
||||
painter.restore()
|
||||
|
||||
|
||||
class CharIconEngine(QtGui.QIconEngine):
|
||||
"""Specialization of QIconEngine used to draw font-based icons."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
iconic: "IconicFont",
|
||||
painter: QtGui.QPainter,
|
||||
options: IconOptions
|
||||
):
|
||||
super(CharIconEngine, self).__init__()
|
||||
self._iconic = iconic
|
||||
self._painter = painter
|
||||
self._options = options
|
||||
|
||||
def paint(self, painter, rect, mode, state):
|
||||
self._painter.paint(
|
||||
self._iconic,
|
||||
painter,
|
||||
rect,
|
||||
mode,
|
||||
state,
|
||||
self._options
|
||||
)
|
||||
|
||||
def pixmap(self, size, mode, state):
|
||||
pm = QtGui.QPixmap(size)
|
||||
pm.fill(QtCore.Qt.transparent)
|
||||
painter = QtGui.QPainter(pm)
|
||||
self.paint(
|
||||
painter,
|
||||
QtCore.QRect(QtCore.QPoint(0, 0), size),
|
||||
mode,
|
||||
state
|
||||
)
|
||||
return pm
|
||||
|
||||
|
||||
class IconicFont(QtCore.QObject):
|
||||
"""Main class for managing icons."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._painter = CharIconPainter()
|
||||
self._icon_cache = {}
|
||||
|
||||
def get_charmap(self) -> Dict[str, int]:
|
||||
return get_char_mapping()
|
||||
|
||||
def get_font(self, size: int) -> QtGui.QFont:
|
||||
"""Return a QFont corresponding to the given size."""
|
||||
font = QtGui.QFont(_get_font_name())
|
||||
font.setPixelSize(round(size))
|
||||
return font
|
||||
|
||||
def get_icon_with_options(self, options: IconOptions) -> QtGui.QIcon:
|
||||
"""Return a QIcon object corresponding to the provided icon name."""
|
||||
if QtWidgets.QApplication.instance() is None:
|
||||
warnings.warn(
|
||||
"You need to have a running QApplication!"
|
||||
)
|
||||
return QtGui.QIcon()
|
||||
|
||||
cache_key = options.identifier
|
||||
if cache_key in self._icon_cache:
|
||||
return self._icon_cache[cache_key]
|
||||
|
||||
output = self._icon_by_painter(self._painter, options)
|
||||
self._icon_cache[cache_key] = output
|
||||
return output
|
||||
|
||||
def get_icon(
|
||||
self,
|
||||
icon_name: Optional[str] = None,
|
||||
color: Optional[Union[QtGui.QColor, str]] = None,
|
||||
opacity: Optional[float] = None,
|
||||
scale_factor: Optional[float] = None,
|
||||
offset: Optional[Position] = None,
|
||||
hflip: Optional[bool] = False,
|
||||
vflip: Optional[bool] = False,
|
||||
rotate: Optional[int] = 0,
|
||||
icon_name_normal: Optional[str] = None,
|
||||
icon_name_active: Optional[str] = None,
|
||||
icon_name_selected: Optional[str] = None,
|
||||
icon_name_disabled: Optional[str] = None,
|
||||
icon_name_on: Optional[str] = None,
|
||||
icon_name_off: Optional[str] = None,
|
||||
icon_name_on_normal: Optional[str] = None,
|
||||
icon_name_off_normal: Optional[str] = None,
|
||||
icon_name_on_active: Optional[str] = None,
|
||||
icon_name_off_active: Optional[str] = None,
|
||||
icon_name_on_selected: Optional[str] = None,
|
||||
icon_name_off_selected: Optional[str] = None,
|
||||
icon_name_on_disabled: Optional[str] = None,
|
||||
icon_name_off_disabled: Optional[str] = None,
|
||||
color_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_on: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_off: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_on_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_off_normal: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_on_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_off_active: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_on_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_off_selected: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_on_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||
color_off_disabled: Optional[Union[QtGui.QColor, str]] = None,
|
||||
) -> QtGui.QIcon:
|
||||
"""Return a QIcon object corresponding to the provided icon name."""
|
||||
if QtWidgets.QApplication.instance() is None:
|
||||
warnings.warn(
|
||||
"You need to have a running QApplication!"
|
||||
)
|
||||
return QtGui.QIcon()
|
||||
|
||||
options = IconOptions.from_data(
|
||||
icon_name=icon_name,
|
||||
color=color,
|
||||
opacity=opacity,
|
||||
scale_factor=scale_factor,
|
||||
offset=offset,
|
||||
hflip=hflip,
|
||||
vflip=vflip,
|
||||
rotate=rotate,
|
||||
icon_name_normal=icon_name_normal,
|
||||
icon_name_active=icon_name_active,
|
||||
icon_name_selected=icon_name_selected,
|
||||
icon_name_disabled=icon_name_disabled,
|
||||
icon_name_on=icon_name_on,
|
||||
icon_name_off=icon_name_off,
|
||||
icon_name_on_normal=icon_name_on_normal,
|
||||
icon_name_off_normal=icon_name_off_normal,
|
||||
icon_name_on_active=icon_name_on_active,
|
||||
icon_name_off_active=icon_name_off_active,
|
||||
icon_name_on_selected=icon_name_on_selected,
|
||||
icon_name_off_selected=icon_name_off_selected,
|
||||
icon_name_on_disabled=icon_name_on_disabled,
|
||||
icon_name_off_disabled=icon_name_off_disabled,
|
||||
color_normal=color_normal,
|
||||
color_active=color_active,
|
||||
color_selected=color_selected,
|
||||
color_disabled=color_disabled,
|
||||
color_on=color_on,
|
||||
color_off=color_off,
|
||||
color_on_normal=color_on_normal,
|
||||
color_off_normal=color_off_normal,
|
||||
color_on_active=color_on_active,
|
||||
color_off_active=color_off_active,
|
||||
color_on_selected=color_on_selected,
|
||||
color_off_selected=color_off_selected,
|
||||
color_on_disabled=color_on_disabled,
|
||||
color_off_disabled=color_off_disabled,
|
||||
)
|
||||
cache_key = options.identifier
|
||||
if cache_key in self._icon_cache:
|
||||
return self._icon_cache[cache_key]
|
||||
return self.get_icon_with_options(options)
|
||||
|
||||
def _icon_by_painter(self, painter, options):
|
||||
"""Return the icon corresponding to the given painter."""
|
||||
engine = CharIconEngine(self, painter, options)
|
||||
return QtGui.QIcon(engine)
|
||||
3599
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.codepoints
vendored
Normal file
3599
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.codepoints
vendored
Normal file
File diff suppressed because it is too large
Load diff
3601
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.json
vendored
Normal file
3601
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
BIN
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.ttf
vendored
Normal file
BIN
client/ayon_core/vendor/python/qtmaterialsymbols/resources/MaterialSymbolsOutlined.ttf
vendored
Normal file
Binary file not shown.
16
client/ayon_core/vendor/python/qtmaterialsymbols/resources/__init__.py
vendored
Normal file
16
client/ayon_core/vendor/python/qtmaterialsymbols/resources/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def get_font_filepath(
|
||||
font_name: Optional[str] = "MaterialSymbolsOutlined"
|
||||
) -> str:
|
||||
return os.path.join(CURRENT_DIR, f"{font_name}.ttf")
|
||||
|
||||
|
||||
def get_mapping_filepath(
|
||||
font_name: Optional[str] = "MaterialSymbolsOutlined"
|
||||
) -> str:
|
||||
return os.path.join(CURRENT_DIR, f"{font_name}.json")
|
||||
221
client/ayon_core/vendor/python/qtmaterialsymbols/structures.py
vendored
Normal file
221
client/ayon_core/vendor/python/qtmaterialsymbols/structures.py
vendored
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
import collections
|
||||
import json
|
||||
from typing import Optional, Union
|
||||
|
||||
from qtpy import QtGui
|
||||
|
||||
from .utils import get_icon_name_char
|
||||
|
||||
Position = collections.namedtuple("Offset", ["x", "y"])
|
||||
|
||||
|
||||
class IconSubOption:
|
||||
def __init__(
|
||||
self,
|
||||
on_normal,
|
||||
on_disabled=None,
|
||||
on_active=None,
|
||||
on_selected=None,
|
||||
off_normal=None,
|
||||
off_disabled=None,
|
||||
off_active=None,
|
||||
off_selected=None,
|
||||
):
|
||||
if off_normal is None:
|
||||
off_normal = on_normal
|
||||
|
||||
if on_disabled is None:
|
||||
on_disabled = on_normal
|
||||
if off_disabled is None:
|
||||
off_disabled = on_disabled
|
||||
|
||||
if on_active is None:
|
||||
on_active = on_normal
|
||||
if off_active is None:
|
||||
off_active = on_active
|
||||
|
||||
if on_selected is None:
|
||||
on_selected = on_normal
|
||||
if off_selected is None:
|
||||
off_selected = on_selected
|
||||
|
||||
self._identifier = None
|
||||
self.on_normal = on_normal
|
||||
self.on_disabled = on_disabled
|
||||
self.on_active = on_active
|
||||
self.on_selected = on_selected
|
||||
self.off_normal = off_normal
|
||||
self.off_disabled = off_disabled
|
||||
self.off_active = off_active
|
||||
self.off_selected = off_selected
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
if self._identifier is None:
|
||||
self._identifier = self._generate_identifier()
|
||||
return self._identifier
|
||||
|
||||
def get_value_for_state(self, state, mode):
|
||||
if state == QtGui.QIcon.On:
|
||||
if mode == QtGui.QIcon.Disabled:
|
||||
return self.on_disabled
|
||||
if mode == QtGui.QIcon.Active:
|
||||
return self.on_active
|
||||
if mode == QtGui.QIcon.Selected:
|
||||
return self.on_selected
|
||||
return self.on_normal
|
||||
|
||||
if mode == QtGui.QIcon.Disabled:
|
||||
return self.off_disabled
|
||||
if mode == QtGui.QIcon.Active:
|
||||
return self.off_active
|
||||
if mode == QtGui.QIcon.Selected:
|
||||
return self.off_selected
|
||||
return self.off_normal
|
||||
|
||||
def _generate_identifier(self):
|
||||
prev_value = None
|
||||
values = []
|
||||
for value in (
|
||||
self.on_normal,
|
||||
self.off_normal,
|
||||
self.on_active,
|
||||
self.off_active,
|
||||
self.on_selected,
|
||||
self.off_selected,
|
||||
self.on_disabled,
|
||||
self.off_disabled,
|
||||
):
|
||||
id_value = ""
|
||||
if value != prev_value:
|
||||
id_value = self._get_value_id(value)
|
||||
values.append(id_value)
|
||||
prev_value = value
|
||||
|
||||
return "|".join(values)
|
||||
|
||||
def _get_value_id(self, value):
|
||||
if isinstance(value, QtGui.QColor):
|
||||
return value.name()
|
||||
return str(value)
|
||||
|
||||
|
||||
def _prepare_mapping(option_name):
|
||||
mapping = []
|
||||
for store_key, alternative_keys in (
|
||||
("on_normal", ["normal", "on", ""]),
|
||||
("off_normal", ["off"]),
|
||||
("on_active", ["active"]),
|
||||
("off_active", []),
|
||||
("on_selected", ["selected"]),
|
||||
("off_selected", []),
|
||||
("on_disabled", ["disabled"]),
|
||||
("off_disabled", []),
|
||||
):
|
||||
mapping_keys = [f"{option_name}_{store_key}"]
|
||||
for alt_key in alternative_keys:
|
||||
key = option_name
|
||||
if alt_key:
|
||||
key = f"{option_name}_{alt_key}"
|
||||
mapping_keys.append(key)
|
||||
mapping.append((store_key, mapping_keys))
|
||||
return mapping
|
||||
|
||||
|
||||
class IconOptions:
|
||||
mapping_color_keys = _prepare_mapping("color")
|
||||
mapping_name_keys = _prepare_mapping("icon_name")
|
||||
data_keys = {
|
||||
"opacity",
|
||||
"offset",
|
||||
"scale_factor",
|
||||
"hflip",
|
||||
"vflip",
|
||||
"rotate",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
char_option: IconSubOption,
|
||||
color_option: IconSubOption,
|
||||
opacity: Optional[float] = None,
|
||||
scale_factor: Optional[float] = None,
|
||||
offset: Optional[Position] = None,
|
||||
hflip: Optional[bool] = False,
|
||||
vflip: Optional[bool] = False,
|
||||
rotate: Optional[int] = 0,
|
||||
):
|
||||
if opacity is None:
|
||||
opacity = 1.0
|
||||
if scale_factor is None:
|
||||
scale_factor = 1.0
|
||||
|
||||
self._identifier = None
|
||||
self.char_option = char_option
|
||||
self.color_option = color_option
|
||||
self.opacity = opacity
|
||||
self.scale_factor = scale_factor
|
||||
self.offset = offset
|
||||
self.hflip = hflip
|
||||
self.vflip = vflip
|
||||
self.rotate = rotate
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
if self._identifier is None:
|
||||
self._identifier = self._generate_identifier()
|
||||
return self._identifier
|
||||
|
||||
def get_color_for_state(self, state, mode) -> QtGui.QColor:
|
||||
return self.color_option.get_value_for_state(state, mode)
|
||||
|
||||
def get_char_for_state(self, state, mode) -> str:
|
||||
return self.char_option.get_value_for_state(state, mode)
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, **kwargs):
|
||||
new_kwargs = {
|
||||
key: value
|
||||
for key, value in kwargs.items()
|
||||
if key in cls.data_keys and value is not None
|
||||
}
|
||||
color_kwargs = cls._prepare_mapping_values(
|
||||
cls.mapping_color_keys, kwargs
|
||||
)
|
||||
name_kwargs = cls._prepare_mapping_values(
|
||||
cls.mapping_name_keys, kwargs
|
||||
)
|
||||
char_kwargs = {
|
||||
key: get_icon_name_char(value)
|
||||
for key, value in name_kwargs.items()
|
||||
}
|
||||
|
||||
new_kwargs["color_option"] = IconSubOption(**color_kwargs)
|
||||
new_kwargs["char_option"] = IconSubOption(**char_kwargs)
|
||||
return cls(**new_kwargs)
|
||||
|
||||
@classmethod
|
||||
def _prepare_mapping_values(cls, mapping, kwargs):
|
||||
mapping_values = {}
|
||||
for store_key, mapping_keys in mapping:
|
||||
for key in mapping_keys:
|
||||
value = kwargs.pop(key, None)
|
||||
if value is not None:
|
||||
mapping_values[store_key] = value
|
||||
break
|
||||
return mapping_values
|
||||
|
||||
def _generate_identifier(self):
|
||||
return (
|
||||
str(value)
|
||||
for value in (
|
||||
self.char_option.identifier,
|
||||
self.color_option.identifier,
|
||||
self.opacity,
|
||||
self.scale_factor,
|
||||
self.offset,
|
||||
self.hflip,
|
||||
self.vflip,
|
||||
self.rotate,
|
||||
)
|
||||
)
|
||||
69
client/ayon_core/vendor/python/qtmaterialsymbols/utils.py
vendored
Normal file
69
client/ayon_core/vendor/python/qtmaterialsymbols/utils.py
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import json
|
||||
import copy
|
||||
from typing import Dict, Union
|
||||
|
||||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
|
||||
from .exceptions import ApplicationNotRunning, FontError
|
||||
from .resources import get_mapping_filepath, get_font_filepath
|
||||
|
||||
|
||||
class _Cache:
|
||||
mapping = None
|
||||
font_id = None
|
||||
font_name = None
|
||||
|
||||
|
||||
def _load_font():
|
||||
if QtWidgets.QApplication.instance() is None:
|
||||
raise ApplicationNotRunning("No QApplication instance found.")
|
||||
|
||||
if _Cache.font_id is not None:
|
||||
loaded_font_families = QtGui.QFontDatabase.applicationFontFamilies(
|
||||
_Cache.font_id
|
||||
)
|
||||
if loaded_font_families:
|
||||
return
|
||||
|
||||
filepath = get_font_filepath()
|
||||
with open(filepath, "rb") as font_data:
|
||||
font_id = QtGui.QFontDatabase.addApplicationFontFromData(
|
||||
QtCore.QByteArray(font_data.read())
|
||||
)
|
||||
|
||||
loaded_font_families = QtGui.QFontDatabase.applicationFontFamilies(
|
||||
font_id
|
||||
)
|
||||
if not loaded_font_families:
|
||||
raise FontError("Failed to load font.")
|
||||
|
||||
_Cache.font_id = font_id
|
||||
_Cache.font_name = loaded_font_families[0]
|
||||
|
||||
|
||||
def _load_mapping():
|
||||
if _Cache.mapping is not None:
|
||||
return
|
||||
|
||||
filepath = get_mapping_filepath()
|
||||
with open(filepath, "r") as stream:
|
||||
mapping = json.load(stream)
|
||||
_Cache.mapping = {
|
||||
key: chr(value)
|
||||
for key, value in mapping.items()
|
||||
}
|
||||
|
||||
|
||||
def _get_font_name():
|
||||
_load_font()
|
||||
return _Cache.font_name
|
||||
|
||||
|
||||
def get_icon_name_char(icon_name: str) -> Union[int, None]:
|
||||
_load_mapping()
|
||||
return _Cache.mapping.get(icon_name, None)
|
||||
|
||||
|
||||
def get_char_mapping() -> Dict[str, int]:
|
||||
_load_mapping()
|
||||
return copy.deepcopy(_Cache.mapping)
|
||||
1
client/ayon_core/vendor/python/qtmaterialsymbols/version.py
vendored
Normal file
1
client/ayon_core/vendor/python/qtmaterialsymbols/version.py
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
__version__ = "1.0.0"
|
||||
|
|
@ -13,7 +13,15 @@ class CollectFileDependencies(plugin.MayaContextPlugin):
|
|||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
# Disable plug-in if not used for deadline submission anyway
|
||||
settings = project_settings["deadline"]["publish"]["MayaSubmitDeadline"] # noqa
|
||||
if "deadline" not in project_settings:
|
||||
cls.enabled = False
|
||||
return
|
||||
settings = (
|
||||
project_settings
|
||||
["deadline"]
|
||||
["publish"]
|
||||
["MayaSubmitDeadline"]
|
||||
)
|
||||
cls.enabled = settings.get("asset_dependencies", True)
|
||||
|
||||
def process(self, context):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,16 @@ class ExtractImportReference(plugin.MayaExtractorPlugin,
|
|||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
cls.active = project_settings["deadline"]["publish"]["MayaSubmitDeadline"]["import_reference"] # noqa
|
||||
if "deadline" not in project_settings:
|
||||
cls.enabled = False
|
||||
return
|
||||
cls.active = (
|
||||
project_settings
|
||||
["deadline"]
|
||||
["publish"]
|
||||
["MayaSubmitDeadline"]
|
||||
["import_reference"]
|
||||
)
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring AYON addon 'maya' version."""
|
||||
__version__ = "0.2.3"
|
||||
__version__ = "0.2.4"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name = "maya"
|
||||
title = "Maya"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
client_dir = "ayon_maya"
|
||||
|
||||
ayon_required_addons = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue