source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/validator/ValidatorTreePanel.java@ 7239

Last change on this file since 7239 was 7239, checked in by Don-vip, 10 years ago

fix #10135 - Display total number of validator problems

  • Property svn:eol-style set to native
File size: 14.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui.dialogs.validator;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.KeyListener;
7import java.awt.event.MouseEvent;
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.Enumeration;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Map;
15import java.util.Map.Entry;
16import java.util.Set;
17
18import javax.swing.JTree;
19import javax.swing.ToolTipManager;
20import javax.swing.tree.DefaultMutableTreeNode;
21import javax.swing.tree.DefaultTreeModel;
22import javax.swing.tree.TreePath;
23import javax.swing.tree.TreeSelectionModel;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.data.osm.DataSet;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.validation.Severity;
29import org.openstreetmap.josm.data.validation.TestError;
30import org.openstreetmap.josm.data.validation.util.MultipleNameVisitor;
31import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
32import org.openstreetmap.josm.gui.util.GuiHelper;
33import org.openstreetmap.josm.tools.Destroyable;
34import org.openstreetmap.josm.tools.MultiMap;
35
36/**
37 * A panel that displays the error tree. The selection manager
38 * respects clicks into the selection list. Ctrl-click will remove entries from
39 * the list while single click will make the clicked entry the only selection.
40 *
41 * @author frsantos
42 */
43public class ValidatorTreePanel extends JTree implements Destroyable {
44
45 /**
46 * The validation data.
47 */
48 protected DefaultTreeModel valTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
49
50 /** The list of errors shown in the tree */
51 private List<TestError> errors = new ArrayList<>();
52
53 /**
54 * If {@link #filter} is not <code>null</code> only errors are displayed
55 * that refer to one of the primitives in the filter.
56 */
57 private Set<OsmPrimitive> filter = null;
58
59 /** a counter to check if tree has been rebuild */
60 private int updateCount;
61
62 /**
63 * Constructor
64 * @param errors The list of errors
65 */
66 public ValidatorTreePanel(List<TestError> errors) {
67 ToolTipManager.sharedInstance().registerComponent(this);
68 this.setModel(valTreeModel);
69 this.setRootVisible(false);
70 this.setShowsRootHandles(true);
71 this.expandRow(0);
72 this.setVisibleRowCount(8);
73 this.setCellRenderer(new ValidatorTreeRenderer());
74 this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
75 setErrorList(errors);
76 for (KeyListener keyListener : getKeyListeners()) {
77 // Fix #3596 - Remove default keyListener to avoid conflicts with JOSM commands
78 if ("javax.swing.plaf.basic.BasicTreeUI$Handler".equals(keyListener.getClass().getName())) {
79 removeKeyListener(keyListener);
80 }
81 }
82 }
83
84 @Override
85 public String getToolTipText(MouseEvent e) {
86 String res = null;
87 TreePath path = getPathForLocation(e.getX(), e.getY());
88 if (path != null) {
89 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
90 Object nodeInfo = node.getUserObject();
91
92 if (nodeInfo instanceof TestError) {
93 TestError error = (TestError) nodeInfo;
94 MultipleNameVisitor v = new MultipleNameVisitor();
95 v.visit(error.getPrimitives());
96 res = "<html>" + v.getText() + "<br>" + error.getMessage();
97 String d = error.getDescription();
98 if (d != null)
99 res += "<br>" + d;
100 res += "</html>";
101 } else {
102 res = node.toString();
103 }
104 }
105 return res;
106 }
107
108 /** Constructor */
109 public ValidatorTreePanel() {
110 this(null);
111 }
112
113 @Override
114 public void setVisible(boolean v) {
115 if (v) {
116 buildTree();
117 } else {
118 valTreeModel.setRoot(new DefaultMutableTreeNode());
119 }
120 super.setVisible(v);
121 }
122
123 /**
124 * Builds the errors tree
125 */
126 public void buildTree() {
127 updateCount++;
128 final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
129
130 if (errors == null || errors.isEmpty()) {
131 GuiHelper.runInEDTAndWait(new Runnable() {
132 @Override
133 public void run() {
134 valTreeModel.setRoot(rootNode);
135 }
136 });
137 return;
138 }
139 // Sort validation errors - #8517
140 Collections.sort(errors);
141
142 // Remember the currently expanded rows
143 Set<Object> oldSelectedRows = new HashSet<>();
144 Enumeration<TreePath> expanded = getExpandedDescendants(new TreePath(getRoot()));
145 if (expanded != null) {
146 while (expanded.hasMoreElements()) {
147 TreePath path = expanded.nextElement();
148 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
149 Object userObject = node.getUserObject();
150 if (userObject instanceof Severity) {
151 oldSelectedRows.add(userObject);
152 } else if (userObject instanceof String) {
153 String msg = (String) userObject;
154 msg = msg.substring(0, msg.lastIndexOf(" ("));
155 oldSelectedRows.add(msg);
156 }
157 }
158 }
159
160 Map<Severity, MultiMap<String, TestError>> errorTree = new HashMap<>();
161 Map<Severity, HashMap<String, MultiMap<String, TestError>>> errorTreeDeep = new HashMap<>();
162 for (Severity s : Severity.values()) {
163 errorTree.put(s, new MultiMap<String, TestError>(20));
164 errorTreeDeep.put(s, new HashMap<String, MultiMap<String, TestError>>());
165 }
166
167 final Boolean other = ValidatorPreference.PREF_OTHER.get();
168 for (TestError e : errors) {
169 if (e.getIgnored()) {
170 continue;
171 }
172 Severity s = e.getSeverity();
173 if(!other && s == Severity.OTHER) {
174 continue;
175 }
176 String d = e.getDescription();
177 String m = e.getMessage();
178 if (filter != null) {
179 boolean found = false;
180 for (OsmPrimitive p : e.getPrimitives()) {
181 if (filter.contains(p)) {
182 found = true;
183 break;
184 }
185 }
186 if (!found) {
187 continue;
188 }
189 }
190 if (d != null) {
191 MultiMap<String, TestError> b = errorTreeDeep.get(s).get(m);
192 if (b == null) {
193 b = new MultiMap<>(20);
194 errorTreeDeep.get(s).put(m, b);
195 }
196 b.put(d, e);
197 } else {
198 errorTree.get(s).put(m, e);
199 }
200 }
201
202 List<TreePath> expandedPaths = new ArrayList<>();
203 for (Severity s : Severity.values()) {
204 MultiMap<String, TestError> severityErrors = errorTree.get(s);
205 Map<String, MultiMap<String, TestError>> severityErrorsDeep = errorTreeDeep.get(s);
206 if (severityErrors.isEmpty() && severityErrorsDeep.isEmpty()) {
207 continue;
208 }
209
210 // Severity node
211 DefaultMutableTreeNode severityNode = new DefaultMutableTreeNode(s) {
212 @Override
213 public String toString() {
214 return super.toString() + " (" + getLeafCount() + ")";
215 }
216 };
217 rootNode.add(severityNode);
218
219 if (oldSelectedRows.contains(s)) {
220 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode }));
221 }
222
223 for (Entry<String, Set<TestError>> msgErrors : severityErrors.entrySet()) {
224 // Message node
225 Set<TestError> errs = msgErrors.getValue();
226 String msg = tr("{0} ({1})", msgErrors.getKey(), errs.size());
227 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
228 severityNode.add(messageNode);
229
230 if (oldSelectedRows.contains(msgErrors.getKey())) {
231 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
232 }
233
234 for (TestError error : errs) {
235 // Error node
236 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
237 messageNode.add(errorNode);
238 }
239 }
240 for (Entry<String, MultiMap<String, TestError>> bag : severityErrorsDeep.entrySet()) {
241 // Group node
242 MultiMap<String, TestError> errorlist = bag.getValue();
243 DefaultMutableTreeNode groupNode = null;
244 if (errorlist.size() > 1) {
245 String nmsg = tr("{0} ({1})", bag.getKey(), errorlist.size());
246 groupNode = new DefaultMutableTreeNode(nmsg);
247 severityNode.add(groupNode);
248 if (oldSelectedRows.contains(bag.getKey())) {
249 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode }));
250 }
251 }
252
253 for (Entry<String, Set<TestError>> msgErrors : errorlist.entrySet()) {
254 // Message node
255 Set<TestError> errs = msgErrors.getValue();
256 String msg;
257 if (groupNode != null) {
258 msg = tr("{0} ({1})", msgErrors.getKey(), errs.size());
259 } else {
260 msg = tr("{0} - {1} ({2})", msgErrors.getKey(), bag.getKey(), errs.size());
261 }
262 DefaultMutableTreeNode messageNode = new DefaultMutableTreeNode(msg);
263 if (groupNode != null) {
264 groupNode.add(messageNode);
265 } else {
266 severityNode.add(messageNode);
267 }
268
269 if (oldSelectedRows.contains(msgErrors.getKey())) {
270 if (groupNode != null) {
271 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, groupNode,
272 messageNode }));
273 } else {
274 expandedPaths.add(new TreePath(new Object[] { rootNode, severityNode, messageNode }));
275 }
276 }
277
278 for (TestError error : errs) {
279 // Error node
280 DefaultMutableTreeNode errorNode = new DefaultMutableTreeNode(error);
281 messageNode.add(errorNode);
282 }
283 }
284 }
285 }
286
287 valTreeModel.setRoot(rootNode);
288 for (TreePath path : expandedPaths) {
289 this.expandPath(path);
290 }
291 }
292
293 /**
294 * Sets the errors list used by a data layer
295 * @param errors The error list that is used by a data layer
296 */
297 public final void setErrorList(List<TestError> errors) {
298 this.errors = errors;
299 if (isVisible()) {
300 buildTree();
301 }
302 }
303
304 /**
305 * Clears the current error list and adds these errors to it
306 * @param newerrors The validation errors
307 */
308 public void setErrors(List<TestError> newerrors) {
309 if (errors == null)
310 return;
311 clearErrors();
312 DataSet ds = Main.main.getCurrentDataSet();
313 for (TestError error : newerrors) {
314 if (!error.getIgnored()) {
315 errors.add(error);
316 if (ds != null) {
317 ds.addDataSetListener(error);
318 }
319 }
320 }
321 if (isVisible()) {
322 buildTree();
323 }
324 }
325
326 /**
327 * Returns the errors of the tree
328 * @return the errors of the tree
329 */
330 public List<TestError> getErrors() {
331 return errors != null ? errors : Collections.<TestError> emptyList();
332 }
333
334 /**
335 * Returns the filter list
336 * @return the list of primitives used for filtering
337 */
338 public Set<OsmPrimitive> getFilter() {
339 return filter;
340 }
341
342 /**
343 * Set the filter list to a set of primitives
344 * @param filter the list of primitives used for filtering
345 */
346 public void setFilter(Set<OsmPrimitive> filter) {
347 if (filter != null && filter.isEmpty()) {
348 this.filter = null;
349 } else {
350 this.filter = filter;
351 }
352 if (isVisible()) {
353 buildTree();
354 }
355 }
356
357 /**
358 * Updates the current errors list
359 */
360 public void resetErrors() {
361 List<TestError> e = new ArrayList<>(errors);
362 setErrors(e);
363 }
364
365 /**
366 * Expands complete tree
367 */
368 @SuppressWarnings("unchecked")
369 public void expandAll() {
370 DefaultMutableTreeNode root = getRoot();
371
372 int row = 0;
373 Enumeration<DefaultMutableTreeNode> children = root.breadthFirstEnumeration();
374 while (children.hasMoreElements()) {
375 children.nextElement();
376 expandRow(row++);
377 }
378 }
379
380 /**
381 * Returns the root node model.
382 * @return The root node model
383 */
384 public DefaultMutableTreeNode getRoot() {
385 return (DefaultMutableTreeNode) valTreeModel.getRoot();
386 }
387
388 /**
389 * Returns a value to check if tree has been rebuild
390 * @return the current counter
391 */
392 public int getUpdateCount() {
393 return updateCount;
394 }
395
396 private void clearErrors() {
397 if (errors != null) {
398 DataSet ds = Main.main.getCurrentDataSet();
399 if (ds != null) {
400 for (TestError e : errors) {
401 ds.removeDataSetListener(e);
402 }
403 }
404 errors.clear();
405 }
406 }
407
408 @Override
409 public void destroy() {
410 clearErrors();
411 }
412}
Note: See TracBrowser for help on using the repository browser.