blob: 9feba85bb82a8dee744cf6717f7fe90c5c586265 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2004 Sun Microsystems, Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * - Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 *
15 * - Neither the name of Sun Microsystems nor the names of its
16 * contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/*
33 */
34
35import javax.swing.*;
36import javax.swing.event.*;
37import java.awt.BorderLayout;
38import java.awt.Color;
39import java.awt.Dimension;
40import java.awt.FlowLayout;
41import java.awt.event.ActionEvent;
42import java.awt.event.ActionListener;
43import java.awt.event.WindowAdapter;
44import java.awt.event.WindowEvent;
45import java.util.*;
46import javax.swing.border.*;
47import javax.swing.tree.*;
48
49/**
50 * A demo for illustrating how to do different things with JTree.
51 * The data that this displays is rather boring, that is each node will
52 * have 7 children that have random names based on the fonts. Each node
53 * is then drawn with that font and in a different color.
54 * While the data isn't interesting the example illustrates a number
55 * of things:
56 *
57 * For an example of dynamicaly loading children refer to DynamicTreeNode.
58 * For an example of adding/removing/inserting/reloading refer to the inner
59 * classes of this class, AddAction, RemovAction, InsertAction and
60 * ReloadAction.
61 * For an example of creating your own cell renderer refer to
62 * SampleTreeCellRenderer.
63 * For an example of subclassing JTreeModel for editing refer to
64 * SampleTreeModel.
65 *
66 * @author Scott Violet
67 */
68
69public class SampleTree
70{
71 /** Window for showing Tree. */
72 protected JFrame frame;
73 /** Tree used for the example. */
74 protected JTree tree;
75 /** Tree model. */
76 protected DefaultTreeModel treeModel;
77
78 /**
79 * Constructs a new instance of SampleTree.
80 */
81 public SampleTree() {
82 // Force SampleTree to come up in the Cross Platform L&F
83 try {
84 UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
85 // If you want the System L&F instead, comment out the above line and
86 // uncomment the following:
87 // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
88 } catch (Exception exc) {
89 System.err.println("Error loading L&F: " + exc);
90 }
91
92
93 JMenuBar menuBar = constructMenuBar();
94 JPanel panel = new JPanel(true);
95
96 frame = new JFrame("SampleTree");
97 frame.getContentPane().add("Center", panel);
98 frame.setJMenuBar(menuBar);
99 frame.setBackground(Color.lightGray);
100
101 /* Create the JTreeModel. */
102 DefaultMutableTreeNode root = createNewNode("Root");
103 treeModel = new SampleTreeModel(root);
104
105 /* Create the tree. */
106 tree = new JTree(treeModel);
107
108 /* Enable tool tips for the tree, without this tool tips will not
109 be picked up. */
110 ToolTipManager.sharedInstance().registerComponent(tree);
111
112 /* Make the tree use an instance of SampleTreeCellRenderer for
113 drawing. */
114 tree.setCellRenderer(new SampleTreeCellRenderer());
115
116 /* Make tree ask for the height of each row. */
117 tree.setRowHeight(-1);
118
119 /* Put the Tree in a scroller. */
120 JScrollPane sp = new JScrollPane();
121 sp.setPreferredSize(new Dimension(300, 300));
122 sp.getViewport().add(tree);
123
124 /* And show it. */
125 panel.setLayout(new BorderLayout());
126 panel.add("Center", sp);
127 panel.add("South", constructOptionsPanel());
128
129 frame.addWindowListener( new WindowAdapter() {
130 public void windowClosing(WindowEvent e) {System.exit(0);}});
131
132 frame.pack();
133 frame.show();
134 }
135
136 /** Constructs a JPanel containing check boxes for the different
137 * options that tree supports. */
138 private JPanel constructOptionsPanel() {
139 JCheckBox aCheckbox;
140 JPanel retPanel = new JPanel(false);
141 JPanel borderPane = new JPanel(false);
142
143 borderPane.setLayout(new BorderLayout());
144 retPanel.setLayout(new FlowLayout());
145
146 aCheckbox = new JCheckBox("show top level handles");
147 aCheckbox.setSelected(tree.getShowsRootHandles());
148 aCheckbox.addChangeListener(new ShowHandlesChangeListener());
149 retPanel.add(aCheckbox);
150
151 aCheckbox = new JCheckBox("show root");
152 aCheckbox.setSelected(tree.isRootVisible());
153 aCheckbox.addChangeListener(new ShowRootChangeListener());
154 retPanel.add(aCheckbox);
155
156 aCheckbox = new JCheckBox("editable");
157 aCheckbox.setSelected(tree.isEditable());
158 aCheckbox.addChangeListener(new TreeEditableChangeListener());
159 aCheckbox.setToolTipText("Triple click to edit");
160 retPanel.add(aCheckbox);
161
162 borderPane.add(retPanel, BorderLayout.CENTER);
163
164 /* Create a set of radio buttons that dictate what selection should
165 be allowed in the tree. */
166 ButtonGroup group = new ButtonGroup();
167 JPanel buttonPane = new JPanel(false);
168 JRadioButton button;
169
170 buttonPane.setLayout(new FlowLayout());
171 buttonPane.setBorder(new TitledBorder("Selection Mode"));
172 button = new JRadioButton("Single");
173 button.addActionListener(new AbstractAction() {
174 public boolean isEnabled() { return true; }
175 public void actionPerformed(ActionEvent e) {
176 tree.getSelectionModel().setSelectionMode
177 (TreeSelectionModel.SINGLE_TREE_SELECTION);
178 }
179 });
180 group.add(button);
181 buttonPane.add(button);
182 button = new JRadioButton("Contiguous");
183 button.addActionListener(new AbstractAction() {
184 public boolean isEnabled() { return true; }
185 public void actionPerformed(ActionEvent e) {
186 tree.getSelectionModel().setSelectionMode
187 (TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
188 }
189 });
190 group.add(button);
191 buttonPane.add(button);
192 button = new JRadioButton("Discontiguous");
193 button.addActionListener(new AbstractAction() {
194 public boolean isEnabled() { return true; }
195 public void actionPerformed(ActionEvent e) {
196 tree.getSelectionModel().setSelectionMode
197 (TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
198 }
199 });
200 button.setSelected(true);
201 group.add(button);
202 buttonPane.add(button);
203
204 borderPane.add(buttonPane, BorderLayout.SOUTH);
205
206 // NOTE: This will be enabled in a future release.
207 // Create a label and combobox to determine how many clicks are
208 // needed to expand.
209/*
210 JPanel clickPanel = new JPanel();
211 Object[] values = { "Never", new Integer(1),
212 new Integer(2), new Integer(3) };
213 final JComboBox clickCBox = new JComboBox(values);
214
215 clickPanel.setLayout(new FlowLayout());
216 clickPanel.add(new JLabel("Click count to expand:"));
217 clickCBox.setSelectedIndex(2);
218 clickCBox.addActionListener(new ActionListener() {
219 public void actionPerformed(ActionEvent ae) {
220 Object selItem = clickCBox.getSelectedItem();
221
222 if(selItem instanceof Integer)
223 tree.setToggleClickCount(((Integer)selItem).intValue());
224 else // Don't toggle
225 tree.setToggleClickCount(0);
226 }
227 });
228 clickPanel.add(clickCBox);
229 borderPane.add(clickPanel, BorderLayout.NORTH);
230*/
231 return borderPane;
232 }
233
234 /** Construct a menu. */
235 private JMenuBar constructMenuBar() {
236 JMenu menu;
237 JMenuBar menuBar = new JMenuBar();
238 JMenuItem menuItem;
239
240 /* Good ol exit. */
241 menu = new JMenu("File");
242 menuBar.add(menu);
243
244 menuItem = menu.add(new JMenuItem("Exit"));
245 menuItem.addActionListener(new ActionListener() {
246 public void actionPerformed(ActionEvent e) {
247 System.exit(0);
248 }});
249
250 /* Tree related stuff. */
251 menu = new JMenu("Tree");
252 menuBar.add(menu);
253
254 menuItem = menu.add(new JMenuItem("Add"));
255 menuItem.addActionListener(new AddAction());
256
257 menuItem = menu.add(new JMenuItem("Insert"));
258 menuItem.addActionListener(new InsertAction());
259
260 menuItem = menu.add(new JMenuItem("Reload"));
261 menuItem.addActionListener(new ReloadAction());
262
263 menuItem = menu.add(new JMenuItem("Remove"));
264 menuItem.addActionListener(new RemoveAction());
265
266 return menuBar;
267 }
268
269 /**
270 * Returns the TreeNode instance that is selected in the tree.
271 * If nothing is selected, null is returned.
272 */
273 protected DefaultMutableTreeNode getSelectedNode() {
274 TreePath selPath = tree.getSelectionPath();
275
276 if(selPath != null)
277 return (DefaultMutableTreeNode)selPath.getLastPathComponent();
278 return null;
279 }
280
281 /**
282 * Returns the selected TreePaths in the tree, may return null if
283 * nothing is selected.
284 */
285 protected TreePath[] getSelectedPaths() {
286 return tree.getSelectionPaths();
287 }
288
289 protected DefaultMutableTreeNode createNewNode(String name) {
290 return new DynamicTreeNode(new SampleData(null, Color.black, name));
291 }
292
293 /**
294 * AddAction is used to add a new item after the selected item.
295 */
296 class AddAction extends Object implements ActionListener
297 {
298 /** Number of nodes that have been added. */
299 public int addCount;
300
301 /**
302 * Messaged when the user clicks on the Add menu item.
303 * Determines the selection from the Tree and adds an item
304 * after that. If nothing is selected, an item is added to
305 * the root.
306 */
307 public void actionPerformed(ActionEvent e) {
308 DefaultMutableTreeNode lastItem = getSelectedNode();
309 DefaultMutableTreeNode parent;
310
311 /* Determine where to create the new node. */
312 if(lastItem != null) {
313 parent = (DefaultMutableTreeNode)lastItem.getParent();
314 if(parent == null) {
315 parent = (DefaultMutableTreeNode)treeModel.getRoot();
316 lastItem = null;
317 }
318 }
319 else
320 parent = (DefaultMutableTreeNode)treeModel.getRoot();
321 if (parent == null) {
322 // new root
323 treeModel.setRoot(createNewNode("Added " +
324 Integer.toString(addCount++)));
325 }
326 else {
327 int newIndex;
328 if(lastItem == null)
329 newIndex = treeModel.getChildCount(parent);
330 else
331 newIndex = parent.getIndex(lastItem) + 1;
332
333 /* Let the treemodel know. */
334 treeModel.insertNodeInto(createNewNode("Added " +
335 Integer.toString(addCount++)),
336 parent, newIndex);
337 }
338 }
339 } // End of SampleTree.AddAction
340
341
342 /**
343 * InsertAction is used to insert a new item before the selected item.
344 */
345 class InsertAction extends Object implements ActionListener
346 {
347 /** Number of nodes that have been added. */
348 public int insertCount;
349
350 /**
351 * Messaged when the user clicks on the Insert menu item.
352 * Determines the selection from the Tree and inserts an item
353 * after that. If nothing is selected, an item is added to
354 * the root.
355 */
356 public void actionPerformed(ActionEvent e) {
357 DefaultMutableTreeNode lastItem = getSelectedNode();
358 DefaultMutableTreeNode parent;
359
360 /* Determine where to create the new node. */
361 if(lastItem != null) {
362 parent = (DefaultMutableTreeNode)lastItem.getParent();
363 if(parent == null) {
364 parent = (DefaultMutableTreeNode)treeModel.getRoot();
365 lastItem = null;
366 }
367 }
368 else
369 parent = (DefaultMutableTreeNode)treeModel.getRoot();
370 if (parent == null) {
371 // new root
372 treeModel.setRoot(createNewNode("Inserted " +
373 Integer.toString(insertCount++)));
374 }
375 else {
376 int newIndex;
377
378 if(lastItem == null)
379 newIndex = treeModel.getChildCount(parent);
380 else
381 newIndex = parent.getIndex(lastItem);
382
383 /* Let the treemodel know. */
384 treeModel.insertNodeInto(createNewNode("Inserted " +
385 Integer.toString(insertCount++)),
386 parent, newIndex);
387 }
388 }
389 } // End of SampleTree.InsertAction
390
391
392 /**
393 * ReloadAction is used to reload from the selected node. If nothing
394 * is selected, reload is not issued.
395 */
396 class ReloadAction extends Object implements ActionListener
397 {
398 /**
399 * Messaged when the user clicks on the Reload menu item.
400 * Determines the selection from the Tree and asks the treemodel
401 * to reload from that node.
402 */
403 public void actionPerformed(ActionEvent e) {
404 DefaultMutableTreeNode lastItem = getSelectedNode();
405
406 if(lastItem != null)
407 treeModel.reload(lastItem);
408 }
409 } // End of SampleTree.ReloadAction
410
411 /**
412 * RemoveAction removes the selected node from the tree. If
413 * The root or nothing is selected nothing is removed.
414 */
415 class RemoveAction extends Object implements ActionListener
416 {
417 /**
418 * Removes the selected item as long as it isn't root.
419 */
420 public void actionPerformed(ActionEvent e) {
421 TreePath[] selected = getSelectedPaths();
422
423 if (selected != null && selected.length > 0) {
424 TreePath shallowest;
425
426 // The remove process consists of the following steps:
427 // 1 - find the shallowest selected TreePath, the shallowest
428 // path is the path with the smallest number of path
429 // components.
430 // 2 - Find the siblings of this TreePath
431 // 3 - Remove from selected the TreePaths that are descendants
432 // of the paths that are going to be removed. They will
433 // be removed as a result of their ancestors being
434 // removed.
435 // 4 - continue until selected contains only null paths.
436 while ((shallowest = findShallowestPath(selected)) != null) {
437 removeSiblings(shallowest, selected);
438 }
439 }
440 }
441
442 /**
443 * Removes the sibling TreePaths of <code>path</code>, that are
444 * located in <code>paths</code>.
445 */
446 private void removeSiblings(TreePath path, TreePath[] paths) {
447 // Find the siblings
448 if (path.getPathCount() == 1) {
449 // Special case, set the root to null
450 for (int counter = paths.length - 1; counter >= 0; counter--) {
451 paths[counter] = null;
452 }
453 treeModel.setRoot(null);
454 }
455 else {
456 // Find the siblings of path.
457 TreePath parent = path.getParentPath();
458 MutableTreeNode parentNode = (MutableTreeNode)parent.
459 getLastPathComponent();
460 ArrayList toRemove = new ArrayList();
461 int depth = parent.getPathCount();
462
463 // First pass, find paths with a parent TreePath of parent
464 for (int counter = paths.length - 1; counter >= 0; counter--) {
465 if (paths[counter] != null && paths[counter].
466 getParentPath().equals(parent)) {
467 toRemove.add(paths[counter]);
468 paths[counter] = null;
469 }
470 }
471
472 // Second pass, remove any paths that are descendants of the
473 // paths that are going to be removed. These paths are
474 // implicitly removed as a result of removing the paths in
475 // toRemove
476 int rCount = toRemove.size();
477 for (int counter = paths.length - 1; counter >= 0; counter--) {
478 if (paths[counter] != null) {
479 for (int rCounter = rCount - 1; rCounter >= 0;
480 rCounter--) {
481 if (((TreePath)toRemove.get(rCounter)).
482 isDescendant(paths[counter])) {
483 paths[counter] = null;
484 }
485 }
486 }
487 }
488
489 // Sort the siblings based on position in the model
490 if (rCount > 1) {
491 Collections.sort(toRemove, new PositionComparator());
492 }
493 int[] indices = new int[rCount];
494 Object[] removedNodes = new Object[rCount];
495 for (int counter = rCount - 1; counter >= 0; counter--) {
496 removedNodes[counter] = ((TreePath)toRemove.get(counter)).
497 getLastPathComponent();
498 indices[counter] = treeModel.getIndexOfChild
499 (parentNode, removedNodes[counter]);
500 parentNode.remove(indices[counter]);
501 }
502 treeModel.nodesWereRemoved(parentNode, indices, removedNodes);
503 }
504 }
505
506 /**
507 * Returns the TreePath with the smallest path count in
508 * <code>paths</code>. Will return null if there is no non-null
509 * TreePath is <code>paths</code>.
510 */
511 private TreePath findShallowestPath(TreePath[] paths) {
512 int shallowest = -1;
513 TreePath shallowestPath = null;
514
515 for (int counter = paths.length - 1; counter >= 0; counter--) {
516 if (paths[counter] != null) {
517 if (shallowest != -1) {
518 if (paths[counter].getPathCount() < shallowest) {
519 shallowest = paths[counter].getPathCount();
520 shallowestPath = paths[counter];
521 if (shallowest == 1) {
522 return shallowestPath;
523 }
524 }
525 }
526 else {
527 shallowestPath = paths[counter];
528 shallowest = paths[counter].getPathCount();
529 }
530 }
531 }
532 return shallowestPath;
533 }
534
535
536 /**
537 * An Comparator that bases the return value on the index of the
538 * passed in objects in the TreeModel.
539 * <p>
540 * This is actually rather expensive, it would be more efficient
541 * to extract the indices and then do the comparision.
542 */
543 private class PositionComparator implements Comparator {
544 public int compare(Object o1, Object o2) {
545 TreePath p1 = (TreePath)o1;
546 int o1Index = treeModel.getIndexOfChild(p1.getParentPath().
547 getLastPathComponent(), p1.getLastPathComponent());
548 TreePath p2 = (TreePath)o2;
549 int o2Index = treeModel.getIndexOfChild(p2.getParentPath().
550 getLastPathComponent(), p2.getLastPathComponent());
551 return o1Index - o2Index;
552 }
553
554 public boolean equals(Object obj) {
555 return super.equals(obj);
556 }
557 }
558
559 } // End of SampleTree.RemoveAction
560
561
562 /**
563 * ShowHandlesChangeListener implements the ChangeListener interface
564 * to toggle the state of showing the handles in the tree.
565 */
566 class ShowHandlesChangeListener extends Object implements ChangeListener
567 {
568 public void stateChanged(ChangeEvent e) {
569 tree.setShowsRootHandles(((JCheckBox)e.getSource()).isSelected());
570 }
571
572 } // End of class SampleTree.ShowHandlesChangeListener
573
574
575 /**
576 * ShowRootChangeListener implements the ChangeListener interface
577 * to toggle the state of showing the root node in the tree.
578 */
579 class ShowRootChangeListener extends Object implements ChangeListener
580 {
581 public void stateChanged(ChangeEvent e) {
582 tree.setRootVisible(((JCheckBox)e.getSource()).isSelected());
583 }
584
585 } // End of class SampleTree.ShowRootChangeListener
586
587
588 /**
589 * TreeEditableChangeListener implements the ChangeListener interface
590 * to toggle between allowing editing and now allowing editing in
591 * the tree.
592 */
593 class TreeEditableChangeListener extends Object implements ChangeListener
594 {
595 public void stateChanged(ChangeEvent e) {
596 tree.setEditable(((JCheckBox)e.getSource()).isSelected());
597 }
598
599 } // End of class SampleTree.TreeEditableChangeListener
600
601
602 static public void main(String args[]) {
603 new SampleTree();
604 }
605
606}