Source code for ooodev.gui.menu.popup_menu
from __future__ import annotations
from typing import TYPE_CHECKING, Tuple
import contextlib
from logging import DEBUG
try:
# python 3.12+
from typing import override # noqa # type: ignore
except ImportError:
from typing_extensions import override # noqa # type: ignore
from com.sun.star.awt import XPopupMenu
from ooo.dyn.awt.menu_item_type import MenuItemType
from ooodev.adapter.component_prop import ComponentProp
from ooodev.io.log.named_logger import NamedLogger
from ooodev.io.log import logging as logger
from ooodev.adapter.awt.popup_menu_comp import PopupMenuComp
from ooodev.utils import gen_util as mGenUtil
from ooodev.utils.cache.lru_cache import LRUCache
from ooodev.utils.partial.lo_inst_props_partial import LoInstPropsPartial
from ooodev.macro.script.macro_script import MacroScript
from ooodev.loader import lo as mLo
if TYPE_CHECKING:
from ooodev.utils.kind.menu_item_style_kind import MenuItemStyleKind
from ooodev.loader.inst.lo_inst import LoInst
[docs]class PopupMenu(LoInstPropsPartial, PopupMenuComp):
"""
Popup Menu Class.
See Also:
- :ref:`help_working_with_menu_bar`
- :ref:`help_common_gui_menus_menu_bar`
"""
[docs] def __init__(self, component: XPopupMenu, lo_inst: LoInst | None = None) -> None:
"""
Initializes the instance.
Args:
component (XPopupMenu): The popup menu component.
lo_inst (LoInst, optional): LoInst, Defaults to ``Lo.current_lo``.
"""
if lo_inst is None:
lo_inst = mLo.Lo.current_lo
LoInstPropsPartial.__init__(self, lo_inst)
PopupMenuComp.__init__(self, component)
log_level = logger.get_log_level()
if log_level == DEBUG:
self._logger = NamedLogger(f"{self.__class__.__name__} - {id(self)}")
else:
self._logger = NamedLogger(self.__class__.__name__)
self._cache = LRUCache(30)
# region Protected Methods
@override
def _get_index(self, idx: int, allow_greater: bool = False) -> int:
"""
Gets the index.
Args:
idx (int): Index of sheet. Can be a negative value to index from the end of the list.
allow_greater (bool, optional): If True and index is greater then the number of
sheets then the index becomes the next index if sheet were appended. Defaults to False.
Returns:
int: Index value.
"""
count = len(self)
return mGenUtil.Util.get_index(idx, count, allow_greater)
def _get_existing_index(self, itm: int | str) -> int:
"""
Gets the position from the index or command.
Args:
item_pos (int | str): Index or command.
Returns:
int: Position.
"""
result = -1
if isinstance(itm, str):
if itm == "":
return result
result, _ = self.find_item_pos(itm)
else:
with contextlib.suppress(ValueError):
result = self._get_index(itm)
return result
# endregion Protected Methods
# region Find methods
[docs] def find_item_pos(self, cmd: str, search_sub_menu: bool = False) -> Tuple[int, PopupMenu | None]:
"""
Find item position by command.
Args:
cmd (str): A menu command such as ``.uno:Copy``.
search_sub_menu (bool, optional): Search in sub menus. Defaults to ``False``.
Returns:
Tuple[int, PopupMenu | None]: The position of the menu item and the Popup Menu that command was found in. If not found, return ``(-1, None)``.
See Also:
- :meth:`find_item_menu_id`
"""
key = f"find_item_pos_{cmd}_{search_sub_menu}"
if key in self._cache:
return self._cache[key]
def search(pop_mnu: PopupMenu, str_cmd: str) -> Tuple[int, PopupMenu | None]:
nonlocal search_sub_menu
result = -1
submenu = None
if self._logger.is_debug:
self._logger.debug(f"Searching for command: {str_cmd}")
cmd = str_cmd.casefold()
for i, menu_id in enumerate(pop_mnu):
if search_sub_menu:
if self._logger.is_debug:
self._logger.debug(f"Searching {str_cmd} in sub menu: {menu_id}")
submenu = pop_mnu.get_popup_menu(menu_id)
if submenu is not None and len(submenu) > 0:
# turn of caching for sub menu when searching. No real value.
submenu.cache.capacity = 0
if self._logger.is_debug:
self._logger.debug(f"Found a sub Menu {str_cmd}, Searching in sub menu: {menu_id}")
result, sub = submenu.find_item_pos(cmd, search_sub_menu)
if result != -1:
if self._logger.is_debug:
self._logger.debug(f"Found {str_cmd} in sub menu in position: {result}")
return result, sub
else:
if self._logger.is_debug:
self._logger.debug(f"Command {str_cmd} not found in sub menu: {menu_id}")
menu_type = pop_mnu.get_item_type(i)
if menu_type == MenuItemType.SEPARATOR:
continue
command = pop_mnu.get_command(menu_id)
if cmd == command.casefold():
if self._logger.is_debug:
self._logger.debug(f"Found {str_cmd} in menu: {menu_id}")
result = i
submenu = pop_mnu
return result, submenu
if result == -1:
if self._logger.is_debug:
self._logger.debug(f"Command {str_cmd} not found.")
return -1, None
if self._logger.is_debug:
self._logger.debug(f"Command {str_cmd} found.")
return result, submenu
if cmd.startswith(".custom:"):
cmd = cmd[8:]
if not cmd:
return -1, None
pos, found_mnu = search(self, cmd)
if found_mnu is not None:
found_mnu.cache.capacity = 30
if self._logger.is_debug:
self._logger.debug(f"Setting cache capacity back to {found_mnu.cache.capacity} for found menu")
srch_result = (pos, found_mnu)
self._cache[key] = srch_result
return srch_result
# endregion Find methods
# region MenuPartial Overrides
[docs] @override
def clear(self) -> None:
"""
Removes all items from the menu.
"""
super().clear() # type: ignore
self._cache.clear()
[docs] @override
def insert_item(self, menu_id: int, text: str, item_style: int | MenuItemStyleKind = 0, item_pos: int = -1) -> int: # type: ignore
"""
Insert an item into the menu.
Args:
menu_id (int): Menu item id. If set to ``-1``, it will be set to the maximum menu id + 1.
text (str): Text of the menu item.
item_style (int | MenuItemStyleKind): Style Kind. Defaults to ``0`` (none).
item_pos (int): Index position of the new item. Can be a negative value to index from the end of the list. Defaults to ``-1``.
Returns:
int: Menu item id.
"""
if menu_id == -1:
menu_id = self.get_max_menu_id() + 1
idx = self._get_index(item_pos, allow_greater=True)
super().insert_item(menu_id=menu_id, text=text, item_style=item_style, item_pos=idx) # type: ignore
self._cache.clear()
return menu_id
[docs] @override
def insert_item_after(
self, menu_id: int, text: str, item_style: int | MenuItemStyleKind = 0, after: str | int = -1
) -> int:
"""
Insert an item into the menu.
Args:
menu_id (int): Menu item id. If set to ``-1``, it will be set to the maximum menu id + 1.
text (str): Text of the menu item.
item_style (int | MenuItemStyleKind): Style Kind. Defaults to ``0`` (none).
after (str | int): Index of existing item or command to insert after. Can be a negative value to index from the end of the list. Defaults to ``-1``.
Raises:
ValueError: ValueError if ``after`` Item not found.
Returns:
int: Menu item id.
"""
if menu_id == -1:
menu_id = self.get_max_menu_id() + 1
item_pos = self._get_existing_index(after)
if item_pos == -1:
raise ValueError(f"Item not found: {after}")
super().insert_item(menu_id=menu_id, text=text, item_style=item_style, item_pos=item_pos + 1) # type: ignore
self._cache.clear()
return menu_id
[docs] @override
def remove_item(self, item_pos: int, count: int) -> None:
super().remove_item(item_pos=item_pos, count=count) # type: ignore
self._cache.clear()
# endregion MenuPartial Overrides
# region execute command
[docs] def is_dispatch_cmd(self, cmd: str | int) -> bool:
"""
Check if a command is a dispatch command.
Args:
cmd (str, int): Command or the Menu id to get the command from that is to be checked.
Returns:
bool: ``True`` if it is a dispatch command; Otherwise, ``False``.
"""
if isinstance(cmd, int):
cmd = self.get_command(cmd)
if not cmd:
return False
supported_prefixes = self.lo_inst.get_supported_dispatch_prefixes()
return cmd.startswith(supported_prefixes)
[docs] def execute_cmd(self, cmd: str | int, in_thread: bool = False) -> bool:
"""
Executes a command.
Args:
cmd (str, int): Command or the Menu id to get the command from that is to be executed.
"""
if isinstance(cmd, int):
cmd = self.get_command(cmd)
if not cmd:
return False
supported_prefixes = self.lo_inst.get_supported_dispatch_prefixes()
if cmd.startswith(supported_prefixes):
self.lo_inst.dispatch_cmd(cmd, in_thread=in_thread)
return True
try:
_ = MacroScript.call_url(cmd, in_thread=in_thread)
return True
except Exception as e:
self._logger.error(f"Error executing menu item with command value of '{cmd}': {e}")
return False
# endregion execute command
# region Properties
@property
def cache(self) -> LRUCache:
"""
Gets the cache.
Returns:
LRUCache: Cache.
"""
return self._cache
# endregion Properties