-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCheckboxTree.java
More file actions
352 lines (327 loc) · 13.5 KB
/
Copy pathCheckboxTree.java
File metadata and controls
352 lines (327 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package com.cs2212;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.HashMap;
import java.util.HashSet;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.event.EventListenerList;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
/*
* CheckboxTree is a class that extends the JTree class and implements its own checking mechanism. It
* defines a new event type and implements the methods required to handle it.
* @author Sophia Ma
*/
/**
* The CheckboxTree class creates the checkbox tree in the sidebar in main screen.
*/
public class CheckboxTree extends JTree {
CheckboxTree selfPointer = this;
ArrayList<POI> poisToDraw = new ArrayList<POI>(); // List of POIs available for that layer
/**
* The CheckedNode class represents one node in the CheckboxTree
*/
private class CheckedNode {
boolean isSelected;
boolean hasChildren;
boolean allChildrenSelected;
/**
Constructs a new CheckedNode object with selection and child node properties.
@param isSelected_ the selection state of the node
@param hasChildren_ the existence of child nodes for the node
@param allChildrenSelected_ the selection state of all child nodes
*/
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
isSelected = isSelected_;
hasChildren = hasChildren_;
allChildrenSelected = allChildrenSelected_;
}
}
HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Define new event type for "checking" a node
EventListenerList listenerList = new EventListenerList();
/**
* Check if there is a change of state with the checkbox node
*/
private class CheckChangeEvent extends EventObject {
private CheckChangeEvent(Object source) {
super(source);
}
}
/**
* Event Listener to check if there is a change of state with the checkbox node
*/
private interface CheckChangeEventListener extends EventListener {
/**
* Check if there was a change of state
* @param event representing the action taken
*/
public void checkStateChanged(CheckChangeEvent event);
}
/**
* Notifies CheckChangeEventListener objects of a check change event.
* @param event the CheckChangeEvent object to be fired
*/
private void fireCheckChangeEvent(CheckChangeEvent event) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) {
if (listeners[i] == CheckChangeEventListener.class) {
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(event);
}
}
}
/**
* Set the model of the checkbox tree to be the new TreeModel
* @param newModel the TreeModel to be set as the new model
*/
@Override
public void setModel(TreeModel newModel) {
super.setModel(newModel);
resetCheckingState();
}
/**
* Returns the list of nodes to be drawn on the main screen.
* @return the list of nodes to draw
*/
public ArrayList<POI> getPOIDraw(){
return poisToDraw;
}
/**
* Set the list of POIs to draw to a new list.
* @param pois the ArrayList of POIs to draw.
*/
public void setPOIDraw(ArrayList<POI> pois) {
poisToDraw = pois;
}
/**
* Clear the list of POIs to draw.
* Clears the POIs.
*/
public void clearPOI(){
poisToDraw.clear();
}
/**
* Mark all nodes as unchecked.
*/
private void resetCheckingState() {
nodesCheckingState = new HashMap<TreePath, CheckedNode>();
checkedPaths = new HashSet<TreePath>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
if (node == null) {
return;
}
addSubtreeToCheckingStateTracking(node);
}
/**
* Creates a subtree with the specified node as the root to track checking states
* @param node the root node
*/
private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) {
TreeNode[] path = node.getPath();
TreePath tp = new TreePath(path);
CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
nodesCheckingState.put(tp, cn);
for (int i = 0 ; i < node.getChildCount() ; i++) {
addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
}
}
/**
* Turns nodes into checkboxes.
*/
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer {
JCheckBox checkBox;
/**
* Constructor for the checkbox cell.
*/
public CheckBoxCellRenderer() {
super();
this.setLayout(new BorderLayout());
checkBox = new JCheckBox();
add(checkBox, BorderLayout.CENTER);
setOpaque(false);
}
/**
* Returns the object that was used to create the TreeCellRenderer.
* @param tree the tree of all nodes.
* @param value the Object stored in the node.
* @param selected true/false representing if the node was selected.
* @param expanded true/false depending whether its children have been selected.
* @param leaf true/false depending if the node is a leaf.
* @param row index of the row being rendered
* @param hasFocus if the cell has focus
* @return component used as a tree cell renderer for that node
*/
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
Object obj = node.getUserObject();
TreePath tp = new TreePath(node.getPath());
CheckedNode cn = nodesCheckingState.get(tp);
if (cn == null) {
return this;
}
try {
// If obj can be successfully casted as a POI object
if ((POI)obj instanceof POI) {
POI POIobj = (POI)obj;
checkBox.setSelected(cn.isSelected);
checkBox.setText(POIobj.checkBoxText());
checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
}
} catch (Exception e) {
// If obj cannot be successfully casted as a POI object, it is a String object
checkBox.setSelected(cn.isSelected);
checkBox.setText(obj.toString());
checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
}
return this;
}
}
/**
* CheckboxTree class creates the checkbox tree in the sidebar.
*/
public CheckboxTree() {
super();
// Disabling toggling by double-click
this.setToggleClickCount(0);
// Override cell renderer
CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
this.setCellRenderer(cellRenderer);
DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
// Totally disabling the selection mechanism
public void setSelectionPath(TreePath path) {
}
public void addSelectionPath(TreePath path) {
}
public void removeSelectionPath(TreePath path) {
}
public void setSelectionPaths(TreePath[] pPaths) {
}
};
// Calling checking mechanism on mouse click
this.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent arg0) {
TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
if (tp == null) {
return;
} else {
DefaultMutableTreeNode poiNode = (DefaultMutableTreeNode) tp.getLastPathComponent();
if (poiNode.getUserObject() instanceof POI) {
POI poi = (POI) poiNode.getUserObject();
poi.setActive();
// Checks if the poi is active and should be drawn or not
if (poi.isActive() == true){
poisToDraw.add(poi);
} else {
poisToDraw.remove(poi);
}
} else {
// Goes through the child of the layer/poi nodes and marks them for active and drawing
for (int i=0; i<poiNode.getChildCount(); i++) {
DefaultMutableTreeNode poi = (DefaultMutableTreeNode) poiNode.getChildAt(i);
POI p = (POI) poi.getUserObject();
p.setActive();
if (p.isActive() == true){
poisToDraw.add(p);
}
else{
poisToDraw.remove(p);
}
}
}
}
boolean checkMode = ! nodesCheckingState.get(tp).isSelected;
checkSubTree(tp, checkMode);
updatePredecessorsWithCheckMode(tp, checkMode);
// Firing the check change event
fireCheckChangeEvent(new CheckChangeEvent(new Object()));
// Repainting tree after it was updated
selfPointer.repaint();
}
@Override
public void mouseEntered(MouseEvent arg0) {
}
@Override
public void mouseExited(MouseEvent arg0) {
}
@Override
public void mousePressed(MouseEvent arg0) {
}
@Override
public void mouseReleased(MouseEvent arg0) {
}
});
this.setSelectionModel(dtsm);
}
/**
* Updates the parent nodes of children nodes according to their changed states.
* @param tp TreePath representing checked nodes
* @param check checked state of child nodes
*/
private void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
TreePath parentPath = tp.getParentPath();
// If it is the root, stop the recursive calls and return
if (parentPath == null) {
return;
}
CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
parentCheckedNode.allChildrenSelected = false;
parentCheckedNode.isSelected = false;
int selectedChildren = 0;
// for each parent node, loop through each child
for (int i = 0 ; i < parentNode.getChildCount() ; i++) {
TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
// If child is not selected, then the parent cannot be selected
if (childCheckedNode.isSelected == false) {
parentCheckedNode.isSelected = false;
} else {
selectedChildren++;
}
}
if (parentNode.getChildCount() == selectedChildren) {
parentCheckedNode.isSelected = true;
}
if (parentCheckedNode.isSelected) {
checkedPaths.add(parentPath);
} else {
checkedPaths.remove(parentPath);
}
// Go to upper predecessor
updatePredecessorsWithCheckMode(parentPath, check);
}
/**
* Checks/unchecks the subtree rooted at a given TreePath.
* @param tp the TreePath of the root node of the subtree to be checked or unchecked
* @param check the checking state applied to subtree
*/
private void checkSubTree(TreePath tp, boolean check) {
CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
for (int i = 0 ; i < node.getChildCount() ; i++) {
checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
}
cn.allChildrenSelected = check;
if (check) {
checkedPaths.add(tp);
} else {
checkedPaths.remove(tp);
}
}
}