##############################################################################
#
# Copyright (c) 2003-2004 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.
#
##############################################################################
"""interfaces.py: interfaces for the form browser machinery

$Id: interfaces.py 920 2005-03-10 00:20:26Z benji $"""

from zope import interface


class ISchemaView(interface.Interface):
    "the basic schema view interface suitable for extension and for display"

    extra = interface.Attribute(
        """A dictionary to store values desired by a given schema view.
        The add view is the only standard interface that explicitly declares a
        use for this dictionary.""")

    buttons = interface.Attribute(
        """A dictionary of a list of dictionaries,
        representing the available buttons.

        The top level dictionary has keys of unicode names representing
        button categories.  The default category defined in the buttons
        subdirective, for example, is u'main'.  The values are lists of
        dictionaries. Each dictionary represents a button within the category
        and contains the keys 'label,', which should be used to draw the
        button; 'id,' which should be used as the name of the submit button
        and typically as the id of the widget on the form (for DOM
        getElementById); 'action,' the name of the method on this class to
        which __call__ should dispatch if the id is activated; 'css_class,'
        the name of the css_class that the button should be assigned;
        'category', the name of the containing category, as described above;
        and 'active,' a boolean indicating if the action is currently
        available for this context (user, condition, etc.).  If a button is
        not active, the id and action are empty strings.  Button dictionaries
        may contain additional keys and values as form subclasses desire.

        Typically, the button_views method is used to render buttons, but
        the buttons property provides a more raw presentation of the
        information.

        All values are essentially advisory except for 'action' and 'id'.  'id'
        is typically auto-generated in such a manner as to minimize the
        likelihood of conflicting with other names on the form.
        """)

    label = interface.Attribute(
        """The suggested label for the form, as might be rendered in a header
        at the top of the page.""")

    fieldNames = interface.Attribute(
        "list of field names in suggested order for the form")

    active_button = interface.Attribute(
        """after the form machinery has determined the button pressed, if any,
        and before the button action has been dispatched, this holds the button
        dictionary, as decribed in the buttons attribute above, of the button
        that was submitted.""")

    update_status = interface.Attribute(
        """The status message, as translated from the status returned from
        the dispatched action.""")

    schema = interface.Attribute(
        """The schema that is the source of this form.""")

    errors = interface.Attribute(
        """a tuple of the error objects generated by the dispatched action
        for each field.""")

    invariant_errors = interface.Attribute(
        """a tuple of the error objects generated by the dispatched action
        for each invariant rule.""")

    widgets = interface.Attribute(
        """After setUpWidgets is called, this contains a collection of widgets
        with a mapping interface.  Iterating over it is in the order of the
        view's fieldNames.  Widgets can be obtained by field name using
        __getitem__ or get.
        """)

    def setUpWidgets():
        """the call to set up widgets for the view.  After this call, the
        widgets attribute is in place.  Usually called in view
        initialization"""

    def setPrefix(prefix):
        "Set the desired form prefix on all widgets."

    def invariant_error_views(self):
        """return a view of each error object in the invariant_errors
        attribute, as found for the 'snippet' name."""

    def error_views():
        """return a view of each error object in the errors
        attribute, as found for the 'snippet' name."""

    def button_views(category=None, show_disabled=False):
        """return an iterator of standard html submit buttons for each button
        definition. If category is None (default), all buttons in all
        categories are rendered; otherwise the value is used to look up and
        only render buttons in the specified category.  A KeyError is raised
        if the category does not exist.  By default, disabled buttons
        (buttons that are not active) are not rendered; if show_disabled is
        True, these are rendered as disabled buttons.
        """

    def beforeRender(self):
        """hook for performing things before rendering.  Note this is called
        whether or not the form is actually rendered: check self.halt_rendering
        if you need to perform different behavior in this circumstance."""

    def __call__(template_usage='', *args, **kw):
        """a dispatcher to button actions and renderer of the form.

        __call__ searches the request for each of the ids in the buttons
        attribute.  As soon as it finds one, it dispatches to the associated
        action, assuming there will be only one action per submission.  The
        active button is stored as active_button, and then the beforeRender
        hook is called.  if halt_rendering has been set to a Boolean True
        value, rendering is stopped and an empty unicode string is returned.
        Otherwise, the return value of the dispatch is (translated to be) the
        update_status, and the form is rendered with the passed arguments.
        """

    def validateInvariants(obj):
        """Given an object, validates the schema invariants and returns a
        list of any generated errors."""

    def getPseudoObject(data):
        """returns an object that claims to implement the form's schema and
        has the data supplied stored on the object.  A helper for validating
        invariants."""

    halt_rendering = interface.Attribute(
        """if halt_rendering is set to True by a dispatched action in
        __call__,  the form should not render.""")

    label_msgid = interface.Attribute(
        """the message id of the label that should  typically be rendered at
        the top of this form.""")

    label_title = interface.Attribute(
        """substituted into label_msgid if the message id translation
        includes a "title" substitution slot (a la "Add ${title}").""")

    raw_buttons = interface.Attribute(
        """the default source of the buttons attribute. It is a  dictionary of
        button group id to sequence of dicts, in which each dict has keys id,
        label, action, permission, condition, and css_class.  action is the
        name of a method on the (composite, with optional bases provided in
        zcml) view class; the value returned is set as the update_status.  The
        user must have the provided permission, if any, and the condition must
        pass, in order for the action to be available as a button.  The
        condition is a TALES expression with 'context', 'adapted', 'nothing',
        'request', and 'modules' available.""")

    # form.pt in this product uses this hook.  This functionality is
    # exercised by forms in other products.
    extra_script = interface.Attribute(
        """a hook point for schema views to suggest arbitrary
        javascript to be rendered in the form.""")


class IEditView(ISchemaView):
    "the basic edit view interface"
    def attempt_commit():
        """The standard action method for an edit view's submit button: will
        validate the entered data and, if it passes, make the changes.
        """

    def commit_changes():
        """If no errors are found in attempt_commit, commit_changes is called
        to actually edit the object.  Override to change what "edit the object"
        means.
        """

    def changed():
        """A hook.  Override to execute logic *after* changes have been made
        successfully."""


class IAddView(ISchemaView):
    "the basic add view interface"

    extra = interface.Attribute(
        """The standard extra-value dictionary described in ISchemaView must
        have "content_factory" as a key to the factory (such as a class) that
        instantiates the object.  It must also have one or more of the keys
        "arguments", "keyword_arguments", "set_before_add", "set_after_add"
        with names of arguments to be used for creation.
        """)

    def attempt_commit():
        """The standard action method for an add view's submit button: will
        validate the entered data and, if it passes, create and add the object.
        """

    def perform_cancel():
        """An action to cancel the add and redirect to the add's container or
        to the previous add form for nested adds.
        """

    def abstract_attempt_commit():
        """The guts of attempting a commit; does everything except redirect, so
        you can override attempt_commit and redirect as desired."""

    def createAndAdd(data):
        """Add the desired object using the data in the data argument.

        The data argument is a dictionary with the data entered in the form.

        returns created object.
        """

    def create(*args, **kw):
        "actually instantiates object and returns it"

    def add(content):
        "adds the content; typically delegates to the adding."

    def changed():
        """A hook.  Override to execute logic *after* an object has been
        created and added successfully."""

    def nextURL():
        "returns the next URL; usually defers to the adding."
