Source code for z3c.form.button

##############################################################################
#
# Copyright (c) 2007 Zope Foundation 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.
#
##############################################################################
"""Button and Button Manager implementation

$Id$
"""
__docformat__ = "reStructuredText"
import sys
import zope.event
import zope.interface
import zope.location
import zope.schema
import zope.traversing.api

from zope.component import hooks
from zope.interface import adapter
from zope.schema.fieldproperty import FieldProperty
from z3c.form import action, interfaces, util, value
from z3c.form.browser import image, submit
from z3c.form.widget import AfterWidgetUpdateEvent


StaticButtonActionAttribute = value.StaticValueCreator(
    discriminators = ('form', 'request', 'content', 'button', 'manager')
    )
ComputedButtonActionAttribute = value.ComputedValueCreator(
    discriminators = ('form', 'request', 'content', 'button', 'manager')
    )


@zope.interface.implementer(interfaces.IButton)
class Button(zope.schema.Field):
    """A simple button in a form."""

    accessKey = FieldProperty(interfaces.IButton['accessKey'])
    actionFactory = FieldProperty(interfaces.IButton['actionFactory'])

    def __init__(self, *args, **kwargs):
        # Provide some shortcut ways to specify the name
        if args:
            kwargs['__name__'] = args[0]
            args = args[1:]
        if 'name' in kwargs:
            kwargs['__name__'] = kwargs['name']
            del kwargs['name']
        # Extract button-specific arguments
        self.accessKey = kwargs.pop('accessKey', None)
        self.condition = kwargs.pop('condition', None)
        # Initialize the button
        super(Button, self).__init__(*args, **kwargs)

    def __repr__(self):
        return '<%s %r %r>' %(
            self.__class__.__name__, self.__name__, self.title)


@zope.interface.implementer(interfaces.IImageButton)
class ImageButton(Button):
    """A simple image button in a form."""

    image = FieldProperty(interfaces.IImageButton['image'])

    def __init__(self, image=None, *args, **kwargs):
        self.image = image
        super(ImageButton, self).__init__(*args, **kwargs)

    def __repr__(self):
        return '<%s %r %r>' %(
            self.__class__.__name__, self.__name__, self.image)


@zope.interface.implementer(interfaces.IButtons)
class Buttons(util.SelectionManager):
    """Button manager."""

    managerInterface = interfaces.IButtons
    prefix = 'buttons'

    def __init__(self, *args):
        buttons = []
        for arg in args:
            if zope.interface.interfaces.IInterface.providedBy(arg):
                for name, button in zope.schema.getFieldsInOrder(arg):
                    if interfaces.IButton.providedBy(button):
                        buttons.append((name, button))
            elif self.managerInterface.providedBy(arg):
                buttons += arg.items()
            elif interfaces.IButton.providedBy(arg):
                if not arg.__name__:
                    arg.__name__ = util.createId(arg.title)
                buttons.append((arg.__name__, arg))
            else:
                raise TypeError("Unrecognized argument type", arg)
        keys = []
        seq = []
        byname = {}
        for name, button in buttons:
            keys.append(name)
            seq.append(button)
            byname[name] = button

        self._data_keys = keys
        self._data_values = seq
        self._data = byname


@zope.interface.implementer(interfaces.IButtonHandlers)
class Handlers(object):
    """Action Handlers for a Button-based form."""

    def __init__(self):
        self._registry = adapter.AdapterRegistry()
        self._handlers = ()

    def addHandler(self, button, handler):
        """See interfaces.IButtonHandlers"""
        # Create a specification for the button
        buttonSpec = util.getSpecification(button)
        if isinstance(buttonSpec, util.classTypes):
            buttonSpec = zope.interface.implementedBy(buttonSpec)
        # Register the handler
        self._registry.register(
            (buttonSpec,), interfaces.IButtonHandler, '', handler)
        self._handlers += ((button, handler),)

    def getHandler(self, button):
        """See interfaces.IButtonHandlers"""
        buttonProvided = zope.interface.providedBy(button)
        return self._registry.lookup1(buttonProvided, interfaces.IButtonHandler)

    def copy(self):
        """See interfaces.IButtonHandlers"""
        handlers = Handlers()
        for button, handler in self._handlers:
            handlers.addHandler(button, handler)
        return handlers

    def __add__(self, other):
        """See interfaces.IButtonHandlers"""
        if not isinstance(other, Handlers):
            raise NotImplementedError
        handlers = self.copy()
        for button, handler in other._handlers:
            handlers.addHandler(button, handler)
        return handlers

    def __repr__(self):
        return '<Handlers %r>' %[handler for button, handler in self._handlers]


@zope.interface.implementer(interfaces.IButtonHandler)
class Handler(object):

    def __init__(self, button, func):
        self.button = button
        self.func = func

    def __call__(self, form, action):
        return self.func(form, action)

    def __repr__(self):
        return '<%s for %r>' %(self.__class__.__name__, self.button)


def handler(button):
    """A decorator for defining a success handler."""
    def createHandler(func):
        handler = Handler(button, func)
        frame = sys._getframe(1)
        f_locals = frame.f_locals
        handlers = f_locals.setdefault('handlers', Handlers())
        handlers.addHandler(button, handler)
        return handler
    return createHandler


def buttonAndHandler(title, **kwargs):
    # Add the title to button constructor keyword arguments
    kwargs['title'] = title
    # Extract directly provided interfaces:
    provides = kwargs.pop('provides', ())
    # Create button and add it to the button manager
    button = Button(**kwargs)
    zope.interface.alsoProvides(button, provides)
    frame = sys._getframe(1)
    f_locals = frame.f_locals
    buttons = f_locals.setdefault('buttons', Buttons())
    f_locals['buttons'] += Buttons(button)
    # Return the handler decorator
    return handler(button)


@zope.interface.implementer(interfaces.IButtonAction)
class ButtonAction(action.Action, submit.SubmitWidget, zope.location.Location):
    zope.component.adapts(interfaces.IFormLayer, interfaces.IButton)

    def __init__(self, request, field):
        action.Action.__init__(self, request, field.title)
        submit.SubmitWidget.__init__(self, request)
        self.field = field

    @property
    def accesskey(self):
        return self.field.accessKey

    @property
    def value(self):
        return self.title

    @property
    def id(self):
        return self.name.replace('.', '-')


class ImageButtonAction(image.ImageWidget, ButtonAction):
    zope.component.adapts(interfaces.IFormLayer, interfaces.IImageButton)

    def __init__(self, request, field):
        action.Action.__init__(self, request, field.title)
        submit.SubmitWidget.__init__(self, request)
        self.field = field

    @property
    def src(self):
        site = hooks.getSite()
        src = util.toUnicode(zope.traversing.api.traverse(
            site, '++resource++' + self.field.image, request=self.request)())
        return src

    def isExecuted(self):
        return self.name + '.x' in self.request.form


class ButtonActions(action.Actions):
    zope.component.adapts(
        interfaces.IButtonForm,
        zope.interface.Interface,
        zope.interface.Interface)

    def update(self):
        """See z3c.form.interfaces.IActions."""
        # Create a unique prefix.
        prefix = util.expandPrefix(self.form.prefix)
        prefix += util.expandPrefix(self.form.buttons.prefix)
        # Walk through each field, making an action out of it.
        uniqueOrderedKeys = []
        for name, button in self.form.buttons.items():
            # Step 1: Only create an action for the button, if the condition is
            #         fulfilled.
            if button.condition is not None and not button.condition(self.form):
                # Step 1.1: If the action already existed, but now the
                #           condition became false, remove the old action.
                if name in self._data:
                    del self._data[name]
                continue
            # Step 2: Get the action for the given button.
            newButton = True
            if name in self._data:
                buttonAction = self._data[name]
                newButton = False
            elif button.actionFactory is not None:
                buttonAction = button.actionFactory(self.request, button)
            else:
                buttonAction = zope.component.getMultiAdapter(
                    (self.request, button), interfaces.IButtonAction)
            # Step 3: Set the name on the button
            buttonAction.name = prefix + name
            # Step 4: Set any custom attribute values.
            title = zope.component.queryMultiAdapter(
                (self.form, self.request, self.content, button, self),
                interfaces.IValue, name='title')
            if title is not None:
                buttonAction.title = title.get()
            # Step 5: Set the form
            buttonAction.form = self.form
            if not interfaces.IFormAware.providedBy(buttonAction):
                zope.interface.alsoProvides(buttonAction, interfaces.IFormAware)
            # Step 6: Update the new action
            buttonAction.update()
            zope.event.notify(AfterWidgetUpdateEvent(buttonAction))
            # Step 7: Add the widget to the manager
            uniqueOrderedKeys.append(name)
            if newButton:
                self._data[name] = buttonAction
                zope.location.locate(buttonAction, self, name)
        # always ensure that we add all keys and keep the order given from
        # button items
        self._data_keys = uniqueOrderedKeys
        self._data_values = [self._data[name] for name in uniqueOrderedKeys]


class ButtonActionHandler(action.ActionHandlerBase):
    zope.component.adapts(
        interfaces.IHandlerForm,
        zope.interface.Interface,
        zope.interface.Interface,
        ButtonAction)

    def __call__(self):
        handler = self.form.handlers.getHandler(self.action.field)
        # If no handler is found, then that's okay too.
        if handler is None:
            return
        return handler(self.form, self.action)