Source code for ooodev.gui.menu.popup.popup_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.gui.menu.popup.popup_item import PopupItem
from ooodev.gui.menu.popup_menu import PopupMenu
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.gui.menu.popup.popup_processor import PopupProcessor
from ooodev.io.json.json_encoder import JsonEncoder
from ooodev.utils.helper.dot_dict import DotDict


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


[docs]class PopupCreator(LoInstPropsPartial, EventsPartial, JsonEncoder): """ Class for creating popup 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_popup_menu() json_str = json.dumps(menus, cls=PopupCreator, indent=4) with open("popup_menu.json", "w") as f: f.write(json_str) See Also: - :ref:`help_popup_from_dict_or_json` - :ref:`help_popup_via_builder_item` """
[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", "command": "command", "style": "style", "checked": "checked", "enabled": "enabled", "default": "default", "help_command": "help_command", "help_text": "help_text", "tip_help_text": "tip_help_text", "shortcut": "shortcut", "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: PopupItem) -> None: """Set dictionary from popup item""" if pop.is_separator(): menu["text"] = "-" return if "text" in self.key_lookups: menu[self.key_lookups["text"]] = pop.text if "command" in self.key_lookups: menu[self.key_lookups["command"]] = pop.command if "style" in self.key_lookups: menu[self.key_lookups["style"]] = pop.style if "checked" in self.key_lookups: menu[self.key_lookups["checked"]] = pop.checked if "enabled" in self.key_lookups: menu[self.key_lookups["enabled"]] = pop.enabled if "default" in self.key_lookups: menu[self.key_lookups["default"]] = pop.default if "help_command" in self.key_lookups: menu[self.key_lookups["help_command"]] = pop.help_command if "help_text" in self.key_lookups: menu[self.key_lookups["help_text"]] = pop.help_text if "tip_help_text" in self.key_lookups: menu[self.key_lookups["tip_help_text"]] = pop.tip_help_text if "shortcut" in self.key_lookups: menu[self.key_lookups["shortcut"]] = pop.shortcut def _process_sub_menu(self, menus: list[dict[str, Any]]) -> None: """Insert submenu""" pm = PopupMenu.from_lo(lo_inst=self.lo_inst) # eargs = EventArgs(self) # eargs.event_data = {"popup_menu": parent_dict} # self.trigger_event("popup_created", eargs) for index, menu in enumerate(menus): submenu = menu.pop("submenu", False) mp = PopupProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.get_popup_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: PopupMenu, parent_menu_id: int, menus: list[dict[str, Any]]) -> None: """Insert submenu""" pm = PopupMenu.from_lo(lo_inst=self.lo_inst) eargs = EventArgs(self) eargs.event_data = DotDict(popup_menu=pm) self.trigger_event("popup_created", eargs) for index, menu in enumerate(menus): submenu = menu.pop("submenu", False) mp = PopupProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.process(menu, index) if pop is None: continue if submenu: self._insert_sub_menu(pm, pop.menu_id, submenu) parent.set_popup_menu(parent_menu_id, pm)
[docs] def create(self, menus: List[Dict[str, Any]]) -> PopupMenu: """ Create popup menu. Args: menus (List[Dict[str, Any]]): Menu Data. """ cpy = copy.deepcopy(menus) pm = PopupMenu.from_lo(lo_inst=self.lo_inst) eargs = EventArgs(self) eargs.event_data = DotDict(popup_menu=pm) self.trigger_event("popup_created", eargs) for index, menu in enumerate(cpy): submenu = menu.pop("submenu", False) mp = PopupProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.process(menu, index) if pop is None: continue if submenu: self._insert_sub_menu(pm, pop.menu_id, submenu) return pm
# region subscribe
[docs] def subscribe_popup_created(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Subscribe on popup created event. The callback ``event_data`` is a dictionary with keys: - ``popup_menu``: PopupMenu instance """ self.subscribe_event("popup_created", callback)
[docs] def unsubscribe_popup_created(self, callback: Callable[[Any, EventArgs], None]) -> None: """ Unsubscribe on popup created event. """ self.unsubscribe_event("popup_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: - ``popup_menu``: PopupMenu instance - ``popup_item``: PopupItem 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: - ``popup_menu``: PopupMenu instance - ``popup_item``: PopupItem 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("popup_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("popup_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 a popup 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]]): Menu Data. This is can be the same data used to create a popup 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 ``PopupCreator`` class. Example: .. code-block:: python # ... menus = get_popup_menu() json_str = json.dumps(menus, cls=PopupCreator, indent=4) with open("popup_menu.json", "w") as f: f.write(json_str) """ result = [] pm = PopupMenu.from_lo(lo_inst=self.lo_inst) for index, menu in enumerate(menus): cpy = copy.deepcopy(menu) submenu = cpy.pop("submenu", False) mp = PopupProcessor(pm) mp.add_event_observers(self.event_observer) pop = mp.get_popup_item(cpy, index) if pop is None: continue cpy.clear() self._set_dict_from_popup_item(cpy, pop) if submenu: 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.popup_menu", "version": version, "dynamic": dynamic, "menus": menu_data} if dynamic: return json.dumps(data, cls=PopupCreator, 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) if data["id"] != "ooodev.popup_menu": 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) if data["id"] != "ooodev.popup_menu": 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