""" Part of the tagit module. A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # standard imports import logging import os # kivy imports from kivy.lang import Builder from kivy.uix.floatlayout import FloatLayout import kivy.properties as kp # tagit imports from tagit import config from tagit.utils.builder import InvalidFactoryName from tagit.actions import ActionBuilder from tagit.external.kivy_garden.contextmenu import ContextMenuItem, AbstractMenuItemHoverable, ContextMenuTextItem, ContextMenu # inner-module imports from .dock import DockBase # exports __all__ = ('Context', ) ## code ## logger = logging.getLogger(__name__) # load kv Builder.load_file(os.path.join(os.path.dirname(__file__), 'context.kv')) # classes class ContextMenuAction(ContextMenuItem, AbstractMenuItemHoverable): """Wraps a context menu item around an action buttons.""" # menu requirements submenu_postfix = kp.StringProperty(' ...') color = kp.ListProperty([1,1,1,1]) # action requirements action = kp.ObjectProperty(None) hide_fu = kp.ObjectProperty(None) @property def content_width(self): """Forward the width from the action button.""" if self.action is None: return 0 return self.action.width def set_action(self, action): """Add the action button.""" self.add_widget(action) self.action = action return self def on_touch_up(self, touch): """Close the menu when an action is triggered.""" if self.collide_point(*touch.pos) and \ touch.button == 'left' and \ self.hide_fu is not None: self.action.on_release() self.hide_fu() return super(ContextMenuAction, self).on_touch_up(touch) class Context(FloatLayout, DockBase): """Context menu.""" root = kp.ObjectProperty(None) def show(self, x, y): """Open the menu.""" self.menu.show(x, y) def on_touch_down(self, touch): """Open the menu via click.""" if touch.button == 'right': self.show(*touch.pos) return super(Context, self).on_touch_down(touch) def on_config_changed(self, session, key, value): if key == ('ui', 'standalone', 'context'): self.on_cfg(session, session.cfg) def on_cfg(self, wx, cfg): """Construct the menu from config.""" self.populate(cfg('ui', 'standalone', 'context')) def populate(self, actions): """Construct the menu.""" # clear old menu items childs = [child for child in self.menu.children if isinstance(child, ContextMenuTextItem)] childs += [child for child in self.menu.children if isinstance(child, ContextMenuAction)] for child in childs: self.menu.remove_widget(child) # add new menu items builder = ActionBuilder() for menu, args in actions.items(): if menu == 'root': # add directly to the context menu wx = self.menu else: # create and add a submenu head = ContextMenuTextItem(text=menu) self.menu.add_widget(head) wx = ContextMenu(width=self.button_width) head.add_widget(wx) wx._on_visible(False) for action in args: try: cls = builder.get(action) if action == 'SortKey': # special case: add as submenu btn = cls(root=self.root) head = ContextMenuTextItem(text=btn.text) wx.add_widget(head) head.add_widget(btn.menu) btn.menu._on_visible(False) else: wx.add_widget(ContextMenuAction( # args to the action wrapper hide_fu=self.menu.hide, height=self.button_height, ).set_action(cls( # args to the button root=self.root, autowidth=False, size=(self.button_width, self.button_height), size_hint=(1, None), show=self.button_show, ))) except InvalidFactoryName: logger.error(f'invalid button name: {action}') ## config ## config.declare(('ui', 'standalone', 'context'), config.Dict(config.String(), config.List(config.Enum(set(ActionBuilder.keys())))), {}, __name__, 'Context menu structure', 'The context menu consists of groups of actions, similar to the button dock. Each group consists of a name and a list of actions. To add actions to the menu directly, use "root" for the group name.', '{"root": ["ShowDashboard", "ShowBrowsing"], "search": ["GoBack", "GoForth"]}') ## EOF ##