cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From sylv...@apache.org
Subject svn commit: r190962 [1/2] - in /cocoon: blocks/forms/trunk/WEB-INF/xconf/ blocks/forms/trunk/java/org/apache/cocoon/forms/ blocks/forms/trunk/java/org/apache/cocoon/forms/event/ blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/ blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/ blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/ blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/ blocks/forms/trunk/java/org/apache/cocoon/forms/generation/ blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/ blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/win/ blocks/forms/trunk/java/org/apache/cocoon/forms/resources/js/ blocks/forms/trunk/samples/ blocks/forms/trunk/samples/flow/ blocks/forms/trunk/samples/forms/ trunk/
Date Thu, 16 Jun 2005 17:17:02 GMT
Author: sylvain
Date: Thu Jun 16 10:17:00 2005
New Revision: 190962

URL: http://svn.apache.org/viewcvs?rev=190962&view=rev
Log:
New Tree widget for CForms

Added:
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionListener.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeWalker.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/JavaTreeModelDefinitionBuilder.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/SourceTreeModelDefinitionBuilder.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/TreeDefinitionBuilder.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/builder/TreeModelDefinitionBuilder.java   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/win/
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/win/collapsed.gif   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/win/expanded.gif   (with props)
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/img/tree/win/leaf.gif   (with props)
    cocoon/blocks/forms/trunk/samples/forms/file_explorer_model.xml   (with props)
    cocoon/blocks/forms/trunk/samples/forms/file_explorer_template.xml   (with props)
    cocoon/blocks/forms/trunk/samples/forms/sampletree_model.xml   (with props)
    cocoon/blocks/forms/trunk/samples/forms/sampletree_template.xml   (with props)
Modified:
    cocoon/blocks/forms/trunk/WEB-INF/xconf/cocoon-forms.xconf
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/WidgetEventMulticaster.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListener.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListenerBuilder.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Form.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/FormDefinition.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Repeater.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Widget.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/forms.roles
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/generation/JXMacrosHelper.java
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/generation/jx-macros.xml
    cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/resources/js/cforms.js
    cocoon/blocks/forms/trunk/samples/flow/forms_flow_example.js
    cocoon/blocks/forms/trunk/samples/forms/datasource_chooser_template.xml
    cocoon/blocks/forms/trunk/samples/forms/dynamicrepeater.xml
    cocoon/blocks/forms/trunk/samples/forms/dynamicrepeater_template.xml
    cocoon/blocks/forms/trunk/samples/forms/tasktree_template.xml
    cocoon/blocks/forms/trunk/samples/welcome.xml
    cocoon/trunk/status.xml

Modified: cocoon/blocks/forms/trunk/WEB-INF/xconf/cocoon-forms.xconf
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/WEB-INF/xconf/cocoon-forms.xconf?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/WEB-INF/xconf/cocoon-forms.xconf (original)
+++ cocoon/blocks/forms/trunk/WEB-INF/xconf/cocoon-forms.xconf Thu Jun 16 10:17:00 2005
@@ -157,6 +157,7 @@
       <widget name="struct" src="org.apache.cocoon.forms.formmodel.StructDefinitionBuilder"/>
       <widget name="union" src="org.apache.cocoon.forms.formmodel.UnionDefinitionBuilder"/>
       <widget name="captcha" src="org.apache.cocoon.forms.formmodel.CaptchaDefinitionBuilder"/>
+      <widget name="tree" src="org.apache.cocoon.forms.formmodel.tree.builder.TreeDefinitionBuilder"/>
     </widgets>
   </forms-formmanager>
 
@@ -184,6 +185,11 @@
     <listener name="java" class="org.apache.cocoon.forms.event.impl.JavaClassWidgetListenerBuilder"/>
     <listener name="javascript" class="org.apache.cocoon.forms.event.impl.JavaScriptWidgetListenerBuilder"/>
   </forms-widgetlisteners>
+
+  <forms-treemodels>
+    <treemodel name="java" class="org.apache.cocoon.forms.formmodel.tree.builder.JavaTreeModelDefinitionBuilder"/>
+    <treemodel name="source" class="org.apache.cocoon.forms.formmodel.tree.builder.SourceTreeModelDefinitionBuilder"/>
+  </forms-treemodels>
 
   <!--+
       | Sitemap components

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/WidgetEventMulticaster.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/WidgetEventMulticaster.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/WidgetEventMulticaster.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/WidgetEventMulticaster.java Thu Jun 16 10:17:00 2005
@@ -18,6 +18,9 @@
 import java.awt.AWTEventMulticaster;
 import java.util.EventListener;
 
+import org.apache.cocoon.forms.formmodel.tree.TreeSelectionEvent;
+import org.apache.cocoon.forms.formmodel.tree.TreeSelectionListener;
+
 /**
  * Convenience class to handle all widget event listeners. See
  * <code>java.awt.AWTEventMulticaster</code> for more information on its use.
@@ -92,6 +95,21 @@
         return (ProcessingPhaseListener)removeInternal(l, oldl);
     }
     
+    //-- TreeSelectionChanged ---------------------------------------------------------
+
+    public static TreeSelectionListener add(TreeSelectionListener a, TreeSelectionListener b) {
+        return (TreeSelectionListener)addInternal(a, b);
+    }
+    
+    public static TreeSelectionListener remove(TreeSelectionListener l, TreeSelectionListener oldl) {
+        return (TreeSelectionListener)removeInternal(l, oldl);
+    }
+    
+    public void selectionChanged(TreeSelectionEvent e) {
+        ((TreeSelectionListener)a).selectionChanged(e);
+        ((TreeSelectionListener)b).selectionChanged(e);
+    }
+
     /**
      * Can't use the superclass method since it creates an AWTEventMulticaster
      */

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListener.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListener.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListener.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListener.java Thu Jun 16 10:17:00 2005
@@ -29,6 +29,8 @@
 import org.apache.cocoon.forms.event.ValueChangedEvent;
 import org.apache.cocoon.forms.event.ValueChangedListener;
 import org.apache.cocoon.forms.event.WidgetEvent;
+import org.apache.cocoon.forms.formmodel.tree.TreeSelectionEvent;
+import org.apache.cocoon.forms.formmodel.tree.TreeSelectionListener;
 import org.apache.cocoon.forms.util.JavaScriptHelper;
 import org.mozilla.javascript.Script;
 
@@ -106,6 +108,17 @@
         }
 
         public void widgetCreated(CreateEvent event) {
+            super.callScript(event);
+        }
+    }
+    
+    public static class JSTreeSelectionListener extends JavaScriptWidgetListener implements TreeSelectionListener {
+
+        public JSTreeSelectionListener(Script script, Context context) {
+            super(script, context);
+        }
+
+        public void selectionChanged(TreeSelectionEvent event) {
             super.callScript(event);
         }
     }

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListenerBuilder.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListenerBuilder.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListenerBuilder.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/event/impl/JavaScriptWidgetListenerBuilder.java Thu Jun 16 10:17:00 2005
@@ -20,6 +20,7 @@
 import org.apache.cocoon.forms.event.ValueChangedListener;
 import org.apache.cocoon.forms.event.WidgetListener;
 import org.apache.cocoon.forms.event.WidgetListenerBuilder;
+import org.apache.cocoon.forms.formmodel.tree.TreeSelectionListener;
 import org.apache.cocoon.forms.util.JavaScriptHelper;
 import org.apache.avalon.framework.thread.ThreadSafe;
 import org.apache.avalon.framework.context.Contextualizable;
@@ -62,6 +63,8 @@
             return new JavaScriptWidgetListener.JSCreateListener(script, context);
         } else if (listenerClass == ValueChangedListener.class) {
             return new JavaScriptWidgetListener.JSValueChangedListener(script, context);
+        } else if (listenerClass == TreeSelectionListener.class) {
+            return new JavaScriptWidgetListener.JSTreeSelectionListener(script, context);
         } else {
             throw new Exception("Unkonwn event class: " + listenerClass);
         }

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/AbstractWidget.java Thu Jun 16 10:17:00 2005
@@ -84,6 +84,10 @@
     public String getId() {
         return getDefinition().getId();
     }
+    
+    public String getName() {
+        return getId();
+    }
 
     /** 
      * Concrete subclasses should allow access to their underlaying Definition
@@ -163,21 +167,46 @@
         return WidgetState.strictest(this.state, this.parent.getCombinedState());
     }
 
+    // Cached param names, used to speed up execution of the method below while
+    // still allowing ids to change (e.g. repeater rows when they are reordered).
+    private String cachedParentParamName;
+    private String cachedParamName;
+    
+    /**
+     * Should be called when a widget's own name has changed, in order to clear
+     * internal caches used to compute request parameters.
+     */
+    protected void widgetNameChanged() {
+        this.cachedParentParamName = null;
+        this.cachedParamName = null;
+    }
+    
+    public String getFullName() {
+        return getRequestParameterName();
+    }
+    
     public String getRequestParameterName() {
         
-        // Default if no parent or parent with empty id
-        String requestParamName = getId();
+        if (this.parent == null) {
+            return getId();
+        }
+
+        String parentParamName = parent.getRequestParameterName();
+        if (parentParamName == this.cachedParentParamName) {
+            // Parent name hasn't changed, so ours hasn't changed too
+            return this.cachedParamName;
+        }
 
-        Widget myParent = getParent();
-        if (myParent != null) {
-            String parentFullId = myParent.getRequestParameterName();
+        // Compute our name and cache it
+        this.cachedParentParamName = parentParamName;
+        if (this.cachedParentParamName.length() == 0) {
             // the top level form returns an id == ""
-            if (parentFullId.length() > 0) {
-                requestParamName = parentFullId + "." + getId();
-            }
+            this.cachedParamName = getId();
+        } else {
+            this.cachedParamName = this.cachedParentParamName + "." + getId();
         }
 
-        return requestParamName;
+        return this.cachedParamName;
     }
 
     public Widget lookupWidget(String path) {

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Form.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Form.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Form.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Form.java Thu Jun 16 10:17:00 2005
@@ -60,6 +60,8 @@
     
     // Widgets that need to be updated in the client when in AJAX mode
     private Set updatedWidgets;
+    // Widgets that have at least one descendant that has to be updated
+    private Set childUpdatedWidgets;
 
     public Form(FormDefinition definition) {
         super(definition);
@@ -111,14 +113,37 @@
         }
     }
     
-    public void addWidgetUpdate(Widget widget) {
+    /**
+     * Mark a widget as being updated. When it Ajax mode, only updated widgets will be redisplayed
+     * 
+     * @param widget the updated widget
+     * @return <code>true</code> if this widget was added to the list (i.e. wasn't alredy marked for update)
+     */
+    public boolean addWidgetUpdate(Widget widget) {
         if (this.updatedWidgets != null) {
-            this.updatedWidgets.add(widget.getRequestParameterName());
+            if (this.updatedWidgets.add(widget.getRequestParameterName())) {
+                // Wasn't already there: register parents
+                Widget parent = widget.getParent();
+                addParents: while (parent != this && parent != null) {
+                    if (this.childUpdatedWidgets.add(parent.getRequestParameterName())) {
+                        parent = getParent();
+                    } else {
+                        // Parent already there, and therefore its own parents.
+                        break addParents;
+                    }
+                }
+                return true;
+            }
         }
+        return false;
+    }
+
+    public Set getUpdatedWidgetIds() {
+        return this.updatedWidgets;
     }
     
-    public Set getUpdatedWidgets() {
-        return this.updatedWidgets == null ? Collections.EMPTY_SET : this.updatedWidgets;
+    public Set getChildUpdatedWidgetIds() {
+        return this.childUpdatedWidgets;
     }
 
     /**
@@ -166,7 +191,8 @@
      */
     public void setSubmitWidget(Widget widget) {
         if (this.submitWidget != null && this.submitWidget != widget) {
-            throw new IllegalStateException("SubmitWidget can only be set once.");
+            throw new IllegalStateException("SubmitWidget already set to " + this.submitWidget +
+                    ". Cannot set also " + widget);
         }
         if (!(widget instanceof Action)) {
             endProcessing(true);
@@ -234,6 +260,7 @@
         // Is this an AJAX request?
         if (formContext.getRequest().getParameter("cocoon-ajax") != null) {
             this.updatedWidgets = new HashSet();
+            this.childUpdatedWidgets = new HashSet();
         }
         
         // Fire the binding phase events

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/FormDefinition.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/FormDefinition.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/FormDefinition.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/FormDefinition.java Thu Jun 16 10:17:00 2005
@@ -59,4 +59,15 @@
             this.listener.phaseEnded(event);
         }
     }
+    
+    public void addWidgetDefinition(WidgetDefinition definition) throws Exception, DuplicateIdException {
+        // Check that no child is named "submit". This causes some weird behaviour in HTML as
+        // it collides with the submit() function on the <form> element...
+        if ("submit".equals(definition.getId())) {
+            throw new IllegalArgumentException("No top-level widget should be named 'submit' to avoid problems "
+                    + " with HTML <form> elements, at " + definition.getLocation());
+        }
+
+        super.addWidgetDefinition(definition);
+    }
 }

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Repeater.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Repeater.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Repeater.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Repeater.java Thu Jun 16 10:17:00 2005
@@ -370,9 +370,28 @@
             return Repeater.this.definition;
         }
 
+        private int cachedPosition = -100;
+        private String cachedId = "--undefined--";
+
         public String getId() {
-            // id of a RepeaterRow is the position of the row in the list of rows.
-            return String.valueOf(rows.indexOf(this));
+            int pos = rows.indexOf(this);
+            if (pos == -1) {
+                throw new IllegalStateException("Row has currently no position");
+            }
+            if (pos != this.cachedPosition) {
+                this.cachedPosition = pos;
+                // id of a RepeaterRow is the position of the row in the list of rows.
+                this.cachedId = String.valueOf(pos);
+                widgetNameChanged();
+            }
+            return this.cachedId;
+        }
+        
+        public String getRequestParameterName() {
+            // Get the id to check potential position change
+            getId();
+
+            return super.getRequestParameterName();
         }
 
         public Form getForm() {

Modified: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Widget.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Widget.java?rev=190962&r1=190961&r2=190962&view=diff
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Widget.java (original)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/Widget.java Thu Jun 16 10:17:00 2005
@@ -70,6 +70,12 @@
      * @return  the source location of this widget.
      */
     public String getLocation();
+    
+    /**
+     * @return the name of this widget.  This should never be <code>null</code>
+     * Top-level container widgets (like 'form') should return <code>""</code>
+     */
+    public String getName();
 
     /**
      * @return the id of this widget.  This should never be <code>null</code>
@@ -129,6 +135,12 @@
      * @return the combined state
      */
     public WidgetState getCombinedState();
+
+    /**
+     * @return the name prefixed with the namespace, this name should be unique
+     * accross all widgets on the form.
+     */
+    public String getFullName();
 
     /**
      * @return the id prefixed with the namespace, this name should be unique

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,179 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * A default tree model, implemented with {@link org.apache.cocoon.forms.formmodel.tree.DefaultTreeModel.TreeNode}s.
+ * 
+ * @version $Id$
+ */
+public class DefaultTreeModel implements TreeModel {
+    
+    /**
+     * Default model that is used by a Tree when no model has been specified.
+     */
+    public static final TreeModel UNSPECIFIED_MODEL = buildUnspecifiedModel();
+    
+    private TreeModelHelper helper = new TreeModelHelper(this);
+    
+    TreeNode root;
+
+    public interface TreeNode {
+        Collection getChildren();
+
+        boolean isLeaf();
+
+        String getChildKey(Object child);
+
+        Object getChild(String key);
+    }
+    
+    public static class DefaultTreeNode implements TreeNode {
+        
+        private Object data;
+        private Map children;
+
+        public DefaultTreeNode(Object data) {
+            this.data = data;
+        }
+        
+        public DefaultTreeNode(Object data, Map children) {
+            this.data = data;
+            this.children = children;
+        }
+        
+        public Object getData() {
+            return this.data;
+        }
+        
+        public void add(String key, TreeNode node) {
+            if (this.children == null) {
+                this.children = new HashMap();
+            }
+            children.put(key, node);
+        }
+
+        public Collection getChildren() {
+            if (this.children == null) {
+                return null;
+            }
+            
+            return this.children.values();
+        }
+
+        public boolean isLeaf() {
+            return this.children == null;
+        }
+
+        public String getChildKey(Object child) {
+            Iterator iter = this.children.entrySet().iterator();
+            while(iter.hasNext()) {
+                Map.Entry entry = (Map.Entry)iter.next();
+                if (child.equals(entry.getValue())) {
+                    return (String)entry.getKey();
+                }
+            }
+            return null;
+        }
+
+        public Object getChild(String key) {
+            return this.children.get(key);
+        }
+    }
+
+    public DefaultTreeModel(TreeNode root) {
+        this.root = root;
+    }
+
+    public Object getRoot() {
+        return this.root;
+    }
+
+    public Collection getChildren(Object parent) {
+        return ((TreeNode)parent).getChildren();
+    }
+
+    public boolean isLeaf(Object node) {
+        return ((TreeNode)node).isLeaf();
+    }
+
+    public String getChildKey(Object parent, Object child) {
+        return ((TreeNode)parent).getChildKey(child);
+    }
+
+    public Object getChild(Object parent, String key) {
+        return ((TreeNode)parent).getChild(key);
+    }
+
+    public Object getNode(TreePath path) {
+        return helper.getNode(path);
+    }
+
+    public void addTreeModelListener(TreeModelListener l) {
+        helper.addTreeModelListener(l);
+    }
+
+    public void removeTreeModelListener(TreeModelListener l) {
+        helper.removeTreeModelListener(l);
+    }
+
+    private static TreeModel buildUnspecifiedModel() {
+        DefaultTreeNode root = new DefaultTreeNode("This tree has no model.");
+        DefaultTreeNode parent = new DefaultTreeNode("Tree model should be defined...");
+        root.add("explanation", parent);
+        parent.add("1", new DefaultTreeNode("in the form definition using <fd:tree-model>"));
+        parent.add("2", new DefaultTreeNode("by the application using flowscript, event listeners, etc."));
+        return new DefaultTreeModel(root);
+    }
+
+    /**
+     * The classical Swing sample tree model, that can be used for demonstration purposes.
+     */
+    public static class Sample extends DefaultTreeModel {
+        public Sample() {
+            super(new DefaultTreeNode("root"));
+            DefaultTreeNode root = (DefaultTreeNode)getRoot();
+
+            DefaultTreeNode      parent;
+            
+            parent = new DefaultTreeNode("Colors");
+            root.add("colors", parent);
+            parent.add("blue", new DefaultTreeNode("Blue"));
+            parent.add("violet", new DefaultTreeNode("Violet"));
+            parent.add("red", new DefaultTreeNode("Red"));
+            parent.add("yellow", new DefaultTreeNode("Yellow"));
+    
+            parent = new DefaultTreeNode("Sports");
+            root.add("sports", parent);
+            parent.add("basketball", new DefaultTreeNode("Basketball"));
+            parent.add("soccer", new DefaultTreeNode("Soccer"));
+            parent.add("football", new DefaultTreeNode("Football"));
+            parent.add("hockey", new DefaultTreeNode("Hockey"));
+    
+            parent = new DefaultTreeNode("Food");
+            root.add("food", parent);
+            parent.add("hotdogs", new DefaultTreeNode("Hot Dogs"));
+            parent.add("pizza", new DefaultTreeNode("Pizza"));
+            parent.add("ravioli", new DefaultTreeNode("Ravioli"));
+            parent.add("bananas", new DefaultTreeNode("Bananas"));
+        }
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/DefaultTreeModel.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,65 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import org.apache.avalon.framework.CascadingRuntimeException;
+import org.apache.avalon.framework.context.Context;
+import org.apache.avalon.framework.context.ContextException;
+import org.apache.avalon.framework.context.Contextualizable;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.cocoon.components.LifecycleHelper;
+
+/**
+ * A {@link org.apache.cocoon.forms.formmodel.tree.TreeModelDefinition} based on an Java class
+ * implementing {@link org.apache.cocoon.forms.formmodel.tree.TreeModel}.
+ * 
+ * @version $Id$
+ */
+public class JavaTreeModelDefinition extends AbstractLogEnabled
+    implements TreeModelDefinition, Contextualizable, Serviceable {
+    
+    private Class modelClass;
+
+    Context ctx;
+    ServiceManager manager;
+    
+    public void contextualize(Context context) throws ContextException {
+        this.ctx = context;
+    }
+
+    public void service(ServiceManager manager) throws ServiceException {
+        this.manager = manager;
+    }
+
+    public void setModelClass(Class clazz) {
+        this.modelClass = clazz;
+    }
+
+    public TreeModel createInstance() {
+        TreeModel model;
+        try {
+            model = (TreeModel)modelClass.newInstance();
+            LifecycleHelper.setupComponent(model, getLogger(), ctx, manager, null);
+        } catch (Exception e) {
+            throw new CascadingRuntimeException("Cannot instanciate class " + modelClass.getName(), e);
+        }
+        
+        return model;
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/JavaTreeModelDefinition.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,187 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.avalon.framework.CascadingRuntimeException;
+import org.apache.cocoon.matching.helpers.WildcardHelper;
+import org.apache.excalibur.source.SourceException;
+import org.apache.excalibur.source.SourceResolver;
+import org.apache.excalibur.source.TraversableSource;
+
+/**
+ * A {@link TreeModel} that builds a hierarchy of <code>TraversableSource</code>s.
+ * 
+ * @version $Id$
+ */
+public class SourceTreeModel implements TreeModel {
+    
+    private TreeModelHelper helper = new TreeModelHelper(this);
+    
+    private int[][] fileIncludePatterns = SourceTreeModelDefinition.NO_PATTERNS;
+    private int[][] fileExcludePatterns = SourceTreeModelDefinition.NO_PATTERNS;
+    private int[][] dirIncludePatterns = SourceTreeModelDefinition.NO_PATTERNS;
+    private int[][] dirExcludePatterns = SourceTreeModelDefinition.NO_PATTERNS;
+    
+    /** optimization hint: don't filter child collections if there are no patterns */
+    private boolean hasPatterns = false;
+
+    private TraversableSource rootSource;
+
+    private String rootURL;
+    private SourceResolver resolver;
+
+    public SourceTreeModel(SourceResolver resolver, String rootURL) {
+        this.resolver = resolver;
+        this.rootURL = rootURL;
+    }
+
+    public SourceTreeModel(SourceTreeModelDefinition definition) {
+        this.rootURL = definition.getRootURL();
+        this.resolver = definition.getResolver();
+        this.fileIncludePatterns = definition.getFileIncludePatterns();
+        this.fileExcludePatterns = definition.getFileExcludePatterns();
+        this.dirIncludePatterns =  definition.getDirectoryIncludePatterns();
+        this.dirExcludePatterns =  definition.getDirectoryExcludePatterns();
+        
+        this.hasPatterns = this.fileIncludePatterns != null || this.fileExcludePatterns != null ||
+            this.dirIncludePatterns != null || this.dirExcludePatterns != null;
+    }
+
+    public Object getRoot() {
+        if (this.rootSource == null) {
+            try {
+                this.rootSource = (TraversableSource) this.resolver.resolveURI(this.rootURL);
+            } catch (Exception e) {
+                throw new CascadingRuntimeException("Cannot resolve " + this.rootURL, e);
+            }
+        }
+        return this.rootSource;
+    }
+
+    public Collection getChildren(Object parent) {
+        if (parent instanceof TraversableSource) {
+            TraversableSource dir = (TraversableSource)parent;
+            try {
+                // Return children if it's a collection, null otherwise
+                return dir.isCollection() ? filterChildren(dir.getChildren()) : null;
+            } catch (SourceException e) {
+                throw new CascadingRuntimeException("getChildren", e);
+            }
+        } else {
+            return null;
+        }
+    }
+    
+    private Collection filterChildren(Collection coll) {
+        if (!this.hasPatterns) {
+            return coll;
+        }
+        
+        ArrayList result = new ArrayList();
+        Iterator iter = coll.iterator();
+        while(iter.hasNext()) {
+            TraversableSource src = (TraversableSource)iter.next();
+
+            // Does it match the patterns?
+            boolean matches = true;
+            if (src.isCollection()) {
+                matches = matches(src, this.dirIncludePatterns, this.dirExcludePatterns);
+            } else {
+                matches = matches(src, this.fileIncludePatterns, this.fileExcludePatterns);
+            }
+
+            if (matches) {
+                result.add(src);
+            }
+        }
+        
+        return result;
+    }
+    
+    private boolean matches(TraversableSource src, int[][]include, int[][]exclude) {
+        boolean matches = true;
+        String name = src.getName();
+        
+        // check include patterns
+        if (include != null && include.length > 0) {
+            matches = false;
+            check: for (int i = 0; i < include.length; i++) {
+                if (WildcardHelper.match(null, name, include[i])) {
+                    matches = true;
+                    break check;
+                }
+            }
+        }
+        
+        // check exclude patterns
+        if (matches && exclude != null && exclude.length > 0) {
+            check: for (int i = 0; i < exclude.length; i++) {
+                if (WildcardHelper.match(null, name, exclude[i])) {
+                    matches = false;
+                    break check;
+                }
+            }
+        }
+        return matches;
+    }
+    
+    public boolean isLeaf(Object obj) {
+        return !(obj instanceof TraversableSource) || !((TraversableSource)obj).isCollection();
+    }
+
+    public String getChildKey(Object parent, Object child) {
+        return ((TraversableSource)child).getName();
+    }
+
+    public Object getChild(Object parent, String key) {
+        try {
+            return ((TraversableSource)parent).getChild(key);
+        } catch (SourceException e) {
+            throw new CascadingRuntimeException("getChild", e);
+        }
+    }
+    
+    public void setRootURL(String url) {
+        if (this.rootSource != null) {
+            this.resolver.release(this.rootSource);
+            this.rootSource = null;
+        }
+        this.rootURL = url;
+        helper.fireTreeStructureChanged(TreePath.ROOT_PATH);
+    }
+
+    public void setRootSource(TraversableSource src) {
+        this.rootSource = src;
+        helper.fireTreeStructureChanged(TreePath.ROOT_PATH);
+    }
+
+    public void addTreeModelListener(TreeModelListener l) {
+        helper.addTreeModelListener(l);
+    }
+
+    public void removeTreeModelListener(TreeModelListener l) {
+        helper.addTreeModelListener(l);
+    }
+
+    public Object getNode(TreePath path) {
+        // FIXME: can be heavily optimized by building a new URL from the path elements.
+        return helper.getNode(path);
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModel.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,80 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import org.apache.excalibur.source.SourceResolver;
+
+/**
+ * Definition for {@link SourceTreeModel}
+ * 
+ * @version $Id$
+ */
+public class SourceTreeModelDefinition implements TreeModelDefinition {
+
+    public static final int[][] NO_PATTERNS = new int[0][];
+    private String url;
+    private int[][] fileIncludePatterns = NO_PATTERNS;
+    private int[][] fileExcludePatterns = NO_PATTERNS;
+    private int[][] dirIncludePatterns = NO_PATTERNS;
+    private int[][] dirExcludePatterns = NO_PATTERNS;
+    private SourceResolver resolver;
+
+    public void setURL(String url) {
+        this.url = url;
+    }
+
+    public void setFilePatterns(int[][] include, int[][] exclude) {
+        this.fileIncludePatterns = include;
+        this.fileExcludePatterns = exclude;
+    }
+
+    public void setDirectoryPatterns(int[][] include, int[][] exclude) {
+        this.dirIncludePatterns = include;
+        this.dirExcludePatterns = exclude;
+    }
+    
+    public TreeModel createInstance() {
+        return new SourceTreeModel(this);
+    }
+
+    public int[][] getDirectoryExcludePatterns() {
+        return dirExcludePatterns;
+    }
+
+    public int[][] getDirectoryIncludePatterns() {
+        return dirIncludePatterns;
+    }
+
+    public int[][] getFileExcludePatterns() {
+        return fileExcludePatterns;
+    }
+
+    public int[][] getFileIncludePatterns() {
+        return fileIncludePatterns;
+    }
+
+    public void setSourceResolver(SourceResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public String getRootURL() {
+        return this.url;
+    }
+    
+    public SourceResolver getResolver() {
+        return this.resolver;
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/SourceTreeModelDefinition.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,432 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.forms.FormContext;
+import org.apache.cocoon.forms.event.WidgetEventMulticaster;
+import org.apache.cocoon.forms.formmodel.AbstractWidget;
+import org.apache.cocoon.forms.formmodel.Widget;
+import org.apache.cocoon.forms.formmodel.WidgetDefinition;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * A tree widget, heavily inspired by Swing's <code>JTree</code>.
+ * 
+ * @version $Id$
+ */
+public class Tree extends AbstractWidget {
+    
+    public static final int SINGLE_SELECTION = 0;
+    public static final int MULTIPLE_SELECTION = 1;
+    
+    public interface ActionHandler {
+        public void act(Tree tree, FormContext context);
+    }
+    
+    private TreeDefinition treeDef;
+
+    private TreeModel treeModel;
+    
+    private Set expandedPaths = new HashSet();
+    
+    private Set selectedPaths = new HashSet();
+    
+    private Set changedPaths = new HashSet();
+    
+    private HashMap pathWidgets = new HashMap();
+    
+    private boolean rootVisible = true;
+
+    private boolean expandSelectedPath = false;
+    
+    private TreeSelectionListener selectionListener;
+    
+    private int selectionModel = MULTIPLE_SELECTION;
+    
+    private TreeModelListener modelListener = new TreeModelListener() {
+        public void treeStructureChanged(TreeModelEvent event) {
+            markForRefresh(event.getPath());
+        }
+    };
+
+    protected Tree(TreeDefinition definition) {
+        super(definition);
+        this.treeDef = definition;
+        this.rootVisible = definition.isRootVisible();
+        if (!this.rootVisible) {
+            // Expand it so that first-level children are visible
+            this.expandedPaths.add(TreePath.ROOT_PATH);
+        }
+        this.treeModel = definition.createModel();
+        this.treeModel.addTreeModelListener(modelListener);
+        this.selectionListener = definition.getSelectionListener();
+        this.selectionModel = definition.getSelectionModel();
+    }
+
+    public WidgetDefinition getDefinition() {
+        return this.treeDef;
+    }
+
+    protected String getXMLElementName() {
+        return "tree";
+    }
+
+    protected void generateItemSaxFragment(ContentHandler contentHandler, Locale locale) throws SAXException {
+	    	throw new UnsupportedOperationException(this + " cannot be rendered using <ft:widget>. Please use <ft:tree>.");
+    }
+
+    public void readFromRequest(FormContext formContext) {
+        //TODO: crawl open nodes, calling their widget's readFromRequest
+        
+        Request req = formContext.getRequest();
+        String paramName = getRequestParameterName();
+        
+        //---------------------------------------------------------------------
+        // Handle node selection using checkboxes named <id>$select
+        //---------------------------------------------------------------------
+        String[] selectValues = req.getParameterValues(paramName + ":select");
+        if (selectValues != null) {
+            // Create the set of paths given by the request
+            Set newSelection = new HashSet();
+            for (int i = 0; i < selectValues.length; i++) {
+                newSelection.add(TreePath.valueOf(selectValues[i]));
+            }
+            
+            // Check if all visible selections are in the new selection
+            TreePath[] currentSelection = (TreePath[])this.selectedPaths.toArray(new TreePath[this.selectedPaths.size()]);
+            for (int i = 0; i < currentSelection.length; i++) {
+                TreePath p = currentSelection[i];
+                if (!newSelection.contains(p) && isVisible(p)) {
+                    removeSelectionPath(p);
+                }
+            }
+
+            // Now add the currently selected items (may be selected already)
+            Iterator iter = newSelection.iterator();
+            while(iter.hasNext()) {
+                addSelectionPath((TreePath)iter.next());
+            }
+        }
+        
+        //---------------------------------------------------------------------
+        // Handle tree actions:
+        // - action is in <name>$action
+        // - path is in <name>$path
+        //---------------------------------------------------------------------
+        String action = req.getParameter(paramName + ":action");
+        
+        if (action == null || action.length() == 0) {
+            // Nothing more to do here
+            return;
+        }
+        
+        getForm().setSubmitWidget(this);
+        String pathValue = req.getParameter(paramName + ":path");
+        
+        if (pathValue == null || pathValue.length() == 0) {
+            //this.treeDef.getLogger().warn("No tree path given");
+            return;
+        }
+        
+        // Parse the path
+        TreePath path = TreePath.valueOf(pathValue);
+        
+        if ("expand".equals(action)) {
+            this.expandPath(path);
+        } else if ("collapse".equals(action)) {
+            this.collapsePath(path);
+        } else if ("toggle-collapse".equals(action)) {
+            if (this.isExpanded(path)) {
+                this.collapsePath(path);
+            } else {
+                this.expandPath(path);
+            }
+        } else if ("select".equals(action)) {
+            this.addSelectionPath(path);
+        } else if ("unselect".equals(action)) {
+            this.removeSelectionPath(path);
+        } else if ("toggle-select".equals(action)) {
+            if (this.isPathSelected(path)) {
+                this.removeSelectionPath(path);
+            } else {
+                this.addSelectionPath(path);
+            }
+        } else {
+            // Unknown action
+            //this.treeDef.getLogger().warn("Unknown action " + action);
+        }
+    }
+
+    public TreeModel getModel() {
+        return this.treeModel;
+    }
+    
+    public void setModel(TreeModel model) {
+        if (model == null) {
+            model = DefaultTreeModel.UNSPECIFIED_MODEL;
+        }
+        this.treeModel.removeTreeModelListener(this.modelListener);
+        this.treeModel = model;
+        model.addTreeModelListener(this.modelListener);
+    }
+    
+    private void markForRefresh(TreePath path) {
+        this.changedPaths.add(path);
+        getForm().addWidgetUpdate(this);
+    }
+    
+    //---------------------------------------------------------------------------------------------
+    // Selection
+    //---------------------------------------------------------------------------------------------
+    
+    public void setSelectionModel(int model) {
+        if (model < 0 || model > MULTIPLE_SELECTION) {
+            throw new IllegalArgumentException("Illegal selection model " + model);
+        }
+        
+        if (model == this.selectionModel) {
+            return;
+        }
+        
+        this.selectionModel = model;
+        if (model == SINGLE_SELECTION && getSelectionCount() > 1) {
+            clearSelection();
+        }
+    }
+    
+    public int getSelectionCount() {
+        return this.selectedPaths.size();
+    }
+
+    public TreePath getSelectionPath() {
+        if (this.selectedPaths.isEmpty()) {
+            return null;
+        } else {
+            return (TreePath)this.selectedPaths.iterator().next();
+        }
+    }
+    
+    public TreePath[] getSelectionPaths() {
+        return (TreePath[])this.selectedPaths.toArray(new TreePath[this.selectedPaths.size()]);
+    }
+    
+    public boolean isPathSelected(TreePath path) {
+        return this.selectedPaths.contains(path);
+    }
+    
+    public boolean isSelectionEmpty() {
+        return this.selectedPaths.isEmpty();
+    }
+    
+    public void setSelectionPath(TreePath path) {
+        clearSelection();
+        addSelectionPath(path);
+    }
+    
+    public void setSelectionPaths(TreePath paths[]) {
+        clearSelection();
+        addSelectionPaths(paths);
+    }
+    
+    public void addSelectionPath(TreePath path) {
+        if (this.selectionModel == SINGLE_SELECTION) {
+            clearSelection();
+        }
+
+        if (this.selectedPaths.add(path)) {
+            markForRefresh(path);
+            if (this.expandSelectedPath) {
+                expandPath(path);
+            }
+            if (this.selectionListener != null) {
+                this.selectionListener.selectionChanged(new TreeSelectionEvent(this, path, true));
+            }
+        }
+    }
+    
+    public void addSelectionPaths(TreePath paths[]) {
+        if (this.selectionModel == SINGLE_SELECTION) {
+            setSelectionPath(paths[0]);
+        } else {
+            for (int i = 0; i < paths.length; i++) {
+                addSelectionPath(paths[i]);
+                // FIXME: use array-based constructors of TreeSelectionEvent
+            }
+        }
+    }
+
+    public void removeSelectionPath(TreePath path) {
+        if (this.selectedPaths.remove(path)) {
+            // Need to redisplay the parent
+            markForRefresh(path.getParentPath());
+            if (this.selectionListener != null) {
+                this.selectionListener.selectionChanged(new TreeSelectionEvent(this, path, false));
+            }
+        }
+    }
+    
+    public void removeSelectionPaths(TreePath paths[]) {
+        for (int i = 0; i < paths.length; i++) {
+            removeSelectionPath(paths[i]);
+            // FIXME: use array-based constructors of TreeSelectionEvent
+        }
+    }
+    
+    public void clearSelection() {
+        if (this.isSelectionEmpty()) {
+            return;
+        }
+
+        TreePath[] paths = (TreePath[])this.selectedPaths.toArray(new TreePath[this.selectedPaths.size()]);
+        for (int i = 0; i < paths.length; i++) {
+            // Need to redisplay the parent
+            markForRefresh(paths[i].getParentPath());
+        }
+        this.selectedPaths.clear();
+        if (this.selectionListener != null) {
+            this.selectionListener.selectionChanged(new TreeSelectionEvent(this, paths, false));
+        }
+    }
+    
+    public void addTreeSelectionListener(TreeSelectionListener listener) {
+        this.selectionListener = WidgetEventMulticaster.add(this.selectionListener, listener);
+    }
+    
+    public void removeTreeSelectionListener(TreeSelectionListener listener) {
+        this.selectionListener = WidgetEventMulticaster.remove(this.selectionListener, listener);
+    }
+    
+    //---------------------------------------------------------------------------------------------
+    // Visibility, expand & collapse
+    //---------------------------------------------------------------------------------------------
+    
+    public boolean isCollapsed(TreePath path) {
+        return !isExpanded(path);
+    }
+    
+    public boolean isExpanded(TreePath path) {
+        if (this.expandedPaths.contains(path)) {
+            // Ensure all parents are expanded
+            TreePath parent = path.getParentPath();
+            return parent == null ? true : isExpanded(parent);
+        } else {
+            return false;
+        }
+    }
+    
+    /**
+     * Returns true if the value identified by path is currently viewable,
+     * which means it is either the root or all of its parents are expanded.
+     * Otherwise, this method returns false. 
+     *
+     * @return true if the node is viewable, otherwise false
+     */
+    public boolean isVisible(TreePath path) {
+        if (path == TreePath.ROOT_PATH) {
+            return true;
+        }
+        if (path != null) {
+            TreePath parent = path.getParentPath();
+            if (parent != null) {
+                return isExpanded(parent);
+            } else {
+                // root node
+                return true;
+            }
+        } else {
+            return false;
+        }
+    }
+    
+    public void makeVisible(TreePath path) {
+        if (path != null) {
+            TreePath parent = path.getParentPath();
+            if (parent != null) {
+                expandPath(parent);
+            }
+        }
+    }
+    
+    public boolean isRootVisible() {
+        return this.rootVisible;
+    }
+    
+    public void setRootVisible(boolean visible) {
+        if (this.rootVisible != visible) {
+            this.markForRefresh(TreePath.ROOT_PATH);
+            this.rootVisible = visible;
+            if (!visible) {
+                // Expand it so that first-level children are visible
+                this.expandPath(TreePath.ROOT_PATH);
+            }
+        }
+    }
+    
+    public void collapsePath(TreePath path) {
+        if (path != null) {
+            if (this.expandedPaths.remove(path)) {
+                markForRefresh(path);
+            }
+        }
+    }
+
+    public void expandPath(TreePath path) {
+        if (path != null) {
+            if (this.expandedPaths.add(path)) {
+                markForRefresh(path);
+            }
+        }
+    }
+    
+    public void setExpandsSelectedPath(boolean value) {
+        this.expandSelectedPath  = value;
+    }
+    
+    //---------------------------------------------------------------------------------------------
+    // Widget management
+    //---------------------------------------------------------------------------------------------
+    
+    public Widget getWidgetForPath(TreePath path) {
+        Widget result = (Widget)this.pathWidgets.get(path);
+        if (result == null && !this.pathWidgets.containsKey(path)) {
+            result = createWidgetForPath(path);
+            if (result != null) {
+                result.setAttribute("TreePath", path);
+            }
+            this.pathWidgets.put(path, result);
+        }
+        
+        return result;
+    }
+    
+    private Widget createWidgetForPath(TreePath path) {
+        //TODO
+        return null;
+    }
+    
+    //---------------------------------------------------------------------------------------------
+    // TreeNode widget, which is the actual parent of widgets contained in a node
+    //---------------------------------------------------------------------------------------------
+    // TODO
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/Tree.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import org.apache.cocoon.forms.event.WidgetEventMulticaster;
+import org.apache.cocoon.forms.formmodel.AbstractWidgetDefinition;
+import org.apache.cocoon.forms.formmodel.Widget;
+
+/**
+ * Definition of a {@link Tree} widget.
+ * 
+ * @version $Id$
+ */
+public class TreeDefinition extends AbstractWidgetDefinition {
+    
+    private TreeModelDefinition modelDefinition;
+    private boolean rootVisible = true;
+    private TreeSelectionListener selectionListener;
+    private int selectionModel = Tree.MULTIPLE_SELECTION;
+
+    public Widget createInstance() {
+        return new Tree(this);
+    }
+    
+    public TreeModel createModel() {
+        TreeModel model;
+        if (this.modelDefinition == null) {
+            model = DefaultTreeModel.UNSPECIFIED_MODEL;
+        } else {
+            model = modelDefinition.createInstance();
+        }
+        return model;
+    }
+
+    public void setModelDefinition(TreeModelDefinition definition) {
+        checkMutable();
+        this.modelDefinition = definition;
+    }
+
+    public void setRootVisible(boolean visible) {
+        checkMutable();
+        this.rootVisible = visible;
+    }
+    
+    public boolean isRootVisible() {
+        return this.rootVisible;
+    }
+    
+    public void setSelectionModel(int model) {
+        checkMutable();
+        this.selectionModel = model;
+    }
+    
+    public int getSelectionModel() {
+        return this.selectionModel;
+    }
+
+    public void addSelectionListener(TreeSelectionListener listener) {
+        checkMutable();
+        this.selectionListener = WidgetEventMulticaster.add(this.selectionListener, listener);
+    }
+    
+    public TreeSelectionListener getSelectionListener() {
+        return this.selectionListener;
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeDefinition.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,72 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.Collection;
+
+/**
+ * Data model for the {@link Tree} widget, inspired by Swing's <code>TreeModel</code>, with
+ * the difference that child nodes are accessed through keys rather than indices.
+ * 
+ * @version $Id$
+ */
+public interface TreeModel {
+
+    /**
+     * Returns the root of the tree.  Returns <code>null</code>
+     * only if the tree has no nodes.
+     *
+     * @return  the root of the tree
+     */
+    public Object getRoot();
+
+
+    public Collection getChildren(Object parent);
+
+    /**
+     * Returns <code>true</code> if <code>node</code> is a leaf.
+     * It is possible for this method to return <code>false</code>
+     * even if <code>node</code> has no children.
+     * A directory in a filesystem, for example,
+     * may contain no files; the node representing
+     * the directory is not a leaf, but it also has no children.
+     *
+     * @param   node  a node in the tree, obtained from this data source
+     * @return  true if <code>node</code> is a leaf
+     */
+    public boolean isLeaf(Object node);
+
+    public String getChildKey(Object parent, Object child);
+    
+    public Object getChild(Object parent, String key);
+    
+    public Object getNode(TreePath path);
+
+    /**
+     * Adds a listener for the {@link TreeModelEvent} posted after the tree changes.
+     *
+     * @param   l       the listener to add
+     */
+    void addTreeModelListener(TreeModelListener l);
+
+    /**
+     * Removes a listener previously added with {@link #addTreeModelListener(TreeModelListener)}.
+     *
+     * @param   l       the listener to remove
+     */  
+    void removeTreeModelListener(TreeModelListener l);
+
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModel.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,28 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+/**
+ * Creates {@link TreeModel}s. Note that the builder/definition/instance pattern is
+ * is necessary here as tree models are mutable things, and therefore a new instance
+ * must be created for each tree widget.
+ * 
+ * @version $Id$
+ */
+public interface TreeModelDefinition {
+
+    TreeModel createInstance();
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelDefinition.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,40 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.EventObject;
+
+/**
+ * Event indicating a change on a {@link TreeModel}.
+ * 
+ * @version $Id$
+ */
+public class TreeModelEvent extends EventObject {
+    
+    private TreePath path;
+    
+    public TreeModelEvent(TreeModel model, TreePath path) {
+        super(model);
+    }
+    
+    public TreeModel getSourceModel() {
+        return (TreeModel)super.getSource();
+    }
+    
+    public TreePath getPath() {
+        return this.path;
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelEvent.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.EventListener;
+
+import org.apache.cocoon.forms.event.WidgetEventMulticaster;
+
+/**
+ * A helper class to ease the implementation of {@link TreeModel}s
+ * 
+ * @version $Id$
+ */
+public class TreeModelHelper {
+    
+    private TreeModel model;
+    
+    private TreeModelListener listener;
+
+    public TreeModelHelper(TreeModel model) {
+        this.model = model;
+    }
+    
+    public Object getNode(TreePath path) {
+        if (path == TreePath.ROOT_PATH) {
+            return model.getRoot();
+        }
+        
+        Object parent = getNode(path.getParentPath());
+        if (parent == null) {
+            return null;
+        }
+        
+        return model.getChild(parent, path.getLastKey());
+    }
+
+    public void addTreeModelListener(TreeModelListener listener) {
+        this.listener = EventMulticaster.add(this.listener, listener);
+    }
+
+    public void removeTreeModelListener(TreeModelListener listener) {
+        this.listener = EventMulticaster.remove(this.listener, listener);
+    }
+
+    boolean hasListeners() {
+        return this.listener != null;
+    }
+
+    public void fireTreeStructureChanged(TreePath path) {
+        if (hasListeners()) {
+            TreeModelEvent event = new TreeModelEvent(model, path);
+            this.listener.treeStructureChanged(event);
+        }
+    }
+
+    private static class EventMulticaster extends WidgetEventMulticaster implements TreeModelListener {
+
+        protected EventMulticaster(EventListener a, EventListener b) {
+            super(a, b);
+        }
+
+        protected static EventListener addInternal(EventListener a, EventListener b) {
+            if (a == null)  return b;
+            if (b == null)  return a;
+            return new EventMulticaster(a, b);
+        }
+        
+        public static TreeModelListener add(TreeModelListener a, TreeModelListener b) {
+            return (TreeModelListener)addInternal(a, b);
+        }
+        
+        public static TreeModelListener remove(TreeModelListener l, TreeModelListener oldl) {
+            return (TreeModelListener)removeInternal(l, oldl);
+        }
+        
+        public void treeStructureChanged(TreeModelEvent event) {
+            ((TreeModelListener)a).treeStructureChanged(event);
+            ((TreeModelListener)b).treeStructureChanged(event);
+        }
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelHelper.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,27 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.EventListener;
+
+/**
+ * Listener of changes to a {@link TreeModel}.
+ * 
+ * @version $Id$
+ */
+public interface TreeModelListener extends EventListener {
+    public void treeStructureChanged(TreeModelEvent event);
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeModelListener.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,298 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import java.util.StringTokenizer;
+
+/**
+ * A path in a {@link TreeModel}.
+ * 
+ * @version $Id$
+ */
+public class TreePath {
+    
+    public static final TreePath ROOT_PATH = new TreePath();
+    /**
+     * Path representing the parent, null if lastPathComponent represents the
+     * root.
+     */
+    private TreePath parentPath;
+
+    /** Last path component. */
+    private String key;
+    
+    /** Cached result of toString() */
+    private String cachedToString;
+    
+    /**
+     * Builds a path representing the root node or a tree model.
+     * Private to only be used by the ROOT_PATH constant.
+     */
+    private TreePath() {
+        this.key = "";
+        this.cachedToString = "/";
+    }
+
+    /**
+     * Constructs a TreePath containing only a single element. This is usually
+     * used to construct a TreePath for the the root of the TreeModel.
+     * <p>
+     * 
+     * @param singlePath
+     *            an Object representing the path to a node
+     * @see #TreePath(Object[])
+     */
+    public TreePath(String key) {
+        if (key == null || key.length() == 0) {
+            throw new IllegalArgumentException("key must be non empty.");
+        }
+        
+        if (key.indexOf('/') != -1) {
+            throw new IllegalArgumentException("key cannot contain a '/'");
+        }
+        this.key = key;
+        parentPath = ROOT_PATH;
+    }
+
+    /**
+     * Constructs a new TreePath, which is the path identified by
+     * <code>parent</code> ending in <code>lastElement</code>.
+     */
+    public TreePath(TreePath parent, String key) {
+        this(key);
+        if (parent == null) {
+            throw new IllegalArgumentException("Parent path must be non null.");
+        }
+        this.parentPath = parent;
+    }
+
+    /**
+     * Returns an ordered array of Objects containing the components of this
+     * TreePath. The first element (index 0) is the root.
+     * 
+     * @return an array of Objects representing the TreePath
+     * @see #TreePath(Object[])
+     */
+    public Object[] getObjectPath(TreeModel model) {
+        int i = getPathCount();
+        Object[] result = new Object[i--];
+
+        for (TreePath path = this; path != null; path = path.parentPath) {
+            result[i--] = path.getLastPathObject(model);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the last component of this path. For a path returned by
+     * DefaultTreeModel this will return an instance of TreeNode.
+     * 
+     * @return the Object at the end of the path
+     * @see #TreePath(Object[])
+     */
+    public Object getLastPathObject(TreeModel model) {
+        Object parent;
+        if (this.parentPath == ROOT_PATH) {
+            parent = model.getRoot();
+        } else {
+            parent = this.parentPath.getLastPathObject(model);
+        }
+        return model.getChild(parent, this.key);
+    }
+
+    /**
+     * Returns the number of elements in the path.
+     * 
+     * @return an int giving a count of items the path
+     */
+    public int getPathCount() {
+        int result = 0;
+        for (TreePath path = this; path != null; path = path.parentPath) {
+            result++;
+        }
+        return result;
+    }
+
+//    /**
+//     * Returns the path component at the specified index.
+//     * 
+//     * @param element
+//     *            an int specifying an element in the path, where 0 is the first
+//     *            element in the path
+//     * @return the Object at that index location
+//     * @throws IllegalArgumentException
+//     *             if the index is beyond the length of the path
+//     * @see #TreePath(Object[])
+//     */
+//    public Object getPathComponent(int element) {
+//        int pathLength = getPathCount();
+//
+//        if (element < 0 || element >= pathLength)
+//            throw new IllegalArgumentException("Index " + element + " is out of the specified range");
+//
+//        TreePath path = this;
+//
+//        for (int i = pathLength - 1; i != element; i--) {
+//            path = path.parentPath;
+//        }
+//        return path.lastPathComponent;
+//    }
+//
+    /**
+     * Tests if two paths are equal. Two paths are considered equal if they are
+     * of same length and contain the same keys.
+     * 
+     * @param obj the object ot compare
+     */
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!(obj instanceof TreePath)) {
+            return false;
+        }
+        
+        TreePath otherPath = (TreePath)obj;
+
+        if (getPathCount() != otherPath.getPathCount()) {
+            return false;
+        }
+        
+        TreePath path = this;
+        do {
+            if (otherPath == null || !path.key.equals(otherPath.key)) {
+                return false;
+            }
+            path = path.parentPath;
+            otherPath = otherPath.parentPath;
+        } while(path != null);
+        
+        return true;
+    }
+
+    public int hashCode() {
+        // Should be enough. We may also xor with parent's hashcode.
+        return key.hashCode();
+    }
+
+    /**
+     * Returns true if <code>aTreePath</code> is a descendant of this
+     * TreePath. A TreePath P1 is a descendent of a TreePath P2 if P1 contains
+     * all of the components that make up P2's path. For example, if this object
+     * has the path [a, b], and <code>aTreePath</code> has the path [a, b, c],
+     * then <code>aTreePath</code> is a descendant of this object. However, if
+     * <code>aTreePath</code> has the path [a], then it is not a descendant of
+     * this object.
+     * 
+     * @return true if <code>aTreePath</code> is a descendant of this path
+     */
+    public boolean isDescendant(TreePath aTreePath) {
+        if (aTreePath == this)
+            return true;
+
+        if (aTreePath != null) {
+            int pathLength = getPathCount();
+            int oPathLength = aTreePath.getPathCount();
+
+            if (oPathLength < pathLength)
+                // Can't be a descendant, has fewer components in the path.
+                return false;
+            while (oPathLength-- > pathLength)
+                aTreePath = aTreePath.getParentPath();
+            return equals(aTreePath);
+        }
+        return false;
+    }
+
+//    /**
+//     * Returns a new path containing all the elements of this object plus
+//     * <code>child</code>. <code>child</code> will be the last element of
+//     * the newly created TreePath. This will throw a NullPointerException if
+//     * child is null.
+//     */
+//    public TreePath pathByAddingChild(Object child) {
+//        if (child == null)
+//            throw new NullPointerException("Null child not allowed");
+//
+//        return new TreePath(this, child);
+//    }
+
+    /**
+     * Returns a path containing all the elements of this object, except the
+     * last path component.
+     */
+    public TreePath getParentPath() {
+        return parentPath;
+    }
+    
+    /**
+     * Returns the key of last element of this path.
+     */
+    public String getLastKey() {
+        return this.key;
+    }
+
+    /**
+     * Returns a string that displays and identifies this object's properties.
+     * 
+     * @return a String representation of this object
+     */
+    public String toString() {
+        if (this.cachedToString == null) {
+            StringBuffer buf = new StringBuffer();
+            appendTo(buf);
+            this.cachedToString = buf.toString();
+        }
+        return this.cachedToString;
+    }
+    
+    /** Recursively build the text representation of a tree path */
+    private void appendTo(StringBuffer buf) {
+        if (this.parentPath != ROOT_PATH) {
+            this.parentPath.appendTo(buf);
+        }
+        buf.append('/');
+        buf.append(this.key);
+    }
+    
+    /**
+     * Returns the <code>TreePath</code> represented by a given String.
+     * @param s the string representation of the path
+     * @return a path object
+     * 
+     * @see #toString()
+     */
+    public static TreePath valueOf(String s) {
+        // FIXME: see if some caching could be useful here.
+        if (s == null || s.length() == 0) {
+            throw new IllegalArgumentException("Invalid empty string");
+        }
+        StringTokenizer stok = new StringTokenizer(s, "/");
+        TreePath current = ROOT_PATH;
+        while (stok.hasMoreTokens()) {
+            String tok = stok.nextToken();
+            current = current == null ? new TreePath(tok) : new TreePath(current, tok);
+        }
+
+        return current;
+    }
+    
+    public Object getObject(TreeModel model) {
+        return this.parentPath == null ?
+                model.getRoot() :
+                model.getChild(this.parentPath.getObject(model), this.key);
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreePath.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java
URL: http://svn.apache.org/viewcvs/cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java?rev=190962&view=auto
==============================================================================
--- cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java (added)
+++ cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java Thu Jun 16 10:17:00 2005
@@ -0,0 +1,86 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.cocoon.forms.formmodel.tree;
+
+import org.apache.cocoon.forms.event.WidgetEvent;
+
+/**
+ * An event fired when the selection of a {@link Tree} changes.
+ * 
+ * @version $Id$
+ */
+public class TreeSelectionEvent extends WidgetEvent {
+    
+    TreePath[] paths;
+    boolean [] isNew;
+
+    public TreeSelectionEvent(Tree source, TreePath path, boolean isNew) {
+        super(source);
+        this.paths = new TreePath[] { path };
+        this.isNew = new boolean[] { isNew };
+    }
+    
+    public TreeSelectionEvent(Tree source, TreePath paths[], boolean areNew[]) {
+        super(source);
+        this.paths = paths;
+        this.isNew = areNew;
+    }
+    
+    public TreeSelectionEvent(Tree source, TreePath paths[], boolean allNew) {
+        super(source);
+        this.paths = paths;
+        
+        // Fill isNew with allNew
+        this.isNew = new boolean[paths.length];
+        for (int i = 0; i < isNew.length; i++) {
+            this.isNew[i] = allNew;
+        }
+    }
+    
+    public Tree getTree() {
+        return (Tree)super.getSource();
+    }
+    
+    /**
+     * Get the first path element.
+     */
+    public TreePath getPath() {
+        return this.paths[0];
+    }
+    
+    /**
+     * Is the first path a new addition to the selection?
+     * 
+     * @return
+     */
+    public boolean isAddedPath() {
+        return this.isNew[0];
+    }
+
+    /**
+     * Get paths that have been added or removed from the selection.
+     */
+    public TreePath[] getPaths() {
+        return this.getPaths();
+    }
+
+    /**
+     * Was the <code>index</code>th path added to the selection?
+     */
+    public boolean isAddedPath(int index) {
+        return this.isNew[index];
+    }
+}

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: cocoon/blocks/forms/trunk/java/org/apache/cocoon/forms/formmodel/tree/TreeSelectionEvent.java
------------------------------------------------------------------------------
    svn:keywords = Id



Mime
View raw message