[Zope3-checkins] SVN: zope.formlib/branches/faassen-zaf/src/zope/formlib/ Moved in widget implementations from zope.app.form.

Martijn Faassen faassen at startifact.com
Wed Dec 30 14:46:13 EST 2009


Log message for revision 107378:
  Moved in widget implementations from zope.app.form.
  

Changed:
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py
  A   zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py

-=-
Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/boolwidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/boolwidgets.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,135 @@
+##############################################################################
+#
+# 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 widgets for items
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.interface import implements
+from zope.schema.vocabulary import SimpleVocabulary
+
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.widget import DisplayWidget
+from zope.app.form.browser.i18n import _
+from zope.app.form.browser.itemswidgets import RadioWidget
+from zope.app.form.browser.itemswidgets import SelectWidget, DropdownWidget
+from zope.formlib.interfaces import IInputWidget
+
+
+class CheckBoxWidget(SimpleInputWidget):
+    """A checkbox widget used to display Bool fields.
+    
+    For more detailed documentation, including sample code, see
+    ``tests/test_checkboxwidget.py``.
+    """    
+    type = 'checkbox'
+    default = 0
+    extra = ''
+
+    def __init__(self, context, request):
+        super(CheckBoxWidget, self).__init__(context, request)
+        self.required = False
+
+    def __call__(self):
+        """Render the widget to HTML."""
+        value = self._getFormValue()
+        if value == 'on':
+            kw = {'checked': 'checked'}
+        else:
+            kw = {}
+        return "%s %s" % (
+            renderElement(self.tag,
+                          type='hidden',
+                          name=self.name+".used",
+                          id=self.name+".used",
+                          value=""
+                          ),
+            renderElement(self.tag,
+                          type=self.type,
+                          name=self.name,
+                          id=self.name,
+                          cssClass=self.cssClass,
+                          extra=self.extra,
+                          value="on",
+                          **kw),
+            )
+
+    def _toFieldValue(self, input):
+        """Convert from HTML presentation to Python bool."""
+        return input == 'on'
+
+    def _toFormValue(self, value):
+        """Convert from Python bool to HTML representation."""
+        return value and 'on' or ''
+
+    def hasInput(self):
+        """Check whether the field is represented in the form."""
+        return self.name + ".used" in self.request.form or \
+            super(CheckBoxWidget, self).hasInput()
+
+    def _getFormInput(self):
+        """Returns the form input used by `_toFieldValue`.
+        
+        Return values:
+        
+          ``'on'``  checkbox is checked
+          ``''``    checkbox is not checked
+          ``None``  form input was not provided
+
+        """
+        if self.request.get(self.name) == 'on':
+            return 'on'
+        elif self.name + '.used' in self.request:
+            return ''
+        else:
+            return None
+
+
+def BooleanRadioWidget(field, request, true=_('on'), false=_('off')):
+    vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) ) 
+    widget = RadioWidget(field, vocabulary, request)
+    widget.required = False
+    return widget
+
+
+def BooleanSelectWidget(field, request, true=_('on'), false=_('off')):
+    vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
+    widget = SelectWidget(field, vocabulary, request)
+    widget.size = 2
+    widget.required = False
+    return widget
+
+
+def BooleanDropdownWidget(field, request, true=_('on'), false=_('off')):
+    vocabulary = SimpleVocabulary.fromItems( ((true, True), (false, False)) )
+    widget = DropdownWidget(field, vocabulary, request)
+    widget.required = False
+    return widget
+
+
+_msg_true = _("True")
+_msg_false = _("False")
+
+class BooleanDisplayWidget(DisplayWidget):
+
+    def __call__(self):
+        if self._renderedValueSet():
+            value = self._data
+        else:
+            value = self.context.default
+        if value:
+            return _msg_true
+        else:
+            return _msg_false

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/itemswidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/itemswidgets.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,634 @@
+##############################################################################
+#
+# 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 widgets for items
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+from xml.sax.saxutils import escape
+
+from zope import component
+from zope.interface import implements
+from zope.i18n import translate
+from zope.schema.interfaces import ValidationError, InvalidValue
+from zope.schema.interfaces import ConstraintNotSatisfied, ITitledTokenizedTerm
+
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.interfaces import IInputWidget, IDisplayWidget
+from zope.formlib.interfaces import ConversionError
+from zope.app.form.browser.i18n import _
+from zope.browserpage import ViewPageTemplateFile
+
+
+# For choices, we want to make the widget a view of the field and vocabulary.
+
+def ChoiceDisplayWidget(field, request):
+    return component.getMultiAdapter((field, field.vocabulary, request),
+                                     IDisplayWidget)
+
+def ChoiceInputWidget(field, request):
+    return component.getMultiAdapter((field, field.vocabulary, request),
+                                     IInputWidget)
+
+# for collections, we want to make the widget a view of the field and the
+# value_type.  If the value_type is None we may fall over.  We may
+# not be able to do any better than that.
+
+def CollectionDisplayWidget(field, request):
+    return component.getMultiAdapter((field, field.value_type, request),
+                                     IDisplayWidget)
+
+def CollectionInputWidget(field, request):
+    return component.getMultiAdapter((field, field.value_type, request),
+                                     IInputWidget)
+
+# for collections of choices, we want to make the widget a view of the field,
+# the value type, and the vocabulary.
+
+def ChoiceCollectionDisplayWidget(field, value_type, request):
+    return component.getMultiAdapter((field, value_type.vocabulary, request),
+                                     IDisplayWidget)
+
+def ChoiceCollectionInputWidget(field, value_type, request):
+    return component.getMultiAdapter((field, value_type.vocabulary, request),
+                                     IInputWidget)
+
+class TranslationHook(object):
+    """A mixin class that provides the translation capabilities."""
+
+    def translate(self, msgid):
+        return translate(msgid, context=self.request, default=msgid)
+
+class ItemsWidgetBase(TranslationHook, SimpleInputWidget):
+    """Convenience base class for widgets displaying items/choices."""
+
+    extra = ""
+
+    def __init__(self, field, vocabulary, request):
+        """Initialize the widget."""
+        # only allow this to happen for a bound field
+        assert field.context is not None
+        self.vocabulary = vocabulary
+        super(ItemsWidgetBase, self).__init__(field, request)
+        self.empty_marker_name = self.name + "-empty-marker"
+
+    def setPrefix(self, prefix):
+        """Set the prefixes for the field names of the form."""
+        super(ItemsWidgetBase, self).setPrefix(prefix)
+        # names for other information from the form
+        self.empty_marker_name = self.name + "-empty-marker"
+
+    def __call__(self):
+        """Render the widget to HTML."""
+        raise NotImplementedError(
+            "__call__() must be implemented by a subclass; use _getFormValue()"
+            )
+
+    def textForValue(self, term):
+        """Extract a string from the `term`.
+
+        The `term` must be a vocabulary tokenized term.
+
+        This can be overridden to support more complex `term`
+        objects. The token is returned here since it's the only thing
+        known to be a string, or str()able.
+
+        """
+        titled = ITitledTokenizedTerm(term, None)
+        if titled is None:
+            return term.token
+        return self.translate(titled.title)
+
+    def convertTokensToValues(self, tokens):
+        """Convert term tokens to the terms themselves.
+
+        Tokens are used in the HTML form to represent terms. This method takes
+        the form tokens and converts them back to terms.
+        """
+        values = []
+        for token in tokens:
+            try:
+                term = self.vocabulary.getTermByToken(token)
+            except LookupError, error:
+                raise InvalidValue("token %r not found in vocabulary" % token)
+            else:
+                values.append(term.value)
+        return values
+
+    def _emptyMarker(self):
+        """Mark the form so that empty selections are also valid."""
+        return '<input name="%s" type="hidden" value="1" />' % (
+            self.empty_marker_name)
+
+    def hasInput(self):
+        """Check whether we have any input."""
+        return (self.name in self.request.form or
+                self.empty_marker_name in self.request.form)
+
+    def _toFieldValue(self, input):
+        """See `SimpleInputWidget`"""
+        raise NotImplementedError(
+            "_toFieldValue(input) must be implemented by a subclass\n"
+            "It may be inherited from the mix-in classes SingleDataHelper\n"
+            "or MultiDataHelper")
+
+
+class SingleDataHelper(object):
+    """Mix-in helper class for getting the term from the HTML form.
+
+    This is used when we expect a single input, i.e. the Choice field.
+    """
+
+    def _toFieldValue(self, input):
+        if input:
+            try:
+                return self.convertTokensToValues([input])[0]
+            except InvalidValue, e:
+                raise ConversionError(_("Invalid value"), e)
+        else:
+            return self.context.missing_value
+
+    def hidden(self):
+        #XXX: _getFormValue() should return a string value that can be
+        #     used in a HTML form, but it doesn't. When
+        #     http://www.zope.org/Collectors/Zope3-dev/584 gets fixed
+        #     this hack should be reverted.
+        #     -- Bjorn Tillenius, 2006-04-12
+        value = self._getFormValue()
+        if value == self._missing:
+            form_value = ''
+        else:
+            form_value = self.vocabulary.getTerm(value).token
+        return renderElement(u'input',
+                             type='hidden',
+                             name=self.name,
+                             id=self.name,
+                             value=form_value,
+                             cssClass=self.cssClass,
+                             extra=self.extra)
+
+
+class MultiDataHelper(object):
+    """Mix-in helper class for getting the term from the HTML form.
+
+    This is used when we expect a multiple inputs, i.e. Sequence fields with a
+    Choice field as value_type.
+    """
+
+    def _toFieldValue(self, input):
+        """See SimpleInputWidget"""
+        if input is None:
+            input = []
+        elif not isinstance(input, list):
+            input = [input]
+        try:
+            values = self.convertTokensToValues(input)
+        except InvalidValue, e:
+            raise ConversionError(_("Invalid value"), e)
+
+        # All AbstractCollection fields have a `_type` attribute specifying
+        # the type of collection. Use it to generate the correct type,
+        # otherwise return a list.  TODO: this breaks encapsulation.
+        if hasattr(self.context, '_type'):
+            _type = self.context._type
+            if isinstance(_type, tuple):
+                _type = _type[0]
+            return _type(values)
+        else:
+            return values
+
+
+    def _getDefault(self):
+        # Return the default value for this widget;
+        # may be overridden by subclasses.
+        val = self.context.default
+        if val is None:
+            val = []
+        return val
+
+
+## Display-Widgets for Items-related fields.
+
+class ItemDisplayWidget(SingleDataHelper, ItemsWidgetBase):
+    """Simple single-selection display that can be used in many cases."""
+
+    def __init__(self, *args, **kw):
+        ItemsWidgetBase.__init__(self, *args, **kw)
+        self.required = False
+
+    _messageNoValue = _("item-missing-single-value-for-display", "")
+
+    def __call__(self):
+        """See IBrowserWidget."""
+        value = self._getFormValue()
+        if not value:
+            return self.translate(self._messageNoValue)
+        else:
+            term = self.vocabulary.getTerm(value)
+            return self.textForValue(term)
+
+
+class ItemsMultiDisplayWidget(MultiDataHelper, ItemsWidgetBase):
+    """Displays a sequence of items."""
+
+    def __init__(self, *args, **kw):
+        ItemsWidgetBase.__init__(self, *args, **kw)
+        self.required = False
+
+    _messageNoValue = _("vocabulary-missing-multiple-value-for-display", "")
+
+    itemTag = 'li'
+    tag = 'ol'
+
+    def __call__(self):
+        """See IBrowserWidget."""
+        value = self._getFormValue()
+        if value:
+            rendered_items = self.renderItems(value)
+            return renderElement(self.tag,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 contents="\n".join(rendered_items),
+                                 extra=self.extra)
+        else:
+            return self.translate(self._messageNoValue)
+
+    def renderItems(self, value):
+        """Render items of sequence."""
+        items = []
+        cssClass = self.cssClass or ''
+        if cssClass:
+            cssClass += "-item"
+        tag = self.itemTag
+        for item in value:
+            term = self.vocabulary.getTerm(item)
+            items.append(renderElement(
+                tag,
+                cssClass=cssClass,
+                contents=escape(self.textForValue(term))))
+        return items
+
+class ListDisplayWidget(ItemsMultiDisplayWidget):
+    """Display widget for ordered multi-selection fields.
+
+    This can be used for both Sequence, List, and Tuple fields.
+    """
+    tag = 'ol'
+
+class SetDisplayWidget(ItemsMultiDisplayWidget):
+    """Display widget for unordered multi-selection fields.
+
+    This can be used for both Set field.
+    """
+    tag = 'ul'
+
+
+## Edit-Widgets for Items-related fields.
+
+# BBB Set to False to never display an item for the missing value if the field
+# is required, which was the behaviour of versions up to and including 3.5.0.
+EXPLICIT_EMPTY_SELECTION = True
+
+
+class ItemsEditWidgetBase(SingleDataHelper, ItemsWidgetBase):
+    """Widget Base for rendering item-related fields.
+
+    These widgets work with Choice fields and Sequence fields that have Choice
+    as value_type.
+    """
+    implements(IInputWidget)
+
+    size = 5
+    tag = 'select'
+
+    _displayItemForMissingValue = True
+
+    # Whether an empty selection should always be made explicit, i.e. even
+    # if the field is required.
+    explicit_empty_selection = False
+
+    def __init__(self, field, vocabulary, request):
+        """Initialize the widget."""
+        super(ItemsEditWidgetBase, self).__init__(field, vocabulary, request)
+
+    def setPrefix(self, prefix):
+        """Set the prefix of the input name.
+
+        Once we set the prefix of input field, we use the name of the input
+        field and the postfix '-query' for the associated query view.
+        """
+        super(ItemsEditWidgetBase, self).setPrefix(prefix)
+
+
+    def __call__(self):
+        """See IBrowserWidget."""
+        value = self._getFormValue()
+        contents = []
+        have_results = False
+
+        contents.append(self._div('value', self.renderValue(value)))
+        contents.append(self._emptyMarker())
+
+        return self._div(self.cssClass, "\n".join(contents))
+
+
+    def _div(self, cssClass, contents, **kw):
+        """Render a simple div tag."""
+        if contents:
+            return renderElement('div',
+                                 cssClass=cssClass,
+                                 contents="\n%s\n" % contents,
+                                 **kw)
+        return ""
+
+
+    def renderItemsWithValues(self, values):
+        """Render the list of possible values, with those found in
+        `values` being marked as selected."""
+
+        cssClass = self.cssClass
+
+        # multiple items with the same value are not allowed from a
+        # vocabulary, so that need not be considered here
+        rendered_items = []
+        count = 0
+
+        # Handle case of missing value
+        missing = self._toFormValue(self.context.missing_value)
+
+        if self._displayItemForMissingValue and (
+            not self.context.required or
+            EXPLICIT_EMPTY_SELECTION and
+            self.explicit_empty_selection and
+            missing in values and
+            self.context.default is None):
+
+            if missing in values:
+                render = self.renderSelectedItem
+            else:
+                render = self.renderItem
+
+            missing_item = render(count,
+                self.translate(self._messageNoValue),
+                missing,
+                self.name,
+                cssClass)
+            rendered_items.append(missing_item)
+            count += 1
+
+        # Render normal values
+        for term in self.vocabulary:
+            item_text = self.textForValue(term)
+
+            if term.value in values:
+                render = self.renderSelectedItem
+            else:
+                render = self.renderItem
+
+            rendered_item = render(count,
+                item_text,
+                term.token,
+                self.name,
+                cssClass)
+
+            rendered_items.append(rendered_item)
+            count += 1
+
+        return rendered_items
+
+    def renderItem(self, index, text, value, name, cssClass):
+        """Render an item for a particular `value`."""
+        return renderElement('option',
+                             contents=escape(text),
+                             value=value,
+                             cssClass=cssClass)
+
+    def renderSelectedItem(self, index, text, value, name, cssClass):
+        """Render an item for a particular `value` that is selected."""
+        return renderElement('option',
+                             contents=escape(text),
+                             value=value,
+                             cssClass=cssClass,
+                             selected='selected')
+
+
+class SelectWidget(ItemsEditWidgetBase):
+    """Provide a selection list for the item."""
+
+    _messageNoValue = _("vocabulary-missing-single-value-for-edit",
+                        "(no value)")
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        contents = "\n%s\n" %"\n".join(rendered_items)
+        return renderElement('select',
+                             name=self.name,
+                             id=self.name,
+                             contents=contents,
+                             size=self.size,
+                             extra=self.extra)
+
+    def renderItems(self, value):
+        return self.renderItemsWithValues([value])
+
+
+class DropdownWidget(SelectWidget):
+    """Variation of the SelectWidget that uses a drop-down list."""
+    size = 1
+    explicit_empty_selection = True
+
+
+class RadioWidget(SelectWidget):
+    """Radio widget for single item choices.
+
+    This widget can be used when the number of selections is going
+    to be small.
+    """
+    orientation = "vertical"
+
+    _messageNoValue = _("vocabulary-missing-single-value-for-edit",
+                        "(no value)")
+
+    def renderItem(self, index, text, value, name, cssClass):
+        """Render an item of the list."""
+        return self._renderItem(index, text, value, name, cssClass)
+
+    def renderSelectedItem(self, index, text, value, name, cssClass):
+        """Render a selected item of the list."""
+        return self._renderItem(index, text, value, name, cssClass,
+                                checked=True)
+
+    def _renderItem(self, index, text, value, name, cssClass, checked=False):
+        kw = {}
+        if checked:
+            kw['checked'] = 'checked'
+        id = '%s.%s' % (name, index)
+        elem = renderElement(u'input',
+                             value=value,
+                             name=name,
+                             id=id,
+                             cssClass=cssClass,
+                             type='radio',
+                             **kw)
+        return renderElement(u'label',
+                             contents='%s&nbsp;%s' % (elem, text),
+                             **{'for': id})
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        if self.orientation == 'horizontal':
+            return "&nbsp;&nbsp;".join(rendered_items)
+        else:
+            return "<br />".join(rendered_items)
+
+
+class ItemsMultiEditWidgetBase(MultiDataHelper, ItemsEditWidgetBase):
+    """Items widget supporting multiple selections."""
+
+    _messageNoValue = _("vocabulary-missing-multiple-value-for-edit",
+                        "(no values)")
+    _displayItemForMissingValue = False
+
+    def renderItems(self, value):
+        if value == self.context.missing_value:
+            values = []
+        else:
+            values = list(value)
+        return self.renderItemsWithValues(values)
+
+    def renderValue(self, value):
+        # All we really add here is the ':list' in the name argument
+        # and mutliple='multiple' to renderElement().
+        rendered_items = self.renderItems(value)
+        return renderElement(self.tag,
+                             name=self.name + ':list',
+                             id=self.name,
+                             multiple='multiple',
+                             size=self.size,
+                             contents="\n".join(rendered_items),
+                             extra=self.extra)
+
+    def hidden(self):
+        items = []
+        for item in self._getFormValue():
+            items.append(
+                renderElement(u'input',
+                              type='hidden',
+                              name=self.name+':list',
+                              id=self.name,
+                              value=self.vocabulary.getTerm(item).token,
+                              cssClass=self.cssClass,
+                              extra=self.extra))
+        return '\n'.join(items)
+
+
+class MultiSelectWidget(ItemsMultiEditWidgetBase):
+    """Provide a selection list for the list to be selected."""
+
+
+class MultiSelectSetWidget(MultiSelectWidget):
+    """Provide a selection list for the set to be selected."""
+
+    def _toFieldValue(self, input):
+        value = super(MultiSelectSetWidget, self)._toFieldValue(input)
+        if isinstance(value, list):
+            value = set(value)
+        return value
+
+
+class MultiSelectFrozenSetWidget(MultiSelectWidget):
+    """Provide a selection list for the set to be selected."""
+
+    def _toFieldValue(self, input):
+        value = super(MultiSelectFrozenSetWidget, self)._toFieldValue(input)
+        if isinstance(value, list):
+            value = frozenset(value)
+        return value
+
+class OrderedMultiSelectWidget(ItemsMultiEditWidgetBase):
+    """A multi-selection widget with ordering support."""
+
+    template = ViewPageTemplateFile('orderedSelectionList.pt')
+
+    def choices(self):
+        """Return a set of tuples (text, value) that are available."""
+        # Not all content objects must necessarily support the attributes
+        if hasattr(self.context.context, self.context.__name__):
+            available_values = self.context.get(self.context.context)
+        else:
+            available_values = []
+        return [{'text': self.textForValue(term), 'value': term.token}
+                for term in self.vocabulary
+                if term.value not in available_values]
+
+    def selected(self):
+        """Return a list of tuples (text, value) that are selected."""
+        # Get form values
+        values = self._getFormValue()
+        # Not all content objects must necessarily support the attributes
+        if hasattr(self.context.context, self.context.__name__):
+            # merge in values from content
+            for value in self.context.get(self.context.context):
+                if value not in values:
+                    values.append(value)
+
+        terms = [self.vocabulary.getTerm(value)
+                 for value in values]
+        return [{'text': self.textForValue(term), 'value': term.token}
+                for term in terms]
+
+    def __call__(self):
+        return self.template()
+
+
+class MultiCheckBoxWidget(ItemsMultiEditWidgetBase):
+    """Provide a list of checkboxes that provide the choice for the list."""
+
+    orientation = "vertical"
+
+    _joinButtonToMessageTemplate = u"%s&nbsp;%s"
+
+    def renderValue(self, value):
+        rendered_items = self.renderItems(value)
+        if self.orientation == 'horizontal':
+            return "&nbsp;&nbsp;".join(rendered_items)
+        else:
+            return "<br />".join(rendered_items)
+
+    def renderItem(self, index, text, value, name, cssClass):
+        """Render an item of the list."""
+        return self._renderItem(index, text, value, name, cssClass)
+
+    def renderSelectedItem(self, index, text, value, name, cssClass):
+        """Render a selected item of the list."""
+        return self._renderItem(index, text, value, name, cssClass,
+                                checked=True)
+
+    def _renderItem(self, index, text, value, name, cssClass, checked=False):
+        kw = {}
+        if checked:
+            kw['checked'] = 'checked'
+        id = '%s.%s' % (name, index)
+        elem = renderElement('input',
+                             type="checkbox",
+                             cssClass=cssClass,
+                             name=name,
+                             id=id,
+                             value=value,
+                             **kw)
+        contents = self._joinButtonToMessageTemplate % (elem, text)
+        return renderElement(u'label',
+                             contents=contents,
+                             **{'for': id})
+

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/objectwidget.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.pt	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,7 @@
+<fieldset>
+  <legend tal:content="context/legendTitle"
+        i18n:translate="">The Legend</legend>
+  <div class="row" tal:repeat="widget context/subwidgets">
+    <metal:block use-macro="context/@@form_macros/widget_row" />
+  </div>
+</fieldset>

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py (from rev 107376, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/objectwidget.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/objectwidget.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,193 @@
+##############################################################################
+#
+# 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 widgets for text-like data
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope.interface import implements
+from zope.schema import getFieldNamesInOrder
+
+from zope.formlib.interfaces import IInputWidget
+from zope.formlib.widget import InputWidget
+from zope.formlib.widget import BrowserWidget
+from zope.app.form.utility import setUpWidgets, applyWidgetsChanges
+from zope.browserpage import ViewPageTemplateFile
+from zope.app.form.browser.interfaces import IWidgetInputErrorView
+
+
+class ObjectWidgetView:
+
+    template = ViewPageTemplateFile('objectwidget.pt')
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self):
+        return self.template()
+
+
+class ObjectWidget(BrowserWidget, InputWidget):
+    """A widget over an Interface that contains Fields.
+
+    ``factory``
+
+      factory used to create content that this widget (field) represents
+
+    ``*_widget``
+
+      Optional CustomWidgets used to generate widgets for the fields in this
+      widget
+    """
+
+    implements(IInputWidget)
+
+    _object = None      # the object value (from setRenderedValue & request)
+    _request_parsed = False
+
+    def __init__(self, context, request, factory, **kw):
+        super(ObjectWidget, self).__init__(context, request)
+
+        # define view that renders the widget
+        self.view = ObjectWidgetView(self, request)
+
+        # factory used to create content that this widget (field)
+        # represents
+        self.factory = factory
+
+        # handle foo_widget specs being passed in
+        self.names = getFieldNamesInOrder(self.context.schema)
+        for k, v in kw.items():
+            if k.endswith('_widget'):
+                setattr(self, k, v)
+
+        # set up my subwidgets
+        self._setUpEditWidgets()
+
+    def setPrefix(self, prefix):
+        super(ObjectWidget, self).setPrefix(prefix)
+        self._setUpEditWidgets()
+
+    def _setUpEditWidgets(self):
+        # subwidgets need a new name
+        setUpWidgets(self, self.context.schema, IInputWidget,
+                         prefix=self.name, names=self.names,
+                         context=self.context)
+
+    def __call__(self):
+        return self.view()
+
+    def legendTitle(self):
+        return self.context.title or self.context.__name__
+
+    def getSubWidget(self, name):
+        return getattr(self, '%s_widget' % name)
+
+    def subwidgets(self):
+        return [self.getSubWidget(name) for name in self.names]
+
+    def hidden(self):
+        """Render the object as hidden fields."""
+        result = []
+        for name in self.names:
+            result.append(self.getSubwidget(name).hidden())
+        return "".join(result)
+
+    def error(self):
+        if self._error:
+            errormessages = []
+            keys = self._error.keys(); keys.sort()
+            for key in keys:
+                errormessages.append(str(key) + ': ')
+                errormessages.append(component.getMultiAdapter(
+                    (self._error[key], self.request),
+                    IWidgetInputErrorView).snippet())
+                errormessages.append(str(key) + ', ')
+            return ''.join(errormessages[0:-1])
+        return ""
+
+    def getInputValue(self):
+        """Return converted and validated widget data.
+
+        The value for this field will be represented as an `ObjectStorage`
+        instance which holds the subfield values as attributes. It will
+        need to be converted by higher-level code into some more useful
+        object (note that the default EditView calls `applyChanges`, which
+        does this).
+        """
+
+        errors = []
+        content = self.factory()
+        for name in self.names:
+            try:
+                setattr(content, name, self.getSubWidget(name).getInputValue())
+            except Exception, e:
+                errors.append(e)
+                if self._error is None:
+                    self._error = {}
+                
+                if name not in self._error:
+                    self._error[name] = e
+
+        if errors:
+            raise errors[0]
+
+        return content
+
+
+    def applyChanges(self, content):
+        field = self.context
+
+        # create our new object value
+        value = field.query(content, None)
+        if value is None:
+            # TODO: ObjectCreatedEvent here would be nice
+            value = self.factory()
+
+        # apply sub changes, see if there *are* any changes
+        # TODO: ObjectModifiedEvent here would be nice
+        changes = applyWidgetsChanges(self, field.schema, target=value,
+                                      names=self.names)
+
+        # if there's changes, then store the new value on the content
+        if changes:
+            field.set(content, value)
+        # TODO: If value implements ILocation, set name to field name and
+        # parent to content
+
+        return changes
+
+    def hasInput(self):
+        """Is there input data for the field
+
+        Return ``True`` if there is data and ``False`` otherwise.
+        """
+        for name in self.names:
+            if self.getSubWidget(name).hasInput():
+                return True
+        return False
+
+    def setRenderedValue(self, value):
+        """Set the default data for the widget.
+
+        The given value should be used even if the user has entered
+        data.
+        """
+        # re-call setupwidgets with the content
+        self._setUpEditWidgets()
+        for name in self.names:
+            self.getSubWidget(name).setRenderedValue(getattr(value, name, None))

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/orderedSelectionList.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/orderedSelectionList.pt	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,198 @@
+<script type="text/javascript">
+
+function moveItems(from, to)
+  {
+  // shortcuts for selection fields
+  var src = document.getElementById(from);
+  var tgt = document.getElementById(to);
+
+  if (src.selectedIndex == -1) selectionError();
+  else
+    {
+    // iterate over all selected items
+    // --> attribute "selectedIndex" doesn't support multiple selection.
+    // Anyway, it works here, as a moved item isn't selected anymore,
+    // thus "selectedIndex" indicating the "next" selected item :)
+    while (src.selectedIndex > -1)
+      if (src.options[src.selectedIndex].selected)
+        {
+        // create a new virtal object with values of item to copy
+        temp = new Option(src.options[src.selectedIndex].text,
+                      src.options[src.selectedIndex].value);
+        // append virtual object to targe
+        tgt.options[tgt.length] = temp;
+        // want to select newly created item
+        temp.selected = true;
+        // delete moved item in source
+        src.options[src.selectedIndex] = null;
+      }
+    }
+  }
+
+// move item from "from" selection to "to" selection
+function from2to(name)
+  {
+  moveItems(name+".from", name+".to");
+  copyDataForSubmit(name);
+  }
+
+// move item from "to" selection back to "from" selection
+function to2from(name)
+  {
+  moveItems(name+".to", name+".from");
+  copyDataForSubmit(name);
+  }
+
+function swapFields(a, b)
+  {
+  // swap text
+  var temp = a.text;
+  a.text = b.text;
+  b.text = temp;
+  // swap value
+  temp = a.value;
+  a.value = b.value;
+  b.value = temp;
+  // swap selection
+  temp = a.selected;
+  a.selected = b.selected;
+  b.selected = temp;
+  }
+
+// move selected item in "to" selection one up
+function moveUp(name)
+  {
+  // shortcuts for selection field
+  var toSel = document.getElementById(name+".to");
+
+  if (toSel.selectedIndex == -1)
+      selectionError();
+  else if (toSel.options[0].selected)
+      alert("Cannot move further up!");
+  else for (var i = 0; toSel.length > i; i++)
+    if (toSel.options[i].selected)
+      {
+      swapFields(toSel.options[i-1], toSel.options[i]);
+      copyDataForSubmit(name);
+      }
+  }
+
+// move selected item in "to" selection one down
+function moveDown(name)
+  {
+    // shortcuts for selection field
+    var toSel = document.getElementById(name+".to");
+
+    if (toSel.selectedIndex == -1) {
+        selectionError();
+    } else if (toSel.options[toSel.length-1].selected) {
+        alert("Cannot move further down!");
+    } else {
+      for (var i = toSel.length-1; i >= 0; i--) {
+        if (toSel.options[i].selected) {
+          swapFields(toSel.options[i+1], toSel.options[i]);
+        }
+      }
+      copyDataForSubmit(name);
+    }
+  }
+
+// copy each item of "toSel" into one hidden input field
+function copyDataForSubmit(name)
+  {
+  // shortcuts for selection field and hidden data field
+  var toSel = document.getElementById(name+".to");
+  var toDataContainer = document.getElementById(name+".toDataContainer");
+
+  // delete all child nodes (--> complete content) of "toDataContainer" span
+  while (toDataContainer.hasChildNodes())
+      toDataContainer.removeChild(toDataContainer.firstChild);
+
+  // create new hidden input fields - one for each selection item of
+  // "to" selection
+  for (var i = 0; toSel.options.length > i; i++)
+    {
+    // create virtual node with suitable attributes
+    var newNode = document.createElement("input");
+    var newAttr = document.createAttribute("name");
+    newAttr.nodeValue = name;
+    newNode.setAttributeNode(newAttr);
+
+    newAttr = document.createAttribute("type");
+    newAttr.nodeValue = "hidden";
+    newNode.setAttributeNode(newAttr);
+
+    newAttr = document.createAttribute("value");
+    newAttr.nodeValue = toSel.options[i].value;
+    newNode.setAttributeNode(newAttr);
+
+    // actually append virtual node to DOM tree
+    toDataContainer.appendChild(newNode);
+    }
+  }
+
+// error message for missing selection
+function selectionError()
+  {alert("Must select something!")}
+
+</script>
+
+<table border="0" class="ordered-selection-field">
+  <tr>
+    <td>
+      <select id="from" name="from" size="5" multiple=""
+          tal:attributes="name string:${view/name}.from;
+                          id string:${view/name}.from;
+                          size view/size"
+                          >
+        <option tal:repeat="entry view/choices"
+                tal:attributes="value entry/value"
+                tal:content="entry/text" i18n:translate=""/>
+      </select>
+    </td>
+    <td>
+      <button name="from2toButton" type="button" value=" -&gt;"
+          onclick="javascript:from2to()"
+          tal:attributes="onClick string:javascript:from2to('${view/name}')"
+          >&nbsp;-&gt;</button>
+      <br />
+      <button name="to2fromButton" type="button" value="&lt;- "
+          onclick="javascript:to2from()"
+          tal:attributes="onClick string:javascript:to2from('${view/name}')"
+          >&lt;-&nbsp;</button>
+    </td>
+    <td>
+      <select id="to" name="to" size="5" multiple=""
+          tal:attributes="name string:${view/name}.to;
+                          id string:${view/name}.to;
+                          size view/size">
+        <option tal:repeat="entry view/selected"
+                tal:attributes="value entry/value"
+                tal:content="entry/text" i18n:translate=""/>
+      </select>
+      <input name="foo-empty-marker" type="hidden"
+        tal:attributes="name string:${view/name}-empty-marker"/>
+      <span id="toDataContainer"
+            tal:attributes="id string:${view/name}.toDataContainer">
+        <script type="text/javascript" tal:content="string:
+          copyDataForSubmit('${view/name}');">
+          // initial copying of field "field.to" --> "field"
+          copyDataForSubmit("<i tal:replace="${view/name}"/>");
+        </script>
+      </span>
+    </td>
+    <td>
+      <button
+          name="upButton" type="button" value="^"
+          onclick="javascript:moveUp()"
+          tal:attributes="onClick string:javascript:moveUp('${view/name}')"
+          >^</button>
+      <br />
+      <button
+          name="downButton" type="button" value="v"
+          onclick="javascript:moveDown()"
+          tal:attributes="onClick string:javascript:moveDown('${view/name}')"
+          >v</button>
+    </td>
+  </tr>
+</table>

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt (from rev 107362, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/sequencewidget.pt)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.pt	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,29 @@
+<table border="0" class="sequencewidget"
+  i18n:domain="zope">
+  <tr tal:repeat="widget view/widgets">
+    <td>
+      <input class="editcheck" type="checkbox"
+             tal:attributes="
+                 name string:${view/name}.remove_${repeat/widget/index}"
+             tal:condition="view/need_delete" />
+    </td>
+    <td>
+      <span tal:define="error widget/error"
+            tal:replace="structure error" tal:condition="error" />
+      <input tal:replace="structure widget" />
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2">
+      <input type="submit" value="Remove selected items"
+             tal:condition="view/need_delete"
+             tal:attributes="name string:${view/name}.remove"
+             i18n:attributes="value remove-selected-items" />
+      <input type="submit" value="Add foo"
+             tal:condition="view/need_add"
+             tal:attributes="name string:${view/name}.add;
+                             value view/addButtonLabel" />
+    </td>
+  </tr>
+</table>
+<input tal:replace="structure view/marker" />

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py (from rev 107376, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/sequencewidget.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/sequencewidget.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,315 @@
+##############################################################################
+#
+# 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 widgets for sequences
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import component
+from zope.interface import implements
+from zope.i18n import translate
+from zope.schema.interfaces import ValidationError
+
+from zope.formlib.interfaces import IDisplayWidget, IInputWidget
+from zope.formlib.interfaces import WidgetInputError, MissingInputError
+from zope.formlib.widget import InputWidget
+from zope.app.form.browser.i18n import _
+from zope.formlib.widget import BrowserWidget
+from zope.formlib.widget import DisplayWidget, renderElement
+from zope.browserpage import ViewPageTemplateFile
+
+
+class SequenceWidget(BrowserWidget, InputWidget):
+    """A widget baseclass for a sequence of fields.
+
+    subwidget  - Optional CustomWidget used to generate widgets for the
+                 items in the sequence
+    """
+
+    implements(IInputWidget)
+
+    template = ViewPageTemplateFile('sequencewidget.pt')
+
+    _type = tuple
+
+    def __init__(self, context, field, request, subwidget=None):
+        super(SequenceWidget, self).__init__(context, request)
+        self.subwidget = subwidget
+
+        # The subwidgets are cached in this dict if preserve_widgets is True.
+        self._widgets = {}
+        self.preserve_widgets = False
+
+    def __call__(self):
+        """Render the widget"""
+        self._update()
+        return self.template()
+
+    def _update(self):
+        """Set various attributes for the template"""
+        sequence = self._getRenderedValue()
+        num_items = len(sequence)
+        self.need_add = (not self.context.max_length
+                         or num_items < self.context.max_length)
+        self.need_delete = num_items and num_items > self.context.min_length
+        self.marker = self._getPresenceMarker(num_items)
+
+    def widgets(self):
+        """Return a list of widgets to display"""
+        sequence = self._getRenderedValue()
+        result = []
+        for i, value in enumerate(sequence):
+            widget = self._getWidget(i)
+            widget.setRenderedValue(value)
+            result.append(widget)
+        return result
+
+    def addButtonLabel(self):
+        button_label = _('Add %s')
+        button_label = translate(button_label, context=self.request,
+                                 default=button_label)
+        title = self.context.title or self.context.__name__
+        title = translate(title, context=self.request, default=title)
+        return button_label % title
+
+
+    def _getWidget(self, i):
+        """Return a widget for the i-th number of the sequence.
+
+        Normally this method creates a new widget each time, but when
+        the ``preserve_widgets`` attribute is True, it starts caching
+        widgets.  We need it so that the errors on the subwidgets
+        would appear only if ``getInputValue`` was called.
+
+        ``getInputValue`` on the subwidgets gets called on each
+        request that has data.
+        """
+        if i not in self._widgets:
+            field = self.context.value_type
+            if self.subwidget is not None:
+                widget = self.subwidget(field, self.request)
+            else:
+                widget = component.getMultiAdapter(
+                    (field, self.request), IInputWidget)
+            widget.setPrefix('%s.%d.' % (self.name, i))
+            if not self.preserve_widgets:
+                return widget
+            self._widgets[i] = widget
+        return self._widgets[i]
+
+    def hidden(self):
+        """Render the list as hidden fields."""
+        # length of sequence info
+        sequence = self._getRenderedValue()
+        num_items = len(sequence)
+
+        # generate hidden fields for each value
+        parts = [self._getPresenceMarker(num_items)]
+        for i in range(num_items):
+            value = sequence[i]
+            widget = self._getWidget(i)
+            widget.setRenderedValue(value)
+            parts.append(widget.hidden())
+        return "\n".join(parts)
+
+    def _getRenderedValue(self):
+        """Returns a sequence from the request or _data"""
+        if self._renderedValueSet():
+            if self._data is self.context.missing_value:
+                sequence = []
+            else:
+                sequence = list(self._data)
+        elif self.hasInput():
+            sequence = self._generateSequence()
+        elif self.context.default is not None:
+            sequence = self.context.default
+        else:
+            sequence = []
+        # ensure minimum number of items in the form
+        while len(sequence) < self.context.min_length:
+            # Shouldn't this use self.field.value_type.missing_value,
+            # instead of None?
+            sequence.append(None)
+        return sequence
+
+    def _getPresenceMarker(self, count=0):
+        return ('<input type="hidden" name="%s.count" value="%d" />'
+                % (self.name, count))
+
+    def getInputValue(self):
+        """Return converted and validated widget data.
+
+        If there is no user input and the field is required, then a
+        ``MissingInputError`` will be raised.
+
+        If there is no user input and the field is not required, then
+        the field default value will be returned.
+
+        A ``WidgetInputError`` is raised in the case of one or more
+        errors encountered, inputting, converting, or validating the data.
+        """
+        if self.hasInput():
+            self.preserve_widgets = True
+            sequence = self._type(self._generateSequence())
+            if sequence != self.context.missing_value:
+                # catch and set field errors to ``_error`` attribute
+                try:
+                    self.context.validate(sequence)
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
+                except ValidationError, error:
+                    self._error = WidgetInputError(
+                        self.context.__name__, self.label, error)
+                    raise self._error
+            elif self.context.required:
+                raise MissingInputError(self.context.__name__,
+                                        self.context.title)
+            return sequence
+        raise MissingInputError(self.context.__name__, self.context.title)
+
+    # TODO: applyChanges isn't reporting "change" correctly (we're
+    # re-generating the sequence with every edit, and need to be smarter)
+    def applyChanges(self, content):
+        field = self.context
+        value = self.getInputValue()
+        change = field.query(content, self) != value
+        if change:
+            field.set(content, value)
+        return change
+
+    def hasInput(self):
+        """Is there input data for the field
+
+        Return ``True`` if there is data and ``False`` otherwise.
+        """
+        return (self.name + ".count") in self.request.form
+
+    def _generateSequence(self):
+        """Extract the values of the subwidgets from the request.
+
+        Returns a list of values.
+
+        This can only be called if self.hasInput() returns true.
+        """
+        if self.context.value_type is None:
+            # Why would this ever happen?
+            return []
+        # the marker field tells how many individual items were
+        # included in the input; we check for exactly that many input
+        # widgets
+        try:
+            count = int(self.request.form[self.name + ".count"])
+        except ValueError:
+            # could not convert to int; the input was not generated
+            # from the widget as implemented here
+            raise WidgetInputError(self.context.__name__, self.context.title)
+
+        # pre-populate
+        sequence = [None] * count
+
+        # now look through the request for interesting values
+        # in reverse so that we can remove items as we go
+        removing = self.name + ".remove" in self.request.form
+        for i in reversed(range(count)):
+            widget = self._getWidget(i)
+            if widget.hasValidInput():
+                # catch and set sequence widget errors to ``_error`` attribute
+                try:
+                    sequence[i] = widget.getInputValue()
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
+
+            remove_key = "%s.remove_%d" % (self.name, i)
+            if remove_key in self.request.form and removing:
+                del sequence[i]
+
+        # add an entry to the list if the add button has been pressed
+        if self.name + ".add" in self.request.form:
+            # Should this be using self.context.value_type.missing_value
+            # instead of None?
+            sequence.append(None)
+
+        return sequence
+
+
+class TupleSequenceWidget(SequenceWidget):
+    _type = tuple
+
+
+class ListSequenceWidget(SequenceWidget):
+    _type = list
+
+
+# Basic display widget
+
+class SequenceDisplayWidget(DisplayWidget):
+
+    _missingValueMessage = _("sequence-value-not-provided",
+                             u"(no value available)")
+
+    _emptySequenceMessage = _("sequence-value-is-empty",
+                              u"(no values)")
+
+    tag = "ol"
+    itemTag = "li"
+    cssClass = "sequenceWidget"
+    extra = ""
+
+    def __init__(self, context, field, request, subwidget=None):
+        super(SequenceDisplayWidget, self).__init__(context, request)
+        self.subwidget = subwidget
+
+    def __call__(self):
+        # get the data to display:
+        if self._renderedValueSet():
+            data = self._data
+        else:
+            data = self.context.get(self.context.context)
+
+        # deal with special cases:
+        if data == self.context.missing_value:
+            return translate(self._missingValueMessage, self.request)
+        data = list(data)
+        if not data:
+            return translate(self._emptySequenceMessage, self.request)
+
+        parts = []
+        for i, item in enumerate(data):
+            widget = self._getWidget(i)
+            widget.setRenderedValue(item)
+            s = widget()
+            if self.itemTag:
+                s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
+            parts.append(s)
+        contents = "\n".join(parts)
+        if self.tag:
+            contents = "\n%s\n" % contents
+            contents = renderElement(self.tag,
+                                     cssClass=self.cssClass,
+                                     extra=self.extra,
+                                     contents=contents)
+        return contents
+
+    def _getWidget(self, i):
+        field = self.context.value_type
+        if self.subwidget is not None:
+            widget = self.subwidget(field, self.request)
+        else:
+            widget = component.getMultiAdapter(
+                (field, self.request), IDisplayWidget)
+        widget.setPrefix('%s.%d.' % (self.name, i))
+        return widget

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/textwidgets.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/textwidgets.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,685 @@
+##############################################################################
+#
+# 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 widgets with text-based input
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import decimal
+from xml.sax import saxutils
+from zope.interface import implements
+from zope.datetime import parseDatetimetz
+from zope.datetime import DateTimeError
+from zope.i18n.format import DateTimeParseError
+
+from zope.formlib.interfaces import IInputWidget, ConversionError
+from zope.app.form.browser.i18n import _
+from zope.app.form.browser.interfaces import ITextBrowserWidget
+from zope.formlib.widget import SimpleInputWidget, renderElement
+from zope.formlib.widget import DisplayWidget
+
+
+def escape(str):
+    if str is not None:
+        str = saxutils.escape(str)
+    return str
+
+class TextWidget(SimpleInputWidget):
+    """Text widget.
+
+    Single-line text (unicode) input
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import TextLine
+    >>> field = TextLine(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Bob'})
+    >>> widget = TextWidget(field, request)
+    >>> widget.hasInput()
+    True
+    >>> widget.getInputValue()
+    u'Bob'
+
+    >>> def normalize(s):
+    ...   return '\\n  '.join(filter(None, s.split(' ')))
+
+    >>> print normalize( widget() )
+    <input
+      class="textType"
+      id="field.foo"
+      name="field.foo"
+      size="20"
+      type="text"
+      value="Bob"
+      />
+
+    >>> print normalize( widget.hidden() )
+    <input
+      class="hiddenType"
+      id="field.foo"
+      name="field.foo"
+      type="hidden"
+      value="Bob"
+      />
+
+    Calling `setRenderedValue` will change what gets output:
+
+    >>> widget.setRenderedValue("Barry")
+    >>> print normalize( widget() )
+    <input
+      class="textType"
+      id="field.foo"
+      name="field.foo"
+      size="20"
+      type="text"
+      value="Barry"
+      />
+
+    Check that HTML is correctly encoded and decoded:
+
+    >>> request = TestRequest(
+    ...     form={'field.foo': u'<h1>&copy;</h1>'})
+    >>> widget = TextWidget(field, request)
+    >>> widget.getInputValue()
+    u'<h1>&copy;</h1>'
+
+    >>> print normalize( widget() )
+    <input
+      class="textType"
+      id="field.foo"
+      name="field.foo"
+      size="20"
+      type="text"
+      value="&lt;h1&gt;&amp;copy;&lt;/h1&gt;"
+      />
+    """
+
+    implements(ITextBrowserWidget)
+
+    default = ''
+    displayWidth = 20
+    displayMaxWidth = ""
+    extra = ''
+    style = ''
+    convert_missing_value = True
+
+    def __init__(self, *args):
+        super(TextWidget, self).__init__(*args)
+
+    def __call__(self):
+        value = self._getFormValue()
+        if value is None or value == self.context.missing_value:
+            value = ''
+
+        kwargs = {'type': self.type,
+                  'name': self.name,
+                  'id': self.name,
+                  'value': value,
+                  'cssClass': self.cssClass,
+                  'style': self.style,
+                  'size': self.displayWidth,
+                  'extra': self.extra}
+        if self.displayMaxWidth:
+            kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.
+
+        return renderElement(self.tag, **kwargs)
+
+    def _toFieldValue(self, input):
+        if self.convert_missing_value and input == self._missing:
+            value = self.context.missing_value
+        else:
+            # We convert everything to unicode. This might seem a bit crude,
+            # but anything contained in a TextWidget should be representable
+            # as a string. Note that you always have the choice of overriding
+            # the method.
+            try:
+                value = unicode(input)
+            except ValueError, v:
+                raise ConversionError(_("Invalid text data"), v)
+        return value
+
+
+class Bytes(SimpleInputWidget):
+
+    def _toFieldValue(self, input):
+        value = super(Bytes, self)._toFieldValue(input)
+        if type(value) is unicode:
+            try:
+                value = value.encode('ascii')
+            except UnicodeError, v:
+                raise ConversionError(_("Invalid textual data"), v)
+        return value
+
+class BytesWidget(Bytes, TextWidget):
+    """Bytes widget.
+
+    Single-line data (string) input
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import BytesLine
+    >>> field = BytesLine(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Bob'})
+    >>> widget = BytesWidget(field, request)
+    >>> widget.hasInput()
+    True
+    >>> widget.getInputValue()
+    'Bob'
+    """
+
+class BytesDisplayWidget(DisplayWidget):
+    """Bytes display widget"""
+
+    def __call__(self):
+        if self._renderedValueSet():
+            content = self._data
+        else:
+            content = self.context.default
+        return renderElement("pre", contents=escape(content))
+
+class ASCII(Bytes):
+    """ASCII"""
+
+
+class ASCIIWidget(BytesWidget):
+    """ASCII widget.
+
+    Single-line data (string) input
+    """
+
+class ASCIIDisplayWidget(BytesDisplayWidget):
+    """ASCII display widget"""
+
+class URIDisplayWidget(DisplayWidget):
+    """URI display widget.
+
+    :ivar linkTarget:
+      The value of the ``target`` attribute for the generated hyperlink.
+      If this is not set, no ``target`` attribute is generated.
+
+    """
+
+    linkTarget = None
+
+    def __call__(self):
+        if self._renderedValueSet():
+            content = self._data
+        else:
+            content = self.context.default
+        if not content:
+            # If there is no content it is not useful to render an anchor.
+            return ''
+        content = escape(content)
+        kw = dict(contents=content, href=content)
+        if self.linkTarget:
+            kw["target"] = self.linkTarget
+        return renderElement("a", **kw)
+
+
+class TextAreaWidget(SimpleInputWidget):
+    """TextArea widget.
+
+    Multi-line text (unicode) input.
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Text
+    >>> field = Text(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+    >>> widget = TextAreaWidget(field, request)
+    >>> widget.hasInput()
+    True
+    >>> widget.getInputValue()
+    u'Hello\\nworld!'
+
+    >>> def normalize(s):
+    ...   return '\\n  '.join(filter(None, s.split(' ')))
+
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.foo"
+      name="field.foo"
+      rows="15"
+      >Hello\r
+    world!</textarea>
+
+    >>> print normalize( widget.hidden() )
+    <input
+      class="hiddenType"
+      id="field.foo"
+      name="field.foo"
+      type="hidden"
+      value="Hello&#13;&#10;world!"
+      />
+
+    Calling `setRenderedValue` will change what gets output:
+
+    >>> widget.setRenderedValue("Hey\\ndude!")
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.foo"
+      name="field.foo"
+      rows="15"
+      >Hey\r
+    dude!</textarea>
+
+    Check that HTML is correctly encoded and decoded:
+
+    >>> request = TestRequest(
+    ...     form={'field.foo': u'<h1>&copy;</h1>'})
+    >>> widget = TextAreaWidget(field, request)
+    >>> widget.getInputValue()
+    u'<h1>&copy;</h1>'
+
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.foo"
+      name="field.foo"
+      rows="15"
+      >&lt;h1&gt;&amp;copy;&lt;/h1&gt;</textarea>
+
+    There was a but which caused the content of <textarea> tags not to be
+    rendered correctly when there was a conversion error. Make sure the quoting
+    works correctly::
+
+    >>> from zope.schema import Text
+    >>> field = Text(__name__='description', title=u'Description')
+
+    >>> from zope.formlib.interfaces import ConversionError
+    >>> class TestTextAreaWidget(TextAreaWidget):
+    ...     def _toFieldValue(self, input):
+    ...         if 'foo' in input:
+    ...             raise ConversionError("I don't like foo.")
+    ...         return input
+    ...
+
+    >>> request = TestRequest(form={'field.description': u'<p>bar</p>'})
+    >>> widget = TestTextAreaWidget(field, request)
+    >>> widget.getInputValue()
+    u'<p>bar</p>'
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.description"
+      name="field.description"
+      rows="15"
+      >&lt;p&gt;bar&lt;/p&gt;</textarea>
+
+    >>> request = TestRequest(form={'field.description': u'<p>foo</p>'})
+    >>> widget = TestTextAreaWidget(field, request)
+    >>> try:
+    ...     widget.getInputValue()
+    ... except ConversionError, error:
+    ...     print error.doc()
+    I don't like foo.
+    >>> print normalize( widget() )
+    <textarea
+      cols="60"
+      id="field.description"
+      name="field.description"
+      rows="15"
+      >&lt;p&gt;foo&lt;/p&gt;</textarea>
+    """
+
+    default = ""
+    width = 60
+    height = 15
+    extra = ""
+    style = ''
+
+    def _toFieldValue(self, value):
+        value = super(TextAreaWidget, self)._toFieldValue(value)
+        if value:
+            try:
+                value = unicode(value)
+            except ValueError, v:
+                raise ConversionError(_("Invalid unicode data"), v)
+            else:
+                value = value.replace("\r\n", "\n")
+        return value
+
+    def _toFormValue(self, value):
+        value = super(TextAreaWidget, self)._toFormValue(value)
+        if value:
+            value = value.replace("\n", "\r\n")
+        else:
+            value = u''
+
+        return value
+
+    def __call__(self):
+        return renderElement("textarea",
+                             name=self.name,
+                             id=self.name,
+                             cssClass=self.cssClass,
+                             rows=self.height,
+                             cols=self.width,
+                             style=self.style,
+                             contents=escape(self._getFormValue()),
+                             extra=self.extra)
+
+class BytesAreaWidget(Bytes, TextAreaWidget):
+    """BytesArea widget.
+
+    Multi-line string input.
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import Bytes
+    >>> field = Bytes(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+    >>> widget = BytesAreaWidget(field, request)
+    >>> widget.hasInput()
+    True
+    >>> widget.getInputValue()
+    'Hello\\nworld!'
+    """
+
+class ASCIIAreaWidget(ASCII, TextAreaWidget):
+    """ASCIIArea widget.
+
+    Multi-line string input.
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.schema import ASCII
+    >>> field = ASCII(__name__='foo', title=u'on')
+    >>> request = TestRequest(form={'field.foo': u'Hello\\r\\nworld!'})
+    >>> widget = ASCIIAreaWidget(field, request)
+    >>> widget.hasInput()
+    True
+    >>> widget.getInputValue()
+    'Hello\\nworld!'
+    """
+
+class PasswordWidget(TextWidget):
+    """Password Widget"""
+
+    type = 'password'
+
+    def __call__(self):
+        displayMaxWidth = self.displayMaxWidth or 0
+        if displayMaxWidth > 0:
+            return renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 value='',
+                                 cssClass=self.cssClass,
+                                 style=self.style,
+                                 size=self.displayWidth,
+                                 maxlength=displayMaxWidth,
+                                 extra=self.extra)
+        else:
+            return renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 value='',
+                                 cssClass=self.cssClass,
+                                 style=self.style,
+                                 size=self.displayWidth,
+                                 extra=self.extra)
+
+    def _toFieldValue(self, input):
+        try:
+            existing = self.context.get(self.context.context)
+        except AttributeError:
+            existing = False
+        if (not input) and existing:
+            return self.context.UNCHANGED_PASSWORD
+        return super(PasswordWidget, self)._toFieldValue(input)
+
+    def hidden(self):
+        raise NotImplementedError(
+            'Cannot get a hidden tag for a password field')
+
+
+class FileWidget(TextWidget):
+    """File Widget"""
+
+    type = 'file'
+
+    def __call__(self):
+        displayMaxWidth = self.displayMaxWidth or 0
+        hidden = renderElement(self.tag,
+                               type='hidden',
+                               name=self.name+".used",
+                               id=self.name+".used",
+                               value="")
+        if displayMaxWidth > 0:
+            elem = renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 size=self.displayWidth,
+                                 maxlength=displayMaxWidth,
+                                 extra=self.extra)
+        else:
+            elem = renderElement(self.tag,
+                                 type=self.type,
+                                 name=self.name,
+                                 id=self.name,
+                                 cssClass=self.cssClass,
+                                 size=self.displayWidth,
+                                 extra=self.extra)
+        return "%s %s" % (hidden, elem)
+
+    def _toFieldValue(self, input):
+        if input is None or input == '':
+            return self.context.missing_value
+        try:
+            seek = input.seek
+            read = input.read
+        except AttributeError, e:
+            raise ConversionError(_('Form input is not a file object'), e)
+        else:
+            seek(0)
+            data = read()
+            if data or getattr(input, 'filename', ''):
+                return data
+            else:
+                return self.context.missing_value
+
+    def hasInput(self):
+        return ((self.name+".used" in self.request.form)
+                or
+                (self.name in self.request.form)
+                )
+
+class IntWidget(TextWidget):
+    """Integer number widget.
+
+    Let's make sure that zeroes are rendered properly:
+
+    >>> from zope.schema import Int
+    >>> field = Int(__name__='foo', title=u'on')
+    >>> widget = IntWidget(field, None)
+    >>> widget.setRenderedValue(0)
+
+    >>> 'value="0"' in widget()
+    True
+
+    """
+
+    displayWidth = 10
+
+    def _toFieldValue(self, input):
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                return int(input)
+            except ValueError, v:
+                raise ConversionError(_("Invalid integer data"), v)
+
+
+class FloatWidget(TextWidget):
+    implements(IInputWidget)
+    displayWidth = 10
+
+    def _toFieldValue(self, input):
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                return float(input)
+            except ValueError, v:
+                raise ConversionError(_("Invalid floating point data"), v)
+
+class DecimalWidget(TextWidget):
+    implements(IInputWidget)
+    displayWidth = 10
+
+    def _toFieldValue(self, input):
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                return decimal.Decimal(input)
+            except decimal.InvalidOperation, v:
+                raise ConversionError(_("Invalid decimal data"), v)
+
+    def _toFormValue(self, value):
+        if value == self.context.missing_value:
+            value = self._missing
+        else:
+            return unicode(value)
+
+
+class DatetimeWidget(TextWidget):
+    """Datetime entry widget."""
+
+    displayWidth = 20
+
+    def _toFieldValue(self, input):
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                # TODO: Currently datetimes return in local (server)
+                # time zone if no time zone information was given.
+                # Maybe offset-naive datetimes should be returned in
+                # this case? (DV)
+                return parseDatetimetz(input)
+            except (DateTimeError, ValueError, IndexError), v:
+                raise ConversionError(_("Invalid datetime data"), v)
+
+class DateWidget(DatetimeWidget):
+    """Date entry widget.
+    """
+
+    def _toFieldValue(self, input):
+        v = super(DateWidget, self)._toFieldValue(input)
+        if v != self.context.missing_value:
+            v = v.date()
+        return v
+
+class DateI18nWidget(TextWidget):
+    """I18n date entry widget.
+
+    The `displayStyle` attribute may be set to control the formatting of the
+    value.
+
+    `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+    or None ('' is accepted an an alternative to None to support
+    provision of a value from ZCML).
+    """
+
+    _category = "date"
+
+    displayWidth = 20
+
+    displayStyle = None
+
+    def _toFieldValue(self, input):
+        if input == self._missing:
+            return self.context.missing_value
+        else:
+            try:
+                formatter = self.request.locale.dates.getFormatter(
+                    self._category, (self.displayStyle or None))
+                return formatter.parse(input)
+            except (DateTimeParseError, ValueError), v:
+                raise ConversionError(_("Invalid datetime data"),
+                    "%s (%r)" % (v, input))
+
+    def _toFormValue(self, value):
+        value = super(DateI18nWidget, self)._toFormValue(value)
+        if value:
+            formatter = self.request.locale.dates.getFormatter(
+                self._category, (self.displayStyle or None))
+            value = formatter.format(value)
+        return value
+
+class DatetimeI18nWidget(DateI18nWidget):
+    """I18n datetime entry widget.
+
+    The `displayStyle` attribute may be set to control the formatting of the
+    value.
+
+    `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+    or None ('' is accepted an an alternative to None to support
+    provision of a value from ZCML).
+
+    NOTE: If you need timezone information you need to set `displayStyle`
+    to either 'long' or 'full' since other display styles just ignore it.
+    """
+
+    _category = "dateTime"
+
+class DateDisplayWidget(DisplayWidget):
+    """Date display widget.
+
+    The `cssClass` and `displayStyle` attributes may be set to control
+    the formatting of the value.
+
+    `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+    or None ('' is accepted an an alternative to None to support
+    provision of a value from ZCML).
+    """
+
+    cssClass = "date"
+    displayStyle = None
+
+    _category = "date"
+
+    def __call__(self):
+        if self._renderedValueSet():
+            content = self._data
+        else:
+            content = self.context.default
+        if content == self.context.missing_value:
+            return ""
+        formatter = self.request.locale.dates.getFormatter(
+            self._category, (self.displayStyle or None))
+        content = formatter.format(content)
+        return renderElement("span", contents=escape(content),
+                             cssClass=self.cssClass)
+
+
+class DatetimeDisplayWidget(DateDisplayWidget):
+    """Datetime display widget.
+
+    The `cssClass` and `displayStyle` attributes may be set to control
+    the formatting of the value.
+
+    `displayStyle` must be one of 'full', 'long', 'medium', 'short',
+    or None ('' is accepted an an alternative to None to support
+    provision of a value from ZCML).
+    """
+
+    cssClass = "dateTime"
+
+    _category = "dateTime"

Copied: zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py (from rev 107373, zope.app.form/branches/faassen-zaf/src/zope/app/form/browser/__init__.py)
===================================================================
--- zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py	                        (rev 0)
+++ zope.formlib/branches/faassen-zaf/src/zope/formlib/widgets.py	2009-12-30 19:46:12 UTC (rev 107378)
@@ -0,0 +1,80 @@
+##############################################################################
+#
+# 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 widgets
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope.formlib.widget import BrowserWidget, DisplayWidget
+from zope.formlib.widget import UnicodeDisplayWidget
+
+from zope.formlib.textwidgets import TextWidget, BytesWidget
+from zope.formlib.textwidgets import TextAreaWidget, BytesAreaWidget
+from zope.formlib.textwidgets import PasswordWidget, FileWidget
+from zope.formlib.textwidgets import ASCIIWidget, ASCIIAreaWidget
+from zope.formlib.textwidgets import IntWidget, FloatWidget
+from zope.formlib.textwidgets import DecimalWidget
+from zope.formlib.textwidgets import DatetimeWidget, DateWidget
+from zope.formlib.textwidgets import DatetimeI18nWidget
+from zope.formlib.textwidgets import DateI18nWidget
+from zope.formlib.textwidgets import DatetimeDisplayWidget
+from zope.formlib.textwidgets import DateDisplayWidget
+from zope.formlib.textwidgets import BytesDisplayWidget
+from zope.formlib.textwidgets import ASCIIDisplayWidget
+from zope.formlib.textwidgets import URIDisplayWidget
+
+# Widgets for boolean fields
+from zope.formlib.boolwidgets import CheckBoxWidget
+from zope.formlib.boolwidgets import BooleanRadioWidget
+from zope.formlib.boolwidgets import BooleanSelectWidget
+from zope.formlib.boolwidgets import BooleanDropdownWidget
+
+# Choice and Sequence Display Widgets
+from zope.formlib.itemswidgets import ItemDisplayWidget
+from zope.formlib.itemswidgets import ItemsMultiDisplayWidget
+from zope.formlib.itemswidgets import SetDisplayWidget
+from zope.formlib.itemswidgets import ListDisplayWidget
+
+# Widgets for fields with vocabularies.
+# Note that these are only dispatchers for the widgets below.
+from zope.formlib.itemswidgets import ChoiceDisplayWidget
+from zope.formlib.itemswidgets import ChoiceInputWidget
+from zope.formlib.itemswidgets import CollectionDisplayWidget
+from zope.formlib.itemswidgets import CollectionInputWidget
+from zope.formlib.itemswidgets import ChoiceCollectionDisplayWidget
+from zope.formlib.itemswidgets import ChoiceCollectionInputWidget
+
+# Widgets that let you choose a single item from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.formlib.itemswidgets import SelectWidget
+from zope.formlib.itemswidgets import DropdownWidget
+from zope.formlib.itemswidgets import RadioWidget
+
+# Widgets that let you choose several items from a list
+# These widgets are multi-views on (field, vocabulary)
+from zope.formlib.itemswidgets import MultiSelectWidget
+from zope.formlib.itemswidgets import MultiSelectSetWidget
+from zope.formlib.itemswidgets import MultiSelectFrozenSetWidget
+from zope.formlib.itemswidgets import MultiCheckBoxWidget
+from zope.formlib.itemswidgets import OrderedMultiSelectWidget
+
+# Widgets that let you enter several items in a sequence
+# These widgets are multi-views on (sequence type, value type)
+from zope.formlib.sequencewidget import SequenceWidget
+from zope.formlib.sequencewidget import TupleSequenceWidget
+from zope.formlib.sequencewidget import ListSequenceWidget
+from zope.formlib.sequencewidget import SequenceDisplayWidget
+
+from zope.formlib.objectwidget import ObjectWidget



More information about the Zope3-Checkins mailing list