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("Toggle: {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("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 boolean found = false; 240 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 241 if (ds != null && !ds.selectionEmpty()) { 242 for (TestError e : tree.getErrors()) { 243 for (OsmPrimitive p : e.getPrimitives()) { 244 if (p.isSelected()) { 245 found = true; 246 break; 247 } 248 } 249 } 250 } 251 setEnabled(found); 252 } 253 } 254 255 @Override 256 public void showNotify() { 257 DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED); 258 SelectionEventManager.getInstance().addSelectionListener(this); 259 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 260 if (ds != null) { 261 updateSelection(ds.getAllSelected()); 262 } 263 MainApplication.getLayerManager().addAndFireActiveLayerChangeListener(this); 264 265 } 266 267 @Override 268 public void hideNotify() { 269 DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter); 270 MainApplication.getLayerManager().removeActiveLayerChangeListener(this); 271 SelectionEventManager.getInstance().removeSelectionListener(this); 272 } 273 274 @Override 275 public void setVisible(boolean v) { 276 if (tree != null) { 277 tree.setVisible(v); 278 } 279 super.setVisible(v); 280 } 281 282 /** 283 * Fix selected errors 284 */ 285 private void fixErrors() { 286 TreePath[] selectionPaths = tree.getSelectionPaths(); 287 if (selectionPaths == null) 288 return; 289 290 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>(); 291 292 List<TestError> errorsToFix = new LinkedList<>(); 293 for (TreePath path : selectionPaths) { 294 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 295 if (node != null) { 296 ValidatorTreePanel.visitTestErrors(node, errorsToFix::add, processedNodes); 297 } 298 } 299 300 // run fix task asynchronously 301 MainApplication.worker.submit(new FixTask(errorsToFix)); 302 } 303 304 /** 305 * Set selected errors to ignore state 306 */ 307 private void ignoreErrors() { 308 int asked = JOptionPane.DEFAULT_OPTION; 309 AtomicBoolean changed = new AtomicBoolean(); 310 TreePath[] selectionPaths = tree.getSelectionPaths(); 311 if (selectionPaths == null) 312 return; 313 314 Set<DefaultMutableTreeNode> processedNodes = new HashSet<>(); 315 for (TreePath path : selectionPaths) { 316 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 317 if (node == null) { 318 continue; 319 } 320 321 Object mainNodeInfo = node.getUserObject(); 322 final int depth = node.getDepth(); 323 if (!(mainNodeInfo instanceof TestError)) { 324 Set<Pair<String, String>> state = new HashSet<>(); 325 // ask if the whole set should be ignored 326 if (asked == JOptionPane.DEFAULT_OPTION) { 327 String[] a = {tr("Whole group"), tr("Single elements"), tr("Nothing")}; 328 asked = JOptionPane.showOptionDialog(MainApplication.getMainFrame(), tr("Ignore whole group or individual elements?"), 329 tr("Ignoring elements"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, 330 a, a[1]); 331 } 332 if (asked == JOptionPane.YES_NO_OPTION) { 333 ValidatorTreePanel.visitTestErrors(node, err -> { 334 err.setIgnored(true); 335 changed.set(true); 336 state.add(new Pair<>(depth == 1 ? err.getIgnoreSubGroup() : err.getIgnoreGroup(), err.getMessage())); 337 }, processedNodes); 338 for (Pair<String, String> s : state) { 339 OsmValidator.addIgnoredError(s.a, s.b); 340 } 341 continue; 342 } else if (asked == JOptionPane.CANCEL_OPTION || asked == JOptionPane.CLOSED_OPTION) { 343 continue; 344 } 345 } 346 347 ValidatorTreePanel.visitTestErrors(node, error -> { 348 String state = error.getIgnoreState(); 349 if (state != null) { 350 OsmValidator.addIgnoredError(state, error.getMessage()); 351 } 352 changed.set(true); 353 error.setIgnored(true); 354 }, processedNodes); 355 } 356 if (changed.get()) { 357 tree.resetErrors(); 358 OsmValidator.saveIgnoredErrors(); 359 invalidateValidatorLayers(); 360 } 361 } 362 363 /** 364 * Sets the selection of the map to the current selected items. 365 */ 366 private void setSelectedItems() { 367 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 368 if (tree == null || ds == null) 369 return; 370 371 TreePath[] selectedPaths = tree.getSelectionPaths(); 372 if (selectedPaths == null) 373 return; 374 375 Collection<OsmPrimitive> sel = new HashSet<>(40); 376 for (TreePath path : selectedPaths) { 377 DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); 378 Enumeration<?> children = node.breadthFirstEnumeration(); 379 while (children.hasMoreElements()) { 380 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) children.nextElement(); 381 Object nodeInfo = childNode.getUserObject(); 382 if (nodeInfo instanceof TestError) { 383 TestError error = (TestError) nodeInfo; 384 error.getPrimitives().stream() 385 .filter(OsmPrimitive::isSelectable) 386 .forEach(sel::add); 387 } 388 } 389 } 390 ds.setSelected(sel); 391 } 392 393 /** 394 * Checks for fixes in selected element and, if needed, adds to the sel 395 * parameter all selected elements 396 * 397 * @param sel 398 * The collection where to add all selected elements 399 * @param addSelected 400 * if true, add all selected elements to collection 401 * @return whether the selected elements has any fix 402 */ 403 private boolean setSelection(Collection<OsmPrimitive> sel, boolean addSelected) { 404 AtomicBoolean hasFixes = new AtomicBoolean(); 405 406 DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); 407 if (lastSelectedNode != null && !lastSelectedNode.equals(node)) { 408 ValidatorTreePanel.visitTestErrors(lastSelectedNode, error -> error.setSelected(false)); 409 } 410 411 lastSelectedNode = node; 412 if (node != null) { 413 ValidatorTreePanel.visitTestErrors(node, error -> { 414 error.setSelected(true); 415 416 hasFixes.set(hasFixes.get() || error.isFixable()); 417 if (addSelected) { 418 error.getPrimitives().stream() 419 .filter(OsmPrimitive::isSelectable) 420 .forEach(sel::add); 421 } 422 }); 423 selectAction.setEnabled(true); 424 if (ignoreAction != null) { 425 ignoreAction.setEnabled(!(node.getUserObject() instanceof Severity)); 426 } 427 } 428 429 return hasFixes.get(); 430 } 431 432 @Override 433 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) { 434 OsmDataLayer editLayer = e.getSource().getEditLayer(); 435 if (editLayer == null) { 436 tree.setErrorList(new ArrayList<TestError>()); 437 } else { 438 tree.setErrorList(editLayer.validationErrors); 439 } 440 } 441 442 /** 443 * Add a tree selection listener to the validator tree. 444 * @param listener the TreeSelectionListener 445 * @since 5958 446 */ 447 public void addTreeSelectionListener(TreeSelectionListener listener) { 448 tree.addTreeSelectionListener(listener); 449 } 450 451 /** 452 * Remove the given tree selection listener from the validator tree. 453 * @param listener the TreeSelectionListener 454 * @since 5958 455 */ 456 public void removeTreeSelectionListener(TreeSelectionListener listener) { 457 tree.removeTreeSelectionListener(listener); 458 } 459 460 /** 461 * Replies the popup menu handler. 462 * @return The popup menu handler 463 * @since 5958 464 */ 465 public PopupMenuHandler getPopupMenuHandler() { 466 return popupMenuHandler; 467 } 468 469 /** 470 * Replies the currently selected error, or {@code null}. 471 * @return The selected error, if any. 472 * @since 5958 473 */ 474 public TestError getSelectedError() { 475 Object comp = tree.getLastSelectedPathComponent(); 476 if (comp instanceof DefaultMutableTreeNode) { 477 Object object = ((DefaultMutableTreeNode) comp).getUserObject(); 478 if (object instanceof TestError) { 479 return (TestError) object; 480 } 481 } 482 return null; 483 } 484 485 /** 486 * Watches for double clicks and launches the popup menu. 487 */ 488 class MouseEventHandler extends PopupMenuLauncher { 489 490 MouseEventHandler() { 491 super(popupMenu); 492 } 493 494 @Override 495 public void mouseClicked(MouseEvent e) { 496 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 497 if (selPath == null) { 498 tree.clearSelection(); 499 } 500 501 fixAction.setEnabled(false); 502 if (ignoreAction != null) { 503 ignoreAction.setEnabled(false); 504 } 505 selectAction.setEnabled(false); 506 507 boolean isDblClick = isDoubleClick(e); 508 509 Collection<OsmPrimitive> sel = isDblClick ? new HashSet<>(40) : null; 510 511 boolean hasFixes = setSelection(sel, isDblClick); 512 fixAction.setEnabled(hasFixes); 513 514 if (isDblClick) { 515 DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 516 if (ds != null) { 517 ds.setSelected(sel); 518 } 519 if (Config.getPref().getBoolean("validator.autozoom", false)) { 520 AutoScaleAction.zoomTo(sel); 521 } 522 } 523 } 524 525 @Override 526 public void launch(MouseEvent e) { 527 TreePath selPath = tree.getPathForLocation(e.getX(), e.getY()); 528 if (selPath == null) 529 return; 530 DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getPathComponent(selPath.getPathCount() - 1); 531 if (!(node.getUserObject() instanceof TestError)) 532 return; 533 super.launch(e); 534 } 535 } 536 537 /** 538 * Watches for tree selection. 539 */ 540 public class SelectionWatch implements TreeSelectionListener { 541 @Override 542 public void valueChanged(TreeSelectionEvent e) { 543 if (ignoreAction != null) { 544 ignoreAction.setEnabled(false); 545 } 546 selectAction.setEnabled(false); 547 548 Collection<OsmPrimitive> sel = new HashSet<>(); 549 boolean hasFixes = setSelection(sel, true); 550 fixAction.setEnabled(hasFixes); 551 popupMenuHandler.setPrimitives(sel); 552 invalidateValidatorLayers(); 553 } 554 } 555 556 /** 557 * A visitor that is used to compute the bounds of an error. 558 */ 559 public static class ValidatorBoundingXYVisitor extends BoundingXYVisitor implements ValidatorVisitor { 560 @Override 561 public void visit(OsmPrimitive p) { 562 if (p.isUsable()) { 563 p.accept((PrimitiveVisitor) this); 564 } 565 } 566 567 @Override 568 public void visit(WaySegment ws) { 569 if (ws.lowerIndex < 0 || ws.lowerIndex + 1 >= ws.way.getNodesCount()) 570 return; 571 visit(ws.getFirstNode()); 572 visit(ws.getSecondNode()); 573 } 574 575 @Override 576 public void visit(List<Node> nodes) { 577 for (Node n: nodes) { 578 visit(n); 579 } 580 } 581 582 @Override 583 public void visit(TestError error) { 584 if (error != null) { 585 error.visitHighlighted(this); 586 } 587 } 588 } 589 590 /** 591 * Called when the selection was changed to update the list of displayed errors 592 * @param newSelection The new selection 593 */ 594 public void updateSelection(Collection<? extends OsmPrimitive> newSelection) { 595 if (!Config.getPref().getBoolean(ValidatorPrefHelper.PREF_FILTER_BY_SELECTION, false)) 596 return; 597 if (newSelection.isEmpty()) { 598 tree.setFilter(null); 599 } 600 tree.setFilter(new HashSet<>(newSelection)); 601 } 602 603 @Override 604 public void selectionChanged(SelectionChangeEvent event) { 605 updateSelection(event.getSelection()); 606 lookupAction.updateEnabledState(); 607 } 608 609 /** 610 * Task for fixing a collection of {@link TestError}s. Can be run asynchronously. 611 */ 612 class FixTask extends PleaseWaitRunnable { 613 private final Collection<TestError> testErrors; 614 private final List<Command> fixCommands = new ArrayList<>(); 615 private boolean canceled; 616 617 FixTask(Collection<TestError> testErrors) { 618 super(tr("Fixing errors ..."), false /* don't ignore exceptions */); 619 this.testErrors = testErrors == null ? new ArrayList<>() : testErrors; 620 } 621 622 @Override 623 protected void cancel() { 624 this.canceled = true; 625 } 626 627 @Override 628 protected void finish() { 629 // do nothing 630 } 631 632 protected void fixError(TestError error) throws InterruptedException, InvocationTargetException { 633 if (error.isFixable()) { 634 if (error.getPrimitives().stream().noneMatch(p -> p.isDeleted() || p.getDataSet() == null)) { 635 final Command fixCommand = error.getFix(); 636 if (fixCommand != null) { 637 SwingUtilities.invokeAndWait(fixCommand::executeCommand); 638 fixCommands.add(fixCommand); 639 } 640 } 641 // It is wanted to ignore an error if it said fixable, even if fixCommand was null 642 // This is to fix #5764 and #5773: 643 // a delete command, for example, may be null if all concerned primitives have already been deleted 644 error.setIgnored(true); 645 } 646 } 647 648 @Override 649 protected void realRun() throws SAXException, IOException, OsmTransferException { 650 ProgressMonitor monitor = getProgressMonitor(); 651 try { 652 monitor.setTicksCount(testErrors.size()); 653 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 654 int i = 0; 655 SwingUtilities.invokeAndWait(ds::beginUpdate); 656 tree.setResetScheduled(); 657 try { 658 for (TestError error: testErrors) { 659 i++; 660 monitor.subTask(tr("Fixing ({0}/{1}): ''{2}''", i, testErrors.size(), error.getMessage())); 661 if (this.canceled) 662 return; 663 fixError(error); 664 monitor.worked(1); 665 } 666 } finally { 667 SwingUtilities.invokeAndWait(ds::endUpdate); 668 } 669 monitor.subTask(tr("Updating map ...")); 670 SwingUtilities.invokeAndWait(() -> { 671 if (!fixCommands.isEmpty()) { 672 UndoRedoHandler.getInstance().add( 673 fixCommands.size() > 1 ? new AutofixCommand(fixCommands) : fixCommands.get(0), false); 674 } 675 invalidateValidatorLayers(); 676 }); 677 } catch (InterruptedException e) { 678 tryUndo(); 679 throw new JosmRuntimeException(e); 680 } catch (InvocationTargetException e) { 681 // FIXME: signature of realRun should have a generic checked exception we could throw here 682 throw new JosmRuntimeException(e); 683 } finally { 684 if (monitor.isCanceled()) { 685 tryUndo(); 686 } 687 GuiHelper.runInEDTAndWait(tree::resetErrors); 688 monitor.finishTask(); 689 } 690 } 691 692 /** 693 * Undo commands as they were not yet added to the UndoRedo Handler 694 */ 695 private void tryUndo() { 696 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet(); 697 int i = fixCommands.size() - 1; 698 ds.beginUpdate(); 699 for (; i >= 0; i--) { 700 fixCommands.get(i).undoCommand(); 701 } 702 ds.endUpdate(); 703 } 704 705 } 706 707 private static void invalidateValidatorLayers() { 708 MainApplication.getLayerManager().getLayersOfType(ValidatorLayer.class).forEach(ValidatorLayer::invalidate); 709 } 710 711 @Override 712 public void processDatasetEvent(AbstractDatasetChangedEvent event) { 713 validateAction.updateEnabledState(); 714 lookupAction.updateEnabledState(); 715 } 716 717 private static class AutofixCommand extends SequenceCommand { 718 AutofixCommand(Collection<Command> sequenz) { 719 super(tr("auto-fixed validator issues"), sequenz, true); 720 setSequenceComplete(true); 721 } 722 723 @Override 724 public void undoCommand() { 725 getAffectedDataSet().beginUpdate(); 726 super.undoCommand(); 727 getAffectedDataSet().endUpdate(); 728 } 729 730 @Override 731 public boolean executeCommand() { 732 getAffectedDataSet().beginUpdate(); 733 boolean rc = super.executeCommand(); 734 getAffectedDataSet().endUpdate(); 735 return rc; 736 } 737 } 738 739 @Override 740 public void destroy() { 741 super.destroy(); 742 if (ignoreForNowAction != null) { 743 ignoreForNowAction.destroy(); 744 } 745 } 746}