diff --git a/client/ayon_core/tools/loader/abstract.py b/client/ayon_core/tools/loader/abstract.py index d3de8fb7c2..2e90a51a5b 100644 --- a/client/ayon_core/tools/loader/abstract.py +++ b/client/ayon_core/tools/loader/abstract.py @@ -322,9 +322,9 @@ class ActionItem: entity_ids (set[str]): Entity ids. entity_type (str): Entity type. label (str): Action label. - group_label (str): Group label. - icon (dict[str, Any]): Action icon definition. - tooltip (str): Action tooltip. + group_label (Optional[str]): Group label. + icon (Optional[dict[str, Any]]): Action icon definition. + tooltip (Optional[str]): Action tooltip. order (int): Action order. options (Union[list[AbstractAttrDef], list[qargparse.QArgument]]): Action options. Note: 'qargparse' is considered as deprecated. @@ -332,16 +332,16 @@ class ActionItem: """ def __init__( self, - plugin_identifier, - identifier, - entity_ids, - entity_type, - label, - group_label, - icon, - tooltip, - order, - options, + plugin_identifier: str, + identifier: str, + entity_ids: set[str], + entity_type: str, + label: str, + group_label: Optional[str], + icon: Optional[dict[str, Any]], + tooltip: Optional[str], + order: int, + options: Optional[list], ): self.plugin_identifier = plugin_identifier self.identifier = identifier @@ -364,13 +364,12 @@ class ActionItem: # future development of detached UI tools it would be better to be # prepared for it. raise NotImplementedError( - "{}.to_data is not implemented. Use Attribute definitions" - " from 'ayon_core.lib' instead of 'qargparse'.".format( - self.__class__.__name__ - ) + f"{self.__class__.__name__}.to_data is not implemented." + " Use Attribute definitions from 'ayon_core.lib'" + " instead of 'qargparse'." ) - def to_data(self): + def to_data(self) -> dict[str, Any]: options = self._options_to_data() return { "plugin_identifier": self.plugin_identifier, @@ -386,7 +385,7 @@ class ActionItem: } @classmethod - def from_data(cls, data): + def from_data(cls, data) -> "ActionItem": options = data["options"] if options: options = deserialize_attr_defs(options) diff --git a/client/ayon_core/tools/loader/ui/actions_utils.py b/client/ayon_core/tools/loader/ui/actions_utils.py index 3281a170bd..cf39bc348c 100644 --- a/client/ayon_core/tools/loader/ui/actions_utils.py +++ b/client/ayon_core/tools/loader/ui/actions_utils.py @@ -15,15 +15,18 @@ from ayon_core.tools.utils import get_qt_icon from ayon_core.tools.loader.abstract import ActionItem -def _actions_sorter(item: tuple[str, ActionItem]): +def _actions_sorter(item: tuple[ActionItem, str, str]): """Sort the Loaders by their order and then their name. Returns: tuple[int, str]: Sort keys. """ - label, action_item = item - return action_item.order, label + action_item, group_label, label = item + if group_label is None: + group_label = label + label = "" + return action_item.order, group_label, label def show_actions_menu( @@ -46,21 +49,21 @@ def show_actions_menu( action_items_with_labels = [] for action_item in action_items: - label = action_item.label - if action_item.group_label: - label = f"{action_item.group_label} ({label})" - action_items_with_labels.append((label, action_item)) + action_items_with_labels.append( + (action_item, action_item.group_label, action_item.label) + ) + group_menu_by_label = {} action_items_by_id = {} for item in sorted(action_items_with_labels, key=_actions_sorter): - label, action_item = item + action_item, _, _ = item item_id = uuid.uuid4().hex action_items_by_id[item_id] = action_item item_options = action_item.options icon = get_qt_icon(action_item.icon) use_option = bool(item_options) action = OptionalAction( - label, + action_item.label, icon, use_option, menu @@ -76,7 +79,18 @@ def show_actions_menu( action.setData(item_id) - menu.addAction(action) + group_label = action_item.group_label + if group_label: + group_menu = group_menu_by_label.get(group_label) + if group_menu is None: + group_menu = OptionalMenu(group_label, menu) + if icon is not None: + group_menu.setIcon(icon) + menu.addMenu(group_menu) + group_menu_by_label[group_label] = group_menu + group_menu.addAction(action) + else: + menu.addAction(action) action = menu.exec_(global_point) if action is not None: