[Zope3-checkins] SVN: Zope3/trunk/ Re-implemnted menus. This allows us to define custom menu implementations

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Mar 13 20:07:34 EST 2005


Log message for revision 29457:
  Re-implemnted menus. This allows us to define custom menu implementations 
  that can dynamically generate their menu items. This required me to change 
  the API to look up menus by id again, instead of the menu item type 
  interface. I did not provide BBB, since all this code was added after 3.0 
  anyways.
  
  I added a small demo for custom menus to zope.app.demo.menu.
  
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/app/container/browser/adding.py
  U   Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py
  U   Zope3/trunk/src/zope/app/container/browser/tests/test_directive.py
  U   Zope3/trunk/src/zope/app/demo/menu/configure.zcml
  A   Zope3/trunk/src/zope/app/demo/menu/menu.py
  U   Zope3/trunk/src/zope/app/form/browser/metaconfigure.py
  U   Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py
  U   Zope3/trunk/src/zope/app/publisher/browser/menu.py
  U   Zope3/trunk/src/zope/app/publisher/browser/menu.txt
  A   Zope3/trunk/src/zope/app/publisher/browser/menumeta.py
  U   Zope3/trunk/src/zope/app/publisher/browser/meta.zcml
  U   Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py
  U   Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py
  U   Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py
  U   Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py
  U   Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py
  U   Zope3/trunk/src/zope/app/publisher/browser/viewmeta.py
  U   Zope3/trunk/src/zope/app/publisher/interfaces/browser.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/doc/CHANGES.txt	2005-03-14 01:07:34 UTC (rev 29457)
@@ -440,10 +440,12 @@
           PrincipalLookupError.
 
       - Removed Browser Menu Service and implemented menus as subscriber
-        adapters. Menu Item Types (in other words, menus) are now utilities
-        that provide `IMenuItemType`.
+        adapters. Menus are utilities providing `IBrowserMenu`. The default
+        menu implementation looks up menu items as views on the provided
+        object. The view must provide `IMenuItemType`.
 
-        + Implemented sub-menus.
+        + Implemented sub-menus. Sub-menus keep track of the menu id that
+          provides the sub-entries.
 
         + Completes http://dev.zope.org/Zope3/AdaptersForMenuItems. New
           features such as sub-menus, icons, disabled entries and so on were

Modified: Zope3/trunk/src/zope/app/container/browser/adding.py
===================================================================
--- Zope3/trunk/src/zope/app/container/browser/adding.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/container/browser/adding.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -40,7 +40,6 @@
 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
 from zope.app.publisher.browser import BrowserView
 from zope.app.publisher.browser.menu import getMenu
-from zope.app.publisher.interfaces.browser import AddMenu
 
 class Adding(BrowserView):
     implements(IAdding, IPublishTraverse)
@@ -171,7 +170,7 @@
         """
         container = self.context
         result = []
-        for menu_id in (self.menu_id, AddMenu):
+        for menu_id in (self.menu_id, 'zope.app.container.add'):
             if not menu_id:
                 continue
             for item in getMenu(menu_id, self, self.request):

Modified: Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py
===================================================================
--- Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -36,7 +36,8 @@
 from zope.app.exception.interfaces import UserError
 from zope.app.publisher.browser import BrowserView
 from zope.app.publisher.interfaces.browser import AddMenu
-from zope.app.publisher.browser.menu import BrowserMenuItem
+from zope.app.publisher.interfaces.browser import IMenuItemType, IBrowserMenu
+from zope.app.publisher.browser.menu import BrowserMenuItem, BrowserMenu
 from zope.app.container.interfaces import IAdding
 from zope.app.container.interfaces import IObjectAddedEvent
 from zope.app.container.interfaces import IContainerNamesContainer
@@ -94,7 +95,13 @@
     zope.interface.classImplements(newclass, menuItemType)
     ztapi.provideAdapter((for_, IBrowserRequest), menuItemType, newclass, title)
 
+def registerAddMenu():
+  ztapi.provideUtility(IMenuItemType, AddMenu, 'zope.app.container.add')
+  ztapi.provideUtility(IBrowserMenu,
+                       BrowserMenu('zope.app.container.add', u'', u''),
+                       'zope.app.container.add')
 
+
 class Test(PlacelessSetup, unittest.TestCase):
 
     def setUp(self):
@@ -206,10 +213,16 @@
 def test_constraint_driven_addingInfo():
     """
     >>> setUp()
+    >>> registerAddMenu()
 
     >>> class TestMenu(zope.interface.Interface):
     ...     pass
+    >>> zope.interface.directlyProvides(TestMenu, IMenuItemType)
 
+    >>> ztapi.provideUtility(IMenuItemType, TestMenu, 'TestMenu')
+    >>> ztapi.provideUtility(IBrowserMenu, BrowserMenu('TestMenu', u'', u''),
+    ...                      'TestMenu')
+
     >>> defineMenuItem(TestMenu, IAdding, '', 'item1')
     >>> defineMenuItem(TestMenu, IAdding, '', 'item2')
 
@@ -252,7 +265,7 @@
     >>> items[0]['title']
     'item3'
     
-    >>> adding.menu_id = TestMenu
+    >>> adding.menu_id = 'TestMenu'
     >>> items = adding.addingInfo()
     >>> len(items)
     3
@@ -400,6 +413,7 @@
     the container contains only a single content object
     
     >>> setUp()
+    >>> registerAddMenu()
     >>> defineMenuItem(AddMenu, IAdding, '', 'item3', extra={'factory': 'f1'})
 
     >>> class F1(object):
@@ -459,6 +473,7 @@
     implement IContainerNamesContainer
     
     >>> setUp()
+    >>> registerAddMenu()
     >>> defineMenuItem(AddMenu, None, '', 'item3', extra={'factory': ''})
     >>> class F1(object):
     ...     pass
@@ -515,6 +530,7 @@
     and the container uses IContainerNamesContaienr
 
     >>> setUp()
+    >>> registerAddMenu()
     >>> defineMenuItem(AddMenu, None, '', 'item3', extra={'factory': ''})
     
     >>> class F1(object):

Modified: Zope3/trunk/src/zope/app/container/browser/tests/test_directive.py
===================================================================
--- Zope3/trunk/src/zope/app/container/browser/tests/test_directive.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/container/browser/tests/test_directive.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -52,7 +52,7 @@
 
 def test_containerViews():
     """
-    >>> from zope.app.publisher.browser.menu import menus
+    >>> from zope.app.publisher.browser.menumeta import menus
     >>> from zope.interface.interface import InterfaceClass
     >>> zmi_views = InterfaceClass('zmi_views', __module__='zope.app.menus')
     >>> menus.zmi_views = zmi_views
@@ -65,16 +65,16 @@
     >>> context
     ((('adapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_views>,
        u'Contents'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_views>,
        u'Contents',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        '')),
      (None,
       <function provideInterface>,
@@ -86,7 +86,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
      (None,
       <function provideInterface>,
       ('',
@@ -123,16 +123,16 @@
        'info')),
      (('adapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_actions>,
        u'Add'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_actions>,
        u'Add',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        'info')),
      (None,
       <function provideInterface>,
@@ -144,7 +144,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
      (None,
       <function provideInterface>,
       ('',
@@ -169,7 +169,7 @@
 
 def test_containerViews_layer():
     """
-    >>> from zope.app.publisher.browser.menu import menus
+    >>> from zope.app.publisher.browser.menumeta import menus
     >>> from zope.interface.interface import InterfaceClass
     >>> zmi_views = InterfaceClass('zmi_views', __module__='zope.app.menus')
     >>> menus.zmi_views = zmi_views
@@ -182,16 +182,16 @@
     >>> context
     ((('adapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_views>,
        u'Contents'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_views>,
        u'Contents',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        '')),
      (None,
       <function provideInterface>,
@@ -203,7 +203,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
      (None,
       <function provideInterface>,
       ('',
@@ -240,16 +240,16 @@
        'info')),
      (('adapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_actions>,
        u'Add'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.menus.zmi_actions>,
        u'Add',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        'info')),
      (None,
       <function provideInterface>,
@@ -261,7 +261,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)),
      (None,
       <function provideInterface>,
       ('',
@@ -284,123 +284,7 @@
        'info')))
     """
 
-def test_containerViews_layer():
-    """
-    >>> from zope.app.publisher.browser.menu import menus
-    >>> from zope.interface.interface import InterfaceClass
-    >>> zmi_views = InterfaceClass('zmi_views', __module__='zope.app.menus')
-    >>> menus.zmi_views = zmi_views
-    >>> zmi_actions = InterfaceClass('zmi_actions', __module__='zope.app.menus')
-    >>> menus.zmi_actions = zmi_actions
 
-    >>> context = Context()
-    >>> containerViews(context, for_=I, contents='zope.ManageContent',
-    ...                add='zope.ManageContent', index='zope.View', layer=ITestLayer)
-    >>> context
-    ((('adapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
-       <InterfaceClass zope.app.menus.zmi_views>,
-       u'Contents'),
-      <function handler>,
-      ('provideAdapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
-       <InterfaceClass zope.app.menus.zmi_views>,
-       u'Contents',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
-       '')),
-     (None,
-      <function provideInterface>,
-      ('', <InterfaceClass zope.app.menus.zmi_views>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>)),
-     (('view',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-       'contents.html',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
-       <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-      <function handler>,
-      ('provideAdapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-       <InterfaceClass zope.interface.Interface>,
-       'contents.html',
-       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
-       'info')),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>)),
-     (('view',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-       'index.html',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>,
-       <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-      <function handler>,
-      ('provideAdapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-       <InterfaceClass zope.interface.Interface>,
-       'index.html',
-       <class 'zope.app.publisher.browser.viewmeta.Contents'>,
-       'info')),
-     (('adapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
-       <InterfaceClass zope.app.menus.zmi_actions>,
-       u'Add'),
-      <function handler>,
-      ('provideAdapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
-       <InterfaceClass zope.app.menus.zmi_actions>,
-       u'Add',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
-       'info')),
-     (None,
-      <function provideInterface>,
-      ('', <InterfaceClass zope.app.menus.zmi_actions>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)),
-     (None,
-      <function provideInterface>,
-      ('',
-       <InterfaceClass zope.app.container.browser.tests.test_directive.I>)),
-     (None,
-      <function provideInterface>,
-      ('', <InterfaceClass zope.interface.Interface>)),
-     (('view',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-       '+',
-       <InterfaceClass zope.interface.Interface>),
-      <function handler>,
-      ('provideAdapter',
-       (<InterfaceClass zope.app.container.browser.tests.test_directive.I>,
-        <InterfaceClass zope.app.container.browser.tests.test_directive.ITestLayer>),
-       <InterfaceClass zope.interface.Interface>,
-       '+',
-       <class 'zope.app.publisher.browser.viewmeta.+'>,
-       'info')))
-    """
-
 def test_suite():
     return unittest.TestSuite((
         DocTestSuite(),

Modified: Zope3/trunk/src/zope/app/demo/menu/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/demo/menu/configure.zcml	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/demo/menu/configure.zcml	2005-03-14 01:07:34 UTC (rev 29457)
@@ -25,7 +25,7 @@
 
   <browser:page
       name="menudemo.html"
-      for="zope.interface.Interface"
+      for="zope.app.container.interfaces.IReadContainer"
       template="menudemo.pt"
       permission="zope.Public" />
 
@@ -73,31 +73,9 @@
 
   <browser:menu 
       id="openrecent"
-      title="Open Recent" />
+      title="Open Recent" 
+      class=".menu.RecentlyOpened" />
 
-  <browser:menuItems 
-      menu="openrecent" 
-      for="zope.interface.Interface">
-
-    <browser:menuItem 
-        action="javascript:alert('Open myfile.txt')" 
-        title="myfile.txt"
-        permission="zope.Public"
-        />
-
-    <browser:menuItem 
-        action="javascript:alert('Open folderX')" 
-        title="folderX"
-        permission="zope.Public"
-        />
-
-    <browser:menuItem 
-        action="javascript:alert('Open picture.png')" 
-        title="picture.png" 
-        permission="zope.Public"/>
-
-  </browser:menuItems>
-
   <browser:menu 
       id="save"
       title="Save" />

Added: Zope3/trunk/src/zope/app/demo/menu/menu.py
===================================================================
--- Zope3/trunk/src/zope/app/demo/menu/menu.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/demo/menu/menu.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -0,0 +1,59 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Special Menu Implementations
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.component.hooks import getSite
+from zope.app.dublincore.interfaces import IZopeDublinCore
+from zope.app.publisher.interfaces.browser import IBrowserMenu
+
+
+class RecentlyOpened(object):
+    """Menu showing recently opened files.
+
+    This implementation is actually a quick hack and will display all of the
+    items in a container.
+    """
+    implements(IBrowserMenu)
+    
+    def __init__(self, id, title=u'', description=u''):
+        self.id = id
+        self.title = title
+        self.description = description
+
+    def getMenuItems(self, object, request):
+        """Return menu item entries in a TAL-friendly form."""
+        result = []
+        site = getSite()
+        url = zapi.absoluteURL(site, request)        
+
+        for name, item in object.items():
+            dc = IZopeDublinCore(item, None)
+            zmi_icon = zapi.queryMultiAdapter((item, request), name='zmi_icon')
+
+            result.append(
+                {'title': name,
+                 'description': dc.title,
+                 'action': name + '/manage',
+                 'selected': u'',
+                 'icon': zmi_icon and zmi_icon.url() or None,
+                 'extra': {},
+                 'submenu': None})
+    
+        return result


Property changes on: Zope3/trunk/src/zope/app/demo/menu/menu.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/form/browser/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/form/browser/metaconfigure.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/form/browser/metaconfigure.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -28,7 +28,7 @@
 from zope.app.container.interfaces import IAdding
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.browser import IDefaultBrowserLayer
-from zope.app.publisher.browser.menu import menuItemDirective
+from zope.app.publisher.browser.menumeta import menuItemDirective
 
 from zope.app.form import CustomWidgetFactory
 from zope.app.form.interfaces import IInputWidget, IDisplayWidget

Modified: Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -21,7 +21,6 @@
 from zope.app import zapi
 from zope.app.publisher.browser import BrowserView
 from zope.app.publisher.browser.menu import getFirstMenuItem
-from zope.app.publisher.interfaces.browser import IMenuItemType
 
 class ManagementViewSelector(BrowserView):
     """View that selects the first available management view."""
@@ -31,8 +30,7 @@
         return self, ()
 
     def __call__(self):
-        zmi_views = zapi.getUtility(IMenuItemType, 'zmi_views')
-        item = getFirstMenuItem(zmi_views, self.context, self.request)
+        item = getFirstMenuItem('zmi_views', self.context, self.request)
 
         if item:
             self.request.response.redirect(item['action'])

Modified: Zope3/trunk/src/zope/app/publisher/browser/menu.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/menu.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/menu.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -16,38 +16,70 @@
 $Id$
 """
 __docformat__ = "reStructuredText"
-from zope.component.interfaces import IFactory
-from zope.configuration.exceptions import ConfigurationError
+import sys
 
 from zope.interface import Interface, implements, classImplements
-from zope.interface import directlyProvides, providedBy
-from zope.interface.interface import InterfaceClass
-from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.interface import providedBy
 from zope.security import checkPermission, canAccess
-from zope.security.checker import InterfaceChecker, CheckerPublic
 from zope.security.interfaces import Unauthorized, Forbidden
 from zope.security.proxy import ProxyFactory, removeSecurityProxy
 
 from zope.app import zapi
-from zope.app.component.interface import provideInterface
-from zope.app.component.metaconfigure import adapter, proxify
 from zope.app.pagetemplate.engine import Engine
 from zope.app.publication.browser import PublicationTraverser
 from zope.app.publisher.browser import BrowserView
 from zope.app.publisher.interfaces.browser import IMenuAccessView
+from zope.app.publisher.interfaces.browser import IBrowserMenu
 from zope.app.publisher.interfaces.browser import IBrowserMenuItem
 from zope.app.publisher.interfaces.browser import IBrowserSubMenuItem
 from zope.app.publisher.interfaces.browser import IMenuItemType
 
-# Create special modules that contain all menu item types
-from types import ModuleType as module
-import sys
-menus = module('menus')
-sys.modules['zope.app.menus'] = menus
 
+class BrowserMenu(object):
+    """Browser Menu"""
+    implements(IBrowserMenu)
+    
+    def __init__(self, id, title=u'', description=u''):
+        self.id = id
+        self.title = title
+        self.description = description
 
-_order_counter = {}
+    def getMenuItemType(self):
+        return zapi.getUtility(IMenuItemType, self.id)
 
+    def getMenuItems(self, object, request):
+        """Return menu item entries in a TAL-friendly form."""
+        result = []
+        for name, item in zapi.getAdapters((object, request),
+                                           self.getMenuItemType()):
+            if item.available():
+                result.append(item)
+            
+        # Now order the result. This is not as easy as it seems.
+        #
+        # (1) Look at the interfaces and put the more specific menu entries
+        #     to the front.
+        # (2) Sort unabigious entries by order and then by title.
+        ifaces = list(providedBy(removeSecurityProxy(object)).__iro__)
+        result = [
+            (ifaces.index(item._for or Interface), item.order, item.title, item)
+            for item in result]
+        result.sort()
+        
+        result = [
+            {'title': item.title,
+             'description': item.description,
+             'action': item.action,
+             'selected': (item.selected() and u'selected') or u'',
+             'icon': item.icon,
+             'extra': item.extra,
+             'submenu': (IBrowserSubMenuItem.providedBy(item) and
+                         getMenu(item.submenuId, object, request)) or None}
+            for index, order, title, item in result]
+    
+        return result
+
+
 class BrowserMenuItem(BrowserView):
     """Browser Menu Item Class"""
     implements(IBrowserMenuItem)
@@ -133,7 +165,7 @@
     implements(IBrowserSubMenuItem)
 
     # See zope.app.publisher.interfaces.browser.IBrowserSubMenuItem
-    submenuType = None
+    submenuId = None
 
     def selected(self):
         """See zope.app.publisher.interfaces.browser.IBrowserMenuItem"""
@@ -142,41 +174,15 @@
         return super(BrowserSubMenuItem, self).selected()
 
 
-def getMenu(menuItemType, object, request):
+def getMenu(id, object, request):
     """Return menu item entries in a TAL-friendly form."""
-    result = []
-    for name, item in zapi.getAdapters((object, request), menuItemType):
-        if item.available():
-            result.append(item)
-        
-    # Now order the result. This is not as easy as it seems.
-    #
-    # (1) Look at the interfaces and put the more specific menu entries to the
-    #     front.
-    # (2) Sort unabigious entries by order and then by title.
-    ifaces = list(providedBy(removeSecurityProxy(object)).__iro__)
-    result = [
-        (ifaces.index(item._for or Interface), item.order, item.title, item)
-        for item in result]
-    result.sort()
-    
-    result = [
-        {'title': item.title,
-         'description': item.description,
-         'action': item.action,
-         'selected': (item.selected() and u'selected') or u'',
-         'icon': item.icon,
-         'extra': item.extra,
-         'submenu': (IBrowserSubMenuItem.providedBy(item) and
-                     getMenu(item.submenuType, object, request)) or None}
-        for index, order, title, item in result]
+    menu = zapi.getUtility(IBrowserMenu, id)
+    return menu.getMenuItems(object, request)
 
-    return result
 
-
-def getFirstMenuItem(menuItemType, object, request):
+def getFirstMenuItem(id, object, request):
     """Get the first item of a menu."""
-    items = getMenu(menuItemType, object, request)
+    items = getMenu(id, object, request)
     if items:
         return items[0]
     return None
@@ -186,154 +192,5 @@
     """A view allowing easy access to menus."""
     implements(IMenuAccessView)
 
-    def __getitem__(self, typeString):
-        # Convert the menu item type identifyer string to the type interface
-        menuItemType = zapi.getUtility(IMenuItemType, typeString)
-        return getMenu(menuItemType, self.context, self.request)
-
-
-def menuDirective(_context, id=None, interface=None,
-                  title=u'', description=u''):
-    """Provides a new menu (item type)."""
-    if id is None and interface is None: 
-        raise ConfigurationError(
-            "You must specify the 'id' or 'interface' attribute.")
-
-    if interface is None:
-        interface = InterfaceClass(id, (),
-                                   __doc__='Menu Item Type: %s' %id,
-                                   __module__='zope.app.menus')
-        # Add the menu item type to the `menus` module.
-        # Note: We have to do this immediately, so that directives using the
-        # MenuField can find the menu item type.
-        setattr(menus, id, interface)
-        path = 'zope.app.menus.' + id
-    else:
-        path = interface.__module__ + '.' + interface.getName()
-
-        # If an id was specified, make this menu available under this id.
-        # Note that the menu will be still available under its path, since it
-        # is an adapter, and the `MenuField` can resolve paths as well.
-        if id is None:
-            id = path
-        else:
-            # Make the interface available in the `zope.app.menus` module, so
-            # that other directives can find the interface under the name
-            # before the CA is setup.
-            _context.action(
-                discriminator = ('browser', 'MenuItemType', path),
-                callable = provideInterface,
-                args = (path, interface, IMenuItemType, _context.info)
-                )
-            setattr(menus, id, interface)
-
-    # Set the title and description of the menu item type
-    interface.setTaggedValue('title', title)
-    interface.setTaggedValue('description', description)
-
-    # Register the layer interface as an interface
-    _context.action(
-        discriminator = ('interface', path),
-        callable = provideInterface,
-        args = (path, interface),
-        kw = {'info': _context.info}
-        )
-
-    # Register the menu item type interface as an IMenuItemType
-    _context.action(
-        discriminator = ('browser', 'MenuItemType', id),
-        callable = provideInterface,
-        args = (id, interface, IMenuItemType, _context.info)
-        )
-
-
-def menuItemDirective(_context, menu, for_,
-                      action, title, description=u'', icon=None, filter=None,
-                      permission=None, layer=IBrowserRequest, extra=None,
-                      order=0):
-    """Register a single menu item."""
-    return menuItemsDirective(_context, menu, for_).menuItem(
-        _context, action, title, description, icon, filter,
-        permission, extra, order)
-
-
-def subMenuItemDirective(_context, menu, for_, title, submenu,
-                         action=u'', description=u'', icon=None, filter=None,
-                         permission=None, layer=IBrowserRequest, extra=None,
-                         order=0):
-    """Register a single sub-menu menu item."""
-    return menuItemsDirective(_context, menu, for_).subMenuItem(
-        _context, submenu, title, description, action, icon, filter,
-        permission, extra, order)
-
-
-class MenuItemFactory(object):
-    """generic factory for menu items."""
-
-    def __init__(self, factory, **kwargs):
-        self.factory = factory
-        if 'permission' in kwargs and kwargs['permission'] == 'zope.Public':
-            kwargs['permission'] = CheckerPublic
-        self.kwargs = kwargs
-    
-    def __call__(self, context, request):
-        item = self.factory(context, request)
-
-        for key, value in self.kwargs.items():
-            setattr(item, key, value)
-
-        if item.permission is not None:
-            checker = InterfaceChecker(IBrowserMenuItem, item.permission)
-            item = proxify(item, checker)
-
-        return item
-
-
-class menuItemsDirective(object):
-    """Register several menu items for a particular menu."""
-
-    def __init__(self, _context, menu, for_, layer=IBrowserRequest):
-        self.for_ = for_
-        self.menuItemType = menu
-        self.layer = layer
-
-    def menuItem(self, _context, action, title, description=u'',
-                 icon=None, filter=None, permission=None, extra=None, order=0):
-
-        if filter is not None:
-            filter = Engine.compile(filter)
-
-        if order == 0:
-            order = _order_counter.get(self.for_, 1)
-            _order_counter[self.for_] = order + 1
-
-        factory = MenuItemFactory(
-            BrowserMenuItem,
-            title=title, description=description, icon=icon, action=action,
-            filter=filter, permission=permission, extra=extra, order=order,
-            _for=self.for_)
-        adapter(_context, (factory,), self.menuItemType,
-                (self.for_, self.layer), name=title)
-
-    def subMenuItem(self, _context, submenu, title, description=u'',
-                    action=u'', icon=None, filter=None, permission=None,
-                    extra=None, order=0):
-
-        if filter is not None:
-            filter = Engine.compile(filter)
-
-        if order == 0:
-            order = _order_counter.get(self.for_, 1)
-            _order_counter[self.for_] = order + 1
-
-        factory = MenuItemFactory(
-            BrowserSubMenuItem,
-            title=title, description=description, icon=icon, action=action,
-            filter=filter, permission=permission, extra=extra, order=order,
-            _for=self.for_, submenuType=submenu)
-        adapter(_context, (factory,), self.menuItemType,
-                (self.for_, self.layer), name=title)
-        
-    def __call__(self, _context):
-        # Nothing to do.
-        pass
+    def __getitem__(self, menuId):
+        return getMenu(menuId, self.context, self.request)

Modified: Zope3/trunk/src/zope/app/publisher/browser/menu.txt
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/menu.txt	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/menu.txt	2005-03-14 01:07:34 UTC (rev 29457)
@@ -6,22 +6,39 @@
 content component or the addable components of a container. In essence they
 provide the same functionality as menu bars in desktop application.
 
-  >>> from zope.app.publisher.browser import menu
+  >>> from zope.app.publisher.browser import menu, menumeta
 
-The concept of a menu is more of a pattern than concrete
-implementation. Interfaces are used to denote a menu. So let's define a simple
-edit menu:
+Menus are simple components that have an id, title and description. They also
+must provide a method called ``getMenuItems(object, request)`` that returns a
+TAL-friendly list of information dictionaries. We will see this in detail
+later. The default menu implementation, however, makes the menu be very
+transparent by identifying the menu through an interface. So let's define and
+register a simple edit menu:
 
   >>> import zope.interface
   >>> class EditMenu(zope.interface.Interface):
   ...     """This is an edit menu."""
 
-An item in a menu is simply an adapter that provides. In the following section
-we will have a closer look at the browser menu item:
+  >>> from zope.app.publisher.interfaces.browser import IMenuItemType
+  >>> zope.interface.directlyProvides(EditMenu, IMenuItemType)
 
-`BrowserMenuItem` class
------------------------
+  >>> from zope.app.testing import ztapi
+  >>> ztapi.provideUtility(IMenuItemType, EditMenu, 'edit')
 
+Now we have to create and register the menu itself:
+
+  >>> from zope.app.publisher.interfaces.browser import IBrowserMenu
+  >>> ztapi.provideUtility(
+  ...     IBrowserMenu, menu.BrowserMenu('edit', u'Edit', u'Edit Menu'), 'edit')
+
+Note that these steps seem like a lot of boilerplate, but all this work is
+commonly done for you via ZCML. An item in a menu is simply an adapter that
+provides. In the following section we will have a closer look at the browser
+menu item:
+
+``BrowserMenuItem`` class
+-------------------------
+
 The browser menu item represents an entry in the menu. Essentially, the menu
 item is a browser view of a content component. Thus we have to create a
 content component first:
@@ -48,10 +65,10 @@
   ...             raise Unauthorized, name
   ...         return self.foo
 
-We also implemented the `IBrowserPublisher` interface, because we want to make
-the object traversable, so that we can make availability checks later.
+We also implemented the ``IBrowserPublisher`` interface, because we want to
+make the object traversable, so that we can make availability checks later.
 
-Since the `BrowserMenuItem` is just a view, we can initiate it with an
+Since the ``BrowserMenuItem`` is just a view, we can initiate it with an
 object and a request.
 
   >>> from zope.publisher.browser import TestRequest
@@ -95,7 +112,7 @@
 
 There are two ways to deny availability of a menu item: (1) the current
 user does not have the correct permission to access the action or the menu
-item itself, or (2) the filter returns `False`, in which case the menu
+item itself, or (2) the filter returns ``False``, in which case the menu
 item should also not be shown. 
 
   >>> from zope.app.testing import ztapi
@@ -184,8 +201,8 @@
   False
 
 
-`BrowserSubMenuItem` class
---------------------------
+``BrowserSubMenuItem`` class
+----------------------------
 
 The menu framework also allows for submenus. Submenus can be inserted by
 creating a special menu item that simply points to another menu to be
@@ -193,20 +210,26 @@
 
   >>> item = menu.BrowserSubMenuItem(Content(), TestRequest())
 
-The framework will always set the sub-menu type automatically (we do it
+The framework will always set the sub-menu automatically (we do it
 manually here):
 
   >>> class SaveOptions(zope.interface.Interface):
   ...     "A sub-menu that describes available save options for the content."
 
-  >>> item.submenuType = SaveOptions
+  >>> zope.interface.directlyProvides(SaveOptions, IMenuItemType)
 
-  >>> item.submenuType
-  <InterfaceClass __builtin__.SaveOptions>
+  >>> ztapi.provideUtility(IMenuItemType, SaveOptions, 'save')
+  >>> ztapi.provideUtility(IBrowserMenu,
+  ...                      menu.BrowserMenu('save', u'Save', u'Save Menu'),
+  ...                      'save')
 
-Also, the `action` attribute for the browser sub-menu item is optional,
+Now we can assign the sub-menu id to the menu item: 
+
+  >>> item.submenuId = 'save'
+
+Also, the ``action`` attribute for the browser sub-menu item is optional,
 because you often do not want the item itself to represent something. The rest
-of the class is identical to the `BrowserMenuItem` class.
+of the class is identical to the ``BrowserMenuItem`` class.
 
 
 Getting a Menu
@@ -222,38 +245,39 @@
   >>> from zope.app.testing import ztapi
   >>> from zope.publisher.interfaces.browser import IBrowserRequest
 
-  >>> undo = menu.MenuItemFactory(menu.BrowserMenuItem, title="Undo", 
-  ...                             action="undo.html")
+  >>> undo = menumeta.MenuItemFactory(menu.BrowserMenuItem, title="Undo", 
+  ...                                 action="undo.html")
   >>> ztapi.provideAdapter((IContent, IBrowserRequest), EditMenu, undo, 'undo')
 
-  >>> redo = menu.MenuItemFactory(menu.BrowserMenuItem, title="Redo",
-  ...                             action="redo.html", icon="/@@/redo.png")
+  >>> redo = menumeta.MenuItemFactory(menu.BrowserMenuItem, title="Redo",
+  ...                                 action="redo.html", icon="/@@/redo.png")
   >>> ztapi.provideAdapter((IContent, IBrowserRequest), EditMenu, redo, 'redo')
 
-  >>> save = menu.MenuItemFactory(menu.BrowserSubMenuItem, title="Save", 
-  ...                             submenuType=SaveOptions, order=2)
+  >>> save = menumeta.MenuItemFactory(menu.BrowserSubMenuItem, title="Save", 
+  ...                                 submenuId='save', order=2)
   >>> ztapi.provideAdapter((IContent, IBrowserRequest), EditMenu, save, 'save')
 
 And now the save options:
 
-  >>> saveas = menu.MenuItemFactory(menu.BrowserMenuItem, title="Save as", 
-  ...                               action="saveas.html")
+  >>> saveas = menumeta.MenuItemFactory(menu.BrowserMenuItem, title="Save as", 
+  ...                                   action="saveas.html")
   >>> ztapi.provideAdapter((IContent, IBrowserRequest), 
   ...                      SaveOptions, saveas, 'saveas')
 
-  >>> saveall = menu.MenuItemFactory(menu.BrowserMenuItem, title="Save all",
-  ...                                action="saveall.html")
+  >>> saveall = menumeta.MenuItemFactory(menu.BrowserMenuItem, title="Save all",
+  ...                                    action="saveall.html")
   >>> ztapi.provideAdapter((IContent, IBrowserRequest), 
   ...                      SaveOptions, saveall, 'saveall')
 
 The utility that is used to generate the menu into a TAL-friendly
-data-structure is `getMenu()`::
+data-structure is ``getMenu()``::
 
-  getMenu(menuItemType, object, request)
+  getMenu(menuId, object, request)
 
-where `menuItemType` is the menu interface. Let's look up the menu now:
+where ``menuId`` is the id originally specified for the menu. Let's look up the
+menu now:
 
-  >>> pprint(menu.getMenu(EditMenu, Content(), TestRequest()))
+  >>> pprint(menu.getMenu('edit', Content(), TestRequest()))
   [{'action': 'redo.html',
     'description': u'',
     'extra': None,
@@ -290,33 +314,99 @@
     'title': 'Save'}]
 
 
-`MenuItemFactory` class
------------------------
+Custom ``IBrowserMenu`` Implementations
+---------------------------------------
 
+Until now we have only seen how to use the default menu implementation. Much
+of the above boilerplate was necessary just to support custom menus. But what
+could custom menus do? Sometimes menu items are dynamically generated based on
+a certain state of the object the menu is for. For example, you might want to
+show all items in a folder-like component. So first let's create this
+folder-like component:
+
+  >>> class Folderish(Content):
+  ...     names = ['README.txt', 'logo.png', 'script.py']
+
+Now we create a menu using the names to create a menu:
+
+  >>> from zope.app.publisher.interfaces.browser import IBrowserMenu
+
+  >>> class Items(object):
+  ...     zope.interface.implements(IBrowserMenu)
+  ...  
+  ...     def __init__(self, id, title=u'', description=u''):
+  ...         self.id = id
+  ...         self.title = title
+  ...         self.description = description
+  ...     
+  ...     def getMenuItems(self, object, request):
+  ...         return [{'title': name,
+  ...                  'description': None,
+  ...                  'action': name + '/manage',
+  ...                  'selected': u'',
+  ...                  'icon': None,
+  ...                  'extra': {},
+  ...                  'submenu': None}
+  ...                 for name in object.names]
+
+and register it:
+
+  >>> ztapi.provideUtility(IBrowserMenu,
+  ...                      Items('items', u'Items', u'Items Menu'),
+  ...                      'items')
+
+We can now get the menu items using the previously introduced API:
+
+  >>> pprint(menu.getMenu('items', Folderish(), TestRequest()))
+  [{'action': 'README.txt/manage',
+    'description': None,
+    'extra': {},
+    'icon': None,
+    'selected': u'',
+    'submenu': None,
+    'title': 'README.txt'},
+   {'action': 'logo.png/manage',
+    'description': None,
+    'extra': {},
+    'icon': None,
+    'selected': u'',
+    'submenu': None,
+    'title': 'logo.png'},
+   {'action': 'script.py/manage',
+    'description': None,
+    'extra': {},
+    'icon': None,
+    'selected': u'',
+    'submenu': None,
+    'title': 'script.py'}]
+
+
+``MenuItemFactory`` class
+-------------------------
+
 As you have seen above already, we have used the menu item factory to generate
 adapter factories for menu items. The factory needs a particular
-`IBrowserMenuItem` class to instantiate. Here is an example using a dummy menu
-item class:
+``IBrowserMenuItem`` class to instantiate. Here is an example using a dummy
+menu item class:
   
   >>> class DummyBrowserMenuItem(object):
   ...     "a dummy factory for menu items"
   ...     def __init__(self, context, request):
   ...         self.context = context
   ...         self.request = request
-  ... 
   
 To instantiate this class, pass the factory and the other arguments as keyword
 arguments (every key in the arguments should map to an attribute of the menu
 item class). We use dummy values for this example.
   
-  >>> factory = menu.MenuItemFactory(
+  >>> factory = menumeta.MenuItemFactory(
   ...     DummyBrowserMenuItem, title='Title', description='Description', 
   ...     icon='Icon', action='Action', filter='Filter', 
   ...     permission='zope.Public', extra='Extra', order='Order', _for='For')
   >>> factory.factory is DummyBrowserMenuItem
   True
   
-The "zope.Public" permission needs to be translated to `CheckerPublic.`
+The "zope.Public" permission needs to be translated to ``CheckerPublic``.
   
   >>> from zope.security.checker import CheckerPublic
   >>> factory.kwargs['permission'] is CheckerPublic
@@ -327,8 +417,8 @@
   
   >>> item = factory('Context', 'Request')
   
-The returned value should be an instance of the `DummyBrowserMenuItem`, and have
-all of the values we initially set on the factory.
+The returned value should be an instance of the ``DummyBrowserMenuItem``, and
+have all of the values we initially set on the factory.
   
   >>> isinstance(item, DummyBrowserMenuItem)
   True
@@ -355,10 +445,10 @@
   >>> item._for
   'For'
   
-If you pass a permission other than `zope.Public` to the `MenuItemFactory`,
-it should pass through unmodified.
+If you pass a permission other than ``zope.Public`` to the
+``MenuItemFactory``, it should pass through unmodified.
   
-  >>> factory = menu.MenuItemFactory(
+  >>> factory = menumeta.MenuItemFactory(
   ...     DummyBrowserMenuItem, title='Title', description='Description', 
   ...     icon='Icon', action='Action', filter='Filter', 
   ...     permission='another.Permission', extra='Extra', order='Order', 
@@ -370,8 +460,8 @@
 Directive Handlers
 ------------------
 
-`menu` Directive Handler
-~~~~~~~~~~~~~~~~~~~~~~~~
+``menu`` Directive Handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Provides a new menu (item type).
 
@@ -387,14 +477,10 @@
 ++++++++++++++++++++++++++
   
   >>> context = Context()
-  >>> menu.menuDirective(context, u'menu1', title=u'Menu 1')
+  >>> menumeta.menuDirective(context, u'menu1', title=u'Menu 1')
   >>> iface = context.actions[0]['args'][1]
   >>> iface.getName()
   u'menu1'
-  >>> iface.getTaggedValue('title')
-  u'Menu 1'
-  >>> iface.getTaggedValue('description')
-  u''
 
   >>> import sys
   >>> hasattr(sys.modules['zope.app.menus'], 'menu1')
@@ -409,7 +495,7 @@
   ...     pass
 
   >>> context = Context()
-  >>> menu.menuDirective(context, interface=menu1)
+  >>> menumeta.menuDirective(context, interface=menu1)
   >>> context.actions[0]['args'][1] is menu1
   True
 
@@ -417,29 +503,33 @@
 +++++++++++++++++++++++++++++++++++++++++++++
 
   >>> context = Context()
-  >>> menu.menuDirective(context, id='menu1', interface=menu1)
+  >>> menumeta.menuDirective(context, id='menu1', interface=menu1)
 
   >>> pprint([action['discriminator'] for action in context.actions])
   [('browser', 'MenuItemType', '__builtin__.menu1'),
    ('interface', '__builtin__.menu1'),
-   ('browser', 'MenuItemType', 'menu1')]
+   ('browser', 'MenuItemType', 'menu1'),
+   ('utility',
+    <InterfaceClass zope.app.publisher.interfaces.browser.IBrowserMenu>,
+    'menu1'),
+   None]
    
 Here are some disallowed configurations.
 
   >>> context = Context()
-  >>> menu.menuDirective(context)
+  >>> menumeta.menuDirective(context)
   Traceback (most recent call last):
   ...
   ConfigurationError: You must specify the 'id' or 'interface' attribute.
 
-  >>> menu.menuDirective(context, title='Menu 1')
+  >>> menumeta.menuDirective(context, title='Menu 1')
   Traceback (most recent call last):
   ...
   ConfigurationError: You must specify the 'id' or 'interface' attribute.
 
 
-`menuItems` Directive Handler
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+``menuItems`` Directive Handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Register several menu items for a particular menu.
 
@@ -450,7 +540,7 @@
   ...     pass
 
   >>> context = Context()
-  >>> items = menu.menuItemsDirective(context, TestMenuItemType, ITest)
+  >>> items = menumeta.menuItemsDirective(context, TestMenuItemType, ITest)
   >>> context.actions
   []
   >>> items.menuItem(context, u'view.html', 'View')
@@ -461,11 +551,11 @@
   >>> pprint(disc[-2:])
   [('adapter',
     (<InterfaceClass __builtin__.ITest>,
-     <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+     <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
     <InterfaceClass __builtin__.TestMenuItemType>,
     'Save'),
    ('adapter',
     (<InterfaceClass __builtin__.ITest>,
-     <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+     <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
     <InterfaceClass __builtin__.TestMenuItemType>,
     'View')]

Added: Zope3/trunk/src/zope/app/publisher/browser/menumeta.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/menumeta.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/menumeta.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Menu Directives Configuration Handlers
+
+$Id$
+"""
+from zope.configuration.exceptions import ConfigurationError
+from zope.interface.interface import InterfaceClass
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.security.checker import InterfaceChecker, CheckerPublic
+
+from zope.app.component.interface import provideInterface
+from zope.app.component.metaconfigure import adapter, proxify
+from zope.app.component.metaconfigure import utility
+from zope.app.pagetemplate.engine import Engine
+from zope.app.publisher.browser.menu import BrowserMenu
+from zope.app.publisher.browser.menu import BrowserMenuItem, BrowserSubMenuItem
+from zope.app.publisher.interfaces.browser import IBrowserMenu
+from zope.app.publisher.interfaces.browser import IBrowserMenuItem
+from zope.app.publisher.interfaces.browser import IMenuItemType
+
+
+# Create special modules that contain all menu item types
+from types import ModuleType as module
+import sys
+menus = module('menus')
+sys.modules['zope.app.menus'] = menus
+
+
+_order_counter = {}
+
+
+def menuDirective(_context, id=None, class_=BrowserMenu, interface=None,
+                  title=u'', description=u''):
+    """Registers a new browser menu."""
+    if id is None and interface is None: 
+        raise ConfigurationError(
+            "You must specify the 'id' or 'interface' attribute.")
+
+    if interface is None:
+        interface = InterfaceClass(id, (),
+                                   __doc__='Menu Item Type: %s' %id,
+                                   __module__='zope.app.menus')
+        # Add the menu item type to the `menus` module.
+        # Note: We have to do this immediately, so that directives using the
+        # MenuField can find the menu item type.
+        setattr(menus, id, interface)
+        path = 'zope.app.menus.' + id
+    else:
+        path = interface.__module__ + '.' + interface.getName()
+
+        # If an id was specified, make this menu available under this id.
+        # Note that the menu will be still available under its path, since it
+        # is an adapter, and the `MenuField` can resolve paths as well.
+        if id is None:
+            id = path
+        else:
+            # Make the interface available in the `zope.app.menus` module, so
+            # that other directives can find the interface under the name
+            # before the CA is setup.
+            _context.action(
+                discriminator = ('browser', 'MenuItemType', path),
+                callable = provideInterface,
+                args = (path, interface, IMenuItemType, _context.info)
+                )
+            setattr(menus, id, interface)
+
+    # Register the layer interface as an interface
+    _context.action(
+        discriminator = ('interface', path),
+        callable = provideInterface,
+        args = (path, interface),
+        kw = {'info': _context.info}
+        )
+
+    # Register the menu item type interface as an IMenuItemType
+    _context.action(
+        discriminator = ('browser', 'MenuItemType', id),
+        callable = provideInterface,
+        args = (id, interface, IMenuItemType, _context.info)
+        )
+
+    # Register the menu as a utility
+    utility(_context, IBrowserMenu, class_(id, title, description), name=id)
+
+
+def menuItemDirective(_context, menu, for_,
+                      action, title, description=u'', icon=None, filter=None,
+                      permission=None, layer=IDefaultBrowserLayer, extra=None,
+                      order=0):
+    """Register a single menu item."""
+    return menuItemsDirective(_context, menu, for_).menuItem(
+        _context, action, title, description, icon, filter,
+        permission, extra, order)
+
+
+def subMenuItemDirective(_context, menu, for_, title, submenu,
+                         action=u'', description=u'', icon=None, filter=None,
+                         permission=None, layer=IDefaultBrowserLayer,
+                         extra=None, order=0):
+    """Register a single sub-menu menu item."""
+    return menuItemsDirective(_context, menu, for_).subMenuItem(
+        _context, submenu, title, description, action, icon, filter,
+        permission, extra, order)
+
+
+class MenuItemFactory(object):
+    """generic factory for menu items."""
+
+    def __init__(self, factory, **kwargs):
+        self.factory = factory
+        if 'permission' in kwargs and kwargs['permission'] == 'zope.Public':
+            kwargs['permission'] = CheckerPublic
+        self.kwargs = kwargs
+    
+    def __call__(self, context, request):
+        item = self.factory(context, request)
+
+        for key, value in self.kwargs.items():
+            setattr(item, key, value)
+
+        if item.permission is not None:
+            checker = InterfaceChecker(IBrowserMenuItem, item.permission)
+            item = proxify(item, checker)
+
+        return item
+
+
+class menuItemsDirective(object):
+    """Register several menu items for a particular menu."""
+
+    def __init__(self, _context, menu, for_, layer=IDefaultBrowserLayer):
+        self.for_ = for_
+        self.menuItemType = menu
+        self.layer = layer
+
+    def menuItem(self, _context, action, title, description=u'',
+                 icon=None, filter=None, permission=None, extra=None, order=0):
+
+        if filter is not None:
+            filter = Engine.compile(filter)
+
+        if order == 0:
+            order = _order_counter.get(self.for_, 1)
+            _order_counter[self.for_] = order + 1
+
+        factory = MenuItemFactory(
+            BrowserMenuItem,
+            title=title, description=description, icon=icon, action=action,
+            filter=filter, permission=permission, extra=extra, order=order,
+            _for=self.for_)
+        adapter(_context, (factory,), self.menuItemType,
+                (self.for_, self.layer), name=title)
+
+    def subMenuItem(self, _context, submenu, title, description=u'',
+                    action=u'', icon=None, filter=None, permission=None,
+                    extra=None, order=0):
+
+        if filter is not None:
+            filter = Engine.compile(filter)
+
+        if order == 0:
+            order = _order_counter.get(self.for_, 1)
+            _order_counter[self.for_] = order + 1
+
+        factory = MenuItemFactory(
+            BrowserSubMenuItem,
+            title=title, description=description, icon=icon, action=action,
+            filter=filter, permission=permission, extra=extra, order=order,
+            _for=self.for_, submenuId=submenu)
+        adapter(_context, (factory,), self.menuItemType,
+                (self.for_, self.layer), name=title)
+        
+    def __call__(self, _context):
+        # Nothing to do.
+        pass


Property changes on: Zope3/trunk/src/zope/app/publisher/browser/menumeta.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/app/publisher/browser/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/meta.zcml	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/meta.zcml	2005-03-14 01:07:34 UTC (rev 29457)
@@ -104,13 +104,13 @@
     <meta:directive
         name="menu"
         schema=".metadirectives.IMenuDirective"
-        handler=".menu.menuDirective"
+        handler=".menumeta.menuDirective"
         />
 
     <meta:complexDirective
         name="menuItems"
         schema=".metadirectives.IMenuItemsDirective"
-        handler=".menu.menuItemsDirective"
+        handler=".menumeta.menuItemsDirective"
         >
 
       <meta:subdirective
@@ -128,13 +128,13 @@
     <meta:directive
         name="menuItem"
         schema=".metadirectives.IMenuItemDirective"
-        handler=".menu.menuItemDirective"
+        handler=".menumeta.menuItemDirective"
         />
 
     <meta:directive
         name="subMenuItem"
         schema=".metadirectives.ISubMenuItemDirective"
-        handler=".menu.subMenuItemDirective"
+        handler=".menumeta.subMenuItemDirective"
         />
 
     <!-- misc. directives -->

Modified: Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -25,7 +25,7 @@
 from zope.app import zapi
 from zope.app.component.metaconfigure import handler
 from zope.app.container.interfaces import IAdding
-from zope.app.publisher.browser.menu import menuItemDirective
+from zope.app.publisher.browser.menumeta import menuItemDirective
 from zope.app.component.contentdirective import ContentDirective
 from zope.app.publisher.interfaces.browser import AddMenu
 

Modified: Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -386,6 +386,12 @@
         required=False
         )
 
+    class_ = GlobalObject(
+        title=u"Menu Class",
+        description=u"The menu class used to generate the menu.",
+        required=False
+        )
+
     interface = GlobalInterface(
         title=u"The menu's interface.",
         required=False
@@ -506,8 +512,8 @@
         required=False
         )
 
-    submenu = MenuField(
-        title=u"Sub-Menu name",
+    submenu = TextLine(
+        title=u"Sub-Menu Id",
         description=u"The menu that will be used to provide the sub-entries.",
         required=True,
         )

Modified: Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -31,16 +31,16 @@
    <InterfaceClass zope.component.interfaces.IFactory>)),
  (('adapter',
    (<InterfaceClass zope.app.container.interfaces.IAdding>,
-    <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+    <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
    <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
    'Add an X'),
   <function handler>,
   ('provideAdapter',
    (<InterfaceClass zope.app.container.interfaces.IAdding>,
-    <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+    <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
    <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
    'Add an X',
-   <zope.app.publisher.browser.menu.MenuItemFactory object>,
+   <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
    '')),
  (None,
   <function provideInterface>,
@@ -53,7 +53,7 @@
  (None,
   <function provideInterface>,
   ('',
-   <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
+   <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)))
 
 $Id$
 """
@@ -94,16 +94,16 @@
     >>> context
     ((('adapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        '')),
      (None,
       <function provideInterface>,
@@ -116,7 +116,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)))
     """
 
 def test_w_factory_and_view():
@@ -128,16 +128,16 @@
     >>> context
     ((('adapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        '')),
      (None,
       <function provideInterface>,
@@ -150,7 +150,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)))
     """
 
 def test_w_factory_class_view():
@@ -175,16 +175,16 @@
        <InterfaceClass zope.component.interfaces.IFactory>)),
      (('adapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X'),
       <function handler>,
       ('provideAdapter',
        (<InterfaceClass zope.app.container.interfaces.IAdding>,
-        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+        <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>),
        <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
        'Add an X',
-       <zope.app.publisher.browser.menu.MenuItemFactory object>,
+       <zope.app.publisher.browser.menumeta.MenuItemFactory object>,
        '')),
      (None,
       <function provideInterface>,
@@ -197,7 +197,7 @@
      (None,
       <function provideInterface>,
       ('',
-       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
+       <InterfaceClass zope.publisher.interfaces.browser.IDefaultBrowserLayer>)))
     """
 
 

Modified: Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -178,8 +178,7 @@
                 />
             ''' % testtemplate
             )))
-        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
-        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+        menuItem = getFirstMenuItem('test_menu', ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryMultiAdapter((ob, request), name='test')
@@ -206,8 +205,7 @@
             ''' % testtemplate
             )))
 
-        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
-        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+        menuItem = getFirstMenuItem('test_menu', ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryMultiAdapter((ob, request), name='test')
@@ -236,8 +234,7 @@
             ''' % testtemplate
             )))
 
-        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
-        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+        menuItem = getFirstMenuItem('test_menu', ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryMultiAdapter((ob, request), name='test')
@@ -268,8 +265,7 @@
             ''' % testtemplate
             )))
 
-        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
-        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
+        menuItem = getFirstMenuItem('test_menu', ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryMultiAdapter((ob, request), name='test')

Modified: Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -21,6 +21,7 @@
 from zope.interface import Interface, implements
 from zope.publisher.browser import TestRequest
 from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
 from zope.security.interfaces import Unauthorized, Forbidden
 
 from zope.app.testing.placelesssetup import PlacelessSetup
@@ -61,6 +62,9 @@
 class IMyLayer(Interface):
     pass
 
+class IMySkin(IMyLayer, IDefaultBrowserLayer):
+    pass
+
 class Test(PlacelessSetup, unittest.TestCase):
 
     def setUp(self):
@@ -70,10 +74,8 @@
     def testMenusAndMenuItems(self):
         XMLConfig('tests/menus.zcml', zope.app.publisher.browser)()
 
-        from zope.app.menus import test_id
-        
         menu = zope.app.publisher.browser.menu.getMenu(
-            test_id, TestObject(), TestRequest())
+            'test_id', TestObject(), TestRequest())
 
         def d(n):
             return {'action': "a%s" % n,
@@ -102,20 +104,23 @@
              'icon': None})
 
         first = zope.app.publisher.browser.menu.getFirstMenuItem(
-            test_id, TestObject(), TestRequest())
+            'test_id', TestObject(), TestRequest())
 
         self.assertEqual(first, d(5))
 
     def testMenuItemWithLayer(self):
         XMLConfig('tests/menus.zcml', zope.app.publisher.browser)()
-        from zope.app.menus import test_id
         
         menu = zope.app.publisher.browser.menu.getMenu(
-            test_id, TestObject(), TestRequest())
+            'test_id', TestObject(), TestRequest())
         self.assertEqual(len(menu), 6)
 
         menu = zope.app.publisher.browser.menu.getMenu(
-            test_id, TestObject(), TestRequest(skin=IMyLayer))
+            'test_id', TestObject(), TestRequest(skin=IMyLayer))
+        self.assertEqual(len(menu), 2)
+
+        menu = zope.app.publisher.browser.menu.getMenu(
+            'test_id', TestObject(), TestRequest(skin=IMySkin))
         self.assertEqual(len(menu), 8)
 
 

Modified: Zope3/trunk/src/zope/app/publisher/browser/viewmeta.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/viewmeta.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/browser/viewmeta.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -33,7 +33,7 @@
 from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
 from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
 from zope.app.publisher.browser import BrowserView
-from zope.app.publisher.browser.menu import menuItemDirective
+from zope.app.publisher.browser.menumeta import menuItemDirective
 
 
 

Modified: Zope3/trunk/src/zope/app/publisher/interfaces/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/interfaces/browser.py	2005-03-12 18:31:02 UTC (rev 29456)
+++ Zope3/trunk/src/zope/app/publisher/interfaces/browser.py	2005-03-14 01:07:34 UTC (rev 29457)
@@ -39,6 +39,41 @@
 directlyProvides(AddMenu, IMenuItemType)
 
 
+class IBrowserMenu(Interface):
+    """Menu
+
+    Menus are objects that can return a list of menu items they contain. How
+    they generate this list is up to them. Commonly, however, they will look
+    up adapters that provide the ``IBrowserMenuItem`` interface.
+    """
+
+    id = TextLine(
+        title=_("Menu Id"),
+        description=_("The id uniquly identifies this menu."),
+        required=True
+        )
+
+    title = TextLine(
+        title=_("Menu title"),
+        description=_("The title provides the basic label for the menu."),
+        required=False
+        )
+
+    description = Text(
+        title=_("Menu description"),
+        description=_("A description of the menu. This might be shown "
+                      "on menu pages or in pop-up help for menus."),
+        required=False
+        )
+
+    def getMenuItems(object, request):
+        """Return a TAL-friendly list of menu items.
+
+        The object (acts like the context) and request can be used to select
+        the items that are available.
+        """
+
+
 class IBrowserMenuItem(Interface):
     """Menu type
 
@@ -104,9 +139,9 @@
 class IBrowserSubMenuItem(IBrowserMenuItem):
     """A menu item that points to a sub-menu."""
 
-    submenuType = InterfaceField(
-        title=_("Sub-Menu Type"),
-        description=_("The menu interface of the menu that describes the "
+    submenuId = TextLine(
+        title=_("Sub-Menu Id"),
+        description=_("The menu id of the menu that describes the "
                       "sub-menu below this item."),
         required=True)
         



More information about the Zope3-Checkins mailing list