Initial commit
[debian/openrocket] / src / net / sf / openrocket / gui / main / ComponentTreeModel.java
1 package net.sf.openrocket.gui.main;
2
3 import java.util.ArrayList;
4 import java.util.Enumeration;
5 import java.util.Iterator;
6 import java.util.List;
7
8 import javax.swing.JTree;
9 import javax.swing.event.TreeModelEvent;
10 import javax.swing.event.TreeModelListener;
11 import javax.swing.tree.TreeModel;
12 import javax.swing.tree.TreePath;
13
14 import net.sf.openrocket.rocketcomponent.ComponentChangeEvent;
15 import net.sf.openrocket.rocketcomponent.ComponentChangeListener;
16 import net.sf.openrocket.rocketcomponent.RocketComponent;
17
18
19 /**
20  * A TreeModel that implements viewing of the rocket tree structure.
21  * This transforms the internal view (which has nested Stages) into the user-view
22  * (which has parallel Stages).
23  * 
24  * To view with the internal structure, switch to using BareComponentTreeModel in
25  * ComponentTree.java.  NOTE: This class's makeTreePath will still be used, which
26  * will create illegal paths, which results in problems with selections. 
27  * 
28  * TODO: MEDIUM: When converting a component to another component this model given 
29  * outdated information, since it uses the components themselves as the nodes.
30  * 
31  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
32  */
33
34 public class ComponentTreeModel implements TreeModel, ComponentChangeListener {
35         ArrayList<TreeModelListener> listeners = new ArrayList<TreeModelListener>();
36
37         private final RocketComponent root;
38         private final JTree tree;
39
40         public ComponentTreeModel(RocketComponent root, JTree tree) {
41                 this.root = root;
42                 this.tree = tree;
43                 root.addComponentChangeListener(this);
44         }
45         
46         
47         public Object getChild(Object parent, int index) {
48                 RocketComponent component = (RocketComponent)parent;
49                 
50                 try {
51                         return component.getChild(index);
52                 } catch (IndexOutOfBoundsException e) {
53                         return null;
54                 }
55         }
56         
57
58         public int getChildCount(Object parent) {
59                 RocketComponent c = (RocketComponent)parent;
60
61                 return c.getChildCount();
62         }
63
64         
65         public int getIndexOfChild(Object parent, Object child) {
66                 if (parent==null || child==null)
67                         return -1;
68                 
69                 RocketComponent p = (RocketComponent)parent;
70                 RocketComponent c = (RocketComponent)child;
71                 
72                 return p.getChildPosition(c);
73         }
74
75         public Object getRoot() {
76                 return root;
77         }
78
79         public boolean isLeaf(Object node) {
80                 RocketComponent c = (RocketComponent)node;
81
82                 return (c.getChildCount() == 0);
83         }
84
85         public void addTreeModelListener(TreeModelListener l) {
86                 listeners.add(l);
87         }
88
89         public void removeTreeModelListener(TreeModelListener l) {
90                 listeners.remove(l);
91         }
92         
93         private void fireTreeNodesChanged() {
94                 Object[] path = { root };
95                 TreeModelEvent e = new TreeModelEvent(this,path);
96                 Object[] l = listeners.toArray();
97                 for (int i=0; i<l.length; i++)
98                         ((TreeModelListener)l[i]).treeNodesChanged(e);
99         }
100         
101         
102         @SuppressWarnings("unused")
103         private void printStructure(TreePath p, int level) {
104                 String indent="";
105                 for (int i=0; i<level; i++)
106                         indent += "  ";
107                 System.out.println(indent+p+
108                                 ": isVisible:"+tree.isVisible(p)+
109                                 " isCollapsed:"+tree.isCollapsed(p)+
110                                 " isExpanded:"+tree.isExpanded(p));
111                 Object parent = p.getLastPathComponent();
112                 for (int i=0; i<getChildCount(parent); i++) {
113                         Object child = getChild(parent,i);
114                         TreePath path = makeTreePath((RocketComponent)child);
115                         printStructure(path,level+1);
116                 }
117         }
118         
119         
120         private void fireTreeStructureChanged(RocketComponent source) {
121                 Object[] path = { root };
122                 
123                 
124                 // Get currently expanded path IDs
125                 Enumeration<TreePath> enumer = tree.getExpandedDescendants(new TreePath(path));
126                 ArrayList<String> expanded = new ArrayList<String>();
127                 if (enumer != null) {
128                         while (enumer.hasMoreElements()) {
129                                 TreePath p = enumer.nextElement();
130                                 expanded.add(((RocketComponent)p.getLastPathComponent()).getID());
131                         }
132                 }
133                 
134                 // Send structure change event
135                 TreeModelEvent e = new TreeModelEvent(this,path);
136                 Object[] l = listeners.toArray();
137                 for (int i=0; i<l.length; i++)
138                         ((TreeModelListener)l[i]).treeStructureChanged(e);
139                 
140                 // Re-expand the paths
141                 for (String id: expanded) {
142                         RocketComponent c = root.findComponent(id);
143                         if (c==null)
144                                 continue;
145                         tree.expandPath(makeTreePath(c));
146                 }
147                 if (source != null) {
148                         TreePath p = makeTreePath(source);
149                         tree.makeVisible(p);
150                         tree.expandPath(p);
151                 }
152         }
153         
154         public void valueForPathChanged(TreePath path, Object newValue) {
155                 System.err.println("ERROR: valueForPathChanged called?!");
156         }
157
158
159         public void componentChanged(ComponentChangeEvent e) {
160                 if (e.isTreeChange() || e.isUndoChange()) {
161                         // Tree must be fully updated also in case of an undo change 
162                         fireTreeStructureChanged((RocketComponent)e.getSource());
163                         if (e.isTreeChange() && e.isUndoChange()) {
164                                 // If the undo has changed the tree structure, some elements may be hidden
165                                 // unnecessarily
166                                 // TODO: LOW: Could this be performed better?
167                                 expandAll();
168                         }
169                 } else if (e.isOtherChange()) {
170                         fireTreeNodesChanged();
171                 }
172         }
173
174         public void expandAll() {
175                 Iterator<RocketComponent> iterator = root.deepIterator();
176                 while (iterator.hasNext()) {
177                         tree.makeVisible(makeTreePath(iterator.next()));
178                 }
179         }
180
181         
182         public static TreePath makeTreePath(RocketComponent component) {
183                 RocketComponent c = component;
184         
185                 List<RocketComponent> list = new ArrayList<RocketComponent>();
186                 
187                 while (c != null) {
188                         list.add(0,c);
189                         c = c.getParent();
190                 }
191                 
192                 return new TreePath(list.toArray());
193         }
194         
195 }