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

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

see #13001 - replace calls to Main.main.getCurrentDataSet() by Main.getLayerManager().getEditDataSet()

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