create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / main / componenttree / ComponentTreeModel.java
1 package net.sf.openrocket.gui.main.componenttree;
2
3 import java.util.ArrayList;
4 import java.util.Enumeration;
5 import java.util.Iterator;
6 import java.util.LinkedList;
7 import java.util.List;
8
9 import javax.swing.JTree;
10 import javax.swing.event.TreeModelEvent;
11 import javax.swing.event.TreeModelListener;
12 import javax.swing.tree.TreeModel;
13 import javax.swing.tree.TreePath;
14
15 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
16 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
17 import net.sf.openrocket.rocketcomponent.RocketComponent;
18 import net.sf.openrocket.util.BugException;
19
20
21 /**
22  * A TreeModel that implements viewing of the rocket tree structure.
23  * 
24  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
25  */
26
27 public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
28         ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
29         
30         private final RocketComponent root;
31         private final JTree tree;
32         
33         public ComponentTreeModel(RocketComponent root, JTree tree) {
34                 this.root = root;
35                 this.tree = tree;
36                 root.addComponentChangeListener(this);
37         }
38         
39         
40         @Override
41         public Object getChild(Object parent, int index) {
42                 RocketComponent component = (RocketComponent) parent;
43                 
44                 try {
45                         return component.getChild(index);
46                 } catch (IndexOutOfBoundsException e) {
47                         return null;
48                 }
49         }
50         
51         
52         @Override
53         public int getChildCount(Object parent) {
54                 RocketComponent c = (RocketComponent) parent;
55                 
56                 return c.getChildCount();
57         }
58         
59         
60         @Override
61         public int getIndexOfChild(Object parent, Object child) {
62                 if (parent == null || child == null)
63                         return -1;
64                 
65                 RocketComponent p = (RocketComponent) parent;
66                 RocketComponent c = (RocketComponent) child;
67                 
68                 return p.getChildPosition(c);
69         }
70         
71         @Override
72         public Object getRoot() {
73                 return root;
74         }
75         
76         @Override
77         public boolean isLeaf(Object node) {
78                 return !((RocketComponent) node).allowsChildren();
79         }
80         
81         @Override
82         public void addTreeModelListener(TreeModelListener l) {
83                 listeners.add(l);
84         }
85         
86         @Override
87         public void removeTreeModelListener(TreeModelListener l) {
88                 listeners.remove(l);
89         }
90         
91         private void fireTreeNodeChanged(RocketComponent node) {
92                 TreeModelEvent e = new TreeModelEvent(this, makeTreePath(node), null, null);
93                 Object[] l = listeners.toArray();
94                 for (int i = 0; i < l.length; i++)
95                         ((TreeModelListener) l[i]).treeNodesChanged(e);
96         }
97         
98         
99         private void fireTreeStructureChanged(RocketComponent source) {
100                 Object[] path = { root };
101                 
102
103                 // Get currently expanded path IDs
104                 Enumeration<TreePath> enumer = tree.getExpandedDescendants(new TreePath(path));
105                 ArrayList<String> expanded = new ArrayList<String>();
106                 if (enumer != null) {
107                         while (enumer.hasMoreElements()) {
108                                 TreePath p = enumer.nextElement();
109                                 expanded.add(((RocketComponent) p.getLastPathComponent()).getID());
110                         }
111                 }
112                 
113                 // Send structure change event
114                 TreeModelEvent e = new TreeModelEvent(this, path);
115                 Object[] l = listeners.toArray();
116                 for (int i = 0; i < l.length; i++)
117                         ((TreeModelListener) l[i]).treeStructureChanged(e);
118                 
119                 // Re-expand the paths
120                 for (String id : expanded) {
121                         RocketComponent c = root.findComponent(id);
122                         if (c == null)
123                                 continue;
124                         tree.expandPath(makeTreePath(c));
125                 }
126                 if (source != null) {
127                         TreePath p = makeTreePath(source);
128                         tree.makeVisible(p);
129                         tree.expandPath(p);
130                 }
131         }
132         
133         @Override
134         public void valueForPathChanged(TreePath path, Object newValue) {
135                 System.err.println("ERROR: valueForPathChanged called?!");
136         }
137         
138         
139         @Override
140         public void componentChanged(ComponentChangeEvent e) {
141                 if (e.isTreeChange() || e.isUndoChange()) {
142                         // Tree must be fully updated also in case of an undo change 
143                         fireTreeStructureChanged(e.getSource());
144                         if (e.isTreeChange() && e.isUndoChange()) {
145                                 // If the undo has changed the tree structure, some elements may be hidden
146                                 // unnecessarily
147                                 // TODO: LOW: Could this be performed better?
148                                 expandAll();
149                         }
150                 } else if (e.isOtherChange()) {
151                         fireTreeNodeChanged(e.getSource());
152                 }
153         }
154         
155         public void expandAll() {
156                 Iterator<RocketComponent> iterator = root.iterator(false);
157                 while (iterator.hasNext()) {
158                         tree.makeVisible(makeTreePath(iterator.next()));
159                 }
160         }
161         
162         
163         /**
164          * Return the rocket component that a TreePath object is referring to.
165          * 
166          * @param path  the TreePath
167          * @return              the RocketComponent the path is referring to.
168          * @throws NullPointerException         if path is null
169          * @throws BugException                         if the path does not refer to a RocketComponent
170          */
171         public static RocketComponent componentFromPath(TreePath path) {
172                 Object last = path.getLastPathComponent();
173                 if (!(last instanceof RocketComponent)) {
174                         throw new BugException("Tree path does not refer to a RocketComponent: " + path.toString());
175                 }
176                 return (RocketComponent) last;
177         }
178         
179         
180         /**
181          * Return a TreePath corresponding to the specified rocket component.
182          * 
183          * @param component             the rocket component
184          * @return                              a TreePath corresponding to this RocketComponent
185          */
186         public static TreePath makeTreePath(RocketComponent component) {
187                 if (component == null) {
188                         throw new NullPointerException();
189                 }
190                 
191                 RocketComponent c = component;
192                 
193                 List<RocketComponent> list = new LinkedList<RocketComponent>();
194                 
195                 while (c != null) {
196                         list.add(0, c);
197                         c = c.getParent();
198                 }
199                 
200                 return new TreePath(list.toArray());
201         }
202         
203         
204         /**
205          * Return a string describing the path, using component normal names.
206          * 
207          * @param treePath      the tree path
208          * @return                      a string representation
209          */
210         public static String pathToString(TreePath treePath) {
211                 StringBuffer sb = new StringBuffer();
212                 sb.append("[");
213                 for (Object o : treePath.getPath()) {
214                         if (sb.length() > 1) {
215                                 sb.append("; ");
216                         }
217                         if (o instanceof RocketComponent) {
218                                 sb.append(((RocketComponent) o).getComponentName());
219                         } else {
220                                 sb.append(o.toString());
221                         }
222                 }
223                 sb.append("]");
224                 return sb.toString();
225         }
226 }