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

Last change on this file since 4947 was 4707, checked in by simon04, 12 years ago

fix #6747 - missing connection on long ways hard to find

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