Ticket #21825: 21825.5.patch
File 21825.5.patch, 20.5 KB (added by , 3 years ago) |
---|
-
src/org/openstreetmap/josm/data/osm/IRelation.java
diff --git a/src/org/openstreetmap/josm/data/osm/IRelation.java b/src/org/openstreetmap/josm/data/osm/IRelation.java index 6d59bd427a..9a7897d36f 100644
a b 2 2 package org.openstreetmap.josm.data.osm; 3 3 4 4 import java.util.Collection; 5 import java.util.Collections; 5 6 import java.util.List; 6 7 import java.util.stream.Collectors; 7 8 9 import org.openstreetmap.josm.data.validation.TestError; 8 10 import org.openstreetmap.josm.tools.Utils; 9 11 10 12 /** … … public interface IRelation<M extends IRelationMember<?>> extends IPrimitive { 138 140 return getMembers().stream().filter(rmv -> role.equals(rmv.getRole())) 139 141 .map(IRelationMember::getMember).collect(Collectors.toList()); 140 142 } 143 144 /** 145 * Check if this relation is useful 146 * @return {@code true} if this relation is useful 147 */ 148 default boolean isUseful() { 149 return !this.isEmpty() && this.hasKey("type"); 150 } 151 152 /** 153 * Check if this relation is valid 154 * @return A collection of errors, if any 155 */ 156 default Collection<TestError> validate() { 157 return Collections.emptyList(); 158 } 141 159 } -
src/org/openstreetmap/josm/data/osm/Relation.java
diff --git a/src/org/openstreetmap/josm/data/osm/Relation.java b/src/org/openstreetmap/josm/data/osm/Relation.java index 3b12c48c80..1653b30f2e 100644
a b import java.util.stream.Stream; 14 14 15 15 import org.openstreetmap.josm.data.osm.visitor.OsmPrimitiveVisitor; 16 16 import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor; 17 import org.openstreetmap.josm.data.validation.OsmValidator; 18 import org.openstreetmap.josm.data.validation.TestError; 19 import org.openstreetmap.josm.data.validation.tests.MultipolygonTest; 20 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 21 import org.openstreetmap.josm.data.validation.tests.TurnrestrictionTest; 22 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 17 23 import org.openstreetmap.josm.spi.preferences.Config; 18 24 import org.openstreetmap.josm.tools.CopyList; 19 25 import org.openstreetmap.josm.tools.SubclassFilteredCollection; … … public final class Relation extends OsmPrimitive implements IRelation<RelationMe 567 573 public UniqueIdGenerator getIdGenerator() { 568 574 return idGenerator; 569 575 } 576 577 @Override 578 public Collection<TestError> validate() { 579 return Stream.of(MultipolygonTest.class, TurnrestrictionTest.class, RelationChecker.class) 580 .map(OsmValidator::getTest).filter(test -> test.enabled || test.testBeforeUpload).flatMap(test -> { 581 test.startTest(NullProgressMonitor.INSTANCE); 582 test.visit(this); 583 test.endTest(); 584 return test.getErrors().stream(); 585 }).collect(Collectors.toList()); 586 } 570 587 } -
src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java
diff --git a/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java b/src/org/openstreetmap/josm/data/validation/tests/RelationChecker.java index 959f73d1d0..1c10a7c575 100644
a b public class RelationChecker extends Test implements TaggingPresetListener { 154 154 .message(tr("Route scheme is unspecified. Add {0} ({1}=public_transport; {2}=legacy)", "public_transport:version", "2", "1")) 155 155 .primitives(n) 156 156 .build()); 157 } else if ( n.hasKey("type") && allroles.isEmpty()) {157 } else if ((n.hasKey("type") && allroles.isEmpty()) || !n.hasKey("type")) { 158 158 errors.add(TestError.builder(this, Severity.OTHER, RELATION_UNKNOWN) 159 159 .message(tr("Relation type is unknown")) 160 160 .primitives(n) -
src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java b/src/org/openstreetmap/josm/gui/dialogs/relation/GenericRelationEditor.java index 743e05f4e0..ccd3e41ad4 100644
a b package org.openstreetmap.josm.gui.dialogs.relation; 3 3 4 4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 5 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 import static org.openstreetmap.josm.tools.I18n.trn; 6 7 7 8 import java.awt.BorderLayout; 8 9 import java.awt.Component; … … import java.util.ArrayList; 26 27 import java.util.Arrays; 27 28 import java.util.Collection; 28 29 import java.util.Collections; 30 import java.util.Comparator; 29 31 import java.util.EnumSet; 30 32 import java.util.List; 31 33 import java.util.Set; 34 import java.util.concurrent.Future; 35 import java.util.concurrent.atomic.AtomicReference; 32 36 import java.util.stream.Collectors; 33 37 34 38 import javax.swing.AbstractAction; … … import javax.swing.JTabbedPane; 47 51 import javax.swing.JTable; 48 52 import javax.swing.JToolBar; 49 53 import javax.swing.KeyStroke; 54 import javax.swing.event.TableModelListener; 50 55 51 56 import org.openstreetmap.josm.actions.JosmAction; 52 57 import org.openstreetmap.josm.command.ChangeMembersCommand; … … import org.openstreetmap.josm.command.Command; 54 59 import org.openstreetmap.josm.data.UndoRedoHandler; 55 60 import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener; 56 61 import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 62 import org.openstreetmap.josm.data.osm.IRelation; 57 63 import org.openstreetmap.josm.data.osm.OsmPrimitive; 58 64 import org.openstreetmap.josm.data.osm.Relation; 59 65 import org.openstreetmap.josm.data.osm.RelationMember; 60 66 import org.openstreetmap.josm.data.osm.Tag; 67 import org.openstreetmap.josm.data.validation.TestError; 61 68 import org.openstreetmap.josm.data.validation.tests.RelationChecker; 62 69 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 63 70 import org.openstreetmap.josm.gui.MainApplication; … … import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 105 112 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 106 113 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 107 114 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 115 import org.openstreetmap.josm.gui.util.GuiHelper; 108 116 import org.openstreetmap.josm.gui.util.WindowGeometry; 109 117 import org.openstreetmap.josm.spi.preferences.Config; 110 118 import org.openstreetmap.josm.tools.CheckParameterUtil; 119 import org.openstreetmap.josm.tools.GBC; 120 import org.openstreetmap.josm.tools.ImageProvider; 111 121 import org.openstreetmap.josm.tools.InputMapUtils; 112 122 import org.openstreetmap.josm.tools.Logging; 113 123 import org.openstreetmap.josm.tools.Shortcut; … … public class GenericRelationEditor extends RelationEditor implements CommandQueu 132 142 private final SelectionTableModel selectionTableModel; 133 143 134 144 private final AutoCompletingTextField tfRole; 145 private final RelationEditorActionAccess actionAccess; 135 146 136 147 /** 137 148 * the menu item in the windows menu. Required to properly hide on dialog close. … … public class GenericRelationEditor extends RelationEditor implements CommandQueu 262 273 selectedTabPane = sourceTabbedPane.getSelectedComponent(); 263 274 }); 264 275 265 IRelationEditorActionAccessactionAccess = new RelationEditorActionAccess();276 actionAccess = new RelationEditorActionAccess(); 266 277 267 278 refreshAction = new RefreshAction(actionAccess); 268 279 applyAction = new ApplyAction(actionAccess); 269 280 selectAction = new SelectAction(actionAccess); 270 281 duplicateAction = new DuplicateRelationAction(actionAccess); 271 282 deleteAction = new DeleteCurrentRelationAction(actionAccess); 283 284 this.memberTableModel.addTableModelListener(applyAction); 285 this.tagEditorPanel.getModel().addTableModelListener(applyAction); 286 272 287 addPropertyChangeListener(deleteAction); 273 288 274 289 okAction = new OKAction(actionAccess); … … public class GenericRelationEditor extends RelationEditor implements CommandQueu 276 291 277 292 getContentPane().add(buildToolBar(refreshAction, applyAction, selectAction, duplicateAction, deleteAction), BorderLayout.NORTH); 278 293 getContentPane().add(tabbedPane, BorderLayout.CENTER); 279 getContentPane().add(buildOkCancelButtonPanel(okAction, cancelAction), BorderLayout.SOUTH);294 getContentPane().add(buildOkCancelButtonPanel(okAction, deleteAction, cancelAction), BorderLayout.SOUTH); 280 295 281 296 setSize(findMaxDialogSize()); 282 297 … … public class GenericRelationEditor extends RelationEditor implements CommandQueu 407 422 * 408 423 * @return the panel with the OK and the Cancel button 409 424 */ 410 protected static JPanel buildOkCancelButtonPanel(OKAction okAction, CancelAction cancelAction) { 411 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); 412 pnl.add(new JButton(okAction)); 425 protected JPanel buildOkCancelButtonPanel(OKAction okAction, DeleteCurrentRelationAction deleteAction, 426 CancelAction cancelAction) { 427 JPanel mainPanel = new JPanel(new GridBagLayout()); 428 final JLabel errorLabel = new JLabel(" "); 429 final JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); 430 mainPanel.add(errorLabel, GBC.eol().anchor(GridBagConstraints.CENTER)); 431 mainPanel.add(pnl, GBC.eol().anchor(GridBagConstraints.CENTER)); 432 final JButton okButton = new JButton(okAction); 433 final JButton deleteButton = new JButton(deleteAction); 434 okButton.setPreferredSize(deleteButton.getPreferredSize()); 435 pnl.add(okButton); 436 pnl.add(deleteButton); 413 437 pnl.add(new JButton(cancelAction)); 414 438 pnl.add(new JButton(new ContextSensitiveHelpAction(ht("/Dialog/RelationEditor")))); 415 return pnl; 439 // Keep users from saving invalid relations -- a relation MUST have at least a tag with the key "type" 440 // AND must contain at least one other OSM object. 441 final AtomicReference<Future<?>> testErrorsUpdate = new AtomicReference<>(); 442 final TableModelListener listener = l -> { 443 final IRelation<?> newRelation = this.actionAccess.getChangedRelation(); 444 updateOkPanel(newRelation, okButton, deleteButton); 445 final Future<?> oldFuture = testErrorsUpdate.getAndSet(MainApplication.worker.submit(() -> { 446 GuiHelper.runInEDTAndWait(() -> errorLabel.setText(tr("Validation in progress"))); 447 final Collection<TestError> testErrors = newRelation.validate(); 448 GuiHelper.runInEDTAndWait(() -> updateErrorMessage(testErrors, errorLabel)); 449 })); 450 if (oldFuture != null) { 451 oldFuture.cancel(true); 452 } 453 }; 454 listener.tableChanged(null); 455 this.memberTableModel.addTableModelListener(listener); 456 this.tagEditorPanel.getModel().addTableModelListener(listener); 457 return mainPanel; 458 } 459 460 /** 461 * Update the OK panel area 462 * @param newRelation What the new relation would "look" like if it were to be saved now 463 * @param okButton The OK button 464 * @param deleteButton The delete button 465 */ 466 private void updateOkPanel(IRelation<?> newRelation, JButton okButton, JButton deleteButton) { 467 okButton.setVisible(newRelation.isUseful() || this.getRelationSnapshot() == null); 468 deleteButton.setVisible(!newRelation.isUseful() && this.getRelationSnapshot() != null); 469 if (this.getRelationSnapshot() == null && !newRelation.isUseful()) { 470 okButton.setText(tr("Delete")); 471 } else { 472 okButton.setText(tr("OK")); 473 } 474 } 475 476 /** 477 * Update the error message 478 * @param newRelation What the new relation would "look" like if it were to be saved now 479 * @param errorLabel The label to update 480 */ 481 private void updateErrorMessage(Collection<TestError> errors, JLabel errorLabel) { 482 if (errors.isEmpty()) { 483 // Setting " " helps keep the label in place for layout calculations 484 errorLabel.setText(" "); 485 errorLabel.setIcon(null); 486 } else { 487 final TestError error = errors.stream() 488 .min(Comparator.comparingInt(testError -> testError.getSeverity().getLevel())) 489 .orElse(errors.iterator().next()); 490 final StringBuilder sb = new StringBuilder(); 491 if (error.getDescription() != null) { 492 sb.append(error.getDescription()).append(": "); 493 } 494 sb.append(error.getMessage()); 495 if (errors.size() > 1) { 496 sb.append(trn(" with {0} more problem", " with {0} more problems", errors.size() - 1, errors.size() - 1)); 497 } 498 errorLabel.setIcon(ImageProvider.get("data", error.getSeverity().getIcon())); 499 errorLabel.setText(sb.toString()); 500 } 416 501 } 417 502 418 503 /** … … public class GenericRelationEditor extends RelationEditor implements CommandQueu 1001 1086 @Override 1002 1087 public void mouseClicked(MouseEvent e) { 1003 1088 if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { 1004 new EditAction( new RelationEditorActionAccess()).actionPerformed(null);1089 new EditAction(actionAccess).actionPerformed(null); 1005 1090 } 1006 1091 } 1007 1092 } -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/ApplyAction.java index 7ddfae63ac..2a51047659 100644
a b import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.event.ActionEvent; 7 7 8 import org.openstreetmap.josm.data.osm.IRelation; 8 9 import org.openstreetmap.josm.tools.ImageProvider; 9 10 10 11 /** … … public class ApplyAction extends SavingAction { 35 36 36 37 @Override 37 38 public void updateEnabledState() { 38 setEnabled(isEditorDirty()); 39 final IRelation<?> currentRelation = editorAccess.getChangedRelation(); 40 setEnabled(currentRelation.isUseful() && isEditorDirty()); 39 41 } 40 42 } -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/CancelAction.java index 6efdaeabdf..a51be40834 100644
a b import java.awt.event.ActionEvent; 8 8 import javax.swing.JOptionPane; 9 9 import javax.swing.RootPaneContainer; 10 10 11 import org.openstreetmap.josm.data.osm.IRelation; 11 12 import org.openstreetmap.josm.data.osm.Relation; 12 13 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 13 14 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; … … public class CancelAction extends SavingAction { 47 48 if ((!getMemberTableModel().hasSameMembersAs(snapshot) || getTagModel().isDirty()) 48 49 && !(snapshot == null && getTagModel().getTags().isEmpty())) { 49 50 //give the user a chance to save the changes 50 int ret = confirmClosingByCancel( );51 int ret = confirmClosingByCancel(this.editorAccess.getChangedRelation()); 51 52 if (ret == 0) { //Yes, save the changes 52 53 //copied from OKAction.run() 53 54 Config.getPref().put("relation.editor.generic.lastrole", Utils.strip(tfRole.getText())); … … public class CancelAction extends SavingAction { 60 61 hideEditor(); 61 62 } 62 63 63 protected int confirmClosingByCancel( ) {64 protected int confirmClosingByCancel(final IRelation<?> newRelation) { 64 65 ButtonSpec[] options = { 65 66 new ButtonSpec( 66 67 tr("Yes, save the changes and close"), … … public class CancelAction extends SavingAction { 82 83 ) 83 84 }; 84 85 86 // Keep users from saving invalid relations -- a relation MUST have at least a tag with the key "type" 87 // AND must contain at least one other OSM object. 88 options[0].setEnabled(newRelation.isUseful()); 89 85 90 return HelpAwareOptionPane.showOptionDialog( 86 91 MainApplication.getMainFrame(), 87 92 tr("<html>The relation has been changed.<br><br>Do you want to save your changes?</html>"), -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/IRelationEditorActionAccess.java index cddedaf165..b1b79a3128 100644
a b package org.openstreetmap.josm.gui.dialogs.relation.actions; 3 3 4 4 import javax.swing.Action; 5 5 6 import org.openstreetmap.josm.data.osm.IRelation; 7 import org.openstreetmap.josm.data.osm.Relation; 6 8 import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor; 7 9 import org.openstreetmap.josm.gui.dialogs.relation.MemberTable; 8 10 import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel; … … public interface IRelationEditorActionAccess { 65 67 */ 66 68 TagEditorModel getTagModel(); 67 69 70 /** 71 * Get the changed relation 72 * @return The changed relation (note: will not be part of a dataset). This should never be {@code null}. 73 * @since xxx 74 */ 75 default IRelation<?> getChangedRelation() { 76 final Relation newRelation; 77 if (getEditor().getRelation() != null) { 78 newRelation = new Relation(getEditor().getRelation()); 79 } else { 80 newRelation = new Relation(); 81 } 82 getTagModel().applyToPrimitive(newRelation); 83 getMemberTableModel().applyToRelation(newRelation); 84 return newRelation; 85 } 86 68 87 /** 69 88 * Get the text field that is used to edit the role. 70 89 * @return The role text field. -
src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java
diff --git a/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java b/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java index 26813f23c1..35ca1dcfb7 100644
a b abstract class SavingAction extends AbstractRelationEditorAction { 54 54 tagEditorModel.applyToPrimitive(newRelation); 55 55 getMemberTableModel().applyToRelation(newRelation); 56 56 // If the user wanted to create a new relation, but hasn't added any members or 57 // tags , don't add an emptyrelation58 if ( newRelation.isEmpty() && !newRelation.hasKeys())57 // tags (specifically the "type" tag), don't add the relation 58 if (!newRelation.isUseful()) 59 59 return; 60 60 UndoRedoHandler.getInstance().add(new AddCommand(getLayer().getDataSet(), newRelation)); 61 61 -
src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java
diff --git a/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java b/src/org/openstreetmap/josm/io/session/MarkerSessionImporter.java index 3a05dea2e0..e7d008324e 100644
a b import static org.openstreetmap.josm.tools.I18n.tr; 6 6 import java.io.IOException; 7 7 import java.io.InputStream; 8 8 9 import javax.xml.XMLConstants; 9 10 import javax.xml.xpath.XPath; 10 11 import javax.xml.xpath.XPathConstants; 11 12 import javax.xml.xpath.XPathExpression; 12 13 import javax.xml.xpath.XPathExpressionException; 13 14 import javax.xml.xpath.XPathFactory; 15 import javax.xml.xpath.XPathFactoryConfigurationException; 14 16 15 17 import org.openstreetmap.josm.gui.io.importexport.GpxImporter; 16 18 import org.openstreetmap.josm.gui.layer.Layer; … … public class MarkerSessionImporter implements SessionLayerImporter { 35 37 } 36 38 try { 37 39 XPathFactory xPathFactory = XPathFactory.newInstance(); 40 xPathFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 38 41 XPath xpath = xPathFactory.newXPath(); 39 42 XPathExpression fileExp = xpath.compile("file/text()"); 40 43 String fileStr = (String) fileExp.evaluate(elem, XPathConstants.STRING); … … public class MarkerSessionImporter implements SessionLayerImporter { 50 53 51 54 return importData.getMarkerLayer(); 52 55 } 53 } catch (XPathExpressionException e) {56 } catch (XPathExpressionException | XPathFactoryConfigurationException e) { 54 57 throw new IllegalDataException(e); 55 58 } 56 59 }