#
#		Python GUI - Menus - Generic
#

from Properties import Properties, overridable_property

class Menu:
    """Pull-down or pop-up menu class.
    
    Menu(title, item_descriptor, ...)
        constructs a menu with the given title and items. Each
        item_descriptor is of the form
        
            "-"
        
        for a separator,
        
            ("text/key", 'command_name')
        
        for a single menu item, or
        
            (["text/key", ...], 'command_name')
    
        for an indexed item group. An indexed group is a group
        of items sharing the same command name and distinguished
        by an integer index. Items can be added to and removed
        from the group dynamically, to implement e.g. a font
        menu or windows menu.
        
        The "key" part of the item descriptor (which is optional)
        specifies the keyboard equivalent. It should consist of
        a single character together with the following optional
        modifiers:
        
            ^		representing the Shift key
            @		representing the Alt or Option key
    """
    
    title = overridable_property('title')
    
    def __init__(self, title, *item_descriptors):
        self._title = title
        self._items = [self._make_item(desc) for desc in item_descriptors]
    
    def get_title(self):
        return self._title
    
    def _make_item(self, item):
        if item == "-":
            return _menu_separator
        else:
            (text, cmd) = item
            if isinstance(text, str):
                return SingleMenuItem(text, cmd)
            else:
                return MenuItemGroup(text, cmd)
    
    def _command_and_args_for_item(self, item_num):
        i = 1
        for item in self._items:
            n = item._num_subitems()
            if item_num < i + n:
                return item._command_and_args_for_subitem(item_num - i)
            i += n
        return '', ()
    
    def _update_platform_menu(self):
        self._clear_platform_menu()
        for item in self._items:
            item._add_to_platform_menu(self)

#----------------------------------------------------------------------

class MenuItem(Properties):
    #	 Internal class representing a menu item, group or separator.
    
    def _num_subitems(self):
        return 1

    def _split_text(self, text):
        # Split menu text into label and key combination.
        if "/" in text:
            return text.split("/")
        else:
            return text, ""
    
#----------------------------------------------------------------------

class MenuSeparator(MenuItem):
    #	 Internal class representing a menu separator.
    
    _command_name = ''
    
    def _add_to_platform_menu(self, menu):
        menu._add_separator_to_platform_menu()

#----------------------------------------------------------------------

class SingleMenuItem(MenuItem):
    """Class representing a menu item.
    
    Properties:
        enabled    boolean
        checked    boolean
    """
    
    enabled = 0
    checked = 0
    _key = None
    _shift = 0
    _option = 0
    #_index = None
    
    def __init__(self, text, cmd):
        self._label, keycomb = self._split_text(text)
        for c in keycomb:
            if c == '^':
                self._shift = 1
            elif c == '@':
                self._option = 1
            else:
                self._key = c
        self._command_name = cmd
    
    def _add_to_platform_menu(self, menu):
        menu._add_item_to_platform_menu(self, self._command_name)
    
    def _command_and_args_for_subitem(self, i):
        return self._command_name, ()

#----------------------------------------------------------------------

class MenuItemGroup(MenuItem):
    """Class representing a menu item group.
    
    Properties:
        enabled  <-  boolean     Assigning to these changes the corresponding
        checked  <-  boolean     property of all the group's items.
    
    Operators:
        group[index]  ->  MenuItem 
    
    Methods:
        set_items(["text/key", ...])
            Replaces all the items in the group by the specified items.
    """
    
    enabled = overridable_property('enabled')
    checked = overridable_property('checked')
        
    def __init__(self, text_list, cmd):
        self.set_items(text_list)
        self._command_name = cmd
    
    def _num_subitems(self):
        return len(self._items)
    
    def _command_and_args_for_subitem(self, i):
        return self._command_name, (i,)
    
    def get_enabled(self):
        raise AttributeError("'enabled' property of MenuItemGroup is write-only")
    
    def set_enabled(self, state):
        for item in self._items:
            item.enabled = state
    
    def get_checked(self):
        raise AttributeError("'checked' property of MenuItemGroup is write-only")
    
    def set_checked(self, state):
        for item in self._items:
            item.checked = state
    
    def __getitem__(self, index):
        return self._items[index]
    
    def set_items(self, text_list):
        self._items = [SingleMenuItem(text, '') for text in text_list]
    
    def _add_to_platform_menu(self, menu):
        #for item in self._items:
        #	item._add_to_platform_menu(menu)
        cmd = self._command_name
        for index, item in enumerate(self._items):
            menu._add_item_to_platform_menu(item, cmd, index)

#----------------------------------------------------------------------

_menu_separator = MenuSeparator()
_dummy_menu_item = SingleMenuItem("", '')

#----------------------------------------------------------------------

class MenuState:
    """A MenuState object is used to enable/disable and check/uncheck
    menu items, and to add or remove items of indexed groups,
    during the menu setup phase of menu command handling.
    
    Each single menu item or item group appears as an attribute of
    the MenuState object, with the command name as the attribute name,
    allowing operations such as
    
        menu_state.copy_cmd.enabled = 1
        menu_state.font_cmd[current_font].checked = 1
    
    The command name may also be used as a mapping index.
    
    Operators:
        menu_state.command_name	 ->	 MenuItem
        menu_state['command_name']	 ->	 MenuItem
    """

    def __init__(self, menu_list):
        mapping = {}
        for menu in menu_list:
            for item in menu._items:
                cmd = item._command_name
                if cmd:
                    mapping[cmd] = item
        self._mapping = mapping
    
    def __getattr__(self, name):
        try:
            return self._mapping[name]
        except KeyError:
            return _dummy_menu_item
    
    __getitem__ = __getattr__
