001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.formmodel.tree;
018:
019: import java.util.HashMap;
020: import java.util.HashSet;
021: import java.util.Iterator;
022: import java.util.Locale;
023: import java.util.Set;
024:
025: import org.apache.cocoon.environment.Request;
026: import org.apache.cocoon.forms.FormContext;
027: import org.apache.cocoon.forms.event.WidgetEvent;
028: import org.apache.cocoon.forms.event.WidgetEventMulticaster;
029: import org.apache.cocoon.forms.formmodel.AbstractWidget;
030: import org.apache.cocoon.forms.formmodel.Widget;
031: import org.apache.cocoon.forms.formmodel.WidgetDefinition;
032: import org.xml.sax.ContentHandler;
033: import org.xml.sax.SAXException;
034:
035: /**
036: * A tree widget, heavily inspired by Swing's <code>JTree</code>.
037: *
038: * @version $Id: Tree.java 506705 2007-02-12 22:23:59Z simoneg $
039: */
040: public class Tree extends AbstractWidget {
041:
042: public static final int SINGLE_SELECTION = 0;
043: public static final int MULTIPLE_SELECTION = 1;
044:
045: public interface ActionHandler {
046: public void act(Tree tree, FormContext context);
047: }
048:
049: private TreeDefinition treeDef;
050:
051: private TreeModel treeModel;
052:
053: private Set expandedPaths = new HashSet();
054:
055: private Set selectedPaths = new HashSet();
056:
057: private Set changedPaths = new HashSet();
058:
059: private HashMap pathWidgets = new HashMap();
060:
061: private boolean rootVisible = true;
062:
063: private boolean expandSelectedPath = false;
064:
065: private TreeSelectionListener selectionListener;
066:
067: private int selectionModel = MULTIPLE_SELECTION;
068:
069: private TreeModelListener modelListener = new TreeModelListener() {
070: public void treeStructureChanged(TreeModelEvent event) {
071: markForRefresh(event.getPath());
072: }
073: };
074:
075: protected Tree(TreeDefinition definition) {
076: super (definition);
077: this .treeDef = definition;
078: this .rootVisible = definition.isRootVisible();
079: if (!this .rootVisible) {
080: // Expand it so that first-level children are visible
081: this .expandedPaths.add(TreePath.ROOT_PATH);
082: }
083: this .treeModel = definition.createModel();
084: this .treeModel.addTreeModelListener(modelListener);
085: this .selectionListener = definition.getSelectionListener();
086: this .selectionModel = definition.getSelectionModel();
087: }
088:
089: public WidgetDefinition getDefinition() {
090: return this .treeDef;
091: }
092:
093: protected String getXMLElementName() {
094: return "tree";
095: }
096:
097: protected void generateItemSaxFragment(
098: ContentHandler contentHandler, Locale locale)
099: throws SAXException {
100: throw new UnsupportedOperationException(
101: this
102: + " cannot be rendered using <ft:widget>. Please use <ft:tree>.");
103: }
104:
105: public void readFromRequest(FormContext formContext) {
106: //TODO: crawl open nodes, calling their widget's readFromRequest
107:
108: Request req = formContext.getRequest();
109: String paramName = getRequestParameterName();
110:
111: //---------------------------------------------------------------------
112: // Handle node selection using checkboxes named <id>$select
113: //---------------------------------------------------------------------
114: String[] selectValues = req.getParameterValues(paramName
115: + ":select");
116: if (selectValues != null) {
117: // Create the set of paths given by the request
118: Set newSelection = new HashSet();
119: for (int i = 0; i < selectValues.length; i++) {
120: newSelection.add(TreePath.valueOf(selectValues[i]));
121: }
122:
123: // Check if all visible selections are in the new selection
124: TreePath[] currentSelection = (TreePath[]) this .selectedPaths
125: .toArray(new TreePath[this .selectedPaths.size()]);
126: for (int i = 0; i < currentSelection.length; i++) {
127: TreePath p = currentSelection[i];
128: if (!newSelection.contains(p) && isVisible(p)) {
129: removeSelectionPath(p);
130: }
131: }
132:
133: // Now add the currently selected items (may be selected already)
134: Iterator iter = newSelection.iterator();
135: while (iter.hasNext()) {
136: addSelectionPath((TreePath) iter.next());
137: }
138: }
139:
140: //---------------------------------------------------------------------
141: // Handle tree actions:
142: // - action is in <name>$action
143: // - path is in <name>$path
144: //---------------------------------------------------------------------
145: String action = req.getParameter(paramName + ":action");
146:
147: if (action == null || action.length() == 0) {
148: // Nothing more to do here
149: return;
150: }
151:
152: getForm().setSubmitWidget(this );
153: String pathValue = req.getParameter(paramName + ":path");
154:
155: if (pathValue == null || pathValue.length() == 0) {
156: //this.treeDef.getLogger().warn("No tree path given");
157: return;
158: }
159:
160: // Parse the path
161: TreePath path = TreePath.valueOf(pathValue);
162:
163: if ("expand".equals(action)) {
164: this .expandPath(path);
165: } else if ("collapse".equals(action)) {
166: this .collapsePath(path);
167: } else if ("toggle-collapse".equals(action)) {
168: if (this .isExpanded(path)) {
169: this .collapsePath(path);
170: } else {
171: this .expandPath(path);
172: }
173: } else if ("select".equals(action)) {
174: this .addSelectionPath(path);
175: } else if ("unselect".equals(action)) {
176: this .removeSelectionPath(path);
177: } else if ("toggle-select".equals(action)) {
178: if (this .isPathSelected(path)) {
179: this .removeSelectionPath(path);
180: } else {
181: this .addSelectionPath(path);
182: }
183: } else {
184: // Unknown action
185: //this.treeDef.getLogger().warn("Unknown action " + action);
186: }
187: }
188:
189: public TreeModel getModel() {
190: return this .treeModel;
191: }
192:
193: public void setModel(TreeModel model) {
194: if (model == null) {
195: model = DefaultTreeModel.UNSPECIFIED_MODEL;
196: }
197: this .treeModel.removeTreeModelListener(this .modelListener);
198: this .treeModel = model;
199: model.addTreeModelListener(this .modelListener);
200: }
201:
202: private void markForRefresh(TreePath path) {
203: this .changedPaths.add(path);
204: getForm().addWidgetUpdate(this );
205: }
206:
207: //---------------------------------------------------------------------------------------------
208: // Selection
209: //---------------------------------------------------------------------------------------------
210:
211: public void setSelectionModel(int model) {
212: if (model < 0 || model > MULTIPLE_SELECTION) {
213: throw new IllegalArgumentException(
214: "Illegal selection model " + model);
215: }
216:
217: if (model == this .selectionModel) {
218: return;
219: }
220:
221: this .selectionModel = model;
222: if (model == SINGLE_SELECTION && getSelectionCount() > 1) {
223: clearSelection();
224: }
225: }
226:
227: public int getSelectionCount() {
228: return this .selectedPaths.size();
229: }
230:
231: public TreePath getSelectionPath() {
232: if (this .selectedPaths.isEmpty()) {
233: return null;
234: } else {
235: return (TreePath) this .selectedPaths.iterator().next();
236: }
237: }
238:
239: public TreePath[] getSelectionPaths() {
240: return (TreePath[]) this .selectedPaths
241: .toArray(new TreePath[this .selectedPaths.size()]);
242: }
243:
244: public boolean isPathSelected(TreePath path) {
245: return this .selectedPaths.contains(path);
246: }
247:
248: public boolean isSelectionEmpty() {
249: return this .selectedPaths.isEmpty();
250: }
251:
252: public void setSelectionPath(TreePath path) {
253: clearSelection();
254: addSelectionPath(path);
255: }
256:
257: public void setSelectionPaths(TreePath paths[]) {
258: clearSelection();
259: addSelectionPaths(paths);
260: }
261:
262: public void addSelectionPath(TreePath path) {
263: if (this .selectionModel == SINGLE_SELECTION) {
264: clearSelection();
265: }
266:
267: if (this .selectedPaths.add(path)) {
268: markForRefresh(path);
269: if (this .expandSelectedPath) {
270: expandPath(path);
271: }
272: this .getForm().addWidgetEvent(
273: new TreeSelectionEvent(this , path, true));
274: }
275: }
276:
277: public void addSelectionPaths(TreePath paths[]) {
278: if (this .selectionModel == SINGLE_SELECTION) {
279: setSelectionPath(paths[0]);
280: } else {
281: for (int i = 0; i < paths.length; i++) {
282: addSelectionPath(paths[i]);
283: // FIXME: use array-based constructors of TreeSelectionEvent
284: }
285: }
286: }
287:
288: public void removeSelectionPath(TreePath path) {
289: if (this .selectedPaths.remove(path)) {
290: // Need to redisplay the parent
291: markForRefresh(path.getParentPath());
292: this .getForm().addWidgetEvent(
293: new TreeSelectionEvent(this , path, false));
294: }
295: }
296:
297: public void removeSelectionPaths(TreePath paths[]) {
298: for (int i = 0; i < paths.length; i++) {
299: removeSelectionPath(paths[i]);
300: // FIXME: use array-based constructors of TreeSelectionEvent
301: }
302: }
303:
304: public void clearSelection() {
305: if (this .isSelectionEmpty()) {
306: return;
307: }
308:
309: TreePath[] paths = (TreePath[]) this .selectedPaths
310: .toArray(new TreePath[this .selectedPaths.size()]);
311: for (int i = 0; i < paths.length; i++) {
312: // Need to redisplay the parent
313: markForRefresh(paths[i].getParentPath());
314: }
315: this .selectedPaths.clear();
316: this .getForm().addWidgetEvent(
317: new TreeSelectionEvent(this , paths, false));
318: }
319:
320: public void addTreeSelectionListener(TreeSelectionListener listener) {
321: this .selectionListener = WidgetEventMulticaster.add(
322: this .selectionListener, listener);
323: }
324:
325: public void removeTreeSelectionListener(
326: TreeSelectionListener listener) {
327: this .selectionListener = WidgetEventMulticaster.remove(
328: this .selectionListener, listener);
329: }
330:
331: //---------------------------------------------------------------------------------------------
332: // Visibility, expand & collapse
333: //---------------------------------------------------------------------------------------------
334:
335: public boolean isCollapsed(TreePath path) {
336: return !isExpanded(path);
337: }
338:
339: public boolean isExpanded(TreePath path) {
340: if (this .expandedPaths.contains(path)) {
341: // Ensure all parents are expanded
342: TreePath parent = path.getParentPath();
343: return parent == null ? true : isExpanded(parent);
344: } else {
345: return false;
346: }
347: }
348:
349: /**
350: * Returns true if the value identified by path is currently viewable,
351: * which means it is either the root or all of its parents are expanded.
352: * Otherwise, this method returns false.
353: *
354: * @return true if the node is viewable, otherwise false
355: */
356: public boolean isVisible(TreePath path) {
357: if (path == TreePath.ROOT_PATH) {
358: return true;
359: }
360: if (path != null) {
361: TreePath parent = path.getParentPath();
362: if (parent != null) {
363: return isExpanded(parent);
364: } else {
365: // root node
366: return true;
367: }
368: } else {
369: return false;
370: }
371: }
372:
373: public void makeVisible(TreePath path) {
374: if (path != null) {
375: TreePath parent = path.getParentPath();
376: if (parent != null) {
377: expandPath(parent);
378: // Make visible also all parent paths
379: makeVisible(parent);
380: }
381: }
382: }
383:
384: public boolean isRootVisible() {
385: return this .rootVisible;
386: }
387:
388: public void setRootVisible(boolean visible) {
389: if (this .rootVisible != visible) {
390: this .markForRefresh(TreePath.ROOT_PATH);
391: this .rootVisible = visible;
392: if (!visible) {
393: // Expand it so that first-level children are visible
394: this .expandPath(TreePath.ROOT_PATH);
395: }
396: }
397: }
398:
399: public void collapsePath(TreePath path) {
400: if (path != null) {
401: if (this .expandedPaths.remove(path)) {
402: markForRefresh(path);
403: }
404: }
405: }
406:
407: public void expandPath(TreePath path) {
408: if (path != null) {
409: if (this .expandedPaths.add(path)) {
410: markForRefresh(path);
411: }
412: }
413: }
414:
415: public void collapseAll() {
416: this .expandedPaths.clear();
417: if (!this .rootVisible) {
418: this .expandedPaths.add(TreePath.ROOT_PATH);
419: }
420: }
421:
422: public void expandAll() {
423: collapseAll();
424: this .expandedPaths.add(TreePath.ROOT_PATH);
425: TreeWalker tw = new TreeWalker(this );
426: tw.enterChildren();
427: while (tw.hasNext()) {
428: tw.next();
429: if (!tw.isLeaf()) {
430: expandPath(tw.getPath());
431: tw.enterChildren();
432: }
433: if (!tw.hasNext()) {
434: tw.leave();
435: }
436: }
437: }
438:
439: public void setExpandsSelectedPath(boolean value) {
440: this .expandSelectedPath = value;
441: }
442:
443: //---------------------------------------------------------------------------------------------
444: // Widget management
445: //---------------------------------------------------------------------------------------------
446:
447: public Widget getWidgetForPath(TreePath path) {
448: Widget result = (Widget) this .pathWidgets.get(path);
449: if (result == null && !this .pathWidgets.containsKey(path)) {
450: result = createWidgetForPath(path);
451: if (result != null) {
452: result.setAttribute("TreePath", path);
453: }
454: this .pathWidgets.put(path, result);
455: }
456:
457: return result;
458: }
459:
460: private Widget createWidgetForPath(TreePath path) {
461: //TODO
462: return null;
463: }
464:
465: public void broadcastEvent(WidgetEvent event) {
466: if (event instanceof TreeSelectionEvent) {
467: if (this .selectionListener != null) {
468: this .selectionListener
469: .selectionChanged((TreeSelectionEvent) event);
470: }
471: } else {
472: super .broadcastEvent(event);
473: }
474: }
475: //---------------------------------------------------------------------------------------------
476: // TreeNode widget, which is the actual parent of widgets contained in a node
477: //---------------------------------------------------------------------------------------------
478: // TODO
479: }
|