create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / print / components / CheckTreeSelectionModel.java
1 /*
2  * CheckTreeSelectionModel.java
3  */
4 package net.sf.openrocket.gui.print.components;
5
6 import javax.swing.tree.DefaultTreeSelectionModel;
7 import javax.swing.tree.TreeModel;
8 import javax.swing.tree.TreePath;
9 import javax.swing.tree.TreeSelectionModel;
10 import java.util.ArrayList;
11 import java.util.Stack;
12
13 /**
14  * This class implements the selection model for the checkbox tree.  This specifically is used to keep
15  * track of the TreePaths that have a selected CheckBox.
16  */
17 public class CheckTreeSelectionModel extends DefaultTreeSelectionModel {
18
19     /**
20      * The tree model.
21      */
22     private TreeModel model;
23
24     /**
25      * Constructor.
26      *
27      * @param theModel the model in use for the tree
28      */
29     public CheckTreeSelectionModel (TreeModel theModel) {
30         model = theModel;
31         setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
32     }
33
34     /**
35      * Tests whether there is any selected node in the subtree of given path.
36      *
37      * @param path the path to walk
38      *
39      * @return true if any item in the path or its descendants are selected
40      */
41     public boolean isPartiallySelected (TreePath path) {
42         if (isPathSelected(path, true)) {
43             return false;
44         }
45         TreePath[] selectionPaths = getSelectionPaths();
46         if (selectionPaths == null) {
47             return false;
48         }
49         for (TreePath selectionPath : selectionPaths) {
50             if (isDescendant(selectionPath, path)) {
51                 return true;
52             }
53         }
54         return false;
55     }
56
57     /**
58      * Tells whether given path is selected. If dig is true, then a path is assumed to be selected, if one of its
59      * ancestor is selected.
60      *
61      * @param path the path to interrogate
62      * @param dig  if true then check if an ancestor is selected
63      *
64      * @return true if the path is selected
65      */
66     public boolean isPathSelected (TreePath path, boolean dig) {
67         if (!dig) {
68             return super.isPathSelected(path);
69         }
70         while (path != null && !super.isPathSelected(path)) {
71             path = path.getParentPath();
72         }
73         return path != null;
74     }
75
76     /**
77      * Determines if path1 is a descendant of path2.
78      *
79      * @param path1 descendant?
80      * @param path2 ancestor?
81      *
82      * @return true if path1 is a descendant of path2
83      */
84     private boolean isDescendant (TreePath path1, TreePath path2) {
85         Object obj1[] = path1.getPath();
86         Object obj2[] = path2.getPath();
87         for (int i = 0; i < obj2.length; i++) {
88             if (i < obj1.length) {
89                 if (obj1[i] != obj2[i]) {
90                     return false;
91                 }
92             }
93             else {
94                 return false;
95             }
96         }
97         return true;
98     }
99
100
101     /**
102      * Unsupported exception.
103      *
104      * @param pPaths an array of paths
105      */
106     public void setSelectionPaths (TreePath[] pPaths) {
107         TreePath selected[] = getSelectionPaths();
108         for (TreePath aSelected : selected) {
109             removeSelectionPath(aSelected);
110         }
111         for (TreePath pPath : pPaths) {
112             addSelectionPath(pPath);
113         }
114     }
115
116     /**
117      * Add a set of TreePath nodes to the selection model.
118      *
119      * @param paths an array of tree path nodes
120      */
121     public void addSelectionPaths (TreePath[] paths) {
122         // deselect all descendants of paths[] 
123         for (TreePath path : paths) {
124             TreePath[] selectionPaths = getSelectionPaths();
125             if (selectionPaths == null) {
126                 break;
127             }
128             ArrayList<TreePath> toBeRemoved = new ArrayList<TreePath>();
129             for (TreePath selectionPath : selectionPaths) {
130                 if (isDescendant(selectionPath, path)) {
131                     toBeRemoved.add(selectionPath);
132                 }
133             }
134             super.removeSelectionPaths(toBeRemoved.toArray(new TreePath[toBeRemoved.size()]));
135         }
136
137         // if all siblings are selected then deselect them and select parent recursively 
138         // otherwise just select that path. 
139         for (TreePath path : paths) {
140             TreePath temp = null;
141             while (areSiblingsSelected(path)) {
142                 temp = path;
143                 if (path.getParentPath() == null) {
144                     break;
145                 }
146                 path = path.getParentPath();
147             }
148             if (temp != null) {
149                 if (temp.getParentPath() != null) {
150                     addSelectionPath(temp.getParentPath());
151                 }
152                 else {
153                     if (!isSelectionEmpty()) {
154                         removeSelectionPaths(getSelectionPaths());
155                     }
156                     super.addSelectionPaths(new TreePath[]{temp});
157                 }
158             }
159             else {
160                 super.addSelectionPaths(new TreePath[]{path});
161             }
162         }
163     }
164
165     /**
166      * Tells whether all siblings of given path are selected.
167      *
168      * @param path the tree path node
169      *
170      * @return true if all sibling nodes are selected
171      */
172     private boolean areSiblingsSelected (TreePath path) {
173         TreePath parent = path.getParentPath();
174         if (parent == null) {
175             return true;
176         }
177         Object node = path.getLastPathComponent();
178         Object parentNode = parent.getLastPathComponent();
179
180         int childCount = model.getChildCount(parentNode);
181         for (int i = 0; i < childCount; i++) {
182             Object childNode = model.getChild(parentNode, i);
183             if (childNode == node) {
184                 continue;
185             }
186             if (!isPathSelected(parent.pathByAddingChild(childNode))) {
187                 return false;
188             }
189         }
190         return true;
191     }
192
193     /**
194      * Remove paths from the selection model.
195      *
196      * @param paths the array of path nodes
197      */
198     public void removeSelectionPaths (TreePath[] paths) {
199         for (TreePath path : paths) {
200             if (path.getPathCount() == 1) {
201                 super.removeSelectionPaths(new TreePath[]{path});
202             }
203             else {
204                 toggleRemoveSelection(path);
205             }
206         }
207     }
208
209     /**
210      * If any ancestor node of given path is selected then deselect it and selection all its descendants except given
211      * path and descendants. otherwise just deselect the given path.
212      *
213      * @param path the tree path node
214      */
215     private void toggleRemoveSelection (TreePath path) {
216         Stack<TreePath> stack = new Stack<TreePath>();
217         TreePath parent = path.getParentPath();
218         while (parent != null && !isPathSelected(parent)) {
219             stack.push(parent);
220             parent = parent.getParentPath();
221         }
222         if (parent != null) {
223             stack.push(parent);
224         }
225         else {
226             super.removeSelectionPaths(new TreePath[]{path});
227             return;
228         }
229
230         while (!stack.isEmpty()) {
231             TreePath temp = stack.pop();
232             TreePath peekPath = stack.isEmpty() ? path : stack.peek();
233             Object node = temp.getLastPathComponent();
234             Object peekNode = peekPath.getLastPathComponent();
235             int childCount = model.getChildCount(node);
236             for (int i = 0; i < childCount; i++) {
237                 Object childNode = model.getChild(node, i);
238                 if (childNode != peekNode) {
239                     super.addSelectionPaths(new TreePath[]{temp.pathByAddingChild(childNode)});
240                 }
241             }
242         }
243         super.removeSelectionPaths(new TreePath[]{parent});
244     }
245 }