home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / GUI / menu.py < prev    next >
Encoding:
Python Source  |  2007-01-11  |  37.7 KB  |  1,267 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35.  
  36. ## \file menu.py
  37. ## Contains the menu tree classes.
  38.  
  39. import wx
  40. import types
  41.  
  42.  
  43. class MenuNodeNotFound(Exception):
  44.     """Exception class."""
  45.     pass
  46.  
  47. class DuplicateNames(Exception):
  48.     """Exception class."""
  49.     pass
  50.  
  51. class InvalidMenuRoot(Exception):
  52.     """Exception class."""
  53.     pass
  54.  
  55. class NoWxMenuObjectFound(Exception):
  56.     """Exception class."""
  57.     pass
  58.  
  59. class MenuAlreadyInUse(Exception):
  60.     """Exception class."""
  61.     pass
  62.  
  63. class NoMenuRoot(Exception):
  64.     """Exception class."""
  65.     pass
  66.  
  67. NODE_NORMAL_ITEM  = 0x01
  68. NODE_CHECK_ITEM   = 0x02
  69. NODE_COMMAND_ITEM = NODE_NORMAL_ITEM | NODE_CHECK_ITEM
  70. NODE_SEPARATOR    = 0x04
  71. NODE_MENU         = 0x08
  72. NODE_MARKER       = 0x10
  73. NODE_ALL_NODES    = 0xff
  74.  
  75.  
  76. # MenuNode
  77. class MenuNode(object):
  78.     """Base class for a menu node.
  79.  
  80.     This is the base class for all menu nodes (inner node or leaf).
  81.     A %MenuNode class can be used as a sequence where the items are
  82.     the children nodes. Leaf nodes are always empty.
  83.     
  84.     Each node has the following attributes:
  85.  
  86.     - \b name (\c str): Node name
  87.     - \b absname (\c str): Absolute node name
  88.     - \b parent (\c %MenuNode): Parent node
  89.     - \b root (\c %MenuNode): Root node
  90.     - \b text (\c str): Text that's visible to the user
  91.     - \b enabled (\c bool): If this flag is false the menu or menu item is grayed out.
  92.     - \b visible (\c bool): If this flag is false the menu or menu item isn't displayed at all.
  93.     - \b wx (\c wxMenu, \c wxMenuBar or \c wxMenuItem): Corresponding wxPython object (or None if the menu isn't attached).
  94.     
  95.     - flags
  96.  
  97.     A menu node can be either in an \em attached or \em detached state
  98.     which tells if a node is attached to a wxPython menu or not. An
  99.     attached menu is "active" and usually has a corresponding wxPython
  100.     menu object associated with it. When you create a menu node it's
  101.     not attached to a menu. A node automatically becomes attached when
  102.     it's added to another node that is already attached.
  103.  
  104.     Any class that is derived from this base class has to implement the
  105.     following methods:
  106.  
  107.     - setEnabled()
  108.     - setVisibility()
  109.     - _wx_menu_count()
  110.     - _create_wx()
  111.     
  112.     """
  113.     
  114.     def __init__(self, name=None, parent=None, text=None,
  115.                  enabled=True, visible=True):
  116.         """Constructor.
  117.  
  118.         If no name is given the constructor tries to create a name
  119.         from the \a text argument (using _create_auto_name()).
  120.  
  121.         \param name (\c str) Node name
  122.         \param parent (\c %MenuNode) Parent node
  123.         \param text (\c str) %Menu title or item text
  124.         \param enabled (\c bool) Initial enabled state
  125.         \param visible (\c bool) Initial visibility state
  126.         """
  127.         # Node name
  128.         self._name = name
  129.         # Parent node
  130.         self._parent = parent
  131.         # Text (menu title or item text)
  132.         self._text = text
  133.         # NODE_xxx flags
  134.         self._flags = 0
  135.         # Enabled state
  136.         self._enabled = enabled
  137.         # Visbility state
  138.         self._visible = visible
  139.         # The corresponding wxPython object (or None)
  140.         self._wx = None
  141.         # The wxPython ID of the object
  142.         self._id = None
  143.         # Activation flag
  144.         self._attached = False
  145.  
  146.         if self._name==None:
  147.             self._create_auto_name(self._text)
  148.  
  149.  
  150.     # isAttached
  151.     def isAttached(self):
  152.         return self._attached
  153.  
  154.     def setEnabled(self, flag=True):
  155.         """Set the enabled flag of this node.
  156.  
  157.         If a node is disabled all of its children are considered disabled
  158.         as well.
  159.  
  160.         This method should be overridden by derived classes. Those
  161.         implementations should call the inherited method (where the
  162.         internal flag _enabled is set). If the node is attached to a
  163.         menu this method should update their corresponding menu items
  164.         to reflect the state change.
  165.  
  166.         This method is called automatically when the \em enabled property
  167.         receives a new value.
  168.  
  169.         \param flag (\c bool) New enabled state.
  170.         """
  171.         self._enabled = flag
  172.  
  173.     def setVisibility(self, flag=True):
  174.         """Set the visibility flag of this node.
  175.  
  176.         If a node is invisible all of its children are considered invisible
  177.         as well.
  178.  
  179.         This method should be overridden by derived classes. Those
  180.         implementations should call the inherited method (where the
  181.         internal flag _visible is set). If the node is attached to a
  182.         menu this method should remove their corresponding menu items
  183.         to reflect the state change.
  184.  
  185.         This method is called automatically when the \em visible property
  186.         receives a new value.
  187.  
  188.         \param flag (\c bool) New visibility state.
  189.         """
  190.         
  191.         self._visible = flag
  192.  
  193.         if flag:
  194.             if not self.isAttached():
  195.                 self._create_wx()
  196.         else:
  197.             if self.isAttached():
  198.                 self._destroy_wx()
  199.  
  200.  
  201.     # remove
  202.     def remove(self, node=""):
  203.         """Remove a menu node.
  204.  
  205.         Removes the node with the given name. The name may contain an
  206.         entire (relative) path to a menu node or may be left blank if
  207.         self should be removed (which is the default behavior). The node
  208.         is removed from the visible wx menu and from the menu tree and
  209.         is returned to the caller which may ignore it or use it to add
  210.         it later or to another menu.
  211.  
  212.         \param node (\c str) Name of the node which should be removed (default: "").
  213.         \return The removed menu node object (\c MenuNode).
  214.         """
  215.  
  216.         # Find the node n which is to be removed
  217.         n = self.findNode(node)
  218.  
  219.         # Remove the corresponding wx objects from the visible menu
  220.         if n.isAttached():
  221.             n._destroy_wx()
  222.  
  223.         parent = n.parent
  224.         if parent!=None:
  225.             del parent[n]
  226.  
  227.         return n
  228.  
  229.     def findCommandNodes(self, func):
  230.         """Return all nodes that are bound to func.
  231.  
  232.         \return A list of all nodes that are bound to func.
  233.         """
  234.  
  235.         res = []
  236.         
  237.         # Check if this node is bound to func
  238.         f = getattr(self, "_command", None)
  239.         if f!=None:
  240.             if f==func:
  241.                 res.append(self)
  242.  
  243.         # Now check the children
  244.         for n in self:
  245.             res+=n.findCommandNodes(func)
  246.  
  247.         return res                
  248.         
  249.  
  250.     # findNode
  251.     def findNode(self, path, exc=True):
  252.         """Search for a node with a specific relative name.
  253.  
  254.         The name can include an entire path. Each node name is separated
  255.         by a dot. If path is empty then self is returned.
  256.  
  257.         \param path (\c str) Name of the item
  258.         \param exc (\c bool) If True then an exception is generated when the
  259.                              node is not found.
  260.         \return Node or None/Exception.
  261.         """
  262.  
  263.         if path=="":
  264.             return self
  265.  
  266.         f = path.split(".")
  267.         name = f[0]
  268.         for n in self:
  269.             if name==n.name:
  270.                 if len(f)==1:
  271.                     return n
  272.                 else:
  273.                     return n.findNode(".".join(f[1:]))
  274.  
  275.         if exc:
  276.             errpath = name
  277.             s = self.absname
  278.             if s!="":
  279.                 errpath = ".".join([s,errpath])
  280.             raise MenuNodeNotFound, 'Menu node "%s" not found.'%(errpath)
  281.         else:
  282.             return None
  283.             
  284.  
  285.     ######################################################################
  286.     ## protected:
  287.  
  288.     def __len__(self):
  289.         return 0
  290.  
  291.     def __getitem__(self, key):
  292.         if isinstance(key, int):
  293.             raise IndexError, "sequence index out of range"
  294.         else:
  295.             raise TypeError, "sequence indices must be integers"
  296.  
  297.     # _create_auto_name
  298.     def _create_auto_name(self, text):
  299.         """Create a name from the given text.
  300.  
  301.         The argument \a text is the text that's visible to the user
  302.         in the menu. The method tries to generate a node name out
  303.         of the given text. If the text only consists of dashes or is None
  304.         then no name is generated (None), otherwise blanks and tabs are
  305.         collapsed and replaced by underscores, dots and ampersands are
  306.         deleted and the text is made lower case to create the name.
  307.  
  308.         The result of the automatic name generation is stored in the
  309.         name property (_name).
  310.         
  311.         \param text (\c str) Menu text.
  312.         """
  313.         if text==None:
  314.             self._name = None
  315.             return
  316.         text = text.strip()
  317.         # Collapse dash sequences
  318.         while text.find("--")!=-1:
  319.             text = text.replace("--", "-")
  320.         # Was the text that of a separator? then no name is generated
  321.         if text=="-":
  322.             self._name = None
  323.             return
  324.         # Replace tabs with spaces
  325.         text = text.replace("\t", " ")
  326.         # Collapse spaces
  327.         while text.find("  ")!=-1:
  328.             text = text.replace("  ", " ")
  329.         # Replace/delete special characters
  330.         text = text.replace("&","")
  331.         text = text.replace(".","")
  332.         text = text.replace(" ","_")
  333.         self._name = text.lower()
  334.  
  335.     def _wx_menu_count(self):
  336.         """Return the number of menu "slots" that are occupied by this node.
  337.  
  338.         The return value is the number of attached menu items (or
  339.         menus) that are beneath this node. Invisble nodes must not be
  340.         counted. This value is used to determine the position of a
  341.         node in the wx menu.
  342.  
  343.         By default this method returns 0. Usually, a derived class should
  344.         override this method.
  345.  
  346.         \return Number of visible items occupied by this node.
  347.         \see _wx_menu_pos()
  348.         """
  349.         return 0
  350.  
  351.     def _wx_menu_pos(self):
  352.         """Return the position of the node inside the parent wx menu.
  353.  
  354.         The return value is the starting index where self is located
  355.         (or would be located) in the corresponding wx menu. This value
  356.         is used to insert the node into a menu or menubar.
  357.  
  358.         The node has to be added to the menu tree but doesn't have to
  359.         be attached.
  360.         
  361.         \pre self is in the children list of parent.
  362.         \pre The parent nodes must already be attached.
  363.         \return Position (\c int) or None if the node doesn't have a parent.
  364.         \see _wx_menu_count()
  365.         """
  366.  
  367.         parent = self.parent
  368.         if parent==None:
  369.             return None
  370.  
  371.         # Determine the index of self within parent
  372.         idx = 0
  373.         for n in parent:
  374.             if n==self:
  375.                 break
  376.             idx += n._wx_menu_count()
  377.  
  378.         if parent._wx==None:
  379.             idx += parent._wx_menu_pos()
  380.  
  381.         return idx
  382.  
  383.         
  384.  
  385.     def _attach(self, IDmanager, window, wxmenubar):
  386.         """Attach the menu to a window.
  387.         
  388.         \param IDmanager ID generator
  389.         \param window (\c Window) wxPython window
  390.         \param wxmenubar (\c wxMenuBar) wxPython menu bar or None
  391.         """
  392.         root = self.root
  393.         if not isinstance(root, Menu):
  394.             raise InvalidMenuRoot, 'The root of a menu has to be a Menu object.'
  395.         # The node must not be attached to another menu
  396.         if self.isAttached():
  397.             raise MenuAlreadyInUse, "The menu node is already in use."
  398.  
  399.         root._IDmanager = IDmanager
  400.         root._window = window
  401.         if wxmenubar!=None:
  402.             root._wx_menubar = wxmenubar
  403.         root._menu_id_lut = {}
  404.         root._create_wx()
  405.  
  406.     def _detach(self):
  407.         """Detach the menu from a window."""
  408.         
  409.         if self.root!=self:
  410.             raise NoMenuRoot, "Only root nodes can be detached."
  411.  
  412.         self._destroy_wx()
  413.         del self._IDmanager
  414.         del self._window
  415.         if hasattr(self, "_wx_menubar"):
  416.             del self._wx_menubar
  417.         del self._menu_id_lut
  418.         
  419.  
  420.     def _create_wx(self):
  421.         """Create the corresponding wxPython object.
  422.  
  423.         This method has to create the appropriate wxPython object which
  424.         is represented by the node and insert it into the parent menu,
  425.         submenu or menubar at the correct position as returned by
  426.         _wx_menu_pos(). 
  427.         
  428.         The wxPython object has to be stored in the attribute self._wx
  429.         and its ID in self._id.
  430.         The method may only be called if the parent wxPython object
  431.         of the node exists or if the node represents the entire menu bar.
  432.  
  433.         A derived class must call this inherited method!
  434.         """
  435.         self._attached = True
  436.  
  437.     def _destroy_wx(self):
  438.         """Destroy the associated wxPython object.
  439.  
  440.         This method has to remove its corresponding wxPython object
  441.         from the visible wx menu. The method may only be called in
  442.         attached state!
  443.  
  444.         A derived class must call this inherited method!
  445.         """
  446.         self._attached = False
  447.         self._wx = None
  448.         self._id = -1        
  449.  
  450.     def _wx_parent(self):
  451.         """Return the wxPython parent object.
  452.  
  453.         The object returned is either a wx.Menu, a wx.MenuBar or None.
  454.         If a node has no direct wx parent, the wx parent of the
  455.         parent node is returned.
  456.  
  457.         This method must not be called when the node is part of an
  458.         invisible tree.
  459.  
  460.         \return wxPython object or None.
  461.         """
  462.         p = self.parent
  463.  
  464.         while p!=None:
  465.             if p._wx!=None:
  466.                 if isinstance(p._wx, wx.MenuItem):
  467.                     return p._wx.GetSubMenu()
  468.                 else:
  469.                     return p._wx
  470.             p = p.parent
  471.  
  472.         return None
  473.  
  474.  
  475.     def _is_menubar(self):
  476.         return False
  477.     
  478.     def _is_embedded_menu(self):
  479.         return False
  480.  
  481.  
  482.     # "name" property...
  483.     
  484.     def _getName(self):
  485.         """Return the node name.
  486.  
  487.         This method is used for retrieving the \a name property.
  488.  
  489.         \return Node name (\c str).
  490.         """
  491.         return self._name
  492.  
  493.     name = property(_getName, None, None, "Node name")
  494.  
  495.     # "absname" property...
  496.  
  497.     def _getAbsName(self):
  498.         """Return the absolute name of the node.
  499.  
  500.         Returns the absolute name of the node. This name
  501.         uniquely identifies the node within its tree and is composed
  502.         of all the names from the root to the node separated by a dot.
  503.         The root node is always unnamed.
  504.  
  505.         This method is used for retrieving the \a absname property.
  506.  
  507.         \return Absolute name of the node (\c str).
  508.         """
  509.         if self._parent==None:
  510.             return ""
  511.         else:
  512.             pre = self._parent.getFullName()
  513.             if self._name==None:
  514.                 name = "<unnamed>"
  515.             else:
  516.                 name = self._name
  517.             if pre=="":
  518.                 return name
  519.             else:
  520.                 return pre+"."+name
  521.  
  522.     absname = property(_getAbsName, None, None, "Absolute node name")
  523.  
  524.     # "parent" property...
  525.     
  526.     def _getParent(self):
  527.         """Return the parent node.
  528.  
  529.         This method is used for retrieving the \a parent property.
  530.  
  531.         \return Parent node (\c MenuNode).
  532.         """
  533.         return self._parent
  534.  
  535.     parent = property(_getParent, None, None, "Parent node")
  536.  
  537.     # "root" property...
  538.  
  539.     def _getRoot(self):
  540.         """Return the root node.
  541.  
  542.         This method is used for retrieving the \a root property.
  543.  
  544.         \return Root node (\c MenuNode).
  545.         """
  546.         p = self
  547.         while 1:
  548.             if p._parent==None:
  549.                 return p
  550.             p = p._parent
  551.  
  552.     root = property(_getRoot, None, None, "Root node")
  553.  
  554.     # "text" property...
  555.     
  556.     def _getText(self):
  557.         """Return the menu text which is displayed to the user.
  558.  
  559.         This method is used for retrieving the \a text property.
  560.  
  561.         \return Text (\c str).
  562.         """
  563.         return self._text
  564.  
  565.     text = property(_getText, None, None, "Menu text")
  566.  
  567.     # "enabled" property...
  568.     
  569.     def _getEnabled(self):
  570.         """Return the enabled state.
  571.  
  572.         This method is used for retrieving the \a enabled property.
  573.  
  574.         \return True if the node is enabled.
  575.         """
  576.         return self._enabled
  577.  
  578.     def _setEnabled(self, flag=True):
  579.         """Set the enabled state.
  580.  
  581.         This method is used for setting the \a enabled property.
  582.  
  583.         \param flag (\c bool) New enabled state.
  584.         """
  585.         self.setEnabled(flag)
  586.  
  587.     enabled = property(_getEnabled, _setEnabled, None, "Enabled state")
  588.  
  589.     # "visible" property...
  590.     
  591.     def _getVisible(self):
  592.         """Return the visible state.
  593.  
  594.         This method is used for retrieving the \a visible property.
  595.  
  596.         \return True if the node is visible.
  597.         """
  598.         return self._visible
  599.  
  600.     def _setVisible(self, flag=True):
  601.         """Set the visible state.
  602.  
  603.         This method is used for setting the \a visible property.
  604.  
  605.         \param flag (\c bool) New visibility state.
  606.         """
  607.         self.setVisibility(flag)
  608.  
  609.     visible = property(_getVisible, _setVisible, None, "Visibility state")
  610.  
  611.     # "wx" property...
  612.     
  613.     def _getWx(self):
  614.         """Return the corresponding wxPython object.
  615.  
  616.         This method is used for retrieving the \a wx property.
  617.  
  618.         \return wxPython object.
  619.         """
  620.         return self._wx
  621.  
  622.     wx = property(_getWx, None, None, "Corresponding wxPython object")
  623.  
  624.  
  625.  
  626. ######################################################################
  627. ######################################################################
  628.         
  629. # MenuMarker
  630. class MenuMarker(MenuNode):
  631.     """This class represents a position in a menu.
  632.     """
  633.     def __init__(self, name=None, parent=None):
  634.         MenuNode.__init__(self, name=name, parent=parent)
  635.         self._flags = NODE_MARKER
  636.  
  637.     def __repr__(self):
  638.         return "<MenuMarker name=%s>"%(self.name)
  639.  
  640. ######################################################################
  641. ######################################################################
  642.  
  643. # MenuItem
  644. class MenuItem(MenuNode):
  645.     """An individual menu item.
  646.  
  647.     This class represents one individual menu item which can be a
  648.     normal item, a checkable item or a separator. A menu item object
  649.     has the following attributes in addition to the ones inherited by
  650.     MenuNode:
  651.  
  652.     - \b command (\c callable): The command to execute (may be None)
  653.     - \b checked (\c bool): The state of a checkable menu item
  654.     """
  655.     
  656.     def __init__(self, itemdef, name=None, enabled=True, visible=True):
  657.         """Constructor.
  658.  
  659.         The definition \a itemdef of the item is generally composed of
  660.         two items, the text that appears in the menu and the
  661.         corresponding command which is to be executed whenever the
  662.         user selects the menu item. The command must be a callable
  663.         that takes no arguments.
  664.         
  665.         The text and command is given as a 2-tuple (text, command).
  666.         It's also possible to just provide a string which is equivalent
  667.         to (text, None).
  668.  
  669.         If the text starts with "[x]" then a checkable menu item is
  670.         generated.
  671.  
  672.         If the text consists of 3 or more dashes ("---"), then a
  673.         separator is generated.
  674.  
  675.         \param itemdef (\c 2-tuple or \c str) Item definition
  676.         \param name (\c str) The internal name of the item
  677.         \param enabled (\c bool) Initial enabled state
  678.         \param visible (\c bool) Initial visibility state
  679.         """
  680.  
  681.         flags = NODE_NORMAL_ITEM
  682.  
  683.         self._checkitem = False
  684.         self._separator = False
  685.         self._checked   = False
  686.  
  687.         # Split the item definition into the text and command component
  688.         if isinstance(itemdef, types.StringTypes):
  689.             itemstr = itemdef
  690.             itemcmd = None
  691.         else:
  692.             itemstr, itemcmd = itemdef
  693.  
  694.         # Is the menu item a checkable item?
  695.         if itemstr[:3]=="[x]" or itemstr[:3]=="[X]":
  696.             flags = NODE_CHECK_ITEM
  697.             self._checkitem = True
  698.             if itemstr[1]=="X":
  699.                 self._checked=True
  700.             itemstr = itemstr[3:]
  701.  
  702.         # Is the item a separator?
  703.         if len(itemstr)>2 and itemstr.count("-")==len(itemstr):
  704.             flags = NODE_SEPARATOR
  705.             self._separator = True
  706.             itemcmd = None
  707.  
  708.         MenuNode.__init__(self, name=name, text=itemstr, enabled=enabled, visible=visible)
  709.         self._flags = flags
  710.  
  711.         self._command   = itemcmd
  712.  
  713.     def __repr__(self):
  714.         s = 'MenuItem name=%s text="%s"'%(self.name, self.text)
  715.         if self._checkitem:
  716.             s+=" checkable"
  717.         return "<%s>"%s
  718.  
  719.     def isSeparator(self):
  720.         return self._separator
  721.  
  722.     def isCheckItem(self):
  723.         return self._checkitem
  724.  
  725.  
  726.     def setEnabled(self, flag=True):
  727.         MenuNode.setEnabled(self, flag)
  728.         if self._wx!=None:
  729.             self._wx.Enable(flag)
  730.  
  731.     def update(self):
  732.         if self._wx!=None:
  733.             root = self.root
  734.             text = self.text
  735.             keys = root._window.keys.findCommandKeys(self._command)
  736.             if len(keys)>0:
  737.                 text+="\t"+keys[0]
  738.             self._wx.SetText(text)
  739.  
  740.  
  741.     ######################################################################
  742.  
  743.     ## protected:
  744.  
  745.     def _wx_menu_count(self):
  746.         if self._wx==None:
  747.             return 0
  748.         else:
  749.             return 1
  750.  
  751.     def _create_wx(self):
  752.  
  753.         MenuNode._create_wx(self)
  754.  
  755.         root = self.root
  756.  
  757.         # Determine the type of MenuItem to create...
  758.         if self.isCheckItem():
  759.             kind = wx.ITEM_CHECK
  760.             self._id = root._IDmanager.allocID()
  761.         elif self.isSeparator():
  762.             kind = wx.ITEM_SEPARATOR
  763.             self._id = wx.ID_SEPARATOR
  764.         else:
  765.             kind = wx.ITEM_NORMAL
  766.             self._id = root._IDmanager.allocID()
  767.  
  768.         # Get the parent which must be a wxMenu object
  769.         parent = self._wx_parent()
  770.         # Create the MenuItem...
  771.         text = self.text
  772.         keys = root._window.keys.findCommandKeys(self._command)
  773.         if len(keys)>0:
  774.             text+="\t"+keys[0]
  775.         self._wx = wx.MenuItem(parent, self._id, text, self._getHelpString(), kind)
  776.         # ...and insert it into the parent menu
  777.         pos = self._wx_menu_pos()
  778. #        print 'Inserting item "%s" at position %d'%(self._text, pos)
  779.         parent.InsertItem(pos, self._wx)
  780.         # Set initial attributes
  781.         self._wx.Enable(self.enabled)
  782.         if self.isCheckItem():
  783.             self._wx.Check(self.checked)
  784.  
  785.         # Connect the new wxMenuItem to the menu event handler
  786.         if not self.isSeparator():
  787.             root._connect(self)
  788.  
  789.  
  790.     def _destroy_wx(self):
  791.  
  792.         root = self.root
  793.         
  794.         # Disconnect the menu item from the event handler
  795.         if not self.isSeparator():
  796.             root._disconnect(self)
  797.  
  798.         # Remove the wxMenuItem from the parent menu
  799.         parent = self._wx_parent()
  800.         parent.Remove(self._id)
  801.         # Make the ID available again
  802.         if self._id!=-1:
  803.             root._IDmanager.freeID(self._id)
  804.  
  805.         MenuNode._destroy_wx(self)
  806.  
  807.     def _getHelpString(self):
  808.         doc = getattr(self._command, "__doc__", None)
  809.         if doc==None:
  810.             return ""
  811.         else:
  812.             return doc.split("\n")[0]
  813.  
  814.     # "command" property...
  815.  
  816.     def _getCommand(self):
  817.         """Return the associated command (callable object).
  818.  
  819.         This method is used for retrieving the \a command property.
  820.  
  821.         \return Command (\c callable).
  822.         """
  823.         return self._command
  824.  
  825.     def _setCommand(self, command):
  826.         """Set a new command.
  827.  
  828.         This method is used for setting the \a command property.
  829.  
  830.         \param command (\c callable) The new command or None
  831.         """
  832.         self._command = command
  833.  
  834.     command = property(_getCommand, _setCommand, None, "Menu item command")
  835.  
  836.     # "checked" property...
  837.  
  838.     def _getChecked(self):
  839.         """Get the checked flag.
  840.  
  841.         This method is used for retrieving the \a checked property.
  842.  
  843.         \return Checked flag (\c bool).
  844.         """
  845.         return self._checked
  846.     
  847.     def _setChecked(self, flag=True):
  848.         """Get the checked flag.
  849.  
  850.         This method is used for setting the \a checked property.
  851.  
  852.         \param flag (\c bool) New state.
  853.         """
  854.         self._checked = flag
  855.         if self._wx!=None:
  856.             self._wx.Check(flag)
  857.  
  858.     checked = property(_getChecked, _setChecked, None, "Checked flag")
  859.  
  860.  
  861. ######################################################################
  862. ######################################################################
  863.        
  864. # Menu
  865. class Menu(MenuNode):
  866.     """A menu node containing other menus, markers or menu items.
  867.  
  868.     This class is used as a container for MenuNode classes and it
  869.     represents a node in the entire menu tree.
  870.     """
  871.     
  872.     def __init__(self, title=None, name=None, enabled=True, visible=True,
  873.                  items=[]):
  874.         """Constructor.
  875.  
  876.         \param title (\c str) Menu title which will be display in the GUI
  877.         \param name (\c str) Node name
  878.         \param enabled (\c bool) Initial enabled state
  879.         \param visible (\c bool) Initial visibility state
  880.         \param items (\c sequence) A sequence of children node descriptions
  881.         """
  882.  
  883.         MenuNode.__init__(self, name=name, text=title, enabled=enabled, visible=visible)
  884.         self._flags = NODE_MENU
  885.  
  886.         # Children nodes
  887.         self._children = [MenuMarker("START", self), MenuMarker("END", self)]
  888.  
  889.         for item in items:
  890.             if isinstance(item, MenuNode):
  891.                 mi = item
  892.             else:
  893.                 mi = MenuItem(item)
  894.             self.append(mi)
  895.  
  896.  
  897.     def insertAfter(self, newnode, treenodename):
  898.         """Insert a node after another existing node.
  899.  
  900.         \param newnode  The node that should be inserted
  901.         \param treenodename The node name after which newnode is to be inserted
  902.         """
  903.  
  904.         # The new node must not be attached to another menu
  905.         if newnode.isAttached() or newnode.parent!=None:
  906.             raise MenuAlreadyInUse, "The menu node is already in use."
  907.  
  908.         treenode = self.findNode(treenodename)
  909.         # If treenode is not a direct child then delegate the insert
  910.         # operation to the parent of treenode
  911.         if (treenode.parent!=self):
  912.             treenode.parent.insertAfter(newnode, treenodename.split(".")[-1])
  913.             return
  914.  
  915.         # Check if the name of the new node collides with an existing node
  916.         newnodename = newnode.name
  917.         if newnodename!=None and self.findNode(newnodename, False)!=None:
  918.             raise DuplicateNames, 'Menu node "%s" already exists.'%newnodename
  919.  
  920.         # Add the new node into the children list
  921.         idx = self._children.index(treenode)
  922.         self._children.insert(idx+1, newnode)
  923.         newnode._parent = self
  924.  
  925.         if self.isAttached():
  926.             newnode._create_wx()
  927.  
  928.  
  929.     def insertBefore(self, newnode, treenodename):
  930.         """Insert a node before another existing node.
  931.  
  932.         \param newnode  The node that should be inserted
  933.         \param treenodename The node name before which newnode is to be inserted
  934.         """
  935.         
  936.         # The new node must not be attached to another menu
  937.         if newnode.isAttached() or newnode.parent!=None:
  938.             raise MenuAlreadyInUse, "The menu node is already in use."
  939.  
  940.         treenode = self.findNode(treenodename)
  941.         # If treenode is not a direct child then delegate the insert
  942.         # operation to the parent of treenode
  943.         if (treenode.parent!=self):
  944.             treenode.parent.insertBefore(newnode, treenodename.split(".")[-1])
  945.             return
  946.  
  947.         # Check if the name of the new node collides with an existing node
  948.         newnodename = newnode.name
  949.         if newnodename!=None and self.findNode(newnodename, False)!=None:
  950.             raise DuplicateNames, 'Menu node "%s" already exists.'%newnodename
  951.  
  952.         # Add the new node into the children list
  953.         idx = self._children.index(treenode)
  954.         self._children.insert(idx, newnode)
  955.         newnode._parent = self
  956.  
  957.         if self.isAttached():
  958.             newnode._create_wx()
  959.  
  960.     # append
  961.     def append(self, node):
  962.         """Append a node at the end of the children list.
  963.  
  964.         The node is added right before the END marker.
  965.  
  966.         \param node (\c MenuNode) The node the be appended to the menu.
  967.         """
  968.         self.insertBefore(node, "END")
  969.  
  970.        
  971.  
  972.     def setEnabled(self, flag=True, node=""):
  973.         if node!="":
  974.             # Find the node n whose enabled state should be changed
  975.             n = self.findNode(node)
  976.             n.setEnabled(flag)
  977.             return
  978.  
  979.         MenuNode.setEnabled(self, flag)
  980.         if not self.isAttached():
  981.             return
  982.  
  983.         # 1. Node = MenuBar
  984.         if self._is_menubar():
  985.             for n in self:
  986.                 n.setEnabled(flag)
  987.         # 2. Node = Toplevel menu
  988.         elif self.parent==None or self.parent._is_menubar():
  989.             parent = self._wx_parent()
  990.             # parent may be None if the menu is used for a popup menu
  991.             if parent!=None:
  992.                 parent.EnableTop(self._wx_menu_pos(), flag)
  993.         # 3. Node = Sub menu
  994.         elif self.text!=None:
  995.             parent = self._wx_parent()
  996.             parent.Enable(self._wx.GetId(), flag)
  997.         # 3. Node = Embedded menu
  998.         else:
  999.             for n in self:
  1000.                 n.setEnabled(flag)              
  1001.  
  1002.  
  1003.     ######################################################################
  1004.     ## protected:
  1005.  
  1006.     def _wx_menu_count(self):
  1007.         if self._is_embedded_menu():
  1008.             res = 0
  1009.             for n in self:
  1010.                 res += n._wx_menu_count()
  1011.             return res
  1012.         
  1013.         if self._wx==None:
  1014.             return 0
  1015.         else:
  1016.             return 1
  1017.  
  1018.     def __repr__(self):
  1019.         return '<Menu name=%s title="%s">'%(self.name, self.text)
  1020.  
  1021.     def __len__(self):
  1022.         return len(self._children)
  1023.  
  1024.     def __getitem__(self, key):
  1025.         return self._children[key]
  1026.  
  1027.     def __delitem__(self, key):
  1028.         # Remove the node from the tree
  1029.         self._children.remove(key)
  1030.         key._parent = None
  1031.  
  1032.     def __getattr__(self, name):
  1033.         items = filter(lambda x: name==x.name, self._children)
  1034.         if items==[]:
  1035.             raise AttributeError, 'Menu node "%s" not found.'%(name)
  1036.         return items[0]
  1037.  
  1038.     def __setattr__(self, name, value):
  1039.         if name=="":
  1040.             return
  1041.         if name[0]=="_":
  1042.             MenuNode.__setattr__(self, name, value)
  1043.         elif isinstance(value, Menu):
  1044.             self.append(value)
  1045.         elif isinstance(value, MenuItem):
  1046.             self.append(value)
  1047.         elif (isinstance(value, types.StringTypes) or
  1048.               (isinstance(value, tuple) and len(value)==2 and
  1049.               isinstance(value[0], types.StringTypes) and callable(value[1]))):
  1050.             mi = MenuItem(value, name=name)
  1051.             self.append(mi)
  1052.         else:
  1053.             MenuNode.__setattr__(self, name, value)
  1054.             
  1055.  
  1056. ##    def _getIndex(self, name):
  1057. ##        """Return the index of the children with the given name.
  1058.  
  1059. ##        \return Index (0-based).
  1060. ##        """
  1061.         
  1062. ##        items = filter(lambda x: name==x.name, self._children)
  1063. ##        if items==[]:
  1064. ##            raise MenuNodeNotFound, 'Menu node "%s" not found.'%(name)
  1065. ##        return self._children.index(items[0])
  1066.  
  1067.     def _create_wx(self):
  1068.  
  1069.         MenuNode._create_wx(self)
  1070.  
  1071.         # 1. Node = MenuBar
  1072.         if self._is_menubar():
  1073. #            print "Menubar"
  1074.             self._create_wx_menubar()    
  1075.         # 2. Node = Toplevel menu
  1076.         elif self.parent==None or self.parent._is_menubar():
  1077. #            print "Toplevel"
  1078.             self._create_wx_toplevel_menu()
  1079.         # 3. Node = Sub menu
  1080.         elif self.text!=None:
  1081. #            print "Submenu"
  1082.             self._create_wx_submenu()
  1083.         # 3. Node = Embedded menu
  1084.         else:
  1085. #            print "Embedded"
  1086.             self._create_wx_embedded_menu()
  1087.  
  1088.         self.setEnabled(self.enabled)
  1089.  
  1090.  
  1091.     def _create_wx_menubar(self):
  1092.         """Implements _create_wx() for the root node.
  1093.  
  1094.         This method just calls _create_wx() on all its children.
  1095.         (The wxMenuBar object must already be set!)
  1096.  
  1097.         \see _create_wx()
  1098.         """
  1099.         # _wx_menubar must already be initialized with a wx.MenuBar object
  1100.  
  1101.         self._wx = self._wx_menubar
  1102.         
  1103.         # Create wx objects for all menus...
  1104.         for node in self:
  1105.             node._create_wx()
  1106.  
  1107.  
  1108.     def _create_wx_toplevel_menu(self):
  1109.         """Implements _create_wx() for toplevel menus.
  1110.  
  1111.         This method creates a wxMenu object and fills it with its
  1112.         children.
  1113.  
  1114.         \see _create_wx()
  1115.         """
  1116.  
  1117.         self._wx = wx.Menu()
  1118.  
  1119.         # Create wx objects for all items...
  1120.         for node in self:
  1121.             node._create_wx()
  1122.  
  1123.         # Parent is a wx.MenuBar object or None (popup menus)
  1124.         parent = self._wx_parent()
  1125.         if parent!=None:
  1126.             pos = self._wx_menu_pos()
  1127.             parent.Insert(pos, self._wx, self.text)
  1128.  
  1129.  
  1130.     def _create_wx_submenu(self):
  1131.         """Implements _create_wx() for submenus.
  1132.  
  1133.         This method creates a wxMenu object and fills it with its
  1134.         children. Then the wxMenu object is wrapped in a wxMenuItem
  1135.         object which serves as the actual menu item.
  1136.  
  1137.         \see _create_wx()
  1138.         """
  1139.  
  1140.         # Store the menu in _wx temporarily, so that the children have a
  1141.         # valid parent
  1142.         self._wx = wx.Menu()
  1143.  
  1144.         # Create wx objects for all items...
  1145.         for node in self:
  1146.             node._create_wx()
  1147.  
  1148.         # Parent is a wx.Menu object
  1149.         parent   = self._wx_parent()
  1150.         self._id = self.root._IDmanager.allocID()
  1151.         self._wx = wx.MenuItem(parent, self._id, self.text, "",
  1152.                                wx.ITEM_NORMAL, self._wx)
  1153.         pos = self._wx_menu_pos()
  1154.         parent.InsertItem(pos, self._wx)
  1155.        
  1156.  
  1157.     def _create_wx_embedded_menu(self):
  1158.         """Implements _create_wx() for embedded menus.
  1159.  
  1160.         This method does not create a wx menu object but only inserts
  1161.         its children into the parent menu.        
  1162.  
  1163.         \see _create_wx()
  1164.         """
  1165.  
  1166.         self._wx = None
  1167.         self._id = None
  1168.         wxmenu = self._wx_parent()
  1169.         # Create wx objects for all items...
  1170.         for node in self:
  1171.             node._create_wx()
  1172.  
  1173.  
  1174.     def _destroy_wx(self):
  1175.  
  1176.         # Destroy the wx objects for all items...
  1177.         for node in self:
  1178.             node._destroy_wx()
  1179.  
  1180.         # 1. Node = MenuBar
  1181.         if self._is_menubar():
  1182.             pass
  1183.         # 2. Node = Toplevel menu
  1184.         elif self.parent==None or self.parent._is_menubar():
  1185.             # Remove the menu from the menu bar (if it's not a popup menu)
  1186.             parent = self._wx_parent()
  1187.             if parent!=None:
  1188.                 pos = self._wx_menu_pos()
  1189.                 parent.Remove(pos)
  1190.         # 3. Node = Sub menu
  1191.         elif self.text!=None:
  1192.             # Remove the wxMenuItem from the parent menu
  1193.             parent = self._wx_parent()
  1194.             parent.Remove(self._id)
  1195.             # Make the ID available again
  1196.             self.root._IDmanager.freeID(self._id)
  1197.         # 3. Node = Embedded menu
  1198. #        else: pass
  1199.  
  1200.         MenuNode._destroy_wx(self)
  1201.  
  1202.  
  1203.     def _is_menubar(self):
  1204.         """Returns True if the node represents a menu bar."""
  1205.         return hasattr(self, "_wx_menubar")
  1206.  
  1207.     def _is_embedded_menu(self):
  1208.         """Returns True if the node represents an embedded menu."""
  1209.         return self.text==None and self.parent!=None and (not self._is_menubar())
  1210.  
  1211.     def _get_embedded_items(self):
  1212.         """
  1213.         May only be called on embedded menus.
  1214.         """
  1215.  
  1216.         res = []
  1217.         for cn in self._children:
  1218.             if cn._wx!=None:
  1219.                 res.append(cn)
  1220.             elif isinstance(cn, Menu):
  1221.                 res += cn._get_embedded_items()
  1222.         return res
  1223.         
  1224.  
  1225.     def _connect(self, node):
  1226.         """Connect a menu event with the handler.
  1227.  
  1228.         May only be called on the root node.
  1229.         """
  1230.  
  1231.         id = node._wx.GetId()
  1232.         wx.EVT_MENU(self._window, id, self._onMenuSelection)
  1233.         self._menu_id_lut[id] = node
  1234.  
  1235.     def _disconnect(self, node):
  1236.         """Disconnect a menu event from the handler.
  1237.  
  1238.         May only be called on the root node.
  1239.         """
  1240.         
  1241.         id = node._wx.GetId()
  1242.         print "Disconnecting",node.name,"ID:",id
  1243.         wx.EVT_MENU(self._window, id, None)
  1244.         del self._menu_id_lut[id]
  1245.  
  1246.  
  1247.     def _onMenuSelection(self, event):
  1248.         print "Item",event.GetId(),"selected"
  1249.         item = self._menu_id_lut.get(event.GetId(), None)
  1250.         if item==None:
  1251.             print "Menu item ID not found!"
  1252.             return
  1253.  
  1254.         cmd = item.command
  1255.         if cmd!=None:
  1256.             cmd()
  1257.         
  1258.  
  1259.     def _dump(self, depth=0):
  1260.         print "%s%s"%(2*depth*" ",self)
  1261.         for n in self._children:
  1262.             if hasattr(n, "_dump"):
  1263.                 n._dump(depth+1)
  1264.             else:
  1265.                 print "%s%s"%(2*(depth+1)*" ",n)
  1266.  
  1267.