# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****

## \file menu.py
## Contains the menu tree classes.

import wx
import types


class MenuNodeNotFound(Exception):
    """Exception class."""
    pass

class DuplicateNames(Exception):
    """Exception class."""
    pass

class InvalidMenuRoot(Exception):
    """Exception class."""
    pass

class NoWxMenuObjectFound(Exception):
    """Exception class."""
    pass

class MenuAlreadyInUse(Exception):
    """Exception class."""
    pass

class NoMenuRoot(Exception):
    """Exception class."""
    pass

NODE_NORMAL_ITEM  = 0x01
NODE_CHECK_ITEM   = 0x02
NODE_COMMAND_ITEM = NODE_NORMAL_ITEM | NODE_CHECK_ITEM
NODE_SEPARATOR    = 0x04
NODE_MENU         = 0x08
NODE_MARKER       = 0x10
NODE_ALL_NODES    = 0xff


# MenuNode
class MenuNode(object):
    """Base class for a menu node.

    This is the base class for all menu nodes (inner node or leaf).
    A %MenuNode class can be used as a sequence where the items are
    the children nodes. Leaf nodes are always empty.
    
    Each node has the following attributes:

    - \b name (\c str): Node name
    - \b absname (\c str): Absolute node name
    - \b parent (\c %MenuNode): Parent node
    - \b root (\c %MenuNode): Root node
    - \b text (\c str): Text that's visible to the user
    - \b enabled (\c bool): If this flag is false the menu or menu item is grayed out.
    - \b visible (\c bool): If this flag is false the menu or menu item isn't displayed at all.
    - \b wx (\c wxMenu, \c wxMenuBar or \c wxMenuItem): Corresponding wxPython object (or None if the menu isn't attached).
    
    - flags

    A menu node can be either in an \em attached or \em detached state
    which tells if a node is attached to a wxPython menu or not. An
    attached menu is "active" and usually has a corresponding wxPython
    menu object associated with it. When you create a menu node it's
    not attached to a menu. A node automatically becomes attached when
    it's added to another node that is already attached.

    Any class that is derived from this base class has to implement the
    following methods:

    - setEnabled()
    - setVisibility()
    - _wx_menu_count()
    - _create_wx()
    
    """
    
    def __init__(self, name=None, parent=None, text=None,
                 enabled=True, visible=True):
        """Constructor.

        If no name is given the constructor tries to create a name
        from the \a text argument (using _create_auto_name()).

        \param name (\c str) Node name
        \param parent (\c %MenuNode) Parent node
        \param text (\c str) %Menu title or item text
        \param enabled (\c bool) Initial enabled state
        \param visible (\c bool) Initial visibility state
        """
        # Node name
        self._name = name
        # Parent node
        self._parent = parent
        # Text (menu title or item text)
        self._text = text
        # NODE_xxx flags
        self._flags = 0
        # Enabled state
        self._enabled = enabled
        # Visbility state
        self._visible = visible
        # The corresponding wxPython object (or None)
        self._wx = None
        # The wxPython ID of the object
        self._id = None
        # Activation flag
        self._attached = False

        if self._name==None:
            self._create_auto_name(self._text)


    # isAttached
    def isAttached(self):
        return self._attached

    def setEnabled(self, flag=True):
        """Set the enabled flag of this node.

        If a node is disabled all of its children are considered disabled
        as well.

        This method should be overridden by derived classes. Those
        implementations should call the inherited method (where the
        internal flag _enabled is set). If the node is attached to a
        menu this method should update their corresponding menu items
        to reflect the state change.

        This method is called automatically when the \em enabled property
        receives a new value.

        \param flag (\c bool) New enabled state.
        """
        self._enabled = flag

    def setVisibility(self, flag=True):
        """Set the visibility flag of this node.

        If a node is invisible all of its children are considered invisible
        as well.

        This method should be overridden by derived classes. Those
        implementations should call the inherited method (where the
        internal flag _visible is set). If the node is attached to a
        menu this method should remove their corresponding menu items
        to reflect the state change.

        This method is called automatically when the \em visible property
        receives a new value.

        \param flag (\c bool) New visibility state.
        """
        
        self._visible = flag

        if flag:
            if not self.isAttached():
                self._create_wx()
        else:
            if self.isAttached():
                self._destroy_wx()


    # remove
    def remove(self, node=""):
        """Remove a menu node.

        Removes the node with the given name. The name may contain an
        entire (relative) path to a menu node or may be left blank if
        self should be removed (which is the default behavior). The node
        is removed from the visible wx menu and from the menu tree and
        is returned to the caller which may ignore it or use it to add
        it later or to another menu.

        \param node (\c str) Name of the node which should be removed (default: "").
        \return The removed menu node object (\c MenuNode).
        """

        # Find the node n which is to be removed
        n = self.findNode(node)

        # Remove the corresponding wx objects from the visible menu
        if n.isAttached():
            n._destroy_wx()

        parent = n.parent
        if parent!=None:
            del parent[n]

        return n

    def findCommandNodes(self, func):
        """Return all nodes that are bound to func.

        \return A list of all nodes that are bound to func.
        """

        res = []
        
        # Check if this node is bound to func
        f = getattr(self, "_command", None)
        if f!=None:
            if f==func:
                res.append(self)

        # Now check the children
        for n in self:
            res+=n.findCommandNodes(func)

        return res                
        

    # findNode
    def findNode(self, path, exc=True):
        """Search for a node with a specific relative name.

        The name can include an entire path. Each node name is separated
        by a dot. If path is empty then self is returned.

        \param path (\c str) Name of the item
        \param exc (\c bool) If True then an exception is generated when the
                             node is not found.
        \return Node or None/Exception.
        """

        if path=="":
            return self

        f = path.split(".")
        name = f[0]
        for n in self:
            if name==n.name:
                if len(f)==1:
                    return n
                else:
                    return n.findNode(".".join(f[1:]))

        if exc:
            errpath = name
            s = self.absname
            if s!="":
                errpath = ".".join([s,errpath])
            raise MenuNodeNotFound, 'Menu node "%s" not found.'%(errpath)
        else:
            return None
            

    ######################################################################
    ## protected:

    def __len__(self):
        return 0

    def __getitem__(self, key):
        if isinstance(key, int):
            raise IndexError, "sequence index out of range"
        else:
            raise TypeError, "sequence indices must be integers"

    # _create_auto_name
    def _create_auto_name(self, text):
        """Create a name from the given text.

        The argument \a text is the text that's visible to the user
        in the menu. The method tries to generate a node name out
        of the given text. If the text only consists of dashes or is None
        then no name is generated (None), otherwise blanks and tabs are
        collapsed and replaced by underscores, dots and ampersands are
        deleted and the text is made lower case to create the name.

        The result of the automatic name generation is stored in the
        name property (_name).
        
        \param text (\c str) Menu text.
        """
        if text==None:
            self._name = None
            return
        text = text.strip()
        # Collapse dash sequences
        while text.find("--")!=-1:
            text = text.replace("--", "-")
        # Was the text that of a separator? then no name is generated
        if text=="-":
            self._name = None
            return
        # Replace tabs with spaces
        text = text.replace("\t", " ")
        # Collapse spaces
        while text.find("  ")!=-1:
            text = text.replace("  ", " ")
        # Replace/delete special characters
        text = text.replace("&","")
        text = text.replace(".","")
        text = text.replace(" ","_")
        self._name = text.lower()

    def _wx_menu_count(self):
        """Return the number of menu "slots" that are occupied by this node.

        The return value is the number of attached menu items (or
        menus) that are beneath this node. Invisble nodes must not be
        counted. This value is used to determine the position of a
        node in the wx menu.

        By default this method returns 0. Usually, a derived class should
        override this method.

        \return Number of visible items occupied by this node.
        \see _wx_menu_pos()
        """
        return 0

    def _wx_menu_pos(self):
        """Return the position of the node inside the parent wx menu.

        The return value is the starting index where self is located
        (or would be located) in the corresponding wx menu. This value
        is used to insert the node into a menu or menubar.

        The node has to be added to the menu tree but doesn't have to
        be attached.
        
        \pre self is in the children list of parent.
        \pre The parent nodes must already be attached.
        \return Position (\c int) or None if the node doesn't have a parent.
        \see _wx_menu_count()
        """

        parent = self.parent
        if parent==None:
            return None

        # Determine the index of self within parent
        idx = 0
        for n in parent:
            if n==self:
                break
            idx += n._wx_menu_count()

        if parent._wx==None:
            idx += parent._wx_menu_pos()

        return idx

        

    def _attach(self, IDmanager, window, wxmenubar):
        """Attach the menu to a window.
        
        \param IDmanager ID generator
        \param window (\c Window) wxPython window
        \param wxmenubar (\c wxMenuBar) wxPython menu bar or None
        """
        root = self.root
        if not isinstance(root, Menu):
            raise InvalidMenuRoot, 'The root of a menu has to be a Menu object.'
        # The node must not be attached to another menu
        if self.isAttached():
            raise MenuAlreadyInUse, "The menu node is already in use."

        root._IDmanager = IDmanager
        root._window = window
        if wxmenubar!=None:
            root._wx_menubar = wxmenubar
        root._menu_id_lut = {}
        root._create_wx()

    def _detach(self):
        """Detach the menu from a window."""
        
        if self.root!=self:
            raise NoMenuRoot, "Only root nodes can be detached."

        self._destroy_wx()
        del self._IDmanager
        del self._window
        if hasattr(self, "_wx_menubar"):
            del self._wx_menubar
        del self._menu_id_lut
        

    def _create_wx(self):
        """Create the corresponding wxPython object.

        This method has to create the appropriate wxPython object which
        is represented by the node and insert it into the parent menu,
        submenu or menubar at the correct position as returned by
        _wx_menu_pos(). 
        
        The wxPython object has to be stored in the attribute self._wx
        and its ID in self._id.
        The method may only be called if the parent wxPython object
        of the node exists or if the node represents the entire menu bar.

        A derived class must call this inherited method!
        """
        self._attached = True

    def _destroy_wx(self):
        """Destroy the associated wxPython object.

        This method has to remove its corresponding wxPython object
        from the visible wx menu. The method may only be called in
        attached state!

        A derived class must call this inherited method!
        """
        self._attached = False
        self._wx = None
        self._id = -1        

    def _wx_parent(self):
        """Return the wxPython parent object.

        The object returned is either a wx.Menu, a wx.MenuBar or None.
        If a node has no direct wx parent, the wx parent of the
        parent node is returned.

        This method must not be called when the node is part of an
        invisible tree.

        \return wxPython object or None.
        """
        p = self.parent

        while p!=None:
            if p._wx!=None:
                if isinstance(p._wx, wx.MenuItem):
                    return p._wx.GetSubMenu()
                else:
                    return p._wx
            p = p.parent

        return None


    def _is_menubar(self):
        return False
    
    def _is_embedded_menu(self):
        return False


    # "name" property...
    
    def _getName(self):
        """Return the node name.

        This method is used for retrieving the \a name property.

        \return Node name (\c str).
        """
        return self._name

    name = property(_getName, None, None, "Node name")

    # "absname" property...

    def _getAbsName(self):
        """Return the absolute name of the node.

        Returns the absolute name of the node. This name
        uniquely identifies the node within its tree and is composed
        of all the names from the root to the node separated by a dot.
        The root node is always unnamed.

        This method is used for retrieving the \a absname property.

        \return Absolute name of the node (\c str).
        """
        if self._parent==None:
            return ""
        else:
            pre = self._parent.getFullName()
            if self._name==None:
                name = "<unnamed>"
            else:
                name = self._name
            if pre=="":
                return name
            else:
                return pre+"."+name

    absname = property(_getAbsName, None, None, "Absolute node name")

    # "parent" property...
    
    def _getParent(self):
        """Return the parent node.

        This method is used for retrieving the \a parent property.

        \return Parent node (\c MenuNode).
        """
        return self._parent

    parent = property(_getParent, None, None, "Parent node")

    # "root" property...

    def _getRoot(self):
        """Return the root node.

        This method is used for retrieving the \a root property.

        \return Root node (\c MenuNode).
        """
        p = self
        while 1:
            if p._parent==None:
                return p
            p = p._parent

    root = property(_getRoot, None, None, "Root node")

    # "text" property...
    
    def _getText(self):
        """Return the menu text which is displayed to the user.

        This method is used for retrieving the \a text property.

        \return Text (\c str).
        """
        return self._text

    text = property(_getText, None, None, "Menu text")

    # "enabled" property...
    
    def _getEnabled(self):
        """Return the enabled state.

        This method is used for retrieving the \a enabled property.

        \return True if the node is enabled.
        """
        return self._enabled

    def _setEnabled(self, flag=True):
        """Set the enabled state.

        This method is used for setting the \a enabled property.

        \param flag (\c bool) New enabled state.
        """
        self.setEnabled(flag)

    enabled = property(_getEnabled, _setEnabled, None, "Enabled state")

    # "visible" property...
    
    def _getVisible(self):
        """Return the visible state.

        This method is used for retrieving the \a visible property.

        \return True if the node is visible.
        """
        return self._visible

    def _setVisible(self, flag=True):
        """Set the visible state.

        This method is used for setting the \a visible property.

        \param flag (\c bool) New visibility state.
        """
        self.setVisibility(flag)

    visible = property(_getVisible, _setVisible, None, "Visibility state")

    # "wx" property...
    
    def _getWx(self):
        """Return the corresponding wxPython object.

        This method is used for retrieving the \a wx property.

        \return wxPython object.
        """
        return self._wx

    wx = property(_getWx, None, None, "Corresponding wxPython object")



######################################################################
######################################################################
        
# MenuMarker
class MenuMarker(MenuNode):
    """This class represents a position in a menu.
    """
    def __init__(self, name=None, parent=None):
        MenuNode.__init__(self, name=name, parent=parent)
        self._flags = NODE_MARKER

    def __repr__(self):
        return "<MenuMarker name=%s>"%(self.name)

######################################################################
######################################################################

# MenuItem
class MenuItem(MenuNode):
    """An individual menu item.

    This class represents one individual menu item which can be a
    normal item, a checkable item or a separator. A menu item object
    has the following attributes in addition to the ones inherited by
    MenuNode:

    - \b command (\c callable): The command to execute (may be None)
    - \b checked (\c bool): The state of a checkable menu item
    """
    
    def __init__(self, itemdef, name=None, enabled=True, visible=True):
        """Constructor.

        The definition \a itemdef of the item is generally composed of
        two items, the text that appears in the menu and the
        corresponding command which is to be executed whenever the
        user selects the menu item. The command must be a callable
        that takes no arguments.
        
        The text and command is given as a 2-tuple (text, command).
        It's also possible to just provide a string which is equivalent
        to (text, None).

        If the text starts with "[x]" then a checkable menu item is
        generated.

        If the text consists of 3 or more dashes ("---"), then a
        separator is generated.

        \param itemdef (\c 2-tuple or \c str) Item definition
        \param name (\c str) The internal name of the item
        \param enabled (\c bool) Initial enabled state
        \param visible (\c bool) Initial visibility state
        """

        flags = NODE_NORMAL_ITEM

        self._checkitem = False
        self._separator = False
        self._checked   = False

        # Split the item definition into the text and command component
        if isinstance(itemdef, types.StringTypes):
            itemstr = itemdef
            itemcmd = None
        else:
            itemstr, itemcmd = itemdef

        # Is the menu item a checkable item?
        if itemstr[:3]=="[x]" or itemstr[:3]=="[X]":
            flags = NODE_CHECK_ITEM
            self._checkitem = True
            if itemstr[1]=="X":
                self._checked=True
            itemstr = itemstr[3:]

        # Is the item a separator?
        if len(itemstr)>2 and itemstr.count("-")==len(itemstr):
            flags = NODE_SEPARATOR
            self._separator = True
            itemcmd = None

        MenuNode.__init__(self, name=name, text=itemstr, enabled=enabled, visible=visible)
        self._flags = flags

        self._command   = itemcmd

    def __repr__(self):
        s = 'MenuItem name=%s text="%s"'%(self.name, self.text)
        if self._checkitem:
            s+=" checkable"
        return "<%s>"%s

    def isSeparator(self):
        return self._separator

    def isCheckItem(self):
        return self._checkitem


    def setEnabled(self, flag=True):
        MenuNode.setEnabled(self, flag)
        if self._wx!=None:
            self._wx.Enable(flag)

    def update(self):
        if self._wx!=None:
            root = self.root
            text = self.text
            keys = root._window.keys.findCommandKeys(self._command)
            if len(keys)>0:
                text+="\t"+keys[0]
            self._wx.SetText(text)


    ######################################################################

    ## protected:

    def _wx_menu_count(self):
        if self._wx==None:
            return 0
        else:
            return 1

    def _create_wx(self):

        MenuNode._create_wx(self)

        root = self.root

        # Determine the type of MenuItem to create...
        if self.isCheckItem():
            kind = wx.ITEM_CHECK
            self._id = root._IDmanager.allocID()
        elif self.isSeparator():
            kind = wx.ITEM_SEPARATOR
            self._id = wx.ID_SEPARATOR
        else:
            kind = wx.ITEM_NORMAL
            self._id = root._IDmanager.allocID()

        # Get the parent which must be a wxMenu object
        parent = self._wx_parent()
        # Create the MenuItem...
        text = self.text
        keys = root._window.keys.findCommandKeys(self._command)
        if len(keys)>0:
            text+="\t"+keys[0]
        self._wx = wx.MenuItem(parent, self._id, text, self._getHelpString(), kind)
        # ...and insert it into the parent menu
        pos = self._wx_menu_pos()
#        print 'Inserting item "%s" at position %d'%(self._text, pos)
        parent.InsertItem(pos, self._wx)
        # Set initial attributes
        self._wx.Enable(self.enabled)
        if self.isCheckItem():
            self._wx.Check(self.checked)

        # Connect the new wxMenuItem to the menu event handler
        if not self.isSeparator():
            root._connect(self)


    def _destroy_wx(self):

        root = self.root
        
        # Disconnect the menu item from the event handler
        if not self.isSeparator():
            root._disconnect(self)

        # Remove the wxMenuItem from the parent menu
        parent = self._wx_parent()
        parent.Remove(self._id)
        # Make the ID available again
        if self._id!=-1:
            root._IDmanager.freeID(self._id)

        MenuNode._destroy_wx(self)

    def _getHelpString(self):
        doc = getattr(self._command, "__doc__", None)
        if doc==None:
            return ""
        else:
            return doc.split("\n")[0]

    # "command" property...

    def _getCommand(self):
        """Return the associated command (callable object).

        This method is used for retrieving the \a command property.

        \return Command (\c callable).
        """
        return self._command

    def _setCommand(self, command):
        """Set a new command.

        This method is used for setting the \a command property.

        \param command (\c callable) The new command or None
        """
        self._command = command

    command = property(_getCommand, _setCommand, None, "Menu item command")

    # "checked" property...

    def _getChecked(self):
        """Get the checked flag.

        This method is used for retrieving the \a checked property.

        \return Checked flag (\c bool).
        """
        return self._checked
    
    def _setChecked(self, flag=True):
        """Get the checked flag.

        This method is used for setting the \a checked property.

        \param flag (\c bool) New state.
        """
        self._checked = flag
        if self._wx!=None:
            self._wx.Check(flag)

    checked = property(_getChecked, _setChecked, None, "Checked flag")


######################################################################
######################################################################
       
# Menu
class Menu(MenuNode):
    """A menu node containing other menus, markers or menu items.

    This class is used as a container for MenuNode classes and it
    represents a node in the entire menu tree.
    """
    
    def __init__(self, title=None, name=None, enabled=True, visible=True,
                 items=[]):
        """Constructor.

        \param title (\c str) Menu title which will be display in the GUI
        \param name (\c str) Node name
        \param enabled (\c bool) Initial enabled state
        \param visible (\c bool) Initial visibility state
        \param items (\c sequence) A sequence of children node descriptions
        """

        MenuNode.__init__(self, name=name, text=title, enabled=enabled, visible=visible)
        self._flags = NODE_MENU

        # Children nodes
        self._children = [MenuMarker("START", self), MenuMarker("END", self)]

        for item in items:
            if isinstance(item, MenuNode):
                mi = item
            else:
                mi = MenuItem(item)
            self.append(mi)


    def insertAfter(self, newnode, treenodename):
        """Insert a node after another existing node.

        \param newnode  The node that should be inserted
        \param treenodename The node name after which newnode is to be inserted
        """

        # The new node must not be attached to another menu
        if newnode.isAttached() or newnode.parent!=None:
            raise MenuAlreadyInUse, "The menu node is already in use."

        treenode = self.findNode(treenodename)
        # If treenode is not a direct child then delegate the insert
        # operation to the parent of treenode
        if (treenode.parent!=self):
            treenode.parent.insertAfter(newnode, treenodename.split(".")[-1])
            return

        # Check if the name of the new node collides with an existing node
        newnodename = newnode.name
        if newnodename!=None and self.findNode(newnodename, False)!=None:
            raise DuplicateNames, 'Menu node "%s" already exists.'%newnodename

        # Add the new node into the children list
        idx = self._children.index(treenode)
        self._children.insert(idx+1, newnode)
        newnode._parent = self

        if self.isAttached():
            newnode._create_wx()


    def insertBefore(self, newnode, treenodename):
        """Insert a node before another existing node.

        \param newnode  The node that should be inserted
        \param treenodename The node name before which newnode is to be inserted
        """
        
        # The new node must not be attached to another menu
        if newnode.isAttached() or newnode.parent!=None:
            raise MenuAlreadyInUse, "The menu node is already in use."

        treenode = self.findNode(treenodename)
        # If treenode is not a direct child then delegate the insert
        # operation to the parent of treenode
        if (treenode.parent!=self):
            treenode.parent.insertBefore(newnode, treenodename.split(".")[-1])
            return

        # Check if the name of the new node collides with an existing node
        newnodename = newnode.name
        if newnodename!=None and self.findNode(newnodename, False)!=None:
            raise DuplicateNames, 'Menu node "%s" already exists.'%newnodename

        # Add the new node into the children list
        idx = self._children.index(treenode)
        self._children.insert(idx, newnode)
        newnode._parent = self

        if self.isAttached():
            newnode._create_wx()

    # append
    def append(self, node):
        """Append a node at the end of the children list.

        The node is added right before the END marker.

        \param node (\c MenuNode) The node the be appended to the menu.
        """
        self.insertBefore(node, "END")

       

    def setEnabled(self, flag=True, node=""):
        if node!="":
            # Find the node n whose enabled state should be changed
            n = self.findNode(node)
            n.setEnabled(flag)
            return

        MenuNode.setEnabled(self, flag)
        if not self.isAttached():
            return

        # 1. Node = MenuBar
        if self._is_menubar():
            for n in self:
                n.setEnabled(flag)
        # 2. Node = Toplevel menu
        elif self.parent==None or self.parent._is_menubar():
            parent = self._wx_parent()
            # parent may be None if the menu is used for a popup menu
            if parent!=None:
                parent.EnableTop(self._wx_menu_pos(), flag)
        # 3. Node = Sub menu
        elif self.text!=None:
            parent = self._wx_parent()
            parent.Enable(self._wx.GetId(), flag)
        # 3. Node = Embedded menu
        else:
            for n in self:
                n.setEnabled(flag)              


    ######################################################################
    ## protected:

    def _wx_menu_count(self):
        if self._is_embedded_menu():
            res = 0
            for n in self:
                res += n._wx_menu_count()
            return res
        
        if self._wx==None:
            return 0
        else:
            return 1

    def __repr__(self):
        return '<Menu name=%s title="%s">'%(self.name, self.text)

    def __len__(self):
        return len(self._children)

    def __getitem__(self, key):
        return self._children[key]

    def __delitem__(self, key):
        # Remove the node from the tree
        self._children.remove(key)
        key._parent = None

    def __getattr__(self, name):
        items = filter(lambda x: name==x.name, self._children)
        if items==[]:
            raise AttributeError, 'Menu node "%s" not found.'%(name)
        return items[0]

    def __setattr__(self, name, value):
        if name=="":
            return
        if name[0]=="_":
            MenuNode.__setattr__(self, name, value)
        elif isinstance(value, Menu):
            self.append(value)
        elif isinstance(value, MenuItem):
            self.append(value)
        elif (isinstance(value, types.StringTypes) or
              (isinstance(value, tuple) and len(value)==2 and
              isinstance(value[0], types.StringTypes) and callable(value[1]))):
            mi = MenuItem(value, name=name)
            self.append(mi)
        else:
            MenuNode.__setattr__(self, name, value)
            

##    def _getIndex(self, name):
##        """Return the index of the children with the given name.

##        \return Index (0-based).
##        """
        
##        items = filter(lambda x: name==x.name, self._children)
##        if items==[]:
##            raise MenuNodeNotFound, 'Menu node "%s" not found.'%(name)
##        return self._children.index(items[0])

    def _create_wx(self):

        MenuNode._create_wx(self)

        # 1. Node = MenuBar
        if self._is_menubar():
#            print "Menubar"
            self._create_wx_menubar()    
        # 2. Node = Toplevel menu
        elif self.parent==None or self.parent._is_menubar():
#            print "Toplevel"
            self._create_wx_toplevel_menu()
        # 3. Node = Sub menu
        elif self.text!=None:
#            print "Submenu"
            self._create_wx_submenu()
        # 3. Node = Embedded menu
        else:
#            print "Embedded"
            self._create_wx_embedded_menu()

        self.setEnabled(self.enabled)


    def _create_wx_menubar(self):
        """Implements _create_wx() for the root node.

        This method just calls _create_wx() on all its children.
        (The wxMenuBar object must already be set!)

        \see _create_wx()
        """
        # _wx_menubar must already be initialized with a wx.MenuBar object

        self._wx = self._wx_menubar
        
        # Create wx objects for all menus...
        for node in self:
            node._create_wx()


    def _create_wx_toplevel_menu(self):
        """Implements _create_wx() for toplevel menus.

        This method creates a wxMenu object and fills it with its
        children.

        \see _create_wx()
        """

        self._wx = wx.Menu()

        # Create wx objects for all items...
        for node in self:
            node._create_wx()

        # Parent is a wx.MenuBar object or None (popup menus)
        parent = self._wx_parent()
        if parent!=None:
            pos = self._wx_menu_pos()
            parent.Insert(pos, self._wx, self.text)


    def _create_wx_submenu(self):
        """Implements _create_wx() for submenus.

        This method creates a wxMenu object and fills it with its
        children. Then the wxMenu object is wrapped in a wxMenuItem
        object which serves as the actual menu item.

        \see _create_wx()
        """

        # Store the menu in _wx temporarily, so that the children have a
        # valid parent
        self._wx = wx.Menu()

        # Create wx objects for all items...
        for node in self:
            node._create_wx()

        # Parent is a wx.Menu object
        parent   = self._wx_parent()
        self._id = self.root._IDmanager.allocID()
        self._wx = wx.MenuItem(parent, self._id, self.text, "",
                               wx.ITEM_NORMAL, self._wx)
        pos = self._wx_menu_pos()
        parent.InsertItem(pos, self._wx)
       

    def _create_wx_embedded_menu(self):
        """Implements _create_wx() for embedded menus.

        This method does not create a wx menu object but only inserts
        its children into the parent menu.        

        \see _create_wx()
        """

        self._wx = None
        self._id = None
        wxmenu = self._wx_parent()
        # Create wx objects for all items...
        for node in self:
            node._create_wx()


    def _destroy_wx(self):

        # Destroy the wx objects for all items...
        for node in self:
            node._destroy_wx()

        # 1. Node = MenuBar
        if self._is_menubar():
            pass
        # 2. Node = Toplevel menu
        elif self.parent==None or self.parent._is_menubar():
            # Remove the menu from the menu bar (if it's not a popup menu)
            parent = self._wx_parent()
            if parent!=None:
                pos = self._wx_menu_pos()
                parent.Remove(pos)
        # 3. Node = Sub menu
        elif self.text!=None:
            # Remove the wxMenuItem from the parent menu
            parent = self._wx_parent()
            parent.Remove(self._id)
            # Make the ID available again
            self.root._IDmanager.freeID(self._id)
        # 3. Node = Embedded menu
#        else: pass

        MenuNode._destroy_wx(self)


    def _is_menubar(self):
        """Returns True if the node represents a menu bar."""
        return hasattr(self, "_wx_menubar")

    def _is_embedded_menu(self):
        """Returns True if the node represents an embedded menu."""
        return self.text==None and self.parent!=None and (not self._is_menubar())

    def _get_embedded_items(self):
        """
        May only be called on embedded menus.
        """

        res = []
        for cn in self._children:
            if cn._wx!=None:
                res.append(cn)
            elif isinstance(cn, Menu):
                res += cn._get_embedded_items()
        return res
        

    def _connect(self, node):
        """Connect a menu event with the handler.

        May only be called on the root node.
        """

        id = node._wx.GetId()
        wx.EVT_MENU(self._window, id, self._onMenuSelection)
        self._menu_id_lut[id] = node

    def _disconnect(self, node):
        """Disconnect a menu event from the handler.

        May only be called on the root node.
        """
        
        id = node._wx.GetId()
        print "Disconnecting",node.name,"ID:",id
        wx.EVT_MENU(self._window, id, None)
        del self._menu_id_lut[id]


    def _onMenuSelection(self, event):
        print "Item",event.GetId(),"selected"
        item = self._menu_id_lut.get(event.GetId(), None)
        if item==None:
            print "Menu item ID not found!"
            return

        cmd = item.command
        if cmd!=None:
            cmd()
        

    def _dump(self, depth=0):
        print "%s%s"%(2*depth*" ",self)
        for n in self._children:
            if hasattr(n, "_dump"):
                n._dump(depth+1)
            else:
                print "%s%s"%(2*(depth+1)*" ",n)

