Index: /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.pt
===================================================================
--- /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.pt	(revision 5290)
+++ /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.pt	(revision 5290)
@@ -0,0 +1,46 @@
+<table border="0" class="sequencewidget"
+  i18n:domain="zope">
+  <div tal:repeat="widget view/widgets"
+       tal:omit-tag="">
+    <tr tal:condition="repeat/widget/start">
+      <th></th>
+      <th>
+	<span tal:replace="python: widget[0].label">Key Label</span>
+      </th>
+      <th>
+	<span tal:replace="python: widget[1].label">Value Label</span>
+      </th>
+    </tr>
+    <tr>
+      <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 python: widget[0].error"
+	      tal:replace="structure error" tal:condition="error" />
+	<input tal:replace="structure python: widget[0]()" />
+      </td>
+      <td>
+	<span tal:define="error python: widget[1].error"
+	      tal:replace="structure error" tal:condition="error" />
+	<input tal:replace="structure python: widget[1]()" />
+      </td>
+    </tr>
+  </div>
+  <tr>
+    <td colspan="3">
+      <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" />
Index: /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.py
===================================================================
--- /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.py	(revision 5290)
+++ /main/waeup.widgets.core/trunk/src/waeup/widgets/core/dictwidget.py	(revision 5290)
@@ -0,0 +1,314 @@
+##
+## dictwidget.py
+## Login : <uli@pu.smp.net>
+## Started on  Sun Jul 18 03:05:27 2010 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2010 Uli Fouquet
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from zope.browserpage import ViewPageTemplateFile
+from zope.component import getMultiAdapter
+from zope.formlib.i18n import _
+from zope.formlib.interfaces import (
+    ISimpleInputWidget, IInputWidget, IDisplayWidget)
+from zope.formlib.sequencewidget import SequenceWidget
+from zope.formlib.widget import DisplayWidget, renderElement
+from zope.i18n import translate
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.schema.interfaces import IDict, IField
+
+class DictWidget(SequenceWidget):
+    _type = dict
+    #grok.adapts(IDict, IField, IBrowserRequest)
+    #grok.adapts(IDict, IBrowserRequest)
+    #grok.provides(ISimpleInputWidget)
+    template = ViewPageTemplateFile('dictwidget.pt')
+
+    
+    def __init__(self, context, request, subwidget=None):
+        super(DictWidget, self).__init__(context, None, request)
+        self.subwidget = subwidget
+
+        # The subwidgets are cached in this dict if preserve_widgets is True.
+        self._widgets = {}
+        self.preserve_widgets = False
+
+    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:
+            key_field = self.context.key_type
+            if self.subwidget is not None:
+                key_widget = self.subwidget(key_field, self.request)
+            else:
+                key_widget = getMultiAdapter(
+                    (key_field, self.request), IInputWidget)
+            #key_widget.setPrefix('%s.key.%d.' % (self.name, i))
+            key_widget.setPrefix('%s.key.%d.' % (self.name, i))
+            
+            value_field = self.context.value_type
+            if self.subwidget is not None:
+                value_widget = self.subwidget(value_field, self.request)
+            else:
+                value_widget = getMultiAdapter(
+                    (value_field, self.request), IInputWidget)
+            #value_widget.setPrefix('%s.value.%d.' % (self.name, i))
+            value_widget.setPrefix('%s.value.%d.' % (self.name, i))
+            #import pdb; pdb.set_trace()
+            if not self.preserve_widgets:
+                #return super(DictWidget, self)._getWidget(i)
+                return (key_widget, value_widget)
+            self._widgets[i] = (key_widget, value_widget)
+        #return super(DictWidget, self)._getWidget(i)
+        return self._widgets[i]
+            
+        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 = 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 widgets(self):
+        """Return a list of widgets to display"""
+        sequence = self._getRenderedValue()
+        result = []
+        for i, value in enumerate(sequence.items()):
+            widget = self._getWidget(i)
+            widget[0].setRenderedValue(value[0])
+            widget[1].setRenderedValue(value[1])
+            result.append(widget)
+        return result
+
+    def _generateSequence(self):
+        """Extract the values of the subwidgets from the request.
+
+        Returns a list of key-value tuples.
+
+        This can only be called if self.hasInput() returns true.
+        """
+        if self.context.value_type is None:
+            # Why would this ever happen?
+            return []
+        if self.context.key_type is None:
+            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, None)] * count
+        #sequence = [None] * count
+        #sequence = dict()
+
+        # 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)
+            key_widget, value_widget = self._getWidget(i)
+            key = None
+            value = None
+            if key_widget.hasValidInput():
+                # catch and set sequence widget errors to ``_error`` attribute
+                try:
+                    key = key_widget.getInputValue()
+                    #sequence[i] = widget.getInputValue()
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
+            if value_widget.hasValidInput():
+                # catch and set sequence widget errors to ``_error`` attribute
+                try:
+                    value = value_widget.getInputValue()
+                    #sequence[i] = widget.getInputValue()
+                except WidgetInputError, error:
+                    self._error = error
+                    raise self._error
+            sequence[i] = (key, value)
+            #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, None))
+
+        return sequence
+
+    
+    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.
+        """
+        sequence = super(DictWidget, self).getInputValue()
+        return sequence
+        return self._type(sequence)
+
+    def _getRenderedValue(self):
+        """Returns a dict from the request or _data"""
+        if self._renderedValueSet():
+            if self._data is self.context.missing_value:
+                #sequence = []
+                sequence = dict()
+            else:
+                #sequence = list(self._data)
+                sequence = dict(self._data)
+        elif self.hasInput():
+            #sequence = self._generateSequence()
+            sequence = dict(self._generateSequence())
+        elif self.context.default is not None:
+            #sequence = self.context.default
+            sequence = self.context.default
+        else:
+            #sequence = []
+            seuence = dict()
+        # 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)
+        #    pass
+        return sequence
+
+    # 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:
+            print "SET: ", field, content, value
+            field.set(content, value)
+        else:
+            print "NO SET: ", field, content, value
+        return change
+
+    
+# Basic display widget
+
+class DictDisplayWidget(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, request, subwidget=None):
+        # def __init__(self, context, field, request, subwidget=None):
+        super(DictDisplayWidget, self).__init__(context, request)
+        self.subwidget = subwidget
+
+    def __call__(self):
+        # get the data to display:
+        #import pdb; pdb.set_trace()
+        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)
+        data = dict(data)
+        if not data:
+            return translate(self._emptySequenceMessage, self.request)
+
+        parts = []
+        # for i, item in enumerate(data):
+        for i, item in enumerate(data.items()):
+            #widget = self._getWidget(i)
+            key_widget, value_widget = self._getWidget(i)
+            key_widget.setRenderedValue(item[0])
+            value_widget.setRenderedValue(item[1])
+            #widget.setRenderedValue(item)
+            #s = widget()
+            s = "%s: %s" % (key_widget(), value_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):
+        """Get a tuple of key-value widgets for the i-th 'row' of dict.
+        """
+        key_field = self.context.key_type
+        value_field = self.context.value_type
+        if self.subwidget is not None:
+            key_widget = self.subwidget(key_field, self.request)
+            value_widget = self.subwidget(value_field, self.request)
+        else:
+            key_widget = getMultiAdapter(
+                (key_field, self.request), IDisplayWidget)
+            value_widget = getMultiAdapter(
+                (value_field, self.request), IDisplayWidget)
+        key_widget.setPrefix('%s.key.%d.' % (self.name, i))
+        value_widget.setPrefix('%s.value.%d.' % (self.name, i))
+        return (key_widget, value_widget)
+
