8cf5828285f30512f496c3902d3216e6c92d1535
[debian/openrocket] / 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  * This transforms the internal view (which has nested Stages) into the user-view
24  * (which has parallel Stages).
25  * 
26  * To view with the internal structure, switch to using BareComponentTreeModel in
27  * ComponentTree.java.  NOTE: This class's makeTreePath will still be used, which
28  * will create illegal paths, which results in problems with selections. 
29  * 
30  * TODO: MEDIUM: When converting a component to another component this model given 
31  * outdated information, since it uses the components themselves as the nodes.
32  * 
33  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
34  */
35
36 public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
37         ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
38         
39         private final RocketComponent root;
40         private final JTree tree;
41         
42         public ComponentTreeModel(RocketComponent root, JTree tree) {
43                 this.root = root;
44                 this.tree = tree;
45                 root.addComponentChangeListener(this);
46         }
47         
48         
49         @Override
50         public Object getChild(Object parent, int index) {
51                 RocketComponent component = (RocketComponent) parent;
52                 
53                 try {
54                         return component.getChild(index);
55                 } catch (IndexOutOfBoundsException e) {
56                         return null;
57                 }
58         }
59         
60         
61         @Override
62         public int getChildCount(Object parent) {
63                 RocketComponent c = (RocketComponent) parent;
64                 
65                 return c.getChildCount();
66         }
67         
68         
69         @Override
70         public int getIndexOfChild(Object parent, Object child) {
71                 if (parent == null || child == null)
72                         return -1;
73                 
74                 RocketComponent p = (RocketComponent) parent;
75                 RocketComponent c = (RocketComponent) child;
76                 
77                 return p.getChildPosition(c);
78         }
79         
80         @Override
81         public Object getRoot() {
82                 return root;
83         }
84         
85         @Override
86         public boolean isLeaf(Object node) {
87                 return !((RocketComponent) node).allowsChildren();
88         }
89         
90         @Override
91         public void addTreeModelListener(TreeModelListener l) {
92                 listeners.add(l);
93         }
94         
95         @Override
96         public void removeTreeModelListener(TreeModelListener l) {
97                 listeners.remove(l);
98         }
99         
100         private void fireTreeNodeChanged(RocketComponent node) {
101                 TreeModelEvent e = new TreeModelEvent(this, makeTreePath(node), null, null);
102                 Object[] l = listeners.toArray();
103                 for (int i = 0; i < l.length; i++)
104                         ((TreeModelListener) l[i]).treeNodesChanged(e);
105         }
106         
107         private void fireTreeNodesChanged() {
108                 Object[] path = { root };
109                 TreeModelEvent e = new TreeModelEvent(this, path);
110                 Object[] l = listeners.toArray();
111                 for (int i = 0; i < l.length; i++)
112                         ((TreeModelListener) l[i]).treeNodesChanged(e);
113         }
114         
115         
116         @SuppressWarnings("unused")
117         private void printStructure(TreePath p, int level) {
118                 String indent = "";
119                 for (int i = 0; i < level; i++)
120                         indent += "  ";
121                 System.out.println(indent + p +
122                                 ": isVisible:" + tree.isVisible(p) +
123                                 " isCollapsed:" + tree.isCollapsed(p) +
124                                 " isExpanded:" + tree.isExpanded(p));
125                 Object parent = p.getLastPathComponent();
126                 for (int i = 0; i < getChildCount(parent); i++) {
127                         Object child = getChild(parent, i);
128                         TreePath path = makeTreePath((RocketComponent) child);
129                         printStructure(path, level + 1);
130                 }
131         }
132         
133         
134         private void fireTreeStructureChanged(RocketComponent source) {
135                 Object[] path = { root };
136                 
137
138                 // Get currently expanded path IDs
139                 Enumeration<TreePath> enumer = tree.getExpandedDescendants(new TreePath(path));
140                 ArrayList<String> expanded = new ArrayList<String>();
141                 if (enumer != null) {
142                         while (enumer.hasMoreElements()) {
143                                 TreePath p = enumer.nextElement();
144                                 expanded.add(((RocketComponent) p.getLastPathComponent()).getID());
145                         }
146                 }
147                 
148                 // Send structure change event
149                 TreeModelEvent e = new TreeModelEvent(this, path);
150                 Object[] l = listeners.toArray();
151                 for (int i = 0; i < l.length; i++)
152                         ((TreeModelListener) l[i]).treeStructureChanged(e);
153                 
154                 // Re-expand the paths
155                 for (String id : expanded) {
156                         RocketComponent c = root.findComponent(id);
157                         if (c == null)
158                                 continue;
159                         tree.expandPath(makeTreePath(c));
160                 }
161                 if (source != null) {
162                         TreePath p = makeTreePath(source);
163                         tree.makeVisible(p);
164                         tree.expandPath(p);
165                 }
166         }
167         
168         @Override
169         public void valueForPathChanged(TreePath path, Object newValue) {
170                 System.err.println("ERROR: valueForPathChanged called?!");
171         }
172         
173         
174         @Override
175         public void componentChanged(ComponentChangeEvent e) {
176                 if (e.isTreeChange() || e.isUndoChange()) {
177                         // Tree must be fully updated also in case of an undo change 
178                         fireTreeStructureChanged(e.getSource());
179                         if (e.isTreeChange() && e.isUndoChange()) {
180                                 // If the undo has changed the tree structure, some elements may be hidden
181                                 // unnecessarily
182                                 // TODO: LOW: Could this be performed better?
183                                 expandAll();
184                         }
185                 } else if (e.isOtherChange()) {
186                         fireTreeNodeChanged(e.getSource());
187                 }
188         }
189         
190         public void expandAll() {
191                 Iterator<RocketComponent> iterator = root.iterator(false);
192                 while (iterator.hasNext()) {
193                         tree.makeVisible(makeTreePath(iterator.next()));
194                 }
195         }
196         
197         
198         /**
199          * Return the rocket component that a TreePath object is referring to.
200          * 
201          * @param path  the TreePath
202          * @return              the RocketComponent the path is referring to.
203          * @throws NullPointerException         if path is null
204          * @throws BugException                         if the path does not refer to a RocketComponent
205          */
206         public static RocketComponent componentFromPath(TreePath path) {
207                 Object last = path.getLastPathComponent();
208                 if (!(last instanceof RocketComponent)) {
209                         throw new BugException("Tree path does not refer to a RocketComponent: " + path.toString());
210                 }
211                 return (RocketComponent) last;
212         }
213         
214         
215         /**
216          * Return a TreePath corresponding to the specified rocket component.
217          * 
218          * @param component             the rocket component
219          * @return                              a TreePath corresponding to this RocketComponent
220          */
221         public static TreePath makeTreePath(RocketComponent component) {
222                 if (component == null) {
223                         throw new NullPointerException();
224                 }
225                 
226                 RocketComponent c = component;
227                 
228                 List<RocketComponent> list = new LinkedList<RocketComponent>();
229                 
230                 while (c != null) {
231                         list.add(0, c);
232                         c = c.getParent();
233                 }
234                 
235                 return new TreePath(list.toArray());
236         }
237         
238         
239         /**
240          * Return a string describing the path, using component normal names.
241          * 
242          * @param treePath      the tree path
243          * @return                      a string representation
244          */
245         public static String pathToString(TreePath treePath) {
246                 StringBuffer sb = new StringBuffer();
247                 sb.append("[");
248                 for (Object o : treePath.getPath()) {
249                         if (sb.length() > 1) {
250                                 sb.append("; ");
251                         }
252                         if (o instanceof RocketComponent) {
253                                 sb.append(((RocketComponent) o).getComponentName());
254                         } else {
255                                 sb.append(o.toString());
256                         }
257                 }
258                 sb.append("]");
259                 return sb.toString();
260         }
261 }