Source code for ooodev.gui.menu.context.context_creator

from __future__ import annotations
import copy
import json
from enum import Enum
from typing import Any, Dict, List, Callable, TYPE_CHECKING
import ooodev
from ooodev.utils.partial.lo_inst_props_partial import LoInstPropsPartial
from ooodev.loader import lo as mLo
from ooodev.events.partial.events_partial import EventsPartial
from ooodev.events.args.event_args import EventArgs
from ooodev.events.args.cancel_event_args import CancelEventArgs
from ooodev.io.json.json_encoder import JsonEncoder
from ooodev.utils.helper.dot_dict import DotDict
from ooodev.gui.menu.context.context_processor import ContextProcessor
from ooodev.gui.menu.context.action_trigger_item import ActionTriggerItem
from ooodev.gui.menu.context.action_trigger_sep import ActionTriggerSep
from ooodev.gui.menu.context.action_trigger_container import ActionTriggerContainer

if TYPE_CHECKING:
    from ooodev.loader.inst.lo_inst import LoInst


[docs]class ContextCreator(LoInstPropsPartial, EventsPartial, JsonEncoder): """ Class for creating context action menu. This class can also be used to convert menu data to a format that can be used with JSON. Example: .. code-block:: python # ... menus = get_action_menu() json_str = json.dumps(menus, cls=ContextCreator, indent=4) with open("action_menu.json", "w") as f: f.write(json_str) See Also: - :ref:`help_menu_context_incept` - :ref:`help_menu_context_incept_class_ex` """
[docs] def __init__(self, lo_inst: LoInst | None = None, **kwargs) -> None: """ Constructor Args: lo_inst (LoInst | None, optional): LibreOffice instance. Defaults to ``None``. kwargs (Any, optional): Additional keyword arguments. (Used by JsonEncoder) """ if lo_inst is None: lo_inst = mLo.Lo.current_lo LoInstPropsPartial.__init__(self, lo_inst) EventsPartial.__init__(self) JsonEncoder.__init__(self, **kwargs) self._lookups = { "text": "text", "separator_type": "separator_type", "command": "command", "help_command": "help_command", "submenu": "submenu", }
[docs] def on_json_encode(self, obj: Any) -> Any: """ Protected method to encode object to JSON. Can be overridden by subclasses. Args: obj (Any): Object to encode. This is the object that json is currently encoding. Returns: Any: The result of the encoding. The default is ``NULL_OBJ`` which means that the encoding is not handled. """ if isinstance(obj, Enum): return int(obj) # type: ignore return super().on_json_encode(obj)
def _set_dict_from_popup_item(self, menu: dict[str, Any], pop: ActionTriggerItem | ActionTriggerSep) -> None: """Set dictionary from popup item""" if isinstance(pop, ActionTriggerSep): menu[self.key_lookups["text"]] = "-" if "separator_type" in self.key_lookups: menu[self.key_lookups["separator_type"]] = pop.SeparatorType return menu[self.key_lookups["text"]] = pop.Text if pop.CommandURL: menu[self.key_lookups["command"]] = pop.CommandURL if pop.HelpURL: if "help_command" in self.key_lookups: menu[self.key_lookups["help_command"]] = pop.HelpURL def _process_sub_menu(self, menus: list[dict[str, Any]]) -> None: """Insert submenu""" pm = ActionTriggerContainer() for index, menu in enumerate(menus): submenu = menu.pop("submenu", False) mp = ContextProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.get_action_item(menu, index) if pop is None: continue menu.clear() self._set_dict_from_popup_item(menu, pop) if submenu: self._process_sub_menu(submenu) menu[self.key_lookups["submenu"]] = submenu def _insert_sub_menu(self, parent: ActionTriggerItem, menus: List[dict[str, Any]]) -> None: """Insert submenu""" pm = ActionTriggerContainer() eargs = EventArgs(self) eargs.event_data = DotDict(container=pm) self.trigger_event("action_container_created", eargs) for index, menu in enumerate(menus): submenu = menu.pop("submenu", False) mp = ContextProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.process(menu, index) if pop is None: continue if submenu and isinstance(pop, ActionTriggerItem): self._insert_sub_menu(pop, submenu) parent.SubContainer = pm
[docs] def create(self, menus: List[Dict[str, Any]]) -> ActionTriggerContainer: """ Create popup menu. Args: menus (List[Dict[str, Any]]): Menu Data. """ cpy = copy.deepcopy(menus) pm = ActionTriggerContainer() eargs = EventArgs(self) eargs.event_data = DotDict(container=pm) self.trigger_event("action_container_created", eargs) for index, menu in enumerate(cpy): submenu = menu.pop("submenu", False) mp = ContextProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.process(menu, index) if pop is None: continue if submenu and isinstance(pop, ActionTriggerItem): self._insert_sub_menu(pop, submenu) return pm
# region subscribe
[docs] def subscribe_action_container_created(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Subscribe on Action Container created event. The callback ``event_data`` is a dictionary with keys: - ``container``: ActionTriggerContainer instance """ self.subscribe_event("action_container_created", callback)
[docs] def unsubscribe_action_container_created(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Unsubscribe on popup created event. """ self.unsubscribe_event("action_container_created", callback)
[docs] def subscribe_before_process(self, callback: Callable[[Any, CancelEventArgs], None]) -> None: """ Subscribe on before process event. The callback ``event_data`` is a dictionary with keys: - ``container``: ActionTriggerContainer instance - ``action_item``: Action Item instance """ self.subscribe_event("before_process", callback)
[docs] def subscribe_after_process(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Subscribe on after process event. The callback ``event_data`` is a dictionary with keys: - ``container``: ActionTriggerContainer instance - ``action_item``: Action Item instance """ self.subscribe_event("after_process", callback)
[docs] def unsubscribe_before_process(self, callback: Callable[[Any, CancelEventArgs], None]) -> None: """ Unsubscribe on before process event. """ self.unsubscribe_event("before_process", callback)
[docs] def unsubscribe_after_process(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Unsubscribe on after process event. """ self.unsubscribe_event("after_process", callback)
[docs] def subscribe_module_no_text(self, callback: Callable[[Any, CancelEventArgs], None]) -> None: """ Subscribe on no text found for module menu entry. This event occurs when a module menu entry is created using the ``module`` key and the text for the menu entry is not found. This event will not be raised if the ``module`` entry also provides a ``text`` key. A ``text`` key can be provided to the module entry to provide a valid menu text as a replacement if not found. The callback ``event_data`` is a dictionary with keys: - ``module_kind``: ModuleNamesKind - ``cmd``: Command as a string. - ``index``: Index as an integer. - ``menu``: Menu Data as a dictionary. The caller can set ``menu["text"]`` to provide a valid menu text. If the caller cancels the event then the menu item is not created. """ self.subscribe_event("action_item_module_no_text_found", callback)
[docs] def unsubscribe_module_no_text(self, callback: Callable[[Any, CancelEventArgs], None]) -> None: """ Unsubscribe on no text found for module menu entry. """ self.unsubscribe_event("action_item_module_no_text_found", callback)
# endregion subscribe # region JSON
[docs] def get_json_dict(self, menus: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Gets a dictionary that can be converted to JSON. This is an alternative to created json data. This is a more standard way that would not require this library to decode. The dictionary created by this method can also be used with the ``create`` method to create an Action Item menu. The menu dictionaries can have data such as ``{"command": ".uno:Cut", "module": ModuleNamesKind.SPREADSHEET_DOCUMENT}``. For json this sort of data does not work, this this method converts all menu data to a standard format that can be used with json. Args: menus (List[Dict[str, Any]]): Action Item Menu Data. This is can be the same data used to create an Action Item menu. Note: Even though menu data such as ``{"command": ".uno:Cut", "module": ModuleNamesKind.SPREADSHEET_DOCUMENT}`` is not valid for json. It can still be encoded using this ``ContextCreator`` class. Example .. code-block:: python # ... menus = get_action_menu() json_str = json.dumps(menus, cls=ContextCreator, indent=4) with open("action_menu.json", "w") as f: f.write(json_str) """ result = [] pm = ActionTriggerContainer() for index, menu in enumerate(menus): cpy = copy.deepcopy(menu) submenu = cpy.pop("submenu", False) mp = ContextProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.get_action_item(cpy, index) if pop is None: continue cpy.clear() self._set_dict_from_popup_item(cpy, pop) if submenu and isinstance(pop, ActionTriggerItem): self._process_sub_menu(submenu) cpy[self.key_lookups["submenu"]] = submenu result.append(cpy) return result
[docs] def json_dumps(self, menus: List[Dict[str, Any]], dynamic: bool = False) -> str: """ Get JSON data. Args: menus (List[Dict[str, Any]]): Menu Data. Returns: str: JSON data. """ version = ooodev.get_version() if dynamic: menu_data = menus else: menu_data = self.get_json_dict(menus) data = {"id": "ooodev.context_action_menu", "version": version, "dynamic": dynamic, "menus": menu_data} if dynamic: return json.dumps(data, cls=ContextCreator, indent=4) return json.dumps(data, indent=4)
[docs] def json_dump(self, file: Any, menus: List[Dict[str, Any]], dynamic: bool = False) -> None: """ Dump JSON data to file. Args: file (Any): File path. menus (List[Dict[str, Any]]): Menu Data. dynamic (bool, optional): Dynamic data. Defaults to ``False``. """ with open(file, "w") as f: f.write(self.json_dumps(menus, dynamic=dynamic))
[docs] @staticmethod def json_loads(json_str: str, **kwargs) -> List[Dict[str, Any]]: """ Load JSON data. Args: json_str (str): JSON data. Returns: List[Dict[str, Any]]: Menu Data. """ data = json.loads(json_str, **kwargs) allowed = {"ooodev.popup_menu", "ooodev.context_action_menu"} if data["id"] not in allowed: raise ValueError("Invalid JSON data") return data["menus"]
[docs] @staticmethod def json_load(json_file: Any, **kwargs) -> List[Dict[str, Any]]: """ Load JSON data from file. Args: json_file (str): JSON file path. Returns: List[Dict[str, Any]]: Menu Data. """ with open(json_file, "r") as f: data = json.load(f, **kwargs) allowed = {"ooodev.popup_menu", "ooodev.context_action_menu"} if data["id"] not in allowed: raise ValueError("Invalid JSON data") return data["menus"]
# endregion JSON # region Properties @property def key_lookups(self) -> Dict[str, str]: """ Get key lookups. Returns: Dict[str, Any]: Key lookups. """ return self._lookups @key_lookups.setter def key_lookups(self, value: Dict[str, str]) -> None: """ Set key lookups. Args: value (Dict[str, str]): Key lookups. """ self._lookups = value
# endregion Properties