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

Last change on this file since 3669 was 3669, checked in by bastiK, 14 years ago

add validator plugin to josm core. Original author: Francisco R. Santos (frsantos); major contributions by bilbo, daeron, delta_foxtrot, imi, jttt, jrreid, gabriel, guggis, pieren, rrankin, skela, stoecker, stotz and others

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