001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.event.ActionEvent; 007import java.awt.event.KeyEvent; 008import java.awt.event.MouseEvent; 009import java.io.IOException; 010import java.lang.reflect.InvocationTargetException; 011import java.util.ArrayList; 012import java.util.Collection; 013import java.util.Enumeration; 014import java.util.HashSet; 015import java.util.LinkedList; 016import java.util.List; 017import java.util.Set; 018import java.util.concurrent.atomic.AtomicBoolean; 019 020import javax.swing.AbstractAction; 021import javax.swing.Action; 022import javax.swing.JComponent; 023import javax.swing.JOptionPane; 024import javax.swing.JPopupMenu; 025import javax.swing.SwingUtilities; 026import javax.swing.event.TreeSelectionEvent; 027import javax.swing.event.TreeSelectionListener; 028import javax.swing.tree.DefaultMutableTreeNode; 029import javax.swing.tree.TreePath; 030 031import org.openstreetmap.josm.actions.AbstractSelectAction; 032import org.openstreetmap.josm.actions.AutoScaleAction; 033import org.openstreetmap.josm.actions.JosmAction; 034import org.openstreetmap.josm.actions.ValidateAction; 035import org.openstreetmap.josm.actions.relation.EditRelationAction; 036import org.openstreetmap.josm.command.Command; 037import org.openstreetmap.josm.command.SequenceCommand; 038import org.openstreetmap.josm.data.UndoRedoHandler; 039import org.openstreetmap.josm.data.osm.DataSelectionListener; 040import org.openstreetmap.josm.data.osm.DataSet; 041import org.openstreetmap.josm.data.osm.Node; 042import org.openstreetmap.josm.data.osm.OsmPrimitive; 043import org.openstreetmap.josm.data.osm.WaySegment; 044import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent; 045import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter; 046import org.openstreetmap.josm.data.osm.event.DatasetEventManager; 047import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode; 048import org.openstreetmap.josm.data.osm.event.SelectionEventManager; 049import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 050import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 051import org.openstreetmap.josm.data.preferences.sources.ValidatorPrefHelper; 052import org.openstreetmap.josm.data.validation.OsmValidator; 053import org.openstreetmap.josm.data.validation.Severity; 054import org.openstreetmap.josm.data.validation.TestError; 055import org.openstreetmap.josm.data.validation.ValidatorVisitor; 056import org.openstreetmap.josm.gui.MainApplication; 057import org.openstreetmap.josm.gui.PleaseWaitRunnable; 058import org.openstreetmap.josm.gui.PopupMenuHandler; 059import org.openstreetmap.josm.gui.SideButton; 060import org.openstreetmap.josm.gui.dialogs.validator.ValidatorTreePanel; 061import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeEvent; 062import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener; 063import org.openstreetmap.josm.gui.layer.OsmDataLayer; 064import org.openstreetmap.josm.gui.layer.ValidatorLayer; 065import org.openstreetmap.josm.gui.preferences.validator.ValidatorPreference; 066import org.openstreetmap.josm.gui.progress.ProgressMonitor; 067import org.openstreetmap.josm.gui.util.GuiHelper; 068import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 069import org.openstreetmap.josm.io.OsmTransferException; 070import org.openstreetmap.josm.spi.preferences.Config; 071import org.openstreetmap.josm.tools.ImageProvider; 072import org.openstreetmap.josm.tools.InputMapUtils; 073import org.openstreetmap.josm.tools.JosmRuntimeException; 074import org.openstreetmap.josm.tools.Pair; 075import org.openstreetmap.josm.tools.Shortcut; 076import org.xml.sax.SAXException; 077 078/** 079 * A small tool dialog for displaying the current errors. The selection manager 080 * respects clicks into the selection list. Ctrl-click will remove entries from 081 * the list while single click will make the clicked entry the only selection. 082 * 083 * @author frsantos 084 */ 085public class ValidatorDialog extends ToggleDialog 086 implements DataSelectionListener, ActiveLayerChangeListener, DataSetListenerAdapter.Listener { 087 088 /** The display tree */ 089 public final ValidatorTreePanel tree; 090 091 /** The validate action */ 092 public static final ValidateAction validateAction = new ValidateAction(); 093 094 /** The fix action */ 095 private final transient Action fixAction; 096 /** The ignore action */ 097 private final transient Action ignoreAction; 098 /** The ignore-list management action */ 099 private final transient Action ignorelistManagementAction; 100 /** The select action */ 101 private final transient Action selectAction; 102 /** The lookup action */ 103 private final transient LookupAction lookupAction; 104 private final transient JosmAction ignoreForNowAction; 105 106 private final JPopupMenu popupMenu = new JPopupMenu(); 107 private final transient PopupMenuHandler popupMenuHandler = new PopupMenuHandler(popupMenu); 108 private final transient DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this); 109 110 /** Last selected element */ 111 private DefaultMutableTreeNode lastSelectedNode; 112 113 /** 114 * Constructor 115 */ 116 public ValidatorDialog() { 117 super(tr("Validation Results"), "validator", tr("Open the validation window."), 118 Shortcut.registerShortcut("subwindow:validator", tr("Windows: {0}", tr("Validation Results")), 119 KeyEvent.VK_V, Shortcut.ALT_SHIFT), 150, false, ValidatorPreference.class); 120 121 tree = new ValidatorTreePanel(); 122 tree.addMouseListener(new MouseEventHandler()); 123 addTreeSelectionListener(new SelectionWatch()); 124 InputMapUtils.unassignCtrlShiftUpDown(tree, JComponent.WHEN_FOCUSED); 125 126 ignoreForNowAction = new JosmAction(tr("Ignore for now"), "dialogs/delete", 127 tr("Ignore and remove from tree."), Shortcut.registerShortcut("tools:validate:ignore-for-now", 128 tr("Ignore and remove from tree."), KeyEvent.VK_MINUS, Shortcut.SHIFT), 129 false, false) { 130 131 @Override 132 public void actionPerformed(ActionEvent e) { 133 TestError error = getSelectedError(); 134 if (error != null) { 135 error.setIgnored(true); 136 tree.resetErrors(); 137 invalidateValidatorLayers(); 138 } 139 } 140 }; 141 142 popupMenuHandler.addAction(MainApplication.getMenu().autoScaleActions.get(AutoScaleAction.AutoScaleMode.PROBLEM)); 143 popupMenuHandler.addAction(new EditRelationAction()); 144 popupMenuHandler.addAction(ignoreForNowAction); 145 146 List<SideButton> buttons = new LinkedList<>(); 147 148 selectAction = new AbstractSelectAction() { 149 @Override 150 public void actionPerformed(ActionEvent e) { 151 setSelectedItems(); 152 } 153 }; 154 selectAction.setEnabled(false); 155 InputMapUtils.addEnterAction(tree, selectAction); 156 buttons.add(new SideButton(selectAction)); 157 158 lookupAction = new LookupAction(); 159 buttons.add(new SideButton(lookupAction)); 160 161 buttons.add(new SideButton(validateAction)); 162 163 fixAction = new AbstractAction() { 164 { 165 putValue(NAME, tr("Fix")); 166 putValue(SHORT_DESCRIPTION, tr("Fix the selected issue.")); 167 new ImageProvider("dialogs", "fix").getResource().attachImageIcon(this, true); 168 } 169 @Override 170 public void actionPerformed(ActionEvent e) { 171 fixErrors(); 172 } 173 }; 174 fixAction.setEnabled(false); 175 buttons.add(new SideButton(fixAction)); 176 177 if (ValidatorPrefHelper.PREF_USE_IGNORE.get()) { 178 ignoreAction = new AbstractAction() { 179 { 180 putValue(NAME, tr("Ignore")); 181 putValue(SHORT_DESCRIPTION, tr("Ignore the selected issue next time.")); 182 new ImageProvider("dialogs", "fix").getResource().attachImageIcon(this, true); 183 } 184 @Override 185 public void actionPerformed(ActionEvent e) { 186 ignoreErrors(); 187 } 188 }; 189 ignoreAction.setEnabled(false); 190 buttons.add(new SideButton(ignoreAction)); 191 192 ignorelistManagementAction = new IgnorelistManagementAction(); 193 buttons.add(new SideButton(ignorelistManagementAction)); 194 } else { 195 ignoreAction = null; 196 ignorelistManagementAction = null; 197 } 198 199 createLayout(tree, true, buttons); 200 } 201 202 /** 203 * The action to manage the ignore list. 204 */ 205 static class IgnorelistManagementAction extends AbstractAction { 206 IgnorelistManagementAction() { 207 putValue(NAME, tr("Manage Ignore")); 208 putValue(SHORT_DESCRIPTION, tr("Manage the ignore list")); 209 new ImageProvider("dialogs", "fix").getResource().attachImageIcon(this, true); 210 } 211 212 @Override 213 public void actionPerformed(ActionEvent e) { 214 new ValidatorListManagementDialog("Ignore"); 215 } 216 } 217 218 /** 219 * The action to lookup the selection in the error tree. 220 */ 221 class LookupAction extends AbstractAction { 222 LookupAction() { 223 putValue(NAME, tr("Lookup")); 224 putValue(SHORT_DESCRIPTION, tr("Looks up the selected primitives in the error list.")); 225 new ImageProvider("dialogs", "search").getResource().attachImageIcon(this, true); 226 updateEnabledState(); 227 } 228 229 @Override 230 public void actionPerformed(ActionEvent e) { 231 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 232 if (ds == null) { 233 return; 234 } 235 tree.selectRelatedErrors(ds.getSelected()); 236 } 237 238 void updateEnabledState() { 239 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 240 if (ds == null || ds.selectionEmpty()) { 241 setEnabled(false); 242 } else { 243 boolean found = tree.getErrors().stream() 244 .anyMatch(e -> e.getPrimitives().stream().anyMatch(OsmPrimitive::isSelected)); 245 setEnabled(found); 246 } 247 } 248 } 249 250 @Override 251 public void showNotify() { 252 DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED); 253 SelectionEventManager.getInstance().addSelectionListener(this); 254 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 255 if (ds != null) { 256 updateSelection(ds.getAllSelected()); 257 } 258 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(this); 259 260 } 261 262 @Override 263 public void hideNotify() { 264 DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter); 265 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); 266 SelectionEventManager.getInstance().removeSelectionListener(this); 267 } 268 269 @Override 270 public void setVisible(boolean v) { 271 if (tree != null) { 272 tree.setVisible(v); 273 } 274 super.setVisible(v); 275 } 276 277 /** 278 * Fix selected errors 279 */ 280 private void fixErrors() { 281 TreePath[] selectionPaths = tree.getSelectionPaths(); 282 if (selectionPaths == null) 283 return; 284 285 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>(); 286 287 List<TestError> errorsToFix = new LinkedList<>(); 288 for (TreePath path : selectionPaths) { 289 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 290 if (node != null) { 291 ValidatorTreePanel.visitTestErrors(node, errorsToFix::add, processedNodes); 292 } 293 } 294 295 // run fix task asynchronously 296 MainApplication.worker.submit(new FixTask(errorsToFix)); 297 } 298 299 /** 300 * Set selected errors to ignore state 301 */ 302 private void ignoreErrors() { 303 int asked = JOptionPane.DEFAULT_OPTION; 304 AtomicBoolean changed = new AtomicBoolean(); 305 TreePath[] selectionPaths = tree.getSelectionPaths(); 306 if (selectionPaths == null) 307 return; 308 309 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>(); 310 for (TreePath path : selectionPaths) { 311 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 312 if (node == null) { 313 continue; 314 } 315 316 Object mainNodeInfo = node.getUserObject(); 317 final int depth = node.getDepth(); 318 if (!(mainNodeInfo instanceof TestError)) { 319 Set<Pair<String, String>> state = new HashSet<>(); 320 // ask if the whole set should be ignored 321 if (asked == JOptionPane.DEFAULT_OPTION) { 322 String[] a = {tr("Whole group"), tr("Single elements"), tr("Nothing")}; 323 asked = JOptionPane.showOptionDialog(MainApplication.getMainFrame(), tr("Ignore whole group or individual elements?"), 324 tr("Ignoring elements"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, 325 a, a[1]); 326 } 327 if (asked == JOptionPane.YES_NO_OPTION) { 328 ValidatorTreePanel.visitTestErrors(node, err -> { 329 err.setIgnored(true); 330 changed.set(true); 331 state.add(new Pair<>(depth == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup(), err.getMessage())); 332 }, processedNodes); 333 for (Pair<String, String> s : state) { 334 OsmValidator.addIgnoredError(s.a, s.b); 335 } 336 continue; 337 } else if (asked == JOptionPane.CANCEL_OPTION || asked == JOptionPane.CLOSED_OPTION) { 338 continue; 339 } 340 } 341 342 ValidatorTreePanel.visitTestErrors(node, error -> { 343 String state = error.getIgnoreState(); 344 if (state != null) { 345 OsmValidator.addIgnoredError(state, error.getMessage()); 346 } 347 changed.set(true); 348 error.setIgnored(true); 349 }, processedNodes); 350 } 351 if (changed.get()) { 352 tree.resetErrors(); 353 OsmValidator.saveIgnoredErrors(); 354 invalidateValidatorLayers(); 355 } 356 } 357 358 /** 359 * Sets the selection of the map to the current selected items. 360 */ 361 private void setSelectedItems() { 362 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 363 if (tree == null || ds == null) 364 return; 365 366 TreePath[] selectedPaths = tree.getSelectionPaths(); 367 if (selectedPaths == null) 368 return; 369 370 Collection<OsmPrimitive> sel = new HashSet<>(40); 371 for (TreePath path : selectedPaths) { 372 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 373 Enumeration<?> children = node.breadthFirstEnumeration(); 374 while (children.hasMoreElements()) { 375 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) children.nextElement(); 376 Object nodeInfo = childNode.getUserObject(); 377 if (nodeInfo instanceof TestError) { 378 TestError error = (TestError) nodeInfo; 379 error.getPrimitives().stream() 380 .filter(OsmPrimitive::isSelectable) 381 .forEach(sel::add); 382 } 383 } 384 } 385 ds.setSelected(sel); 386 } 387 388 /** 389 * Checks for fixes in selected element and, if needed, adds to the sel 390 * parameter all selected elements 391 * 392 * @param sel 393 * The collection where to add all selected elements 394 * @param addSelected 395 * if true, add all selected elements to collection 396 * @return whether the selected elements has any fix 397 */ 398 private boolean setSelection(Collection<OsmPrimitive> sel, boolean addSelected) { 399 AtomicBoolean hasFixes = new AtomicBoolean(); 400 401 DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); 402 if (lastSelectedNode != null && !lastSelectedNode.equals(node)) { 403 ValidatorTreePanel.visitTestErrors(lastSelectedNode, error -> error.setSelected(false)); 404 } 405 406 lastSelectedNode = node; 407 if (node != null) { 408 ValidatorTreePanel.visitTestErrors(node, error -> { 409 error.setSelected(true); 410 411 hasFixes.set(hasFixes.get() || error.isFixable()); 412 if (addSelected) { 413 error.getPrimitives().stream() 414 .filter(OsmPrimitive::isSelectable) 415 .forEach(sel::add); 416 } 417 }); 418 selectAction.setEnabled(true); 419 if (ignoreAction != null) { 420 ignoreAction.setEnabled(!(node.getUserObject() instanceof Severity)); 421 } 422 } 423 424 return hasFixes.get(); 425 } 426 427 @Override 428 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 429 OsmDataLayer editLayer = e.getSource().getEditLayer(); 430 if (editLayer == null) { 431 tree.setErrorList(new ArrayList<TestError>()); 432 } else { 433 tree.setErrorList(editLayer.validationErrors); 434 } 435 } 436 437 /** 438 * Add a tree selection listener to the validator tree. 439 * @param listener the TreeSelectionListener 440 * @since 5958 441 */ 442 public void addTreeSelectionListener(TreeSelectionListener listener) { 443 tree.addTreeSelectionListener(listener); 444 } 445 446 /** 447 * Remove the given tree selection listener from the validator tree. 448 * @param listener the TreeSelectionListener 449 * @since 5958 450 */ 451 public void removeTreeSelectionListener(TreeSelectionListener listener) { 452 tree.removeTreeSelectionListener(listener); 453 } 454 455 /** 456 * Replies the popup menu handler. 457 * @return The popup menu handler 458 * @since 5958 459 */ 460 public PopupMenuHandler getPopupMenuHandler() { 461 return popupMenuHandler; 462 } 463 464 /** 465 * Replies the currently selected error, or {@code null}. 466 * @return The selected error, if any. 467 * @since 5958 468 */ 469 public TestError getSelectedError() { 470 Object comp = tree.getLastSelectedPathComponent(); 471 if (comp instanceof DefaultMutableTreeNode) { 472 Object object = ((DefaultMutableTreeNode) comp).getUserObject(); 473 if (object instanceof TestError) { 474 return (TestError) object; 475 } 476 } 477 return null; 478 } 479 480 /** 481 * Watches for double clicks and launches the popup menu. 482 */ 483 class MouseEventHandler extends PopupMenuLauncher { 484 485 MouseEventHandler() { 486 super(popupMenu); 487 } 488 489 @Override 490 public void mouseClicked(MouseEvent e) { 491 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 492 if (selPath == null) { 493 tree.clearSelection(); 494 } 495 496 fixAction.setEnabled(false); 497 if (ignoreAction != null) { 498 ignoreAction.setEnabled(false); 499 } 500 selectAction.setEnabled(false); 501 502 boolean isDblClick = isDoubleClick(e); 503 504 Collection<OsmPrimitive> sel = isDblClick ? new HashSet<>(40) : null; 505 506 boolean hasFixes = setSelection(sel, isDblClick); 507 fixAction.setEnabled(hasFixes); 508 509 if (isDblClick) { 510 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 511 if (ds != null) { 512 ds.setSelected(sel); 513 } 514 if (Config.getPref().getBoolean("validator.autozoom", false)) { 515 AutoScaleAction.zoomTo(sel); 516 } 517 } 518 } 519 520 @Override 521 public void launch(MouseEvent e) { 522 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 523 if (selPath == null) 524 return; 525 DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getPathComponent(selPath.getPathCount() - 1); 526 if (!(node.getUserObject() instanceof TestError)) 527 return; 528 super.launch(e); 529 } 530 } 531 532 /** 533 * Watches for tree selection. 534 */ 535 public class SelectionWatch implements TreeSelectionListener { 536 @Override 537 public void valueChanged(TreeSelectionEvent e) { 538 if (ignoreAction != null) { 539 ignoreAction.setEnabled(false); 540 } 541 selectAction.setEnabled(false); 542 543 Collection<OsmPrimitive> sel = new HashSet<>(); 544 boolean hasFixes = setSelection(sel, true); 545 fixAction.setEnabled(hasFixes); 546 popupMenuHandler.setPrimitives(sel); 547 invalidateValidatorLayers(); 548 } 549 } 550 551 /** 552 * A visitor that is used to compute the bounds of an error. 553 */ 554 public static class ValidatorBoundingXYVisitor extends BoundingXYVisitor implements ValidatorVisitor { 555 @Override 556 public void visit(OsmPrimitive p) { 557 if (p.isUsable()) { 558 p.accept((PrimitiveVisitor) this); 559 } 560 } 561 562 @Override 563 public void visit(WaySegment ws) { 564 if (ws.getLowerIndex() < 0 || ws.getUpperIndex() >= ws.getWay().getNodesCount()) 565 return; 566 visit(ws.getFirstNode()); 567 visit(ws.getSecondNode()); 568 } 569 570 @Override 571 public void visit(List<Node> nodes) { 572 for (Node n: nodes) { 573 visit(n); 574 } 575 } 576 577 @Override 578 public void visit(TestError error) { 579 if (error != null) { 580 error.visitHighlighted(this); 581 } 582 } 583 } 584 585 /** 586 * Called when the selection was changed to update the list of displayed errors 587 * @param newSelection The new selection 588 */ 589 public void updateSelection(Collection<? extends OsmPrimitive> newSelection) { 590 if (!Config.getPref().getBoolean(ValidatorPrefHelper.PREF_FILTER_BY_SELECTION, false)) 591 return; 592 if (newSelection.isEmpty()) { 593 tree.setFilter(null); 594 } 595 tree.setFilter(new HashSet<>(newSelection)); 596 } 597 598 @Override 599 public void selectionChanged(SelectionChangeEvent event) { 600 updateSelection(event.getSelection()); 601 lookupAction.updateEnabledState(); 602 } 603 604 /** 605 * Task for fixing a collection of {@link TestError}s. Can be run asynchronously. 606 */ 607 class FixTask extends PleaseWaitRunnable { 608 private final Collection<TestError> testErrors; 609 private final List<Command> fixCommands = new ArrayList<>(); 610 private boolean canceled; 611 612 FixTask(Collection<TestError> testErrors) { 613 super(tr("Fixing errors ..."), false /* don't ignore exceptions */); 614 this.testErrors = testErrors == null ? new ArrayList<>() : testErrors; 615 } 616 617 @Override 618 protected void cancel() { 619 this.canceled = true; 620 } 621 622 @Override 623 protected void finish() { 624 // do nothing 625 } 626 627 protected void fixError(TestError error) throws InterruptedException, InvocationTargetException { 628 if (error.isFixable()) { 629 if (error.getPrimitives().stream().noneMatch(p -> p.isDeleted() || p.getDataSet() == null)) { 630 final Command fixCommand = error.getFix(); 631 if (fixCommand != null) { 632 SwingUtilities.invokeAndWait(fixCommand::executeCommand); 633 fixCommands.add(fixCommand); 634 } 635 } 636 // It is wanted to ignore an error if it said fixable, even if fixCommand was null 637 // This is to fix #5764 and #5773: 638 // a delete command, for example, may be null if all concerned primitives have already been deleted 639 error.setIgnored(true); 640 } 641 } 642 643 @Override 644 protected void realRun() throws SAXException, IOException, OsmTransferException { 645 ProgressMonitor monitor = getProgressMonitor(); 646 try { 647 monitor.setTicksCount(testErrors.size()); 648 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 649 int i = 0; 650 SwingUtilities.invokeAndWait(ds::beginUpdate); 651 tree.setResetScheduled(); 652 try { 653 for (TestError error: testErrors) { 654 i++; 655 monitor.subTask(tr("Fixing ({0}/{1}): ''{2}''", i, testErrors.size(), error.getMessage())); 656 if (this.canceled) 657 return; 658 fixError(error); 659 monitor.worked(1); 660 } 661 } finally { 662 SwingUtilities.invokeAndWait(ds::endUpdate); 663 } 664 monitor.subTask(tr("Updating map ...")); 665 SwingUtilities.invokeAndWait(() -> { 666 if (!fixCommands.isEmpty()) { 667 UndoRedoHandler.getInstance().add( 668 fixCommands.size() > 1 ? new AutofixCommand(fixCommands) : fixCommands.get(0), false); 669 } 670 invalidateValidatorLayers(); 671 }); 672 } catch (InterruptedException e) { 673 tryUndo(); 674 throw new JosmRuntimeException(e); 675 } catch (InvocationTargetException e) { 676 // FIXME: signature of realRun should have a generic checked exception we could throw here 677 throw new JosmRuntimeException(e); 678 } finally { 679 if (monitor.isCanceled()) { 680 tryUndo(); 681 } 682 GuiHelper.runInEDTAndWait(tree::resetErrors); 683 monitor.finishTask(); 684 } 685 } 686 687 /** 688 * Undo commands as they were not yet added to the UndoRedo Handler 689 */ 690 private void tryUndo() { 691 MainApplication.getLayerManager().getActiveDataSet().update(() -> { 692 for (int i = fixCommands.size() - 1; i >= 0; i--) { 693 fixCommands.get(i).undoCommand(); 694 } 695 }); 696 } 697 } 698 699 private static void invalidateValidatorLayers() { 700 MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); 701 } 702 703 @Override 704 public void processDatasetEvent(AbstractDatasetChangedEvent event) { 705 validateAction.updateEnabledState(); 706 lookupAction.updateEnabledState(); 707 } 708 709 private static class AutofixCommand extends SequenceCommand { 710 AutofixCommand(Collection<Command> sequenz) { 711 super(tr("auto-fixed validator issues"), sequenz, true); 712 setSequenceComplete(true); 713 } 714 715 @Override 716 public void undoCommand() { 717 getAffectedDataSet().update(super::undoCommand); 718 } 719 720 @Override 721 public boolean executeCommand() { 722 return getAffectedDataSet().update(super::executeCommand); 723 } 724 } 725 726 @Override 727 public void destroy() { 728 super.destroy(); 729 if (ignoreForNowAction != null) { 730 ignoreForNowAction.destroy(); 731 } 732 } 733}