blob: e7534f3f71f69b4ca6a17bfa1c3687de5f17c9d0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
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.WidgetEvent;
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);
}
private void markForRefresh(List paths) {
this.changedPaths.addAll(paths);
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);
}
this.getForm().addWidgetEvent(new TreeSelectionEvent(this, path, true));
}
}
public void addSelectionPaths(TreePath paths[]) {
if (this.selectionModel == SINGLE_SELECTION) {
setSelectionPath(paths[0]);
} else {
List pathsList = Arrays.asList(paths);
if (this.selectedPaths.addAll(pathsList)) {
markForRefresh(pathsList);
if (this.expandSelectedPath) {
this.expandedPaths.addAll(pathsList);
}
this.getForm().addWidgetEvent(new TreeSelectionEvent(this, paths, true));
}
}
}
public void removeSelectionPath(TreePath path) {
if (this.selectedPaths.remove(path)) {
// Need to redisplay the parent
markForRefresh(path.getParentPath());
this.getForm().addWidgetEvent(new TreeSelectionEvent(this, path, false));
}
}
public void removeSelectionPaths(TreePath paths[]) {
List pathsList = Arrays.asList(paths);
if (this.selectedPaths.removeAll(pathsList)) {
markForRefresh(pathsList);
this.getForm().addWidgetEvent(new TreeSelectionEvent(this, paths, false));
}
}
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();
this.getForm().addWidgetEvent(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);
// Make visible also all parent paths
makeVisible(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 collapseAll() {
this.expandedPaths.clear();
if (!this.rootVisible) {
this.expandedPaths.add(TreePath.ROOT_PATH);
}
}
public void expandAll() {
collapseAll();
this.expandedPaths.add(TreePath.ROOT_PATH);
TreeWalker tw = new TreeWalker(this);
tw.enterChildren();
while (tw.hasNext()) {
tw.next();
if (!tw.isLeaf()) {
expandPath(tw.getPath());
tw.enterChildren();
}
if (!tw.hasNext()) {
tw.leave();
}
}
}
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;
}
public void broadcastEvent(WidgetEvent event) {
if (event instanceof TreeSelectionEvent) {
if (this.selectionListener != null) {
this.selectionListener.selectionChanged((TreeSelectionEvent)event);
}
} else {
super.broadcastEvent(event);
}
}
//---------------------------------------------------------------------------------------------
// TreeNode widget, which is the actual parent of widgets contained in a node
//---------------------------------------------------------------------------------------------
// TODO
}