source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ValidatorDialog.java@ 8419

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

Sonar: various code style cleanup:

  • fix copyright
  • classes that should be final
  • order of javadoc At-clauses
  • unexpected spaces before parenthesis
  • Property svn:eol-style set to native
File size: 23.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.KeyEvent;
8import java.awt.event.MouseEvent;
9import java.io.IOException;
10import java.lang.reflect.InvocationTargetException;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.Enumeration;
14import java.util.HashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Set;
18
19import javax.swing.AbstractAction;
20import javax.swing.JComponent;
21import javax.swing.JOptionPane;
22import javax.swing.JPopupMenu;
23import javax.swing.SwingUtilities;
24import javax.swing.event.TreeSelectionEvent;
25import javax.swing.event.TreeSelectionListener;
26import javax.swing.tree.DefaultMutableTreeNode;
27import javax.swing.tree.TreePath;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.actions.AbstractSelectAction;
31import org.openstreetmap.josm.actions.AutoScaleAction;
32import org.openstreetmap.josm.actions.relation.EditRelationAction;
33import org.openstreetmap.josm.command.Command;
34import org.openstreetmap.josm.data.SelectionChangedListener;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.Node;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.WaySegment;
39import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
40import org.openstreetmap.josm.data.validation.OsmValidator;
41import org.openstreetmap.josm.data.validation.TestError;
42import org.openstreetmap.josm.data.validation.ValidatorVisitor;
43import org.openstreetmap.josm.gui.MapView;
44import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
45import org.openstreetmap.josm.gui.PleaseWaitRunnable;
46import org.openstreetmap.josm.gui.PopupMenuHandler;
47import org.openstreetmap.josm.gui.SideButton;
48import org.openstreetmap.josm.gui.dialogs.validator.ValidatorTreePanel;
49import org.openstreetmap.josm.gui.layer.Layer;
50import org.openstreetmap.josm.gui.layer.OsmDataLayer;
51import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference;
52import org.openstreetmap.josm.gui.progress.ProgressMonitor;
53import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
54import org.openstreetmap.josm.io.OsmTransferException;
55import org.openstreetmap.josm.tools.ImageProvider;
56import org.openstreetmap.josm.tools.InputMapUtils;
57import org.openstreetmap.josm.tools.Shortcut;
58import org.xml.sax.SAXException;
59
60/**
61 * A small tool dialog for displaying the current errors. The selection manager
62 * respects clicks into the selection list. Ctrl-click will remove entries from
63 * the list while single click will make the clicked entry the only selection.
64 *
65 * @author frsantos
66 */
67public class ValidatorDialog extends ToggleDialog implements SelectionChangedListener, LayerChangeListener {
68
69 /** The display tree */
70 public ValidatorTreePanel tree;
71
72 /** The fix button */
73 private SideButton fixButton;
74 /** The ignore button */
75 private SideButton ignoreButton;
76 /** The select button */
77 private SideButton selectButton;
78
79 private final JPopupMenu popupMenu = new JPopupMenu();
80 private final transient PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu);
81
82 /** Last selected element */
83 private DefaultMutableTreeNode lastSelectedNode = null;
84
85 private transient OsmDataLayer linkedLayer;
86
87 /**
88 * Constructor
89 */
90 public ValidatorDialog() {
91 super(tr("Validation Results"), "validator", tr("Open the validation window."),
92 Shortcut.registerShortcut("subwindow:validator", tr("Toggle: {0}", tr("Validation results")),
93 KeyEvent.VK_V, Shortcut.ALT_SHIFT), 150, false, ValidatorPreference.class);
94
95 popupMenuHandler.addAction(Main.main.menu.autoScaleActions.get("problem"));
96 popupMenuHandler.addAction(new EditRelationAction());
97
98 tree = new ValidatorTreePanel();
99 tree.addMouseListener(new MouseEventHandler());
100 addTreeSelectionListener(new SelectionWatch());
101 InputMapUtils.unassignCtrlShiftUpDown(tree, JComponent.WHEN_FOCUSED);
102
103 List<SideButton> buttons = new LinkedList<>();
104
105 selectButton = new SideButton(new AbstractSelectAction() {
106 @Override
107 public void actionPerformed(ActionEvent e) {
108 setSelectedItems();
109 }
110 });
111 InputMapUtils.addEnterAction(tree, selectButton.getAction());
112
113 selectButton.setEnabled(false);
114 buttons.add(selectButton);
115
116 buttons.add(new SideButton(Main.main.validator.validateAction));
117
118 fixButton = new SideButton(new AbstractAction() {
119 {
120 putValue(NAME, tr("Fix"));
121 putValue(SHORT_DESCRIPTION, tr("Fix the selected issue."));
122 putValue(SMALL_ICON, ImageProvider.get("dialogs","fix"));
123 }
124 @Override
125 public void actionPerformed(ActionEvent e) {
126 fixErrors();
127 }
128 });
129 fixButton.setEnabled(false);
130 buttons.add(fixButton);
131
132 if (Main.pref.getBoolean(ValidatorPreference.PREF_USE_IGNORE, true)) {
133 ignoreButton = new SideButton(new AbstractAction() {
134 {
135 putValue(NAME, tr("Ignore"));
136 putValue(SHORT_DESCRIPTION, tr("Ignore the selected issue next time."));
137 putValue(SMALL_ICON, ImageProvider.get("dialogs","fix"));
138 }
139 @Override
140 public void actionPerformed(ActionEvent e) {
141 ignoreErrors();
142 }
143 });
144 ignoreButton.setEnabled(false);
145 buttons.add(ignoreButton);
146 } else {
147 ignoreButton = null;
148 }
149 createLayout(tree, true, buttons);
150 }
151
152 @Override
153 public void showNotify() {
154 DataSet.addSelectionListener(this);
155 DataSet ds = Main.main.getCurrentDataSet();
156 if (ds != null) {
157 updateSelection(ds.getAllSelected());
158 }
159 MapView.addLayerChangeListener(this);
160 Layer activeLayer = Main.map.mapView.getActiveLayer();
161 if (activeLayer != null) {
162 activeLayerChange(null, activeLayer);
163 }
164 }
165
166 @Override
167 public void hideNotify() {
168 MapView.removeLayerChangeListener(this);
169 DataSet.removeSelectionListener(this);
170 }
171
172 @Override
173 public void setVisible(boolean v) {
174 if (tree != null) {
175 tree.setVisible(v);
176 }
177 super.setVisible(v);
178 Main.map.repaint();
179 }
180
181 /**
182 * Fix selected errors
183 */
184 @SuppressWarnings("unchecked")
185 private void fixErrors() {
186 TreePath[] selectionPaths = tree.getSelectionPaths();
187 if (selectionPaths == null)
188 return;
189
190 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>();
191
192 List<TestError> errorsToFix = new LinkedList<>();
193 for (TreePath path : selectionPaths) {
194 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
195 if (node == null) {
196 continue;
197 }
198
199 Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
200 while (children.hasMoreElements()) {
201 DefaultMutableTreeNode childNode = children.nextElement();
202 if (processedNodes.contains(childNode)) {
203 continue;
204 }
205
206 processedNodes.add(childNode);
207 Object nodeInfo = childNode.getUserObject();
208 if (nodeInfo instanceof TestError) {
209 errorsToFix.add((TestError)nodeInfo);
210 }
211 }
212 }
213
214 // run fix task asynchronously
215 //
216 FixTask fixTask = new FixTask(errorsToFix);
217 Main.worker.submit(fixTask);
218 }
219
220 /**
221 * Set selected errors to ignore state
222 */
223 @SuppressWarnings("unchecked")
224 private void ignoreErrors() {
225 int asked = JOptionPane.DEFAULT_OPTION;
226 boolean changed = false;
227 TreePath[] selectionPaths = tree.getSelectionPaths();
228 if (selectionPaths == null)
229 return;
230
231 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>();
232 for (TreePath path : selectionPaths) {
233 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
234 if (node == null) {
235 continue;
236 }
237
238 Object mainNodeInfo = node.getUserObject();
239 if (!(mainNodeInfo instanceof TestError)) {
240 Set<String> state = new HashSet<>();
241 // ask if the whole set should be ignored
242 if (asked == JOptionPane.DEFAULT_OPTION) {
243 String[] a = new String[] { tr("Whole group"), tr("Single elements"), tr("Nothing") };
244 asked = JOptionPane.showOptionDialog(Main.parent, tr("Ignore whole group or individual elements?"),
245 tr("Ignoring elements"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
246 a, a[1]);
247 }
248 if (asked == JOptionPane.YES_NO_OPTION) {
249 Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
250 while (children.hasMoreElements()) {
251 DefaultMutableTreeNode childNode = children.nextElement();
252 if (processedNodes.contains(childNode)) {
253 continue;
254 }
255
256 processedNodes.add(childNode);
257 Object nodeInfo = childNode.getUserObject();
258 if (nodeInfo instanceof TestError) {
259 TestError err = (TestError) nodeInfo;
260 err.setIgnored(true);
261 changed = true;
262 state.add(node.getDepth() == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup());
263 }
264 }
265 for (String s : state) {
266 OsmValidator.addIgnoredError(s);
267 }
268 continue;
269 } else if (asked == JOptionPane.CANCEL_OPTION || asked == JOptionPane.CLOSED_OPTION) {
270 continue;
271 }
272 }
273
274 Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
275 while (children.hasMoreElements()) {
276 DefaultMutableTreeNode childNode = children.nextElement();
277 if (processedNodes.contains(childNode)) {
278 continue;
279 }
280
281 processedNodes.add(childNode);
282 Object nodeInfo = childNode.getUserObject();
283 if (nodeInfo instanceof TestError) {
284 TestError error = (TestError) nodeInfo;
285 String state = error.getIgnoreState();
286 if (state != null) {
287 OsmValidator.addIgnoredError(state);
288 }
289 changed = true;
290 error.setIgnored(true);
291 }
292 }
293 }
294 if (changed) {
295 tree.resetErrors();
296 OsmValidator.saveIgnoredErrors();
297 Main.map.repaint();
298 }
299 }
300
301 /**
302 * Sets the selection of the map to the current selected items.
303 */
304 @SuppressWarnings("unchecked")
305 private void setSelectedItems() {
306 if (tree == null)
307 return;
308
309 Collection<OsmPrimitive> sel = new HashSet<>(40);
310
311 TreePath[] selectedPaths = tree.getSelectionPaths();
312 if (selectedPaths == null)
313 return;
314
315 for (TreePath path : selectedPaths) {
316 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
317 Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
318 while (children.hasMoreElements()) {
319 DefaultMutableTreeNode childNode = children.nextElement();
320 Object nodeInfo = childNode.getUserObject();
321 if (nodeInfo instanceof TestError) {
322 TestError error = (TestError) nodeInfo;
323 sel.addAll(error.getSelectablePrimitives());
324 }
325 }
326 }
327 DataSet ds = Main.main.getCurrentDataSet();
328 if (ds != null) {
329 ds.setSelected(sel);
330 }
331 }
332
333 /**
334 * Checks for fixes in selected element and, if needed, adds to the sel
335 * parameter all selected elements
336 *
337 * @param sel
338 * The collection where to add all selected elements
339 * @param addSelected
340 * if true, add all selected elements to collection
341 * @return whether the selected elements has any fix
342 */
343 @SuppressWarnings("unchecked")
344 private boolean setSelection(Collection<OsmPrimitive> sel, boolean addSelected) {
345 boolean hasFixes = false;
346
347 DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
348 if (lastSelectedNode != null && !lastSelectedNode.equals(node)) {
349 Enumeration<DefaultMutableTreeNode> children = lastSelectedNode.breadthFirstEnumeration();
350 while (children.hasMoreElements()) {
351 DefaultMutableTreeNode childNode = children.nextElement();
352 Object nodeInfo = childNode.getUserObject();
353 if (nodeInfo instanceof TestError) {
354 TestError error = (TestError) nodeInfo;
355 error.setSelected(false);
356 }
357 }
358 }
359
360 lastSelectedNode = node;
361 if (node == null)
362 return hasFixes;
363
364 Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration();
365 while (children.hasMoreElements()) {
366 DefaultMutableTreeNode childNode = children.nextElement();
367 Object nodeInfo = childNode.getUserObject();
368 if (nodeInfo instanceof TestError) {
369 TestError error = (TestError) nodeInfo;
370 error.setSelected(true);
371
372 hasFixes = hasFixes || error.isFixable();
373 if (addSelected) {
374 sel.addAll(error.getSelectablePrimitives());
375 }
376 }
377 }
378 selectButton.setEnabled(true);
379 if (ignoreButton != null) {
380 ignoreButton.setEnabled(true);
381 }
382
383 return hasFixes;
384 }
385
386 @Override
387 public void activeLayerChange(Layer oldLayer, Layer newLayer) {
388 if (newLayer instanceof OsmDataLayer) {
389 linkedLayer = (OsmDataLayer)newLayer;
390 tree.setErrorList(linkedLayer.validationErrors);
391 }
392 }
393
394 @Override
395 public void layerAdded(Layer newLayer) {
396 // Do nothing
397 }
398
399 @Override
400 public void layerRemoved(Layer oldLayer) {
401 if (oldLayer == linkedLayer) {
402 tree.setErrorList(new ArrayList<TestError>());
403 }
404 }
405
406 /**
407 * Add a tree selection listener to the validator tree.
408 * @param listener the TreeSelectionListener
409 * @since 5958
410 */
411 public void addTreeSelectionListener(TreeSelectionListener listener) {
412 tree.addTreeSelectionListener(listener);
413 }
414
415 /**
416 * Remove the given tree selection listener from the validator tree.
417 * @param listener the TreeSelectionListener
418 * @since 5958
419 */
420 public void removeTreeSelectionListener(TreeSelectionListener listener) {
421 tree.removeTreeSelectionListener(listener);
422 }
423
424 /**
425 * Replies the popup menu handler.
426 * @return The popup menu handler
427 * @since 5958
428 */
429 public PopupMenuHandler getPopupMenuHandler() {
430 return popupMenuHandler;
431 }
432
433 /**
434 * Replies the currently selected error, or {@code null}.
435 * @return The selected error, if any.
436 * @since 5958
437 */
438 public TestError getSelectedError() {
439 Object comp = tree.getLastSelectedPathComponent();
440 if (comp instanceof DefaultMutableTreeNode) {
441 Object object = ((DefaultMutableTreeNode)comp).getUserObject();
442 if (object instanceof TestError) {
443 return (TestError) object;
444 }
445 }
446 return null;
447 }
448
449 /**
450 * Watches for double clicks and launches the popup menu.
451 */
452 class MouseEventHandler extends PopupMenuLauncher {
453
454 public MouseEventHandler() {
455 super(popupMenu);
456 }
457
458 @Override
459 public void mouseClicked(MouseEvent e) {
460 fixButton.setEnabled(false);
461 if (ignoreButton != null) {
462 ignoreButton.setEnabled(false);
463 }
464 selectButton.setEnabled(false);
465
466 boolean isDblClick = isDoubleClick(e);
467
468 Collection<OsmPrimitive> sel = isDblClick ? new HashSet<OsmPrimitive>(40) : null;
469
470 boolean hasFixes = setSelection(sel, isDblClick);
471 fixButton.setEnabled(hasFixes);
472
473 if (isDblClick) {
474 Main.main.getCurrentDataSet().setSelected(sel);
475 if (Main.pref.getBoolean("validator.autozoom", false)) {
476 AutoScaleAction.zoomTo(sel);
477 }
478 }
479 }
480
481 @Override public void launch(MouseEvent e) {
482 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
483 if (selPath == null)
484 return;
485 DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getPathComponent(selPath.getPathCount() - 1);
486 if (!(node.getUserObject() instanceof TestError))
487 return;
488 super.launch(e);
489 }
490
491 }
492
493 /**
494 * Watches for tree selection.
495 */
496 public class SelectionWatch implements TreeSelectionListener {
497 @Override
498 public void valueChanged(TreeSelectionEvent e) {
499 fixButton.setEnabled(false);
500 if (ignoreButton != null) {
501 ignoreButton.setEnabled(false);
502 }
503 selectButton.setEnabled(false);
504
505 Collection<OsmPrimitive> sel = new HashSet<>();
506 boolean hasFixes = setSelection(sel, true);
507 fixButton.setEnabled(hasFixes);
508 popupMenuHandler.setPrimitives(sel);
509 if (Main.map != null) {
510 Main.map.repaint();
511 }
512 }
513 }
514
515 public static class ValidatorBoundingXYVisitor extends BoundingXYVisitor implements ValidatorVisitor {
516 @Override
517 public void visit(OsmPrimitive p) {
518 if (p.isUsable()) {
519 p.accept(this);
520 }
521 }
522
523 @Override
524 public void visit(WaySegment ws) {
525 if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount())
526 return;
527 visit(ws.way.getNodes().get(ws.lowerIndex));
528 visit(ws.way.getNodes().get(ws.lowerIndex + 1));
529 }
530
531 @Override
532 public void visit(List<Node> nodes) {
533 for (Node n: nodes) {
534 visit(n);
535 }
536 }
537
538 @Override
539 public void visit(TestError error) {
540 if (error != null) {
541 error.visitHighlighted(this);
542 }
543 }
544 }
545
546 public void updateSelection(Collection<? extends OsmPrimitive> newSelection) {
547 if (!Main.pref.getBoolean(ValidatorPreference.PREF_FILTER_BY_SELECTION, false))
548 return;
549 if (newSelection.isEmpty()) {
550 tree.setFilter(null);
551 }
552 tree.setFilter(new HashSet<>(newSelection));
553 }
554
555 @Override
556 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
557 updateSelection(newSelection);
558 }
559
560 /**
561 * Task for fixing a collection of {@link TestError}s. Can be run asynchronously.
562 *
563 *
564 */
565 class FixTask extends PleaseWaitRunnable {
566 private Collection<TestError> testErrors;
567 private boolean canceled;
568
569 public FixTask(Collection<TestError> testErrors) {
570 super(tr("Fixing errors ..."), false /* don't ignore exceptions */);
571 this.testErrors = testErrors == null ? new ArrayList<TestError>(): testErrors;
572 }
573
574 @Override
575 protected void cancel() {
576 this.canceled = true;
577 }
578
579 @Override
580 protected void finish() {
581 // do nothing
582 }
583
584 protected void fixError(TestError error) throws InterruptedException, InvocationTargetException {
585 if (error.isFixable()) {
586 final Command fixCommand = error.getFix();
587 if (fixCommand != null) {
588 SwingUtilities.invokeAndWait(new Runnable() {
589 @Override
590 public void run() {
591 Main.main.undoRedo.addNoRedraw(fixCommand);
592 }
593 });
594 }
595 // It is wanted to ignore an error if it said fixable, even if fixCommand was null
596 // This is to fix #5764 and #5773: a delete command, for example, may be null if all concerned primitives have already been deleted
597 error.setIgnored(true);
598 }
599 }
600
601 @Override
602 protected void realRun() throws SAXException, IOException,
603 OsmTransferException {
604 ProgressMonitor monitor = getProgressMonitor();
605 try {
606 monitor.setTicksCount(testErrors.size());
607 int i=0;
608 SwingUtilities.invokeAndWait(new Runnable() {
609 @Override
610 public void run() {
611 Main.main.getCurrentDataSet().beginUpdate();
612 }
613 });
614 try {
615 for (TestError error: testErrors) {
616 i++;
617 monitor.subTask(tr("Fixing ({0}/{1}): ''{2}''", i, testErrors.size(),error.getMessage()));
618 if (this.canceled)
619 return;
620 fixError(error);
621 monitor.worked(1);
622 }
623 } finally {
624 SwingUtilities.invokeAndWait(new Runnable() {
625 @Override
626 public void run() {
627 Main.main.getCurrentDataSet().endUpdate();
628 }
629 });
630 }
631 monitor.subTask(tr("Updating map ..."));
632 SwingUtilities.invokeAndWait(new Runnable() {
633 @Override
634 public void run() {
635 Main.main.undoRedo.afterAdd();
636 Main.map.repaint();
637 tree.resetErrors();
638 Main.main.getCurrentDataSet().fireSelectionChanged();
639 }
640 });
641 } catch(InterruptedException | InvocationTargetException e) {
642 // FIXME: signature of realRun should have a generic checked exception we
643 // could throw here
644 throw new RuntimeException(e);
645 } finally {
646 monitor.finishTask();
647 }
648 }
649 }
650}
Note: See TracBrowser for help on using the repository browser.