001: /*
002: * DynamicTree.java
003: *
004: * Copyright (C) 2002, 2003, 2004, 2005, 2006 Takis Diakoumis
005: *
006: * This program is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU General Public License
008: * as published by the Free Software Foundation; either version 2
009: * of the License, or any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: */
021:
022: package org.underworldlabs.swing.tree;
023:
024: import javax.swing.JTree;
025: import javax.swing.ToolTipManager;
026: import javax.swing.text.Position;
027: import javax.swing.tree.DefaultMutableTreeNode;
028: import javax.swing.tree.DefaultTreeModel;
029: import javax.swing.tree.MutableTreeNode;
030: import javax.swing.tree.TreeNode;
031: import javax.swing.tree.TreePath;
032: import javax.swing.tree.TreeSelectionModel;
033:
034: /* ----------------------------------------------------------
035: * CVS NOTE: Changes to the CVS repository prior to the
036: * release of version 3.0.0beta1 has meant a
037: * resetting of CVS revision numbers.
038: * ----------------------------------------------------------
039: */
040:
041: /**
042: * Dynamic JTree allowing moving of nodes up/down
043: * and provides convenience methods for removal/insertion of nodes.
044: *
045: * @author Takis Diakoumis
046: * @version $Revision: 1.6 $
047: * @date $Date: 2006/07/15 12:52:46 $
048: */
049: public class DynamicTree extends JTree {
050: // implements PropertyChangeListener,
051: // TreeSelectionListener {
052:
053: /** directional constant for movements up the tree */
054: public static final int MOVE_UP = 0;
055:
056: /** directional constant for movements down the tree */
057: public static final int MOVE_DOWN = 1;
058:
059: /** the tree's root node */
060: private DefaultMutableTreeNode root;
061:
062: /** the tree model for the display */
063: private DefaultTreeModel treeModel;
064:
065: /** the previously selected path */
066: //private TreePath previousSelectionPath;
067: /** Creates a new instance of DynamicTree */
068: public DynamicTree(DefaultMutableTreeNode root) {
069: this .root = root;
070: init();
071: }
072:
073: private static final int DEFAULT_ROW_HEIGHT = 18;
074:
075: /**
076: * Returns the height of each row. The default swing implementation
077: * allows the renderer to determine the row height. In most cases this
078: * is ok, though i found that on some LAFs the renderer's value is too
079: * small making the rows too cramped (ie. gtk). as a result, this method
080: * return a value of 20 if the rowHeight <= 0.
081: *
082: * This isn't ideal and a bit of a hack, but it works ok.
083: */
084: public int getRowHeight() {
085: int h = super .getRowHeight();
086: if (h < DEFAULT_ROW_HEIGHT) {
087: return DEFAULT_ROW_HEIGHT;
088: }
089: return h;
090: }
091:
092: private void init() {
093: treeModel = new DefaultTreeModel(root);
094: setModel(treeModel);
095:
096: // lines on the branches
097: putClientProperty("JTree.lineStyle", "Angled");
098: setRootVisible(true);
099:
100: // single selection only
101: getSelectionModel().setSelectionMode(
102: TreeSelectionModel.SINGLE_TREE_SELECTION);
103:
104: // add the property change listener
105: //addPropertyChangeListener(this);
106:
107: // add the tree selection listener
108: //addTreeSelectionListener(this);
109:
110: // register for tool tips
111: ToolTipManager.sharedInstance().registerComponent(this );
112: }
113:
114: // --------------------------------------------------
115: // ------- TreeSelectionListener implementation
116: // --------------------------------------------------
117:
118: /**
119: * Called whenever the value of the selection changes.
120: * This will store the current path selection.
121: *
122: * @param the event that characterizes the change
123: *
124: public void valueChanged(TreeSelectionEvent e) {
125: previousSelectionPath = e.getOldLeadSelectionPath();
126: System.out.println("old path: " + previousSelectionPath);
127: }
128: */
129:
130: // --------------------------------------------------
131: // ------- PropertyChangeListener implementation
132: // --------------------------------------------------
133: /*
134: public void propertyChange(PropertyChangeEvent e) {
135: /*
136: System.out.println("property change: " + e.getPropertyName() +
137: " old value: " + e.getOldValue() + " new value: " + e.getNewValue());
138:
139: if ("leadSelectionPath".equals(e.getPropertyName())) {
140: Object oldValue = e.getOldValue();
141: if (oldValue != null && oldValue instanceof TreePath) {
142: //previousSelectionPath = (TreePath)oldValue;
143: }
144: }
145: }
146: */
147:
148: /**
149: * Expands the currently selected row.
150: */
151: public void expandSelectedRow() {
152: int row = getTreeSelectionRow();
153: if (row != -1) {
154: expandRow(row);
155: }
156: }
157:
158: private int getTreeSelectionRow() {
159: int selectedRow = -1;
160: int[] selectedRows = getSelectionRows();
161: if (selectedRows != null) {
162: selectedRow = selectedRows[0];
163: }
164: return selectedRow;
165: }
166:
167: /**
168: * Invoke this method if you've totally changed the children
169: * of node and its childrens children...
170: * This will post a treeStructureChanged event.
171: */
172: public void nodeStructureChanged(TreeNode node) {
173: treeModel.nodeStructureChanged(node);
174: }
175:
176: /**
177: * This sets the user object of the TreeNode identified by
178: * path and posts a node changed. If you use custom user
179: * objects in the TreeModel you're going to need to subclass
180: * this and set the user object of the changed node
181: * to something meaningful.
182: *
183: * @param path to the node that the user has altered
184: * @param the new value from the TreeCellEditor
185: */
186: public void valueForPathChanged(TreePath path, Object newValue) {
187: treeModel.valueForPathChanged(path, newValue);
188: }
189:
190: /**
191: * Returns the tree node from the root node with the
192: * specified user object. This will traverse the tree from
193: * the root node to the root's children only, not its children's
194: * children.
195: *
196: * @param the user object to search for
197: * @return the tree node or null if not found
198: */
199: public DefaultMutableTreeNode getNodeFromRoot(Object userObject) {
200: int childCount = root.getChildCount();
201: for (int i = 0; i < childCount; i++) {
202: DefaultMutableTreeNode node = (DefaultMutableTreeNode) root
203: .getChildAt(i);
204: if (node.getUserObject() == userObject) {
205: return node;
206: }
207: }
208: return null;
209: }
210:
211: /**
212: * Returns the root node of this tree.
213: *
214: * @return the tree's root node
215: */
216: public DefaultMutableTreeNode getRootNode() {
217: return root;
218: }
219:
220: /**
221: * Invoke this method after you've changed how node is to be
222: * represented in the tree.
223: */
224: public void nodeChanged(TreeNode node) {
225: treeModel.nodeChanged(node);
226: }
227:
228: /**
229: * Returns the path component of the selected path.
230: *
231: * @return the component
232: */
233: public Object getLastPathComponent() {
234: TreePath path = getSelectionPath();
235: if (path == null) {
236: return null;
237: }
238: return path.getLastPathComponent();
239: }
240:
241: /**
242: * Invoke this method if you've modified the TreeNodes
243: * upon which this model depends.
244: */
245: public void reload(TreeNode node) {
246: treeModel.reload();
247: }
248:
249: /**
250: * Returns the tree model.
251: *
252: * @return the tree model - an instance of DefaultTreeModel
253: */
254: public DefaultTreeModel getTreeModel() {
255: return treeModel;
256: }
257:
258: /**
259: * Adds the specified node to the root node of this tree.
260: *
261: * @param node - the tree node to add
262: */
263: public void addToRoot(TreeNode node) {
264: DefaultMutableTreeNode _node = (DefaultMutableTreeNode) node;
265: treeModel.insertNodeInto(_node, root, root.getChildCount());
266: TreePath path = new TreePath(_node.getPath());
267: scrollPathToVisible(path);
268: setSelectionPath(path);
269: }
270:
271: /**
272: * Moves the specified node in the specified direction.
273: */
274: private void move(TreeNode node, int direction) {
275: int currentIndex = root.getIndex(node);
276: if (currentIndex <= 0 && direction == MOVE_UP) {
277: return;
278: }
279:
280: int newIndex = -1;
281: if (direction == MOVE_UP) {
282: newIndex = currentIndex - 1;
283: } else {
284: newIndex = currentIndex + 1;
285: int childCount = root.getChildCount();
286: if (newIndex > (childCount - 1)) {
287: return;
288: }
289: }
290:
291: int selectedRow = getTreeSelectionRow();
292:
293: // remove node from root
294: root.remove(currentIndex);
295:
296: // insert into the new index
297: root.insert((MutableTreeNode) node, newIndex);
298:
299: // fire event
300: treeModel.nodeStructureChanged(root);
301:
302: TreePath path = null;
303: if (node instanceof DefaultMutableTreeNode) {
304: path = new TreePath(((DefaultMutableTreeNode) node)
305: .getPath());
306: } else {
307: String prefix = node.toString();
308:
309: // reselect that node
310: if (direction == MOVE_UP) {
311: path = getNextMatch(prefix, selectedRow,
312: Position.Bias.Forward);
313: } else {
314: path = getNextMatch(prefix, selectedRow,
315: Position.Bias.Backward);
316: }
317: }
318:
319: scrollPathToVisible(path);
320: setSelectionPath(path);
321: //fireValueChanged(new TreeSelectionEvent(this, path, true, null, path));
322:
323: }
324:
325: /**
326: * Moves the selected node up in the tree.
327: */
328: public void moveSelectionUp() {
329: TreeNode node = (TreeNode) getLastPathComponent();
330: move(node, MOVE_UP);
331: }
332:
333: /**
334: * Selects the node that matches the specified prefix forward
335: * from the currently selected node.
336: *
337: * @param prefix - the prefix of the node to select
338: */
339: public void selectNextNode(String prefix) {
340: int selectedRow = getTreeSelectionRow();
341: if (selectedRow == -1) {
342: return;
343: }
344:
345: TreePath path = getNextMatch(prefix, selectedRow,
346: Position.Bias.Forward);
347: if (path != null) {
348: scrollPathToVisible(path);
349: setSelectionPath(path);
350: }
351:
352: }
353:
354: /**
355: * Removes the currently selected node and sets the
356: * next selected node beginning with the specified
357: * prefix.
358: *
359: * @param the prefix of the node to select after removal
360: */
361: public void removeSelection(String nextSelectionPrefix) {
362: TreeNode node = (TreeNode) getLastPathComponent();
363:
364: TreePath path = null;
365: if (nextSelectionPrefix != null) {
366: // get the row for the current path
367: int selectedRow = getTreeSelectionRow();
368: path = getNextMatch(nextSelectionPrefix, selectedRow,
369: Position.Bias.Backward);
370: }
371:
372: // remove the node from the tree
373: treeModel.removeNodeFromParent((MutableTreeNode) node);
374: if (path != null) {
375: scrollPathToVisible(path);
376: setSelectionPath(path);
377: }
378: }
379:
380: /**
381: * Removes the specified node and sets the
382: * next selected node beginning with the specified
383: * prefix.
384: *
385: * @param the node to be removed
386: * @param the prefix of the node to select after removal
387: */
388: public void removeNode(TreeNode node, String nextSelectionPrefix) {
389: TreePath path = null;
390: if (nextSelectionPrefix != null) {
391: // get the row for the current path
392: int selectedRow = getTreeSelectionRow();
393: path = getNextMatch(nextSelectionPrefix, selectedRow,
394: Position.Bias.Backward);
395: }
396:
397: // remove the node from the tree
398: treeModel.removeNodeFromParent((MutableTreeNode) node);
399: if (path != null) {
400: scrollPathToVisible(path);
401: setSelectionPath(path);
402: }
403: }
404:
405: /**
406: * Moves the selected node down in the tree.
407: */
408: public void moveSelectionDown() {
409: TreeNode node = (TreeNode) getLastPathComponent();
410: move(node, MOVE_DOWN);
411: }
412:
413: /**
414: * Removes the specified node from the parent node.
415: *
416: * @param node - the node to be removed
417: */
418: public void removeNode(MutableTreeNode node) {
419: treeModel.removeNodeFromParent(node);
420: }
421:
422: }
|