001: /*
002: * @(#)TreeSearchable.java
003: *
004: * Copyright 2002 - 2004 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import javax.swing.*;
009: import javax.swing.event.TreeModelEvent;
010: import javax.swing.event.TreeModelListener;
011: import javax.swing.tree.TreeModel;
012: import javax.swing.tree.TreePath;
013: import java.beans.PropertyChangeEvent;
014: import java.beans.PropertyChangeListener;
015: import java.util.ArrayList;
016: import java.util.List;
017:
018: /**
019: * <code>TreeSearchable</code> is an concrete implementation of {@link Searchable}
020: * that enables the search function in JTree.
021: * <p>It's very simple to use it. Assuming you have a JTree, all you need to do is to
022: * call
023: * <code><pre>
024: * JTree tree = ....;
025: * TreeSearchable searchable = new TreeSearchable(tree);
026: * </pre></code>
027: * Now the JTree will have the search function.
028: * <p/>
029: * There is very little customization you need to do to TreeSearchable. The only thing you might
030: * need is when the element in the JTree needs a special conversion to convert to string. If so, you can overide
031: * convertElementToString() to provide you own algorithm to do the conversion.
032: * <code><pre>
033: * JTree tree = ....;
034: * TreeSearchable searchable = new TreeSearchable(tree) {
035: * protected String convertElementToString(Object object) {
036: * ...
037: * }
038: * };
039: * </pre></code>
040: * <p/>
041: * Additional customization can be done on the base Searchable class such as background and foreground color, keystrokes,
042: * case sensitivity,
043: */
044: public class TreeSearchable extends Searchable implements
045: TreeModelListener, PropertyChangeListener {
046:
047: private boolean _recursive = false;
048: private transient List<TreePath> _treePathes;
049:
050: public TreeSearchable(JTree tree) {
051: super (tree);
052: if (tree.getModel() != null) {
053: tree.getModel().addTreeModelListener(this );
054: }
055:
056: tree.addPropertyChangeListener(JTree.TREE_MODEL_PROPERTY, this );
057: }
058:
059: /**
060: * Checks if the searchable is recursive.
061: *
062: * @return true if searchabe is recursive.
063: */
064: public boolean isRecursive() {
065: return _recursive;
066: }
067:
068: /**
069: * Sets the recursive attribute.
070: * <p/>
071: * If TreeSearchable is recursive, it will all tree nodes including those which are not visible
072: * to find the matching node. Obviously, if your tree has unlimited number of tree nodes
073: * or a potential huge number of tree nodes (such as a tree to represent file system),
074: * the recursive attribute should be false. To avoid this potential problem in this case, we default it to false.
075: *
076: * @param recursive
077: */
078: public void setRecursive(boolean recursive) {
079: _recursive = recursive;
080: resetTreePathes();
081: }
082:
083: @Override
084: public void uninstallListeners() {
085: super .uninstallListeners();
086: if (_component instanceof JTree) {
087: if (((JTree) _component).getModel() != null) {
088: ((JTree) _component).getModel()
089: .removeTreeModelListener(this );
090: }
091: }
092: _component.removePropertyChangeListener(
093: JTree.TREE_MODEL_PROPERTY, this );
094: }
095:
096: @Override
097: protected void setSelectedIndex(int index, boolean incremental) {
098: if (!isRecursive()) {
099: if (incremental) {
100: ((JTree) _component).addSelectionInterval(index, index);
101: } else {
102: ((JTree) _component).setSelectionRow(index);
103: }
104: ((JTree) _component).scrollRowToVisible(index);
105: } else {
106: Object elementAt = getElementAt(index);
107: if (elementAt instanceof TreePath) { // else case should never happen
108: TreePath path = (TreePath) elementAt;
109: if (incremental) {
110: ((JTree) _component).addSelectionPath(path);
111: } else {
112: ((JTree) _component).setSelectionPath(path);
113: }
114: ((JTree) _component).scrollPathToVisible(path);
115: }
116: }
117: }
118:
119: @Override
120: protected int getSelectedIndex() {
121: if (!isRecursive()) {
122: int ai[] = ((JTree) _component).getSelectionRows();
123: return (ai != null && ai.length != 0) ? ai[0] : -1;
124: } else {
125: TreePath[] treePaths = ((JTree) _component)
126: .getSelectionPaths();
127: if (treePaths != null && treePaths.length > 0) {
128: return getTreePathes().indexOf(treePaths[0]);
129: } else
130: return -1;
131: }
132: }
133:
134: @Override
135: protected Object getElementAt(int index) {
136: if (index == -1) {
137: return null;
138: }
139: if (!isRecursive()) {
140: return ((JTree) _component).getPathForRow(index);
141: } else {
142: return getTreePathes().get(index);
143: }
144: }
145:
146: @Override
147: protected int getElementCount() {
148: if (!isRecursive()) {
149: return ((JTree) _component).getRowCount();
150: } else {
151: return getTreePathes().size();
152: }
153: }
154:
155: /**
156: * @deprecated spell error. Use {@link #populateTreePaths()} instead.
157: */
158: protected void populateTreePathes() {
159: populateTreePaths();
160: }
161:
162: /**
163: * Recursively go through the tree to populate the tree pathes into a list and cache them.
164: * <p/>
165: * Tree pathes list is only used when recursive attriubute is true.
166: */
167: protected void populateTreePaths() {
168: _treePathes = new ArrayList<TreePath>();
169: Object root = ((JTree) _component).getModel().getRoot();
170: populateTreePaths0(root, new TreePath(root),
171: ((JTree) _component).getModel());
172: }
173:
174: private void populateTreePaths0(Object node, TreePath path,
175: TreeModel model) {
176: if (((JTree) _component).isRootVisible()
177: || path.getLastPathComponent() != ((JTree) _component)
178: .getModel().getRoot()) {
179: // if root not visible, do not add root
180: _treePathes.add(path);
181: }
182: for (int i = 0; i < model.getChildCount(node); i++) {
183: Object childNode = model.getChild(node, i);
184: populateTreePaths0(childNode, path
185: .pathByAddingChild(childNode), model);
186: }
187: }
188:
189: /**
190: * Reset the cached tree pathes list.
191: * <p/>
192: * Tree pathes list is only used when recursive attriubute is true.
193: */
194: protected void resetTreePathes() {
195: _treePathes = null;
196: }
197:
198: /**
199: * Gets the cached tree pathes list. If it has never been cached before, this method
200: * will create the cache.
201: * <p/>
202: * Tree pathes list is only used when recursive attriubute is true.
203: *
204: * @return the tree pathes list.
205: */
206: protected List<TreePath> getTreePathes() {
207: if (_treePathes == null) {
208: populateTreePaths();
209: }
210: return _treePathes;
211: }
212:
213: /**
214: * Converts the element in JTree to string. The element by default is TreePath.
215: * The returned value will be <code>toString()</code> of the last path component in the TreePath.
216: *
217: * @param object
218: * @return the string representing the TreePath in the JTree.
219: */
220: @Override
221: protected String convertElementToString(Object object) {
222: if (object instanceof TreePath) {
223: Object treeNode = ((TreePath) object)
224: .getLastPathComponent();
225: return treeNode.toString();
226: } else if (object != null) {
227: return object.toString();
228: } else {
229: return "";
230: }
231: }
232:
233: public void treeNodesChanged(TreeModelEvent e) {
234: hidePopup();
235: resetTreePathes();
236: }
237:
238: public void treeNodesInserted(TreeModelEvent e) {
239: hidePopup();
240: resetTreePathes();
241: }
242:
243: public void treeNodesRemoved(TreeModelEvent e) {
244: hidePopup();
245: resetTreePathes();
246: }
247:
248: public void treeStructureChanged(TreeModelEvent e) {
249: hidePopup();
250: resetTreePathes();
251: }
252:
253: public void propertyChange(PropertyChangeEvent evt) {
254: if (JTree.TREE_MODEL_PROPERTY.equals(evt.getPropertyName())) {
255: hidePopup();
256:
257: if (evt.getOldValue() instanceof TreeModel) {
258: ((TreeModel) evt.getOldValue())
259: .removeTreeModelListener(this );
260: }
261:
262: if (evt.getNewValue() instanceof TreeModel) {
263: ((TreeModel) evt.getNewValue())
264: .addTreeModelListener(this);
265: }
266:
267: resetTreePathes();
268: }
269: }
270: }
|