/******************************************************************************* * Copyright 2015 xWic group (http://www.xwic.de) * * 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. * *******************************************************************************/ /* * Created on 04.11.2004 */ package de.jwic.controls; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.StringTokenizer; import javax.swing.tree.TreeNode; import de.jwic.base.Control; import de.jwic.base.IControlContainer; import de.jwic.events.ElementSelectedEvent; import de.jwic.events.ElementSelectedListener; /** * A TreeControl is a tree that displays TreeNode objects that can be clicked. * * $Id: TreeControl.java,v 1.2 2008/09/17 15:19:53 lordsam Exp $ * @version $Revision: 1.2 $ * @author JBornemann */ public class TreeControl extends Control { private static final long serialVersionUID = 1L; /** Click action for clicking on a node's title */ public final static String ACTION_CLICK = "click"; public final static String ACTION_SELECT = "select"; public final static String ACTION_DESELECT = "deselect"; public final static String ACTION_EXPAND = "expand"; public final static String ACTION_COLLAPSE = "collapse"; /** * Select the clicked node. If selected nodes exist before the sel is cleard. * If the node has children it is expanded. * ElemententSelectedEvent is dispatched. * This is the default click mode. */ public final static int CLICK_MODE_SELECT_EXPAND = 0; /** * Select or deselect the clicked node. * ElemententSelectedEvent is dispatched. * Allows multi select. */ public final static int CLICK_MODE_MULTI_SELECT_DESELECT = 1; /** * Only ElemententSelectedEvent is dispatched. */ public final static int CLICK_MODE_DISPATCH_ONLY = 2; /** * Select the clicked node. If selected nodes exist before the sel is cleard. * ElemententSelectedEvent is dispatched. */ public final static int CLICK_MODE_SELECT = 3; protected int clickMode = CLICK_MODE_SELECT_EXPAND; /** Set of nodeID String of selected TreeNodes */ protected HashSet<String> selected = new HashSet<String>(); /** Set of nodeID String of expanded TreeNodes */ protected HashSet<String> expanded = new HashSet<String>(); /** Root TreeNode of this tree */ protected TreeNode rootNode = null; /** List of listender to inform */ protected List<ElementSelectedListener> selectionListeners = null; /** Render root node */ protected boolean renderRootNode = true; /** * @param container */ public TreeControl(IControlContainer container) { super(container, null); } /** * @param container * @param name */ public TreeControl(IControlContainer container, String name) { super(container, name); } /** * Sets the root node. * @param node */ public void setRootNode(TreeNode node) { rootNode = node; if (!renderRootNode && !rootNode.isLeaf()) { // root node is not displayed, so expand root node expand("0"); } requireRedraw(); } /** * Returns the root node. * @return */ public TreeNode getRootNode() { return rootNode; } /** * Returns the TreeNode found by its nodeID. * The root node's nodeID is "0". * Root node's first child's nodeID is "0-0"; * @param nodeID * @return */ public TreeNode getNode(String nodeID) { if (nodeID.equals("0")) { // return root return rootNode; } else { // remove root String "0-" nodeID = nodeID.substring(2); } TreeNode node = rootNode; for (Enumeration<?> en = new StringTokenizer(nodeID, "-"); en.hasMoreElements();) { String token = (String)en.nextElement(); if (!token.equals("-")) { int idx = Integer.parseInt(token); node = node.getChildAt(idx); } } return node; } public static String getNodeID(TreeNode node) { String key = null; for (TreeNode parent = node.getParent(); parent != null; parent = node.getParent()) { int idx = parent.getIndex(node); if (key == null) { key = String.valueOf(idx); } else { key = String.valueOf(idx) + "-" + key; } node = parent; } return key == null ? "0" : "0-" + key; } /* (non-Javadoc) * @see de.jwic.base.IControl#actionPerformed(java.lang.String, java.lang.String) */ public void actionPerformed(String actionId, String parameter) { if (ACTION_CLICK.equals(actionId)) { // node will be clicked click(parameter); } else if (ACTION_SELECT.equals(actionId)) { // node will be selected select(parameter); } else if (ACTION_DESELECT.equals(actionId)) { // node will be deselected deselect(parameter); } else if (ACTION_EXPAND.equals(actionId)) { // node will be expanded expand(parameter); } else if (ACTION_COLLAPSE.equals(actionId)) { // node will be expanded collapse(parameter); } } /** * Click the node. * @param nodeID */ public void click(String nodeID) { switch (clickMode) { case CLICK_MODE_SELECT_EXPAND : { doSelectExpand(this, nodeID); break; } case CLICK_MODE_SELECT : { doSelect(this, nodeID); break; } case CLICK_MODE_MULTI_SELECT_DESELECT : { doMultiSelect(this, nodeID); break; } case CLICK_MODE_DISPATCH_ONLY : { // The event is dispatched, without selecting the node. // This is a somewhat strange behaivior, but it must remain for // compatibility reasons. sendElementSelectedEvent(nodeID); break; } } } /** * Do the multi select. * @param tree * @param nodeID */ public static void doMultiSelect(TreeControl tree, String nodeID) { if (!tree.selected.contains(nodeID)) { // select the node if it is not selected tree.select(nodeID); } else { // deselect selected node tree.deselect(nodeID); } } /** * Do the select and expand. * @param tree * @param nodeID */ public static void doSelectExpand(TreeControl tree, String nodeID) { doSelect(tree, nodeID); if (!tree.getNode(nodeID).isLeaf()) { // node isn't a leaf, so expand it tree.expand(nodeID); } } /** * Do the select and expand. * @param tree * @param nodeID */ public static void doSelect(TreeControl tree, String nodeID) { // select and expand the node if (tree.selected.size() > 0) { // remove proevious selection tree.clearSelection(); } tree.select(nodeID); } /** * Clear the selected elements. */ public void clearSelection() { selected.clear(); } /** * Select the node. * @param nodeID */ public void select(String nodeID) { selected.add(nodeID); // dispatched ElementedSelectedEvent sendElementSelectedEvent(nodeID); setRequireRedraw(true); } /** * Deselect the node. * @param nodeID */ public void deselect(String nodeID) { selected.remove(nodeID); setRequireRedraw(true); } /** * Expand the node. * @param nodeID */ public void expand(String nodeID) { expanded.add(nodeID); setRequireRedraw(true); } /** * Expands the node and all its parents. * @param nodeID */ public void expandAll(String nodeID) { // expand all its parents String parentID = null; for (Enumeration<?> en = new StringTokenizer(nodeID, "-"); en.hasMoreElements();) { String token = (String)en.nextElement(); if (parentID != null) { parentID += "-" + token; } else { parentID = token; } // expand parent expand(parentID); } } /** * Collapse the node. * @param nodeID */ public void collapse(String nodeID) { expanded.remove(nodeID); setRequireRedraw(true); } /** * Returns an Iterator of TreeControlNode objects that hold the TreeNode * for rendering. * @return */ public Iterator<?> getEntries() { return new TreeEntryIterator(this, rootNode); } /** * Returns the Set of the selected nodeID Strings. * @return */ public Set<String> getSelected() { return selected; } /** * Returns the Set of the expanded nodeID Strings. * @return */ public Set<String> getExpanded() { return expanded; } /** * Sets the clicked mode. * CLICK_MODE_SELECT_EXPAND and CLICK_MODE_MULTI_SELECT_DESELECT are supported. * @return Returns the clickMode. */ public int getClickMode() { return clickMode; } /** * Return the clicked mode. * CLICK_MODE_SELECT_EXPAND and CLICK_MODE_MULTI_SELECT_DESELECT are supported. * @param clickMode */ public void setClickMode(int clickMode) { this.clickMode = clickMode; } /** * Register a listener that will be notified when node will be selected. * @param listener */ public void addElementSelectedListener(ElementSelectedListener listener) { if (selectionListeners == null) { selectionListeners = new ArrayList<ElementSelectedListener>(); } selectionListeners.add(listener); } /** * Remove a listener. * @param listener */ public void removeElementSelectedListener(ElementSelectedListener listener) { if (selectionListeners != null) { selectionListeners.remove(listener); } } /** * Send the element selected event to the registerd listeners. */ protected void sendElementSelectedEvent(String nodeID) { if (selectionListeners != null) { ElementSelectedEvent e = new ElementSelectedEvent(this, nodeID); for (Iterator<ElementSelectedListener> it = selectionListeners.iterator(); it.hasNext(); ) { ElementSelectedListener osl = it.next(); osl.elementSelected(e); } } } /** * If true (default) the root node is rendered. * @return renderRootNode */ public boolean isRenderRootNode() { return renderRootNode; } /** * Returns if the root node is renderd. * @param renderRootNode */ public void setRenderRootNode(boolean renderRootNode) { this.renderRootNode = renderRootNode; // set root node again to check that it is expanded if (rootNode != null) { setRootNode(rootNode); } } } /** * TreeEntryIterator provides an Iterator of TreeEntry objects. */ class TreeEntryIterator implements Iterator<Object> { TreeControl treeControl = null; /** Holds the path of TreeEntry */ Stack<TreeEntry> path = new Stack<TreeEntry>(); /** Next TreeNode that will be return */ TreeNode nextNode = null; /** * NodeIterator constructor. * @param rootNode */ public TreeEntryIterator(TreeControl treeControl, TreeNode rootNode) { this.treeControl = treeControl; nextNode = rootNode; if (!treeControl.isRenderRootNode()) { // hide root node, don't return it in method next() next(); } } /* (non-Javadoc) * @see java.util.Iterator#remove() */ public void remove() { throw new UnsupportedOperationException(); } /* (non-Javadoc) * @see java.util.Iterator#hasNext() */ public boolean hasNext() { return nextNode != null; } /* (non-Javadoc) * @see java.util.Iterator#next() */ public Object next() { if (hasNext()) { String nodeID = "0"; int level = 0; boolean isLast = true; if (parent() != null) { // use session nodeID = parent().nodeID + "-" + (parent().curr - 1); level = path.size(); isLast = parent().curr >= parent().node.getChildCount(); } // encapsulate the TreeNode TreeEntry entry = new TreeEntry(); entry.setNode(nextNode); entry.setParent(parent()); entry.setNodeID(nodeID); entry.setSelected(treeControl.selected.contains(nodeID)); entry.setExpanded(treeControl.expanded.contains(nodeID)); entry.setLast(isLast); entry.setLevel(level); // check if new parent needs to be created if (parent() == null || (level > 0 && entry.isExpanded() && nextNode.getChildCount() > 0)) { // create new parent path.push(entry); } // find next node nextNode = null; while (path.size() > 0 && nextNode == null) { if (treeControl.expanded.contains(parent().nodeID) && parent().curr < parent().node.getChildCount()) { // next child exists nextNode = parent().node.getChildAt(parent().curr++); } else { // end of session is reached, remove it from path path.pop(); } } // return current TreeControlNode return entry; } return null; } /** * Returns the current parent. * Null is returned if no parent exists. * @return */ private TreeEntry parent() { if (path.size() > 0) { return path.lastElement(); } return null; } }