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

Last change on this file since 4672 was 4672, checked in by stoecker, 12 years ago

fix #6727 - patch by Kalle Lampila - fix error with deleted objects

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