[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/publisher/ The Browser Menu Service is gone.

Stephan Richter srichter at cosmos.phy.tufts.edu
Thu Sep 23 10:50:47 EDT 2004


Log message for revision 27659:
  The Browser Menu Service is gone.
  
  The new implementation of menus, which uses subscribers, can now be found 
  in zope.app.publisher.browser.menu. The internals of the new code are 
  pretty much the same, except that the code has been reorganized.
  


Changed:
  U   Zope3/trunk/src/zope/app/publisher/browser/configure.zcml
  A   Zope3/trunk/src/zope/app/publisher/browser/fields.py
  D   Zope3/trunk/src/zope/app/publisher/browser/globalbrowsermenuservice.py
  U   Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py
  A   Zope3/trunk/src/zope/app/publisher/browser/menu.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
  A   Zope3/trunk/src/zope/app/publisher/browser/tests/menus.zcml
  U   Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py
  U   Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py
  A   Zope3/trunk/src/zope/app/publisher/browser/tests/test_fields.py
  D   Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservice.py
  D   Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py
  A   Zope3/trunk/src/zope/app/publisher/browser/tests/test_menu.py
  D   Zope3/trunk/src/zope/app/publisher/browser/tests/test_menuaccessview.py
  A   Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py
  U   Zope3/trunk/src/zope/app/publisher/interfaces/browser.py


-=-
Modified: Zope3/trunk/src/zope/app/publisher/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/configure.zcml	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/configure.zcml	2004-09-23 14:50:47 UTC (rev 27659)
@@ -7,17 +7,6 @@
 <!-- this layer. Date: 09/15/2004 -->
 <browser:layer name="default" />
 
-<serviceType
-   id="BrowserMenu"
-   interface="zope.app.publisher.interfaces.browser.IBrowserMenuService"
-   />
-
-<service
-   serviceType="BrowserMenu"
-   permission="zope.Public"
-   component=".globalbrowsermenuservice.globalBrowserMenuService"
-   />
-
 <content class="zope.publisher.browser.BrowserRequest">
   <allow
     interface="zope.publisher.interfaces.browser.IBrowserApplicationRequest"
@@ -94,7 +83,7 @@
     for="*"
     name="view_get_menu"
     permission="zope.Public"
-    class=".globalbrowsermenuservice.MenuAccessView"
+    class=".menu.MenuAccessView"
     allowed_interface="zope.app.publisher.interfaces.browser.IMenuAccessView"
     />
 

Added: Zope3/trunk/src/zope/app/publisher/browser/fields.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/fields.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/fields.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,110 @@
+#############################################################################
+#
+# Copyright (c) 2001, 2002 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.
+#
+##############################################################################
+"""Browser-Presentation related Fields.
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.schema
+from zope.component.exceptions import ComponentLookupError
+from zope.configuration.exceptions import ConfigurationError
+from zope.configuration.fields import GlobalObject
+from zope.interface.interfaces import IInterface
+from zope.app.publisher.interfaces.browser import IMenuItemType
+
+from zope.app import zapi
+
+
+class MenuField(GlobalObject):
+    r"""This fields represents a menu (item type).
+
+    Besides being able to look up the menu by importing it, we also try
+    to look up the name in the utility service.
+
+    >>> from zope.interface import directlyProvides
+    >>> from zope.interface.interface import InterfaceClass
+
+    >>> menu1 = InterfaceClass('menu1', (),
+    ...                        __doc__='Menu Item Type: menu1',
+    ...                        __module__='zope.app.menus')
+    >>> directlyProvides(menu1, IMenuItemType)
+
+    >>> menus = None
+    >>> class Resolver(object):
+    ...     def resolve(self, path):
+    ...         if path.startswith('zope.app.menus') and \
+    ...             hasattr(menus, 'menu1') or \
+    ...             path == 'zope.app.component.menus.menu1':
+    ...             return menu1
+    ...         raise ConfigurationError, 'menu1'
+
+    >>> field = MenuField()
+    >>> field = field.bind(Resolver())
+
+    Test 1: Import the menu
+    -----------------------
+
+    >>> field.fromUnicode('zope.app.component.menus.menu1') is menu1
+    True
+
+    Test 2: We have a shortcut name. Import the menu from `zope.app.menus1`.
+    ------------------------------------------------------------------------
+
+    >>> from types import ModuleType as module
+    >>> import sys
+    >>> menus = module('menus')
+    >>> old = sys.modules.get('zope.app.menus', None)
+    >>> sys.modules['zope.app.menus'] = menus
+    >>> setattr(menus, 'menu1', menu1)
+
+    >>> field.fromUnicode('menu1') is menu1
+    True
+
+    >>> if old is not None:
+    ...     sys.modules['zope.app.menus'] = old
+
+    Test 3: Get the menu from the utility service
+    ---------------------------------------------
+    
+    >>> from zope.app.tests import ztapi
+    >>> ztapi.provideUtility(IMenuItemType, menu1, 'menu1')
+
+    >>> field.fromUnicode('menu1') is menu1
+    True
+    """
+
+    def fromUnicode(self, u):
+        name = str(u.strip())
+
+        try:
+            value = zapi.queryUtility(IMenuItemType, name)
+        except ComponentLookupError:
+            # The component architecture is not up and running.
+            pass
+        else: 
+            if value is not None:
+                self.validate(value)
+                return value
+
+        try:
+            value = self.context.resolve('zope.app.menus.'+name)
+        except ConfigurationError, v:
+            try:
+                value = self.context.resolve(name)
+            except ConfigurationError, v:
+                raise zope.schema.ValidationError(v)
+        
+        self.validate(value)
+        return value

Deleted: Zope3/trunk/src/zope/app/publisher/browser/globalbrowsermenuservice.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/globalbrowsermenuservice.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/globalbrowsermenuservice.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -1,322 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 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.
-#
-##############################################################################
-"""Global Browser Menu Service
-
-$Id$
-"""
-import sys
-from zope.interface import implements, implementedBy
-from zope.exceptions import DuplicationError
-from zope.security.interfaces import Unauthorized, Forbidden
-from zope.security.proxy import ProxyFactory
-from zope.security.checker import CheckerPublic
-from zope.security import checkPermission
-
-from zope.app import zapi
-from zope.app.component.metaconfigure import handler
-from zope.app.pagetemplate.engine import Engine
-from zope.app.publication.browser import PublicationTraverser
-from zope.app.component.interface import provideInterface
-from zope.app.servicenames import BrowserMenu
-from zope.app.publisher.interfaces.browser import IBrowserMenuService
-from zope.app.publisher.interfaces.browser import IGlobalBrowserMenuService
-from zope.app.publisher.interfaces.browser import IBrowserMenu
-from zope.app.publisher.interfaces.browser import IMenuAccessView
-from zope.app.publisher.browser import BrowserView
-
-
-# TODO: This was copied and trimmed down from zope.interface.
-# Eventually, this will be eliminated when the browser menu
-# service is changed to use adapters.
-from zope.interface.interfaces import IInterface
-from zope.interface import providedBy
-import types
-class TypeRegistry(object):
-
-    def __init__(self):
-        self._reg = {}
-
-    def register(self, interface, object):        
-        self._reg[interface] = object
-
-    def get(self, interface, default=None):
-        return self._reg.get(interface, default)
-
-    def getAll(self, interface_spec):
-        result = []
-        for interface in interface_spec.__sro__:
-            object = self._reg.get(interface)
-            if object is not None:
-                result.append(object)
-
-        if interface_spec is not None:
-            object = self._reg.get(None)
-            if object is not None:
-                result.append(object)
-
-        return result
-
-    def getAllForObject(self, object):
-        return self.getAll(providedBy(object))
-
-
-class MenuAccessView(BrowserView):
-
-    implements(IMenuAccessView)
-
-    def __getitem__(self, menu_id):
-        browser_menu_service = zapi.getService(BrowserMenu)
-        return browser_menu_service.getMenu(menu_id, self.context, self.request)
-
-
-class Menu(object):
-    """Browser menu"""
-
-    implements(IBrowserMenu)
-
-    def __init__(self, title, description=u''):
-        self.title = title
-        self.description = description
-        self.registry = TypeRegistry()
-
-    def getMenuItems(self, object=None):
-        """See zope.app.publisher.interfaces.browser.IMenuItem"""
-        results = []
-        if object is None:
-            for items in self.registry._reg.values():
-                results += items
-        else:
-            for items in self.registry.getAllForObject(object):
-                results += items
-        return results
-
-
-class MenuItem(object):
-    """Browser menu item"""
-
-    def __init__(self, action, title, description, filter, permission,
-                 extra = None):
-        self.action = action
-        self.title = title
-        self.description = description
-        self.filter = filter
-        self.permission = permission
-        self.extra = extra
-
-    def __iter__(self):
-        # for backward compatability with code that thinks items are tuples
-        yield self.action
-        yield self.title
-        yield self.description
-        yield self.filter
-        yield self.permission
-
-class BaseBrowserMenuService(object):
-    """Global Browser Menu Service"""
-
-    implements(IBrowserMenuService)
-
-    def __init__(self):
-        self._registry = {}
-
-    def getAllMenuItems(self, menu_id, object):
-        return self._registry[menu_id].getMenuItems(object)
-
-    def getMenu(self, menu_id, object, request, max=999999):
-        traverser = PublicationTraverser()
-
-        result = []
-        seen = {}
-
-        # stuff for figuring out the selected view
-        request_url = request.getURL()
-
-        for item in self.getAllMenuItems(menu_id, object):
-
-            # Make sure we don't repeat a specification for a given title
-            title = item.title
-            if title in seen:
-                continue
-            seen[title] = 1
-
-            if item.filter is not None:
-
-                try:
-                    include = item.filter(Engine.getContext(
-                        context = object,
-                        nothing = None,
-                        request = request,
-                        modules = ProxyFactory(sys.modules),
-                        ))
-                except Unauthorized:
-                    include = 0
-
-                if not include:
-                    continue
-
-            permission = item.permission
-            action = item.action
-            
-            if permission:
-                # If we have an explicit permission, check that we
-                # can access it.
-                if not checkPermission(permission, object):
-                    continue
-
-            elif action:
-                # Otherwise, test access by attempting access
-                path = action
-                l = action.find('?')
-                if l >= 0:
-                   path = action[:l]
-                try:
-                    v = traverser.traverseRelativeURL(
-                        request, object, path)
-                    # TODO:
-                    # tickle the security proxy's checker
-                    # we're assuming that view pages are callable
-                    # this is a pretty sound assumption
-                    v.__call__
-                except (Unauthorized, Forbidden):
-                    continue # Skip unauthorized or forbidden
-
-            normalized_action = action
-            if action.startswith('@@'):
-                normalized_action = action[2:]
-
-            if request_url.endswith('/'+normalized_action):
-                selected='selected'
-            elif request_url.endswith('/++view++'+normalized_action):
-                selected='selected'
-            elif request_url.endswith('/@@'+normalized_action):
-                selected='selected'
-            else:
-                selected=''
-
-            result.append({
-                'title': title,
-                'description': item.description,
-                'action': "%s" % action,
-                'selected': selected,
-                'extra': item.extra,
-                })
-
-            if len(result) >= max:
-                return result
-
-        return result
-
-    def getFirstMenuItem(self, menu_id, object, request):
-        r = self.getMenu(menu_id, object, request, max=1)
-        if r:
-            return r[0]
-        return None
-
-
-class GlobalBrowserMenuService(BaseBrowserMenuService):
-    """Global Browser Menu Service that can be manipulated by adding new menus
-    and menu entries."""
-
-    implements(IGlobalBrowserMenuService)
-
-    def __init__(self):
-        self._registry = {}
-
-    _clear = __init__
-
-    def menu(self, menu_id, title, description=u''):
-        if menu_id in self._registry:
-            raise DuplicationError("Menu %s is already defined." % menu_id)
-
-        self._registry[menu_id] = Menu(title, description)
-
-    def menuItem(self, menu_id, interface, action, title,
-                 description='', filter_string=None, permission=None,
-                 extra=None,
-                 ):
-
-        registry = self._registry[menu_id].registry
-
-        if filter_string:
-            filter = Engine.compile(filter_string)
-        else:
-            filter = None
-
-        if permission:
-            if permission == 'zope.Public':
-                permission = CheckerPublic
-
-
-        if interface is not None and not IInterface.providedBy(interface):
-            if isinstance(interface, (type, types.ClassType)):
-                interface = implementedBy(interface)
-            else:
-                raise TypeError(
-                    "The interface argument must be an interface (or None) "
-                    "or a class.")
-
-        data = registry.get(interface) or []
-        data.append(
-            MenuItem(action, title, description, filter, permission, extra)
-            )
-        registry.register(interface, data)
-
-
-def menuDirective(_context, id, title, description=''):
-    _context.action(
-        discriminator = ('browser:menu', id),
-        callable = globalBrowserMenuService.menu,
-        args = (id, title, description),
-        )
-
-def menuItemDirective(_context, menu, for_,
-                      action, title, description='', filter=None,
-                      permission=None, extra=None):
-    return menuItemsDirective(_context, menu, for_).menuItem(
-        _context, action, title, description, filter, permission, extra)
-
-
-class menuItemsDirective(object):
-
-    def __init__(self, _context, menu, for_):
-        self.interface = for_
-        self.menu = menu
-
-    def menuItem(self, _context, action, title, description='',
-                 filter=None, permission=None, extra=None):
-        _context.action(
-            discriminator = ('browser:menuItem',
-                             self.menu, self.interface, title),
-            callable = globalBrowserMenuService.menuItem,
-            args = (self.menu, self.interface,
-                    action, title, description, filter, permission, extra),
-            ),
-
-    def __call__(self, _context):
-        _context.action(
-            discriminator = None,
-            callable = provideInterface,
-            args = (self.interface.__module__+'.'+self.interface.getName(),
-                    self.interface)
-            )
-        
-
-globalBrowserMenuService = GlobalBrowserMenuService()
-
-_clear = globalBrowserMenuService._clear
-
-# Register our cleanup with Testing.CleanUp to make writing unit tests simpler.
-from zope.testing.cleanup import addCleanUp
-addCleanUp(_clear)
-del addCleanUp

Modified: Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/managementviewselector.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -16,10 +16,12 @@
 $Id$
 """
 from zope.interface import implements
-from zope.app.publisher.browser import BrowserView
 from zope.publisher.interfaces.browser import IBrowserPublisher
+
 from zope.app import zapi
-from zope.app.servicenames import BrowserMenu
+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."""
@@ -29,9 +31,8 @@
         return self, ()
 
     def __call__(self):
-        browser_menu_service = zapi.getService(BrowserMenu)
-        item = browser_menu_service.getFirstMenuItem(
-            'zmi_views', self.context, self.request)
+        zmi_views = zapi.getUtility(IMenuItemType, 'zmi_views')
+        item = getFirstMenuItem(zmi_views, self.context, self.request)
 
         if item:
             self.request.response.redirect(item['action'])

Added: Zope3/trunk/src/zope/app/publisher/browser/menu.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/menu.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/menu.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,563 @@
+##############################################################################
+#
+# 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 Registration code.
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.component.interfaces import IFactory
+from zope.configuration.exceptions import ConfigurationError
+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.security import checkPermission
+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 subscriber, 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 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 = {}
+
+class BrowserMenuItem(BrowserView):
+    """Browser Menu Item Base Class
+
+    >>> from zope.publisher.browser import TestRequest
+
+    >>> class ITestInterface(Interface):
+    ...     pass
+
+    >>> from zope.publisher.interfaces.browser import IBrowserPublisher
+    >>> class TestObject(object):
+    ...     implements(IBrowserPublisher, ITestInterface)
+    ... 
+    ...     def foo(self):
+    ...         pass
+    ... 
+    ...     def browserDefault(self, r):
+    ...         return self, ()
+    ... 
+    ...     def publishTraverse(self, request, name):
+    ...         if name.startswith('f'):
+    ...             raise Forbidden, name
+    ...         if name.startswith('u'):
+    ...             raise Unauthorized, name
+    ...         return self.foo
+
+
+    Since the `BrowserMenuItem` is just a view, we can initiate it with an
+    object and a request.
+
+    >>> item = BrowserMenuItem(TestObject(), TestRequest())
+
+    Now we add a title and description and see whether we can then access the
+    value. Note that these assignments are always automatically done by the
+    framework.
+
+    >>> item.title = u'Item 1'
+    >>> item.title
+    u'Item 1'
+
+    >>> item.description = u'This is Item 1.'
+    >>> item.description
+    u'This is Item 1.'
+
+    >>> item.order
+    0
+    >>> item.order = 1
+    >>> item.order
+    1
+
+    >>> item.icon is None
+    True
+    >>> item.icon = u'/@@/icon.png'
+    >>> item.icon
+    u'/@@/icon.png'
+
+    Since there is no permission or view specified yet, the menu item should
+    be available and not selected.
+
+    >>> item.available()
+    True
+    >>> item.selected()
+    False
+
+    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 should also not be shown. 
+
+    >>> from zope.app.tests import ztapi
+    >>> from zope.app.security.interfaces import IPermission
+    >>> from zope.app.security.permission import Permission
+    >>> perm = Permission('perm', 'Permission')
+    >>> ztapi.provideUtility(IPermission, perm, 'perm')
+
+    >>> class ParticipationStub(object):
+    ...     principal = 'principal'
+    ...     interaction = None
+
+    >>> from zope.security.management import newInteraction, endInteraction
+
+    In the first case, the permission of the menu item was explicitely
+    specified. Make sure that the user needs this permission to make the menu
+    item available.
+
+    >>> item.permission = perm
+
+    Now, we are not setting any user. This means that the menu item should be
+    available.
+    
+    >>> endInteraction()
+    >>> newInteraction()
+    >>> item.available()
+    True
+
+    Now we specify a principal that does not have the specified permission.
+
+    >>> endInteraction()
+    >>> newInteraction(ParticipationStub())
+    >>> item.available()
+    False
+
+    In the second case, the permission is not explicitely defined and the
+    availability is determined by the permission required to access the
+    action.
+
+    >>> item.permission = None
+
+    All views starting with 'f' are forbidden, the ones with 'u' are
+    unauthorized and all others are allowed.
+
+    >>> item.action = u'f'
+    >>> item.available()
+    False
+    >>> item.action = u'u'
+    >>> item.available()
+    False
+    >>> item.action = u'a'
+    >>> item.available()
+    True
+
+    Now let's test filtering. If the filter is specified, it is assumed to be
+    a TALES obejct.
+
+    >>> item.filter = Engine.compile('not:context')
+    >>> item.available()
+    False
+    >>> item.filter = Engine.compile('context')
+    >>> item.available()
+    True
+
+    Finally, make sure that the menu item can be selected.
+
+    >>> item.request = TestRequest(SERVER_URL='http://127.0.0.1/@@view.html',
+    ...                            PATH_INFO='/@@view.html')
+
+    >>> item.selected()
+    False
+    >>> item.action = u'view.html'
+    >>> item.selected()
+    True
+    >>> item.action = u'@@view.html'
+    >>> item.selected()
+    True
+    >>> item.request = TestRequest(
+    ...     SERVER_URL='http://127.0.0.1/++view++view.html',
+    ...     PATH_INFO='/++view++view.html')
+    >>> item.selected()
+    True
+    >>> item.action = u'otherview.html'
+    >>> item.selected()
+    False
+    """
+    implements(IBrowserMenuItem)
+
+    # See zope.app.publisher.interfaces.browser.IBrowserMenuItem
+    title = u''
+    description = u''
+    action = u''
+    extra = None
+    order = 0
+    permission = None
+    filter = None
+    icon = None
+    _for = Interface
+
+    def available(self):
+        """See zope.app.publisher.interfaces.browser.IBrowserMenuItem"""
+        # Make sure we have the permission needed to access the menu's action
+        if self.permission is not None:
+            # If we have an explicit permission, check that we
+            # can access it.
+            if not checkPermission(self.permission, self.context):
+                return False
+
+        elif self.action != u'':
+            # Otherwise, test access by attempting access
+            path = self.action
+            l = self.action.find('?')
+            if l >= 0:
+                path = self.action[:l]
+
+            traverser = PublicationTraverser()
+            try:
+                view = traverser.traverseRelativeURL(
+                    self.request, self.context, path)
+                # TODO:
+                # tickle the security proxy's checker
+                # we're assuming that view pages are callable
+                # this is a pretty sound assumption
+                view.__call__
+            except (Unauthorized, Forbidden):
+                return False
+
+        # Make sure that we really want to see this menu item
+        if self.filter is not None:
+
+            try:
+                include = self.filter(Engine.getContext(
+                    context = self.context,
+                    nothing = None,
+                    request = self.request,
+                    modules = ProxyFactory(sys.modules),
+                    ))
+            except Unauthorized:
+                return False
+            else:
+                if not include:
+                    return False
+
+        return True
+        
+
+    def selected(self):
+        """See zope.app.publisher.interfaces.browser.IBrowserMenuItem"""
+        request_url = self.request.getURL()
+
+        normalized_action = self.action
+        if self.action.startswith('@@'):
+            normalized_action = self.action[2:]
+
+        if request_url.endswith('/'+normalized_action):
+            return True
+        if request_url.endswith('/++view++'+normalized_action):
+            return True
+        if request_url.endswith('/@@'+normalized_action):
+            return True
+
+        return False
+
+
+def getMenu(menuItemType, object, request, max=999999):
+    """Return menu item entries in a TAL-friendly form.
+
+    >>> from zope.publisher.browser import TestRequest
+
+    >>> from zope.app.tests import ztapi
+    >>> def defineMenuItem(menuItemType, for_, title, action=u'', order=0):
+    ...     newclass = type(title, (BrowserMenuItem,),
+    ...                     {'title':title, 'action':action, 'order':order})
+    ...     classImplements(newclass, menuItemType)
+    ...     ztapi.subscribe((for_, IBrowserRequest), menuItemType, newclass)
+
+    >>> class IFoo(Interface): pass
+    >>> class IFooBar(IFoo): pass
+    >>> class IBlah(Interface): pass
+
+    >>> class FooBar(object):
+    ...     implements(IFooBar)
+
+    >>> class Menu1(Interface): pass
+    >>> class Menu2(Interface): pass
+
+    >>> defineMenuItem(Menu1, IFoo,    'i1')
+    >>> defineMenuItem(Menu1, IFooBar, 'i2')
+    >>> defineMenuItem(Menu1, IBlah,   'i3')
+    >>> defineMenuItem(Menu2, IFoo,    'i4')
+    >>> defineMenuItem(Menu2, IFooBar, 'i5')
+    >>> defineMenuItem(Menu2, IBlah,   'i6')
+    >>> defineMenuItem(Menu1, IFoo,    'i7', order=-1)
+
+    >>> items = getMenu(Menu1, FooBar(), TestRequest())
+    >>> [item['title'] for item in items]
+    ['i7', 'i1', 'i2']
+    >>> items = getMenu(Menu2, FooBar(), TestRequest())
+    >>> [item['title'] for item in items]
+    ['i4', 'i5']
+    >>> items = getMenu(Menu2, FooBar(), TestRequest())
+    >>> [item['title'] for item in items]
+    ['i4', 'i5']
+    """
+    result = []
+    seen = {}
+    for item in zapi.subscribers((object, request), menuItemType):
+        # Make sure we don't repeat a specification for a given title
+        if item.title in seen:
+            continue
+        seen[item.title] = 1
+
+        if not item.available():
+            continue
+
+        result.append(item)
+        
+        if len(result) >= max:
+            break
+        
+    # 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}
+              for index, order, title, item in result]
+    return result
+
+
+def getFirstMenuItem(menuItemType, object, request):
+    """Get the first item of a menu."""
+    items = getMenu(menuItemType, object, request)
+    if items:
+        return items[0]
+    return None
+
+class MenuAccessView(BrowserView):
+    """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).
+
+    >>> import pprint
+    >>> class Context(object):
+    ...     info = u'doc'
+    ...     def __init__(self): self.actions = []
+    ...     def action(self, **kw): self.actions.append(kw)
+
+    Possibility 1: The Old Way
+    --------------------------
+    
+    >>> context = Context()
+    >>> 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''
+
+    >>> hasattr(sys.modules['zope.app.menus'], 'menu1')
+    True
+
+    >>> del sys.modules['zope.app.menus'].menu1
+
+    Possibility 2: Just specify an interface
+    ----------------------------------------
+
+    >>> class menu1(Interface):
+    ...     pass
+
+    >>> context = Context()
+    >>> menuDirective(context, interface=menu1)
+    >>> context.actions[0]['args'][1] is menu1
+    True
+
+    Possibility 3: Specify an interface and an id
+    ---------------------------------------------
+
+    >>> context = Context()
+    >>> menuDirective(context, id='menu1', interface=menu1)
+    >>> context.actions[0]['args'][1] is menu1
+    True
+    >>> import pprint
+    >>> pprint.pprint([action['discriminator'] for action in context.actions])
+    [('browser', 'MenuItemType', 'zope.app.publisher.browser.menu.menu1'),
+     ('interface', 'zope.app.publisher.browser.menu.menu1'),
+     ('browser', 'MenuItemType', 'menu1')]
+     
+    Here are some disallowed configurations.
+
+    >>> context = Context()
+    >>> menuDirective(context)
+    Traceback (most recent call last):
+    ...
+    ConfigurationError: You must specify the 'id' or 'interface' attribute.
+    >>> menuDirective(context, title='Menu 1')
+    Traceback (most recent call last):
+    ...
+    ConfigurationError: You must specify the 'id' or 'interface' attribute.
+    """
+    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, extra=None, order=0):
+    """Register a single menu item.
+
+    See the `menuItemsDirective` class for tests.
+    """
+    return menuItemsDirective(_context, menu, for_).menuItem(
+        _context, action, title, description, icon, filter,
+        permission, extra, order)
+
+
+class menuItemsDirective(object):
+    """Register several menu items for a particular menu.
+
+    >>> class Context(object):
+    ...     info = u'doc'
+    ...     def __init__(self): self.actions = []
+    ...     def action(self, **kw): self.actions.append(kw)
+
+    >>> class TestMenuItemType(Interface): pass
+    >>> class ITest(Interface): pass
+
+    >>> context = Context()
+    >>> items = menuItemsDirective(context, TestMenuItemType, ITest)
+    >>> context.actions
+    []
+    >>> items.menuItem(context, u'view.html', 'View')
+    >>> context.actions[0]['args'][:2]
+    ('Adapters', 'subscribe')
+    >>> len(context.actions)
+    4
+    """
+    def __init__(self, _context, menu, for_):
+        self.for_ = for_
+        self.menuItemType = menu
+
+    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
+
+        def MenuItemFactory(context, request):
+            item = BrowserMenuItem(context, request)
+            item.title = title
+            item.description = description
+            item.icon = icon
+            item.action = action
+            item.filter = filter
+            item.permission = permission
+            item.extra = extra
+            item.order = order
+            item._for = self.for_
+
+            if permission is not None:
+                if permission == 'zope.Public':
+                    perm = CheckerPublic
+                else:
+                    perm = permission
+                checker = InterfaceChecker(IBrowserMenuItem, perm)
+                item = proxify(item, checker)
+
+            return item
+
+        subscriber(_context, MenuItemFactory,
+                   (self.for_, IBrowserRequest), self.menuItemType)
+
+    def __call__(self, _context):
+        # Nothing to do.
+        pass

Modified: Zope3/trunk/src/zope/app/publisher/browser/meta.zcml
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/meta.zcml	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/meta.zcml	2004-09-23 14:50:47 UTC (rev 27659)
@@ -104,13 +104,13 @@
     <meta:directive
         name="menu"
         schema=".metadirectives.IMenuDirective"
-        handler=".globalbrowsermenuservice.menuDirective"
+        handler=".menu.menuDirective"
         />
 
     <meta:complexDirective
         name="menuItems"
         schema=".metadirectives.IMenuItemsDirective"
-        handler=".globalbrowsermenuservice.menuItemsDirective"
+        handler=".menu.menuItemsDirective"
         >
 
       <meta:subdirective
@@ -123,7 +123,7 @@
     <meta:directive
         name="menuItem"
         schema=".metadirectives.IMenuItemDirective"
-        handler=".globalbrowsermenuservice.menuItemDirective"
+        handler=".menu.menuItemDirective"
         />
 
     <!-- misc. directives -->

Modified: Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/metaconfigure.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -24,9 +24,9 @@
 from zope.app import zapi
 from zope.app.component.metaconfigure import handler
 from zope.app.container.interfaces import IAdding
-from zope.app.publisher.browser.globalbrowsermenuservice \
-     import menuItemDirective
+from zope.app.publisher.browser.menu import menuItemDirective
 from zope.app.component.contentdirective import ContentDirective
+from zope.app.publisher.interfaces.browser import AddMenu
 
 # referred to through ZCML
 from zope.app.publisher.browser.resourcemeta import resource
@@ -35,8 +35,6 @@
 from zope.app.publisher.browser.viewmeta import view
 from zope.app.component.interface import provideInterface
 
-from zope.app.component.interface import provideInterface
-
 # Create special modules that contain all layers and skins
 from types import ModuleType as module
 import sys
@@ -147,7 +145,7 @@
         interface = InterfaceClass(name, (base, ),
                                    __doc__='Layer: %s' %name,
                                    __module__='zope.app.layers')
-        # Add the layer to the skins module.
+        # Add the layer to the layers module.
         # Note: We have to do this immediately, so that directives using the
         # InterfaceField can find the layer.
         setattr(layers, name, interface)
@@ -182,7 +180,7 @@
         )
 
 def skin(_context, name=None, interface=None, layers=None):
-    """Provides a new layer.
+    """Provides a new skin.
 
     >>> import pprint
     >>> class Context(object):
@@ -351,7 +349,7 @@
         if permission is None:
             raise ValueError(
                 "A permission must be specified when a class is used")
-        factory = "zope.app.browser.add.%s.%s" % (
+        factory = "BrowserAdd__%s.%s" % (
             class_.__module__, class_.__name__) 
         ContentDirective(_context, class_).factory(
             _context,
@@ -364,6 +362,6 @@
     else:
         action = factory
 
-    menuItemDirective(_context, 'zope.app.container.add', IAdding,
-                      action, title, description, filter,
+    menuItemDirective(_context, AddMenu, IAdding,
+                      action, title, description, None, filter,
                       permission, extra)

Modified: Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/metadirectives.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -20,10 +20,11 @@
 from zope.interface import Interface
 from zope.configuration.fields import GlobalObject, Tokens, Path, \
      PythonIdentifier, MessageID
-from zope.schema import TextLine, Text, Id
+from zope.schema import TextLine, Text, Id, Int
 
 from zope.app.component.metadirectives import IBasicViewInformation
 from zope.app.component.fields import LayerField
+from zope.app.publisher.browser.fields import MenuField
 from zope.app.security.fields import Permission
 
 #
@@ -70,7 +71,7 @@
         default=u'',
         )
 
-    menu = TextLine(
+    menu = MenuField(
         title=u"The browser menu to include the page (view) in.",
         description=u"""
           Many views are included in menus. It's convenient to name
@@ -182,7 +183,7 @@
     Subdirective to IPagesDirective
     """
 
-    menu = TextLine(
+    menu = MenuField(
         title=u"The browser menu to include the page (view) in.",
         description=u"""
         Many views are included in menus. It's convenient to name the
@@ -349,22 +350,32 @@
 #
 
 class IMenuDirective(Interface):
-    """
-    Define a browser menu
-    """
+    """Define a browser menu"""
 
     id = TextLine(
         title=u"The name of the menu.",
         description=u"This is, effectively, an id.",
-        required=True
+        required=False
         )
 
     title = MessageID(
         title=u"Title",
         description=u"A descriptive title for documentation purposes",
-        required=True
+        required=False
         )
+    
+    description = MessageID(
+        title=u"Description",
+        description=u"A description title of the menu.",
+        required=False
+        )
 
+    interface = GlobalObject(
+        title=u"The menu's interface.",
+        required=False
+        )
+    
+
 class IMenuItemsDirective(Interface):
     """
     Define a group of browser menu items
@@ -373,7 +384,7 @@
     same interface and menu.
     """
 
-    menu = TextLine(
+    menu = MenuField(
         title=u"Menu name",
         description=u"The (name of the) menu the items are defined for",
         required=True,
@@ -431,6 +442,14 @@
         required=False
         )
 
+    order = Int(
+        title=u"Order",
+        description=u"A relative position of the menu item in the menu.",
+        required=False,
+        default=0
+        )
+
+
 class IMenuItemSubdirective(IMenuItem):
     """
     Define a menu item within a group of menu items

Added: Zope3/trunk/src/zope/app/publisher/browser/tests/menus.zcml
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/menus.zcml	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/menus.zcml	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,44 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:browser="http://namespaces.zope.org/browser"
+    i18n_domain="zope">
+
+  <browser:menu 
+      id="test_id" 
+      title="test menu" />
+
+  <browser:menuItems 
+      menu="test_id" 
+      for="zope.interface.Interface">
+    <browser:menuItem action="a1" title="t1" />
+  </browser:menuItems>
+
+  <browser:menuItems 
+      menu="test_id"
+      for=".tests.test_menudirectives.I1">
+    <browser:menuItem action="a2" title="t2" />
+  </browser:menuItems>
+
+  <browser:menuItems 
+      menu="test_id"
+      for=".tests.test_menudirectives.I11">
+    <browser:menuItem action="a3" title="t3" filter="context" />
+    <browser:menuItem action="a4" title="t4" filter="not:context" />
+  </browser:menuItems>
+
+  <browser:menuItems 
+      menu="test_id"
+      for=".tests.test_menudirectives.I111">
+    <browser:menuItem action="a5" title="t5" />
+    <browser:menuItem action="a6" title="t6" />
+    <browser:menuItem action="f7" title="t7" />
+    <browser:menuItem action="u8" title="t8" />
+  </browser:menuItems>
+
+  <browser:menuItems 
+      menu="test_id"
+      for=".tests.test_menudirectives.I12">
+    <browser:menuItem action="a9" title="t9" />
+  </browser:menuItems>
+
+</configure>

Modified: Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_addMenuItem.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -19,34 +19,37 @@
 >>> context
 ((('utility',
    <InterfaceClass zope.component.interfaces.IFactory>,
-   'zope.app.browser.add.zope.app.publisher.browser.tests.test_addMenuItem.X'),
+   'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X'),
   <function handler>,
   ('Utilities',
    'provideUtility',
    <InterfaceClass zope.component.interfaces.IFactory>,
    <zope.component.factory.Factory object>,
-   'zope.app.browser.add.""" \
-         """zope.app.publisher.browser.tests.test_addMenuItem.X')),
+   'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X')),
  (None,
   <function provideInterface>,
   ('zope.component.interfaces.IFactory',
    <InterfaceClass zope.component.interfaces.IFactory>)),
- (('browser:menuItem',
-   'zope.app.container.add',
-   <InterfaceClass zope.app.container.interfaces.IAdding>,
-   'Add an X'),
-  <bound method GlobalBrowserMenuService.menuItem of """ \
-     """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
-     """GlobalBrowserMenuService object>>,
-  ('zope.app.container.add',
-   <InterfaceClass zope.app.container.interfaces.IAdding>,
-   'zope.app.browser.add.zope.app.publisher.browser.tests.test_addMenuItem.X',
-   'Add an X',
-   '',
-   None,
-   'zope.ManageContent',
-   {'factory': 'zope.app.browser.add.""" \
-         """zope.app.publisher.browser.tests.test_addMenuItem.X'})))
+ (None,
+  <function handler>,
+  ('Adapters',
+   'subscribe',
+   (<InterfaceClass zope.app.container.interfaces.IAdding>,
+    <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+   <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
+   <function MenuItemFactory>)),
+ (None,
+  <function provideInterface>,
+  ('',
+   <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>)),
+ (None,
+  <function provideInterface>,
+  ('',
+   <InterfaceClass zope.app.container.interfaces.IAdding>)),
+ (None,
+  <function provideInterface>,
+  ('',
+   <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
 
 $Id$
 """
@@ -84,21 +87,26 @@
     ...             permission="zope.ManageContent", description="blah blah",
     ...             filter="context/foo")
     >>> context
-    ((('browser:menuItem',
-       'zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'Add an X'),
-      <bound method GlobalBrowserMenuService.menuItem of """ \
-            """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
-            """GlobalBrowserMenuService object>>,
-      ('zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'x.y.z',
-       'Add an X',
-       'blah blah',
-       'context/foo',
-       'zope.ManageContent',
-       {'factory': 'x.y.z'})),)
+    ((None,
+      <function handler>,
+      ('Adapters',
+       'subscribe',
+       (<InterfaceClass zope.app.container.interfaces.IAdding>,
+        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
+       <function MenuItemFactory>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.container.interfaces.IAdding>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
     """
 
 def test_w_factory_and_view():
@@ -108,21 +116,26 @@
     ...             permission="zope.ManageContent", description="blah blah",
     ...             filter="context/foo", view="AddX")
     >>> context
-    ((('browser:menuItem',
-       'zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'Add an X'),
-      <bound method GlobalBrowserMenuService.menuItem of """ \
-            """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
-            """GlobalBrowserMenuService object>>,
-      ('zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'AddX',
-       'Add an X',
-       'blah blah',
-       'context/foo',
-       'zope.ManageContent',
-       {'factory': 'x.y.z'})),)
+    ((None,
+      <function handler>,
+      ('Adapters',
+       'subscribe',
+       (<InterfaceClass zope.app.container.interfaces.IAdding>,
+        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
+       <function MenuItemFactory>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.container.interfaces.IAdding>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
     """
 
 def test_w_factory_class_view():
@@ -131,38 +144,41 @@
     >>> addMenuItem(context, class_=X, title="Add an X",
     ...             permission="zope.ManageContent", description="blah blah",
     ...             filter="context/foo", view="AddX")
+    >>> import pprint
     >>> context
     ((('utility',
        <InterfaceClass zope.component.interfaces.IFactory>,
-       'zope.app.browser.add.""" \
-         """zope.app.publisher.browser.tests.test_addMenuItem.X'),
+       'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X'),
       <function handler>,
       ('Utilities',
        'provideUtility',
        <InterfaceClass zope.component.interfaces.IFactory>,
        <zope.component.factory.Factory object>,
-       'zope.app.browser.add.""" \
-         """zope.app.publisher.browser.tests.test_addMenuItem.X')),
+       'BrowserAdd__zope.app.publisher.browser.tests.test_addMenuItem.X')),
      (None,
       <function provideInterface>,
       ('zope.component.interfaces.IFactory',
        <InterfaceClass zope.component.interfaces.IFactory>)),
-     (('browser:menuItem',
-       'zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'Add an X'),
-      <bound method GlobalBrowserMenuService.menuItem of """ \
-         """<zope.app.publisher.browser.globalbrowsermenuservice.""" \
-         """GlobalBrowserMenuService object>>,
-      ('zope.app.container.add',
-       <InterfaceClass zope.app.container.interfaces.IAdding>,
-       'AddX',
-       'Add an X',
-       'blah blah',
-       'context/foo',
-       'zope.ManageContent',
-       {'factory': 'zope.app.browser.add.""" \
-         """zope.app.publisher.browser.tests.test_addMenuItem.X'})))
+     (None,
+      <function handler>,
+      ('Adapters',
+       'subscribe',
+       (<InterfaceClass zope.app.container.interfaces.IAdding>,
+        <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>),
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>,
+       <function MenuItemFactory>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.publisher.interfaces.browser.AddMenu>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.app.container.interfaces.IAdding>)),
+     (None,
+      <function provideInterface>,
+      ('',
+       <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>)))
 """
 
 

Modified: Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_directives.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -21,36 +21,32 @@
 
 from zope.interface import Interface, implements, directlyProvides, providedBy
 
+import zope.security.management
+from zope.component.service import serviceManager
 from zope.configuration.xmlconfig import xmlconfig, XMLConfig
 from zope.configuration.exceptions import ConfigurationError
-from zope.app.component.tests.views import IC, V1, VZMI, R1, IV
-from zope.app.tests import placelesssetup
-from zope.app.tests.placelesssetup import PlacelessSetup
-from zope.security.proxy import ProxyFactory
-import zope.security.management
-from zope.security.proxy import removeSecurityProxy
-from zope.testing.doctestunit import DocTestSuite
-
-from zope.app.publisher.browser.globalbrowsermenuservice import \
-    globalBrowserMenuService
 from zope.publisher.browser import TestRequest
-
-from zope.app.publisher.browser.fileresource import FileResource
-from zope.app.publisher.browser.i18nfileresource import I18nFileResource
-
-import zope.app.publisher.browser
-from zope.component.service import serviceManager
-
 from zope.publisher.interfaces.browser import IBrowserPublisher
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.publisher.interfaces.browser import ISkin, IDefaultSkin
+from zope.security.proxy import removeSecurityProxy, ProxyFactory
+from zope.testing.doctestunit import DocTestSuite
+
+import zope.app.publisher.browser
 from zope.app import zapi
+from zope.app.component.tests.views import IC, V1, VZMI, R1, IV
+from zope.app.publisher.browser.fileresource import FileResource
+from zope.app.publisher.browser.i18nfileresource import I18nFileResource
+from zope.app.publisher.browser.menu import getFirstMenuItem
+from zope.app.publisher.interfaces.browser import IMenuItemType
+from zope.app.security.permission import Permission 
+from zope.app.security.interfaces import IPermission 
+from zope.app.tests import placelesssetup
 from zope.app.tests import ztapi
+from zope.app.tests.placelesssetup import PlacelessSetup
 from zope.app.traversing.adapters import DefaultTraversable
 from zope.app.traversing.interfaces import ITraversable
 
-from zope.app.security.permission import Permission 
-from zope.app.security.interfaces import IPermission 
 
 tests_path = os.path.join(
     os.path.dirname(zope.app.publisher.browser.__file__),
@@ -144,9 +140,8 @@
                 />
             ''' % testtemplate
             )))
-
-        menuItem = globalBrowserMenuService.getFirstMenuItem(
-            'test_menu', ob, TestRequest())
+        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryView(ob, 'test', request)
@@ -173,8 +168,8 @@
             ''' % testtemplate
             )))
 
-        menuItem = globalBrowserMenuService.getFirstMenuItem(
-            'test_menu', ob, TestRequest())
+        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryView(ob, 'test', request)
@@ -203,8 +198,8 @@
             ''' % testtemplate
             )))
 
-        menuItem = globalBrowserMenuService.getFirstMenuItem(
-            'test_menu', ob, TestRequest())
+        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryView(ob, 'test', request)
@@ -235,8 +230,8 @@
             ''' % testtemplate
             )))
 
-        menuItem = globalBrowserMenuService.getFirstMenuItem(
-            'test_menu', ob, TestRequest())
+        test_menu = zapi.getUtility(IMenuItemType, 'test_menu')
+        menuItem = getFirstMenuItem(test_menu, ob, TestRequest())
         self.assertEqual(menuItem["title"], "Test View")
         self.assertEqual(menuItem["action"], "@@test")
         v = zapi.queryView(ob, 'test', request)

Added: Zope3/trunk/src/zope/app/publisher/browser/tests/test_fields.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_fields.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_fields.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Test fields.
+
+$Id$
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+from zope.app.tests import placelesssetup
+
+def test_suite():
+    return unittest.TestSuite((
+        DocTestSuite('zope.app.publisher.browser.fields',
+                     setUp=placelesssetup.setUp,
+                     tearDown=placelesssetup.tearDown),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Deleted: Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservice.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservice.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservice.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -1,285 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 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.
-#
-##############################################################################
-"""Global Browser Menu Tests
-
-$Id$
-"""
-import unittest
-from zope.exceptions import DuplicationError
-from zope.security.interfaces import Forbidden, Unauthorized
-from zope.interface import Interface, implements
-from zope.publisher.browser import TestRequest
-from zope.publisher.interfaces.browser import IBrowserPublisher
-from zope.security.management import newInteraction, endInteraction
-
-from zope.app import zapi
-from zope.app.tests import ztapi
-from zope.app.security.interfaces import IPermission
-from zope.app.security.permission import Permission
-from zope.app.publisher.browser.globalbrowsermenuservice import \
-     GlobalBrowserMenuService
-from zope.app.tests.placelesssetup import PlacelessSetup
-
-class I1(Interface): pass
-class I11(I1): pass
-class I12(I1): pass
-class I111(I11): pass
-
-class C1(object):
-    implements(I1)
-            
-class TestObject(object):
-    implements(IBrowserPublisher, I111)
-
-    def f(self):
-        pass
-
-    def browserDefault(self, r):
-        return self, ()
-
-    def publishTraverse(self, request, name):
-        if name[:1] == 'f':
-            raise Forbidden, name
-        if name[:1] == 'u':
-            raise Unauthorized, name
-        return self.f
-
-
-def d(n):
-    return {'action': "a%s" % n,
-            'title':  "t%s" % n,
-            'description':  "d%s" % n,
-            'selected': '',
-            'extra': None
-            }
-
-
-class ParticipationStub(object):
-
-    def __init__(self, principal):
-        self.principal = principal
-        self.interaction = None
-
-
-class GlobalBrowserMenuServiceTest(PlacelessSetup, unittest.TestCase):
-
-    def __reg(self):
-        return GlobalBrowserMenuService()
-
-    def testDup(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        self.assertRaises(DuplicationError, r.menu, 'test_id', 'test menu')
-
-    def test(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', I1, 'a2', 't2', 'd2')
-        r.menuItem('test_id', I11, 'a3', 't3', 'd3', 'context')
-        r.menuItem('test_id', I11, 'a4', 't4', 'd4', 'not:context')
-        r.menuItem('test_id', I111, 'a5', 't5', 'd5')
-        r.menuItem('test_id', I111, 'a6', 't6', 'd6')
-        r.menuItem('test_id', I111, 'f7', 't7', 'd7')
-        r.menuItem('test_id', I111, 'u8', 't8', 'd8')
-        r.menuItem('test_id', I12, 'a9', 't9', 'd9')
-
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-        self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
-
-    def test_w_class(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', I1, 'a2', 't2', 'd2')
-        r.menuItem('test_id', I11, 'a3', 't3', 'd3', 'context')
-        r.menuItem('test_id', I11, 'a4', 't4', 'd4', 'not:context')
-        r.menuItem('test_id', I111, 'a5', 't5', 'd5')
-        r.menuItem('test_id', I111, 'a6', 't6', 'd6')
-        r.menuItem('test_id', I111, 'f7', 't7', 'd7')
-        r.menuItem('test_id', I111, 'u8', 't8', 'd8')
-        r.menuItem('test_id', I12, 'a9', 't9', 'd9')
-        r.menuItem('test_id', TestObject, 'a0', 't0', 'd0')
-
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-        self.assertEqual(list(menu), [d(0), d(5), d(6), d(3), d(2), d(1)])
-
-    def test_w_class_that_does_not_implement(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-
-        class C:
-            pass
-
-        # We provide a permission so C doesn't have to implement
-        # IBrowserPublisher.  We use CheckerPublic so we don't have to set
-        # up any other security machinery.
-
-        from zope.security.checker import CheckerPublic
-        r.menuItem('test_id', C, 'a0', 't0', 'd0', permission=CheckerPublic)
-        r.menuItem('test_id', C, 'a10', 't10', 'd10', permission=CheckerPublic)
-
-        menu = r.getMenu('test_id', C(), TestRequest())
-        self.assertEqual(list(menu), [d(0), d(10)])
-
-    def test_w_permission(self):
-        ztapi.provideUtility(IPermission, Permission('p', 'P'), 'p')
-
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', I1, 'a2', 't2', 'd2')
-        r.menuItem('test_id', I11, 'a3', 't3', 'd3', 'context')
-        r.menuItem('test_id', I11, 'a4', 't4', 'd4', 'not:context')
-        r.menuItem('test_id', I111, 'a5', 't5', 'd5', permission='p')
-        r.menuItem('test_id', I111, 'a6', 't6', 'd6')
-        r.menuItem('test_id', I111, 'f7', 't7', 'd7')
-        r.menuItem('test_id', I111, 'u8', 't8', 'd8')
-        r.menuItem('test_id', I12, 'a9', 't9', 'd9')
-
-        endInteraction()
-        newInteraction(ParticipationStub('test'))
-
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-
-        self.assertEqual(list(menu), [d(6), d(3), d(2), d(1)])
-
-        endInteraction()
-        newInteraction()
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-        self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
-
-    def test_no_dups(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', Interface, 'a12', 't2', 'd12')
-        r.menuItem('test_id', I1, 'a2', 't2', 'd2')
-        r.menuItem('test_id', I1, 'a23', 't3', 'd23')
-        r.menuItem('test_id', I1, 'a24', 't4', 'd24')
-        r.menuItem('test_id', I11, 'a3', 't3', 'd3', 'context')
-        r.menuItem('test_id', I11, 'a4', 't4', 'd4', 'not:context')
-        r.menuItem('test_id', I111, 'a5', 't5', 'd5')
-        r.menuItem('test_id', I111, 'a6', 't6', 'd6')
-        r.menuItem('test_id', I111, 'f7', 't7', 'd7')
-        r.menuItem('test_id', I111, 'u8', 't8', 'd8')
-        r.menuItem('test_id', I12, 'a9', 't9', 'd9')
-
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-        self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
-
-    def test_identify_action(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', I11, 'a12', 't12', 'd12')
-        r.menuItem('test_id', I111, 'a2', 't2', 'd2')
-
-        def d(n, selected=''):
-            return {'action': "a%s" % n,
-                    'title':  "t%s" % n,
-                    'description':  "d%s" % n,
-                    'selected': selected,
-                    'extra': None}
-
-        menu = r.getMenu('test_id', TestObject(),
-            TestRequest(SERVER_URL='http://127.0.0.1/a1', PATH_INFO='/a1'))
-        self.assertEqual(list(menu), [d(2), d(12), d(1, 'selected')])
-        menu = r.getMenu('test_id', TestObject(), 
-            TestRequest(SERVER_URL='http://127.0.0.1/a12', PATH_INFO='/a12'))
-        self.assertEqual(list(menu), [d(2), d(12, 'selected'), d(1)])
-        menu = r.getMenu('test_id', TestObject(),
-            TestRequest(SERVER_URL='http://127.0.0.1/@@a1', PATH_INFO='/@@a1'))
-        self.assertEqual(list(menu), [d(2), d(12), d(1, 'selected')])
-        menu = r.getMenu('test_id', TestObject(), 
-            TestRequest(SERVER_URL='http://127.0.0.1/@@a12',
-            PATH_INFO='/@@a12'))
-        self.assertEqual(list(menu), [d(2), d(12, 'selected'), d(1)])
-        menu = r.getMenu('test_id', TestObject(),
-            TestRequest(SERVER_URL='http://127.0.0.1/++view++a1',
-            PATH_INFO='/++view++a1'))
-        self.assertEqual(list(menu), [d(2), d(12), d(1, 'selected')])
-        menu = r.getMenu('test_id', TestObject(), 
-            TestRequest(SERVER_URL='http://127.0.0.1/++view++a12',
-            PATH_INFO='/++view++a12'))
-        self.assertEqual(list(menu), [d(2), d(12, 'selected'), d(1)])
-
-
-    def test_identify_similar_action(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', I11, 'aA', 'tA', 'dA')
-        r.menuItem('test_id', I111, 'aAaA', 'tAaA', 'dAaA')
-
-        def d(s, selected=''):
-            return {'action': "a%s" % s,
-                    'title':  "t%s" % s,
-                    'description':  "d%s" % s,
-                    'selected': selected,
-                    'extra': None}
-
-        menu = r.getMenu('test_id', TestObject(),
-            TestRequest(SERVER_URL='http://127.0.0.1/aA', PATH_INFO='/aA'))
-        self.assertEqual(list(menu), [d('AaA'), d('A', 'selected')])
-        menu = r.getMenu('test_id', TestObject(), 
-            TestRequest(SERVER_URL='http://127.0.0.1/aAaA', PATH_INFO='/aAaA'))
-        self.assertEqual(list(menu), [d('AaA', 'selected'), d('A')])
-
-
-    def testEmpty(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        menu = r.getMenu('test_id', TestObject(), TestRequest())
-        self.assertEqual(list(menu), [])
-
-    def test_getAllMenuItema(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', Interface, 'a1', 't1', 'd1')
-        r.menuItem('test_id', I1, 'a2', 't2', 'd2')
-        r.menuItem('test_id', I11, 'a3', 't3', 'd3')
-        r.menuItem('test_id', I111, 'a5', 't5', 'd5')
-        r.menuItem('test_id', I111, 'a6', 't6', 'd6')
-        r.menuItem('test_id', I111, 'a7', 't7', 'd7')
-        r.menuItem('test_id', I111, 'a8', 't8', 'd8')
-        r.menuItem('test_id', I12, 'a9', 't9', 'd9')
-
-        def d(n):
-            return ('a%s' %n, 't%s' %n, 'd%s' %n, None, None, None) 
-
-        menu = [(item.action, item.title, item.description,
-                 item.filter, item.permission, item.extra)
-                for item in r.getAllMenuItems('test_id', TestObject())
-                ]
-        self.assertEqual(menu, [d(5), d(6), d(7), d(8), d(3), d(2), d(1)])
-
-    def test_addWrong(self):
-        r = self.__reg()
-        
-        x = []
-        r.menu('test_id', 'test menu')
-        self.assertRaises(TypeError, r.menuItem, 'test_id', x, 'a1', 'a2',' a3')
-
-    def test_addClass(self):
-        r = self.__reg()
-        r.menu('test_id', 'test menu')
-        r.menuItem('test_id', C1, 'a1', 'a2', 'a3')
-
-def test_suite():
-    return unittest.TestSuite((
-        unittest.makeSuite(GlobalBrowserMenuServiceTest),
-        ))
-
-if __name__ == '__main__':
-    unittest.main()

Deleted: Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -1,112 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 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.
-#
-##############################################################################
-"""Browser Menu Directives Tests
-
-$Id$
-"""
-
-from StringIO import StringIO
-from unittest import TestCase, main, makeSuite
-
-from zope.configuration.xmlconfig import xmlconfig, XMLConfig
-from zope.publisher.browser import TestRequest
-from zope.app.tests.placelesssetup import PlacelessSetup
-from zope.app.publisher.browser.globalbrowsermenuservice \
-    import globalBrowserMenuService
-
-import zope.app.publisher.browser
-
-template = """<configure
-   xmlns='http://namespaces.zope.org/zope'
-   xmlns:browser='http://namespaces.zope.org/browser'
-   i18n_domain='zope'>
-   %s
-   </configure>"""
-
-class Test(PlacelessSetup, TestCase):
-
-    def setUp(self):
-        super(Test, self).setUp()
-        XMLConfig('meta.zcml', zope.app.publisher.browser)()
-
-    def test(self):
-        xmlconfig(StringIO(template % (
-            """
-            <browser:menu id="test_id" title="test menu" />
-
-            <browser:menuItems menu="test_id" for="zope.interface.Interface">
-              <browser:menuItem action="a1" title="t1" />
-            </browser:menuItems>
-
-            <browser:menuItems menu="test_id"
-              for="
-           zope.app.publisher.browser.tests.test_globalbrowsermenuservice.I1
-              ">
-              <browser:menuItem action="a2" title="t2" />
-            </browser:menuItems>
-
-            <browser:menuItems menu="test_id"
-              for="
-           zope.app.publisher.browser.tests.test_globalbrowsermenuservice.I11
-              ">
-              <browser:menuItem action="a3" title="t3" filter="context" />
-              <browser:menuItem action="a4" title="t4" filter="not:context" />
-            </browser:menuItems>
-
-            <browser:menuItems menu="test_id"
-              for="
-           zope.app.publisher.browser.tests.test_globalbrowsermenuservice.I111
-              ">
-              <browser:menuItem action="a5" title="t5" />
-              <browser:menuItem action="a6" title="t6" />
-              <browser:menuItem action="f7" title="t7" />
-              <browser:menuItem action="u8" title="t8" />
-            </browser:menuItems>
-
-            <browser:menuItems menu="test_id"
-              for="
-           zope.app.publisher.browser.tests.test_globalbrowsermenuservice.I12
-              ">
-              <browser:menuItem action="a9" title="t9" />
-            </browser:menuItems>
-            """)))
-
-
-        from zope.app.publisher.browser.tests.test_globalbrowsermenuservice \
-             import TestObject
-
-        menu = globalBrowserMenuService.getMenu('test_id', TestObject(),
-                                                TestRequest())
-
-        def d(n):
-            return {'action': "a%s" % n,
-                    'title':  "t%s" % n,
-                    'description':  "",
-                    'selected': '',
-                    'extra': None,
-                    }
-
-        self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
-
-        first = globalBrowserMenuService.getFirstMenuItem(
-            'test_id', TestObject(), TestRequest())
-
-        self.assertEqual(first, d(5))
-
-
-def test_suite():
-    return makeSuite(Test)
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

Added: Zope3/trunk/src/zope/app/publisher/browser/tests/test_menu.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_menu.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_menu.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# 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.
+#
+##############################################################################
+"""Browser Menu Item Tests
+
+$Id$
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+from zope.app.tests import placelesssetup
+
+
+def test_suite():
+    return DocTestSuite('zope.app.publisher.browser.menu',
+                        setUp=placelesssetup.setUp,
+                        tearDown=placelesssetup.tearDown)
+    
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Deleted: Zope3/trunk/src/zope/app/publisher/browser/tests/test_menuaccessview.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_menuaccessview.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_menuaccessview.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -1,114 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 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.
-#
-##############################################################################
-"""Browser Menu Browser Tests
-
-$Id$
-"""
-import unittest
-
-from zope.interface import Interface, implements
-from zope.component import getGlobalServices
-
-from zope.security.management import newInteraction
-from zope.security.checker import defineChecker, NamesChecker, CheckerPublic
-from zope.security.proxy import ProxyFactory
-
-from zope.publisher.browser import TestRequest
-from zope.publisher.interfaces.browser import IBrowserPublisher
-from zope.app.publisher.interfaces.browser import IBrowserView
-
-from zope.app.tests import ztapi
-from zope.app.servicenames import BrowserMenu
-from zope.app.site.tests.placefulsetup import PlacefulSetup
-
-from zope.app.publisher.interfaces.browser import IBrowserMenuService
-from zope.app.publisher.browser.globalbrowsermenuservice import MenuAccessView
-from zope.app.publication.traversers import TestTraverser
-from zope.app.site.interfaces import ISimpleService
-
-def d(title, action):
-    return {'action': action, 'title': title, 'description': ''}
-
-class Service(object):
-    implements(IBrowserMenuService, ISimpleService)
-
-    def getMenu(self, name, ob, req):
-        return [d('l1', 'a1'),
-                d('l2', 'a2/a3'),
-                d('l3', '@@a3'),]
-
-class I(Interface): pass
-class C(object):
-    implements(I)
-
-    def __call__(self):
-        pass
-
-ob = C()
-ob.a1 = C()
-ob.a2 = C()
-ob.a2.a3 = C()
-ob.abad = C()
-ob.abad.bad = 1
-
-class V(object):
-    implements(IBrowserView)
-
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __call__(self):
-        pass
-
-class ParticipationStub(object):
-
-    def __init__(self, principal):
-        self.principal = principal
-        self.interaction = None
-
-
-class Test(PlacefulSetup, unittest.TestCase):
-
-    def setUp(self):
-        PlacefulSetup.setUp(self)
-        defineService = getGlobalServices().defineService
-        provideService = getGlobalServices().provideService
-
-        defineService(BrowserMenu, IBrowserMenuService)
-        provideService(BrowserMenu, Service())
-        ztapi.browserView(I, 'a3', V)
-        ztapi.browserViewProviding(None, IBrowserPublisher, TestTraverser)
-        defineChecker(C, NamesChecker(['a1', 'a2', 'a3', '__call__'],
-                                      CheckerPublic,
-                                      abad='waaa'))
-
-    def test(self):
-        from zope.security.management import endInteraction
-        endInteraction()
-        newInteraction(ParticipationStub('who'))
-        v = MenuAccessView(ProxyFactory(ob), TestRequest())
-        self.assertEqual(v['zmi_views'],
-                         [{'description': '', 'title':'l1', 'action':'a1'},
-                          {'description': '', 'title':'l2', 'action':'a2/a3'},
-                          {'description': '', 'title':'l3', 'action':'@@a3'}
-                          ])
-
-
-def test_suite():
-    loader = unittest.TestLoader()
-    return loader.loadTestsFromTestCase(Test)
-
-if __name__=='__main__':
-    unittest.TextTestRunner().run(test_suite())

Copied: Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py (from rev 27631, Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py)
===================================================================
--- Zope3/trunk/src/zope/app/publisher/browser/tests/test_globalbrowsermenuservicedirectives.py	2004-09-18 05:23:27 UTC (rev 27631)
+++ Zope3/trunk/src/zope/app/publisher/browser/tests/test_menudirectives.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2002 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.
+#
+##############################################################################
+"""Browser Menu Directives Tests
+
+$Id$
+"""
+import unittest
+
+from zope.configuration.xmlconfig import XMLConfig
+from zope.interface import Interface, implements
+from zope.publisher.browser import TestRequest
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.security.interfaces import Unauthorized, Forbidden
+
+from zope.app.tests.placelesssetup import PlacelessSetup
+
+import zope.app.publisher.browser
+
+template = """<configure
+   xmlns='http://namespaces.zope.org/zope'
+   xmlns:browser='http://namespaces.zope.org/browser'
+   i18n_domain='zope'>
+   %s
+   </configure>"""
+
+class I1(Interface): pass
+class I11(I1): pass
+class I12(I1): pass
+class I111(I11): pass
+
+class C1(object):
+    implements(I1)
+            
+class TestObject(object):
+    implements(IBrowserPublisher, I111)
+
+    def f(self):
+        pass
+
+    def browserDefault(self, r):
+        return self, ()
+
+    def publishTraverse(self, request, name):
+        if name[:1] == 'f':
+            raise Forbidden, name
+        if name[:1] == 'u':
+            raise Unauthorized, name
+        return self.f
+
+
+class Test(PlacelessSetup, unittest.TestCase):
+
+    def setUp(self):
+        super(Test, self).setUp()
+        XMLConfig('meta.zcml', zope.app.publisher.browser)()
+
+    def test(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())
+
+        def d(n):
+            return {'action': "a%s" % n,
+                    'title':  "t%s" % n,
+                    'description':  "",
+                    'selected': '',
+                    'icon': None,
+                    'extra': None}
+
+        self.assertEqual(list(menu), [d(5), d(6), d(3), d(2), d(1)])
+
+        first = zope.app.publisher.browser.menu.getFirstMenuItem(
+            test_id, TestObject(), TestRequest())
+
+        self.assertEqual(first, d(5))
+
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(Test),
+        ))
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')

Modified: Zope3/trunk/src/zope/app/publisher/interfaces/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/interfaces/browser.py	2004-09-23 14:48:35 UTC (rev 27658)
+++ Zope3/trunk/src/zope/app/publisher/interfaces/browser.py	2004-09-23 14:50:47 UTC (rev 27659)
@@ -17,51 +17,63 @@
 """
 from zope.component.interfaces import IView
 from zope.app.i18n import ZopeMessageIDFactory as _
-from zope.interface import Interface
-from zope.schema import TextLine, Text, Choice
+from zope.interface import Interface, directlyProvides
+from zope.interface.interfaces import IInterface
+from zope.schema import TextLine, Text, Choice, URI, Int
 
 
 class IBrowserView(IView):
     """Browser View"""
 
-class IBrowserMenuItem(Interface):
-    """A menu item represents one view. These views might be conditioned
-    (using a filter) or being selected to be the default view of the menu."""
 
-    interface = Choice(
-        title=_('interface-component', "Interface"),
-        description=_("Specifies the interface this menu item is for."),
-        vocabulary="Interfaces",
-        required=True)
+class IMenuItemType(IInterface):
+    """Menu item type
 
-    action = TextLine(
-        title=_("The relative url to use if the item is selected"),
-        description=_("The url is relative to the object the menu is being "
-                      "displayed for."),
-        required=True)
+    Menu item types are interfaces that define classes of
+    menu items.
+    """
 
+class AddMenu(Interface):
+    """Special menu for providing a list of addable objects."""
+
+directlyProvides(AddMenu, IMenuItemType)
+
+
+class IBrowserMenuItem(Interface):
+    """Menu type
+
+    An interface that defines a menu.
+    """
+
     title = TextLine(
-        title=_("Title"),
-        description=_("The text to be displayed for the menu item"),
-        required=True)
+        title=_("Menu item title"),
+        description=_("The title provides the basic label for the menu item."),
+        required=True
+        )
 
     description = Text(
-        title=_("A longer explanation of the menu item"),
-        description=_("A UI may display this with the item or display it "
-                      "when the user requests more assistance."),
-        required=False)
+        title=_("Menu item description"),
+        description=_("A description of the menu item. This might be shown "
+                      "on menu pages or in pop-up help for menu items."),
+        required=False
+        )
 
-    permission = Choice(
-        title=_("The permission needed access the item"),
-        description=_("This can usually be inferred by the system, however, "
-                      "doing so may be expensive. When displaying a menu, "
-                      "the system tries to traverse to the URLs given in "
-                      "each action to determine whether the url is "
-                      "accessible to the current user. This can be avoided "
-                      "if the permission is given explicitly."),
-        vocabulary="Permissions",
-        required=False)
+    action = TextLine(
+        title=_("The URL to display if the item is selected"),
+        description=_("When a user selects a browser menu item, the URL"
+                      "given in the action is displayed. The action is "
+                      "usually given as a relative URL, relative to the "
+                      "object the menu item is for."),
+       required=True
+       )
 
+    order = Int(
+        title=_("Menu item ordering hint"),
+        description=_("This attribute provides a hint for menu item ordering."
+                      "Menu items will generally be sorted by the `for_`"
+                      "attribute and then by the order.")
+        )
+
     filter_string = TextLine(
         title=_("A condition for displaying the menu item"),
         description=_("The condition is given as a TALES expression. The "
@@ -78,87 +90,17 @@
                       "filter and the filter evaluates to a false value."),
         required=False)
 
-
-class IBrowserMenu(Interface):
-    """A menu can contain a set of views that a represented asa
-    collective."""
-
-    title = TextLine(
-        title=_("Title"),
-        description=_("A descriptive title for documentation purposes"),
-        required=True)
-
-    description = Text(
-        title=_("A longer explanation of the menu"),
-        description=_("A UI may display this with the item or display it "
-                      "when the user requests more assistance."),
-        required=False)
-
-    def getMenuItems(object=None):
-        """Get a list of all menu entries in the usual form:
-
-        (action, title, description, filter, permission)
-
-        If object is None, all items are returned.
+    icon = URI(
+        title=_("Icon URI"),
+        description=_("URI of the icon representing this menu item"))
+       
+    def available():
+        """Test whether the menu item should be displayed
+        
+        A menu item might not be available for an object, for example
+        due to security limitations or constraints.
         """
 
-
-class IBrowserMenuService(Interface):
-
-    def getAllMenuItems(menu_id, object):
-        """Returns a list of all menu items.
-
-        The output is a list/tuple of:
-        (action, title, description, filter, permission)
-
-        This allows us to make the getMenu() method much less
-        implementation-specific.
-        """
-
-    def getMenu(menu_id, object, request):
-        """Get a browser menu for an object and request
-
-        Return a sequence of mapping objects with keys:
-
-        title -- The menu item title
-
-        description -- The item title
-
-        action -- A (possibly relative to object) URL for the menu item.
-
-        The entries returned are accessable to the current user and
-        have passed any menu item filters, if any.
-        """
-
-    def getFirstMenuItem(menu_id, object, request):
-        """Get the first browser menu item for an object and request
-
-        Return a mapping object with keys:
-
-        title -- The menu item title
-
-        description -- The item title
-
-        action -- A (possibly relative to object) URL for the menu item.
-
-        The entry returned is accessable to the current user and
-        has passed any menu item filters, if any.
-
-        If no entry can be found, None is returned.
-        """
-
-class IGlobalBrowserMenuService(IBrowserMenuService):
-    """The global menu defines some additional methods that make it easier to
-    setup the service (via ZCML for example)."""
-
-    def menu(self, menu_id, title, description=u''):
-        """Add a new menu to the service."""
-
-    def menuItem(self, menu_id, interface, action, title,
-                 description='', filter_string=None, permission=None):
-        """Add a menu item to a specific menu."""
-
-
 class IMenuAccessView(Interface):
     """View that provides access to menus"""
 



More information about the Zope3-Checkins mailing list