Path: blob/master/src/demo/share/jfc/SampleTree/SampleTree.java
41149 views
/*1* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.2*3* Redistribution and use in source and binary forms, with or without4* modification, are permitted provided that the following conditions5* are met:6*7* - Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9*10* - Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* - Neither the name of Oracle nor the names of its15* contributors may be used to endorse or promote products derived16* from this software without specific prior written permission.17*18* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS19* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,20* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR21* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR22* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,23* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,24* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR25* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF26* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING27* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS28* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.29*/3031/*32* This source code is provided to illustrate the usage of a given feature33* or technique and has been deliberately simplified. Additional steps34* required for a production-quality application, such as security checks,35* input validation and proper error handling, might not be present in36* this sample code.37*/38394041import java.lang.reflect.InvocationTargetException;42import java.util.logging.Level;43import java.util.logging.Logger;44import javax.swing.*;45import javax.swing.event.*;46import java.awt.BorderLayout;47import java.awt.Color;48import java.awt.Dimension;49import java.awt.FlowLayout;50import java.awt.event.ActionEvent;51import java.awt.event.ActionListener;52import java.util.*;53import javax.swing.UIManager.LookAndFeelInfo;54import javax.swing.border.*;55import javax.swing.tree.*;565758/**59* A demo for illustrating how to do different things with JTree.60* The data that this displays is rather boring, that is each node will61* have 7 children that have random names based on the fonts. Each node62* is then drawn with that font and in a different color.63* While the data isn't interesting the example illustrates a number64* of things:65*66* For an example of dynamicaly loading children refer to DynamicTreeNode.67* For an example of adding/removing/inserting/reloading refer to the inner68* classes of this class, AddAction, RemovAction, InsertAction and69* ReloadAction.70* For an example of creating your own cell renderer refer to71* SampleTreeCellRenderer.72* For an example of subclassing JTreeModel for editing refer to73* SampleTreeModel.74*75* @author Scott Violet76*/77public final class SampleTree {7879/** Window for showing Tree. */80protected JFrame frame;81/** Tree used for the example. */82protected JTree tree;83/** Tree model. */84protected DefaultTreeModel treeModel;8586/**87* Constructs a new instance of SampleTree.88*/89public SampleTree() {90// Trying to set Nimbus look and feel91try {92for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {93if ("Nimbus".equals(info.getName())) {94UIManager.setLookAndFeel(info.getClassName());95break;96}97}98} catch (Exception ignored) {99}100101JMenuBar menuBar = constructMenuBar();102JPanel panel = new JPanel(true);103104frame = new JFrame("SampleTree");105frame.getContentPane().add("Center", panel);106frame.setJMenuBar(menuBar);107frame.setBackground(Color.lightGray);108109/* Create the JTreeModel. */110DefaultMutableTreeNode root = createNewNode("Root");111treeModel = new SampleTreeModel(root);112113/* Create the tree. */114tree = new JTree(treeModel);115116/* Enable tool tips for the tree, without this tool tips will not117be picked up. */118ToolTipManager.sharedInstance().registerComponent(tree);119120/* Make the tree use an instance of SampleTreeCellRenderer for121drawing. */122tree.setCellRenderer(new SampleTreeCellRenderer());123124/* Make tree ask for the height of each row. */125tree.setRowHeight(-1);126127/* Put the Tree in a scroller. */128JScrollPane sp = new JScrollPane();129sp.setPreferredSize(new Dimension(300, 300));130sp.getViewport().add(tree);131132/* And show it. */133panel.setLayout(new BorderLayout());134panel.add("Center", sp);135panel.add("South", constructOptionsPanel());136137frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);138frame.pack();139frame.setVisible(true);140}141142/** Constructs a JPanel containing check boxes for the different143* options that tree supports. */144@SuppressWarnings("serial")145private JPanel constructOptionsPanel() {146JCheckBox aCheckbox;147JPanel retPanel = new JPanel(false);148JPanel borderPane = new JPanel(false);149150borderPane.setLayout(new BorderLayout());151retPanel.setLayout(new FlowLayout());152153aCheckbox = new JCheckBox("show top level handles");154aCheckbox.setSelected(tree.getShowsRootHandles());155aCheckbox.addChangeListener(new ShowHandlesChangeListener());156retPanel.add(aCheckbox);157158aCheckbox = new JCheckBox("show root");159aCheckbox.setSelected(tree.isRootVisible());160aCheckbox.addChangeListener(new ShowRootChangeListener());161retPanel.add(aCheckbox);162163aCheckbox = new JCheckBox("editable");164aCheckbox.setSelected(tree.isEditable());165aCheckbox.addChangeListener(new TreeEditableChangeListener());166aCheckbox.setToolTipText("Triple click to edit");167retPanel.add(aCheckbox);168169borderPane.add(retPanel, BorderLayout.CENTER);170171/* Create a set of radio buttons that dictate what selection should172be allowed in the tree. */173ButtonGroup group = new ButtonGroup();174JPanel buttonPane = new JPanel(false);175JRadioButton button;176177buttonPane.setLayout(new FlowLayout());178buttonPane.setBorder(new TitledBorder("Selection Mode"));179button = new JRadioButton("Single");180button.addActionListener(new AbstractAction() {181182@Override183public boolean isEnabled() {184return true;185}186187public void actionPerformed(ActionEvent e) {188tree.getSelectionModel().setSelectionMode(189TreeSelectionModel.SINGLE_TREE_SELECTION);190}191});192group.add(button);193buttonPane.add(button);194button = new JRadioButton("Contiguous");195button.addActionListener(new AbstractAction() {196197@Override198public boolean isEnabled() {199return true;200}201202public void actionPerformed(ActionEvent e) {203tree.getSelectionModel().setSelectionMode(204TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);205}206});207group.add(button);208buttonPane.add(button);209button = new JRadioButton("Discontiguous");210button.addActionListener(new AbstractAction() {211212@Override213public boolean isEnabled() {214return true;215}216217public void actionPerformed(ActionEvent e) {218tree.getSelectionModel().setSelectionMode(219TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);220}221});222button.setSelected(true);223group.add(button);224buttonPane.add(button);225226borderPane.add(buttonPane, BorderLayout.SOUTH);227228// NOTE: This will be enabled in a future release.229// Create a label and combobox to determine how many clicks are230// needed to expand.231/*232JPanel clickPanel = new JPanel();233Object[] values = { "Never", new Integer(1),234new Integer(2), new Integer(3) };235final JComboBox clickCBox = new JComboBox(values);236237clickPanel.setLayout(new FlowLayout());238clickPanel.add(new JLabel("Click count to expand:"));239clickCBox.setSelectedIndex(2);240clickCBox.addActionListener(new ActionListener() {241public void actionPerformed(ActionEvent ae) {242Object selItem = clickCBox.getSelectedItem();243244if(selItem instanceof Integer)245tree.setToggleClickCount(((Integer)selItem).intValue());246else // Don't toggle247tree.setToggleClickCount(0);248}249});250clickPanel.add(clickCBox);251borderPane.add(clickPanel, BorderLayout.NORTH);252*/253return borderPane;254}255256/** Construct a menu. */257private JMenuBar constructMenuBar() {258JMenu menu;259JMenuBar menuBar = new JMenuBar();260JMenuItem menuItem;261262/* Good ol exit. */263menu = new JMenu("File");264menuBar.add(menu);265266menuItem = menu.add(new JMenuItem("Exit"));267menuItem.addActionListener(new ActionListener() {268269public void actionPerformed(ActionEvent e) {270System.exit(0);271}272});273274/* Tree related stuff. */275menu = new JMenu("Tree");276menuBar.add(menu);277278menuItem = menu.add(new JMenuItem("Add"));279menuItem.addActionListener(new AddAction());280281menuItem = menu.add(new JMenuItem("Insert"));282menuItem.addActionListener(new InsertAction());283284menuItem = menu.add(new JMenuItem("Reload"));285menuItem.addActionListener(new ReloadAction());286287menuItem = menu.add(new JMenuItem("Remove"));288menuItem.addActionListener(new RemoveAction());289290return menuBar;291}292293/**294* Returns the TreeNode instance that is selected in the tree.295* If nothing is selected, null is returned.296*/297protected DefaultMutableTreeNode getSelectedNode() {298TreePath selPath = tree.getSelectionPath();299300if (selPath != null) {301return (DefaultMutableTreeNode) selPath.getLastPathComponent();302}303return null;304}305306/**307* Returns the selected TreePaths in the tree, may return null if308* nothing is selected.309*/310protected TreePath[] getSelectedPaths() {311return tree.getSelectionPaths();312}313314protected DefaultMutableTreeNode createNewNode(String name) {315return new DynamicTreeNode(new SampleData(null, Color.black, name));316}317318319/**320* AddAction is used to add a new item after the selected item.321*/322class AddAction extends Object implements ActionListener {323324/** Number of nodes that have been added. */325public int addCount;326327/**328* Messaged when the user clicks on the Add menu item.329* Determines the selection from the Tree and adds an item330* after that. If nothing is selected, an item is added to331* the root.332*/333public void actionPerformed(ActionEvent e) {334DefaultMutableTreeNode lastItem = getSelectedNode();335DefaultMutableTreeNode parent;336337/* Determine where to create the new node. */338if (lastItem != null) {339parent = (DefaultMutableTreeNode) lastItem.getParent();340if (parent == null) {341parent = (DefaultMutableTreeNode) treeModel.getRoot();342lastItem = null;343}344} else {345parent = (DefaultMutableTreeNode) treeModel.getRoot();346}347if (parent == null) {348// new root349treeModel.setRoot(createNewNode("Added " + Integer.toString(350addCount++)));351} else {352int newIndex;353if (lastItem == null) {354newIndex = treeModel.getChildCount(parent);355} else {356newIndex = parent.getIndex(lastItem) + 1;357}358359/* Let the treemodel know. */360treeModel.insertNodeInto(createNewNode("Added " + Integer.361toString(addCount++)),362parent, newIndex);363}364}365} // End of SampleTree.AddAction366367368/**369* InsertAction is used to insert a new item before the selected item.370*/371class InsertAction extends Object implements ActionListener {372373/** Number of nodes that have been added. */374public int insertCount;375376/**377* Messaged when the user clicks on the Insert menu item.378* Determines the selection from the Tree and inserts an item379* after that. If nothing is selected, an item is added to380* the root.381*/382public void actionPerformed(ActionEvent e) {383DefaultMutableTreeNode lastItem = getSelectedNode();384DefaultMutableTreeNode parent;385386/* Determine where to create the new node. */387if (lastItem != null) {388parent = (DefaultMutableTreeNode) lastItem.getParent();389if (parent == null) {390parent = (DefaultMutableTreeNode) treeModel.getRoot();391lastItem = null;392}393} else {394parent = (DefaultMutableTreeNode) treeModel.getRoot();395}396if (parent == null) {397// new root398treeModel.setRoot(createNewNode("Inserted " + Integer.toString(399insertCount++)));400} else {401int newIndex;402403if (lastItem == null) {404newIndex = treeModel.getChildCount(parent);405} else {406newIndex = parent.getIndex(lastItem);407}408409/* Let the treemodel know. */410treeModel.insertNodeInto(createNewNode("Inserted " + Integer.411toString(insertCount++)),412parent, newIndex);413}414}415} // End of SampleTree.InsertAction416417418/**419* ReloadAction is used to reload from the selected node. If nothing420* is selected, reload is not issued.421*/422class ReloadAction extends Object implements ActionListener {423424/**425* Messaged when the user clicks on the Reload menu item.426* Determines the selection from the Tree and asks the treemodel427* to reload from that node.428*/429public void actionPerformed(ActionEvent e) {430DefaultMutableTreeNode lastItem = getSelectedNode();431432if (lastItem != null) {433treeModel.reload(lastItem);434}435}436} // End of SampleTree.ReloadAction437438439/**440* RemoveAction removes the selected node from the tree. If441* The root or nothing is selected nothing is removed.442*/443class RemoveAction extends Object implements ActionListener {444445/**446* Removes the selected item as long as it isn't root.447*/448public void actionPerformed(ActionEvent e) {449TreePath[] selected = getSelectedPaths();450451if (selected != null && selected.length > 0) {452TreePath shallowest;453454// The remove process consists of the following steps:455// 1 - find the shallowest selected TreePath, the shallowest456// path is the path with the smallest number of path457// components.458// 2 - Find the siblings of this TreePath459// 3 - Remove from selected the TreePaths that are descendants460// of the paths that are going to be removed. They will461// be removed as a result of their ancestors being462// removed.463// 4 - continue until selected contains only null paths.464while ((shallowest = findShallowestPath(selected)) != null) {465removeSiblings(shallowest, selected);466}467}468}469470/**471* Removes the sibling TreePaths of <code>path</code>, that are472* located in <code>paths</code>.473*/474private void removeSiblings(TreePath path, TreePath[] paths) {475// Find the siblings476if (path.getPathCount() == 1) {477// Special case, set the root to null478for (int counter = paths.length - 1; counter >= 0; counter--) {479paths[counter] = null;480}481treeModel.setRoot(null);482} else {483// Find the siblings of path.484TreePath parent = path.getParentPath();485MutableTreeNode parentNode = (MutableTreeNode) parent.486getLastPathComponent();487ArrayList<TreePath> toRemove = new ArrayList<TreePath>();488489// First pass, find paths with a parent TreePath of parent490for (int counter = paths.length - 1; counter >= 0; counter--) {491if (paths[counter] != null && paths[counter].getParentPath().492equals(parent)) {493toRemove.add(paths[counter]);494paths[counter] = null;495}496}497498// Second pass, remove any paths that are descendants of the499// paths that are going to be removed. These paths are500// implicitly removed as a result of removing the paths in501// toRemove502int rCount = toRemove.size();503for (int counter = paths.length - 1; counter >= 0; counter--) {504if (paths[counter] != null) {505for (int rCounter = rCount - 1; rCounter >= 0;506rCounter--) {507if ((toRemove.get(rCounter)).isDescendant(508paths[counter])) {509paths[counter] = null;510}511}512}513}514515// Sort the siblings based on position in the model516if (rCount > 1) {517Collections.sort(toRemove, new PositionComparator());518}519int[] indices = new int[rCount];520Object[] removedNodes = new Object[rCount];521for (int counter = rCount - 1; counter >= 0; counter--) {522removedNodes[counter] = (toRemove.get(counter)).523getLastPathComponent();524indices[counter] = treeModel.getIndexOfChild(parentNode,525removedNodes[counter]);526parentNode.remove(indices[counter]);527}528treeModel.nodesWereRemoved(parentNode, indices, removedNodes);529}530}531532/**533* Returns the TreePath with the smallest path count in534* <code>paths</code>. Will return null if there is no non-null535* TreePath is <code>paths</code>.536*/537private TreePath findShallowestPath(TreePath[] paths) {538int shallowest = -1;539TreePath shallowestPath = null;540541for (int counter = paths.length - 1; counter >= 0; counter--) {542if (paths[counter] != null) {543if (shallowest != -1) {544if (paths[counter].getPathCount() < shallowest) {545shallowest = paths[counter].getPathCount();546shallowestPath = paths[counter];547if (shallowest == 1) {548return shallowestPath;549}550}551} else {552shallowestPath = paths[counter];553shallowest = paths[counter].getPathCount();554}555}556}557return shallowestPath;558}559560561/**562* An Comparator that bases the return value on the index of the563* passed in objects in the TreeModel.564* <p>565* This is actually rather expensive, it would be more efficient566* to extract the indices and then do the comparision.567*/568private class PositionComparator implements Comparator<TreePath> {569570public int compare(TreePath p1, TreePath p2) {571int p1Index = treeModel.getIndexOfChild(p1.getParentPath().572getLastPathComponent(), p1.getLastPathComponent());573int p2Index = treeModel.getIndexOfChild(p2.getParentPath().574getLastPathComponent(), p2.getLastPathComponent());575return p1Index - p2Index;576}577}578} // End of SampleTree.RemoveAction579580581/**582* ShowHandlesChangeListener implements the ChangeListener interface583* to toggle the state of showing the handles in the tree.584*/585class ShowHandlesChangeListener extends Object implements ChangeListener {586587public void stateChanged(ChangeEvent e) {588tree.setShowsRootHandles(((JCheckBox) e.getSource()).isSelected());589}590} // End of class SampleTree.ShowHandlesChangeListener591592593/**594* ShowRootChangeListener implements the ChangeListener interface595* to toggle the state of showing the root node in the tree.596*/597class ShowRootChangeListener extends Object implements ChangeListener {598599public void stateChanged(ChangeEvent e) {600tree.setRootVisible(((JCheckBox) e.getSource()).isSelected());601}602} // End of class SampleTree.ShowRootChangeListener603604605/**606* TreeEditableChangeListener implements the ChangeListener interface607* to toggle between allowing editing and now allowing editing in608* the tree.609*/610class TreeEditableChangeListener extends Object implements ChangeListener {611612public void stateChanged(ChangeEvent e) {613tree.setEditable(((JCheckBox) e.getSource()).isSelected());614}615} // End of class SampleTree.TreeEditableChangeListener616617public static void main(String[] args) {618try {619SwingUtilities.invokeAndWait(new Runnable() {620621@SuppressWarnings(value = "ResultOfObjectAllocationIgnored")622public void run() {623new SampleTree();624}625});626} catch (InterruptedException ex) {627Logger.getLogger(SampleTree.class.getName()).log(Level.SEVERE, null,628ex);629} catch (InvocationTargetException ex) {630Logger.getLogger(SampleTree.class.getName()).log(Level.SEVERE, null,631ex);632}633}634}635636637