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

Last change on this file since 5028 was 5028, checked in by jttt, 12 years ago

Add possibility to hide side buttons in toggle dialogs permanently, show actions from buttons in popup menu

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