Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.pt
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.pt	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.pt	(revision 5330)
@@ -0,0 +1,38 @@
+<table border="0" class="multisequencewidget"
+  i18n:domain="zope">
+  <div tal:repeat="widget view/widgets"
+       tal:omit-tag="" >
+    <tr tal:condition="repeat/widget/start">
+      <th></th>
+      <th tal:repeat="subwidget widget/subwidgets">
+	<span tal:replace="subwidget/label">Column 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 tal:repeat="subwidget widget/subwidgets">
+	<span tal:define="error subwidget/error"
+	      tal:replace="structure error" tal:condition="error" />
+	<input tal:replace="structure subwidget" />
+      </td>
+    </tr>
+  </div>
+  <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" />
Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.py
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.py	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/multilistwidget.py	(revision 5330)
@@ -0,0 +1,75 @@
+##
+## multilistwidget.py
+## Login : <uli@pu.smp.net>
+## Started on  Tue Jul 20 02:53:10 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
+##
+"""A widget that renders multiple contained items horizontally.
+"""
+from zope.browserpage import ViewPageTemplateFile
+from zope.formlib.sequencewidget import (
+    ListSequenceWidget, SequenceDisplayWidget)
+from zope.formlib.widget import renderElement
+from zope.i18n import translate
+
+
+class MultiListWidget(ListSequenceWidget):
+    template = ViewPageTemplateFile('multilistwidget.pt')
+
+class MultiListDisplayWidget(SequenceDisplayWidget):
+    cssClass = "multiSequenceWidget"
+
+    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 = ['<table>']
+        for i, item in enumerate(data):
+            widget = self._getWidget(i)
+            if i == 0:
+                parts.append('<thead><tr>')
+                for x in widget.subwidgets():
+                    parts.append('<th>%s</th>' % x.label)
+                parts.append('</tr></thead><tbody>')
+            widget.setRenderedValue(item)
+            s = widget()
+            #print s
+            if self.itemTag:
+                #s = "<%s>%s</%s>" % (self.itemTag, s, self.itemTag)
+                evenodd = i%2 and 'odd' or 'even'
+                s = '<tr class="%s">%s</tr>' % (evenodd, s)
+            parts.append(s)
+        parts.append('</tbody></table>')
+        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
Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.pt
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.pt	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.pt	(revision 5330)
@@ -0,0 +1,5 @@
+  <td class="row" tal:repeat="widget context/subwidgets">
+    <span tal:define="error widget/error"
+	  tal:replace="structure error" tal:condition="error" />
+    <span tal:replace="structure widget" />
+  </td>
Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.py
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.py	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/objectwidget.py	(revision 5330)
@@ -0,0 +1,77 @@
+##
+## resultswidget.py
+## Login : <uli@pu.smp.net>
+## Started on  Tue Jul 20 02:04:30 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
+##
+"""A widget to display IObject instances in forms.
+"""
+from zope.browserpage import ViewPageTemplateFile
+from zope.formlib.interfaces import IDisplayWidget
+from zope.formlib.objectwidget import ObjectWidget, ObjectWidgetView
+from zope.formlib.utility import setUpWidgets
+from zope.interface import implements, implementsOnly
+from zope.schema import getFieldNamesInOrder
+
+class WAeUPObjectWidgetView(ObjectWidgetView):
+    template = ViewPageTemplateFile('objectwidget.pt')
+
+class WAeUPObjectWidget(ObjectWidget):
+
+    def __init__(self, context, request, factory, **kw):
+        #super(ResultsEntryWidget, self).__init__(context, request)
+        super(ObjectWidget, self).__init__(context, request)
+
+        # define view that renders the widget
+        self.view = self._getView(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._setUpWidgets()
+
+    def subwidgets(self):
+        result = [self.getSubWidget(name) for name in self.names]
+        return result
+
+    def _setUpWidgets(self):
+        return self._setUpEditWidgets()
+
+    def _getView(self, request):
+        return WAeUPObjectWidgetView(self, request)
+
+class WAeUPObjectDisplayWidget(WAeUPObjectWidget):
+
+    implementsOnly(IDisplayWidget)
+
+    def _setUpDisplayWidgets(self):
+        # subwidgets need a new name
+        setUpWidgets(self, self.context.schema, IDisplayWidget,
+                         prefix=self.name, names=self.names,
+                         context=self.context)
+
+    def _setUpWidgets(self):
+        return self._setUpDisplayWidgets()
Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/__init__.py
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/__init__.py	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/__init__.py	(revision 5330)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/test_objectwidget.py
===================================================================
--- main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/test_objectwidget.py	(revision 5330)
+++ main/waeup.sirp/branches/henrik-experimental-layout/src/waeup/sirp/widgets/tests/test_objectwidget.py	(revision 5330)
@@ -0,0 +1,219 @@
+"""Tests for objectwidget.
+"""
+import doctest
+import unittest
+import sys
+from zope.component import testing
+from zope.interface import Interface, implements
+from zope.interface.verify import verifyClass
+from zope.publisher.browser import TestRequest
+from zope.schema import Object, TextLine
+from zope.schema.interfaces import ITextLine
+from zope.traversing.adapters import DefaultTraversable
+from zope.traversing.interfaces import ITraversable
+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+from zope.component import provideAdapter
+
+from zope.formlib.interfaces import (
+    IInputWidget, IDisplayWidget,MissingInputError)
+from zope.formlib.widgets import TextWidget, DisplayWidget
+from zope.formlib.tests.test_browserwidget import BrowserWidgetTest
+from zope.formlib.interfaces import IWidgetInputErrorView
+
+from waeup.sirp.widgets.objectwidget import WAeUPObjectWidget as ObjectWidget
+from waeup.sirp.widgets.objectwidget import (
+    WAeUPObjectDisplayWidget as ObjectDisplayWidget)
+
+class ITestContact(Interface):
+    name = TextLine()
+    email = TextLine()
+    
+class TestContact(object):
+    implements(ITestContact)
+
+class ITestContent(Interface):
+    foo = Object(
+        ITestContact, 
+        title = u'Foo Title',
+        description = u'',
+        )
+    
+class ObjectWidgetInputErrorView(object):
+    implements(IWidgetInputErrorView)
+
+    def __init__(self, error, request):
+        self.error = error
+        self.request = request
+
+    def snippet(self):
+        return repr(self.error)
+
+class ObjectWidgetTest(BrowserWidgetTest):
+    """Documents and tests the object widget.
+
+        >>> from zope.interface.verify import verifyClass
+        >>> verifyClass(IInputWidget, ObjectWidget)
+        True
+    """
+
+    _FieldFactory = Object
+    def _WidgetFactory(self, context, request, **kw):
+        kw.update({'factory': TestContact})
+        return ObjectWidget(context, request, **kw)
+
+    def setUpContent(self, desc=u'', title=u'Foo Title'):
+        provideAdapter(TextWidget, (ITextLine, IDefaultBrowserLayer),
+                       IInputWidget)
+        
+        class TestObject(object):
+            implements(ITestContent)
+
+        self.content = TestObject()
+        self.field = ITestContent['foo']
+        self.request = TestRequest(HTTP_ACCEPT_LANGUAGE='pl')
+        self.request.form['field.foo'] = u'Foo Value'
+        self._widget = self._WidgetFactory(self.field, self.request)
+
+    def setUp(self):
+        super(ObjectWidgetTest, self).setUp()
+        self.field = Object(ITestContact, __name__=u'foo')
+        provideAdapter(TextWidget,
+                       (ITextLine, IDefaultBrowserLayer),
+                       IInputWidget)
+
+    def test_applyChanges(self):
+        self.request.form['field.foo.name'] = u'Foo Name'
+        self.request.form['field.foo.email'] = u'foo@foo.test'
+        widget = self._WidgetFactory(self.field, self.request)
+
+        self.assertEqual(widget.applyChanges(self.content), True)
+        self.assertEqual(hasattr(self.content, 'foo'), True)
+        self.assertEqual(isinstance(self.content.foo, TestContact), True)
+        self.assertEqual(self.content.foo.name, u'Foo Name')
+        self.assertEqual(self.content.foo.email, u'foo@foo.test')
+
+    def test_error(self):
+        provideAdapter(
+            ObjectWidgetInputErrorView,
+            (MissingInputError, TestRequest),
+            IWidgetInputErrorView)
+
+        widget = self._WidgetFactory(self.field, self.request)
+        self.assertRaises(MissingInputError, widget.getInputValue)
+        error_html = widget.error()
+        if sys.version_info < (2, 5):
+            self.failUnless("email: <zope.formlib.interfaces.Mis" 
+                             in error_html)
+            self.failUnless("name: <zope.formlib.interfaces.Miss"
+                             in error_html)
+        else:
+            self.failUnless(
+                "email: MissingInputError(u'field.foo.email', u'', None)"
+                in error_html)
+            self.failUnless(
+                "name: MissingInputError(u'field.foo.name', u'', None)"
+                in error_html)
+
+    def test_applyChangesNoChange(self):
+        self.content.foo = TestContact()
+        self.content.foo.name = u'Foo Name'
+        self.content.foo.email = u'foo@foo.test'
+
+        self.request.form['field.foo.name'] = u'Foo Name'
+        self.request.form['field.foo.email'] = u'foo@foo.test'
+        widget = self._WidgetFactory(self.field, self.request)
+        widget.setRenderedValue(self.content.foo)
+
+        self.assertEqual(widget.applyChanges(self.content), False)
+        self.assertEqual(hasattr(self.content, 'foo'), True)
+        self.assertEqual(isinstance(self.content.foo, TestContact), True)
+        self.assertEqual(self.content.foo.name, u'Foo Name')
+        self.assertEqual(self.content.foo.email, u'foo@foo.test')
+
+    def test_respect_custom_widgets(self):
+        # We can use our own subwidgets when creating ObjectWidgets
+        class CustomTextWidget(TextWidget):
+            pass
+
+        # We create a custom widget for the `name` field of ITestContact
+        name_widget = CustomTextWidget(ITestContact['name'], self.request)
+
+        # Custom widgets are passed by <fieldname>_widget keyword:
+        widget = self._WidgetFactory(self.field, self.request,
+                                     name_widget=name_widget)
+        widget1, widget2 = widget.subwidgets()
+        self.assertTrue(widget1 is name_widget)
+        
+        
+class ObjectDisplayWidgetTest(BrowserWidgetTest):
+    """Documents and tests the display variant of object widget.
+
+        >>> from zope.interface.verify import verifyClass
+        >>> verifyClass(IDisplayWidget, ObjectDisplayWidget)
+        True
+
+   """
+    _FieldFactory = Object
+    def _WidgetFactory(self, context, request, **kw):
+        kw.update({'factory': TestContact})
+        return ObjectDisplayWidget(context, request, **kw)
+
+    def setUpContent(self, desc=u'', title=u'Foo Title'):
+        provideAdapter(TextWidget, (ITextLine, IDefaultBrowserLayer),
+                       IInputWidget)
+        provideAdapter(DisplayWidget, (ITextLine, IDefaultBrowserLayer),
+                       IDisplayWidget)
+        # The widget must be traversable. We register the default
+        # adapter that can turn nearly any object into an ITraversable.
+        provideAdapter(DefaultTraversable, (None,), ITraversable)
+        
+        class TestObject(object):
+            implements(ITestContent)
+
+        self.content = TestObject()
+        self.content.name = u'Foo Name'
+        self.content.email = u'foo@foo.test'
+        self.field = ITestContent['foo']
+        self.request = TestRequest(HTTP_ACCEPT_LANGUAGE='pl')
+        self.request.form['field.foo'] = u'Foo Value'
+        self._widget = self._WidgetFactory(self.field, self.request)
+
+    def setUp(self):
+        super(ObjectDisplayWidgetTest, self).setUp()
+        self.field = Object(ITestContact, __name__=u'foo')
+        provideAdapter(TextWidget,
+                       (ITextLine, IDefaultBrowserLayer),
+                       IInputWidget)
+        provideAdapter(DisplayWidget,
+                       (ITextLine, IDefaultBrowserLayer),
+                       IDisplayWidget)
+
+        
+    def test_interfaces(self):
+        from zope.interface.verify import DoesNotImplement
+        self.assertTrue(IDisplayWidget.providedBy(self._widget))
+        self.assertFalse(IInputWidget.providedBy(self._widget))
+        self.assertTrue(verifyClass(IDisplayWidget, ObjectDisplayWidget))
+
+    def test_render(self):
+        widget = ObjectDisplayWidget(
+            self.field, self.request, TestContact)
+        widget.setRenderedValue(self.content)
+
+        check_list = [
+            '<td', 'class=', 'row',
+            'Foo Name',
+            '</td>', '<td', 'class=', 'row',
+            'foo@foo.test',
+            '</td>']
+        self.verifyResult(widget(), check_list, inorder=True)    
+
+def test_suite():
+    return unittest.TestSuite((
+        unittest.makeSuite(ObjectWidgetTest),
+        unittest.makeSuite(ObjectDisplayWidgetTest),
+        doctest.DocTestSuite(),
+        ))
+
+if __name__=='__main__':
+    unittest.main(defaultTest='test_suite')
