// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs.relation.actions; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; import java.util.ArrayList; import java.util.List; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.AddCommand; import org.openstreetmap.josm.command.ChangeCommand; import org.openstreetmap.josm.command.conflict.ConflictAddCommand; import org.openstreetmap.josm.data.conflict.Conflict; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor; import org.openstreetmap.josm.gui.dialogs.relation.MemberTable; import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel; import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager; import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.gui.tagging.TagEditorModel; import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Utils; /** * Abstract superclass of relation saving actions (OK, Apply, Cancel). * @since 9496 */ abstract class SavingAction extends AbstractRelationEditorAction { protected final TagEditorModel tagModel; protected final AutoCompletingTextField tfRole; protected SavingAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer, IRelationEditor editor, AutoCompletingTextField tfRole) { super(memberTable, memberTableModel, null, layer, editor); this.tagModel = tagModel; this.tfRole = tfRole; } /** * apply updates to a new relation * @param tagEditorModel tag editor model */ protected void applyNewRelation(TagEditorModel tagEditorModel) { final Relation newRelation = new Relation(); tagEditorModel.applyToPrimitive(newRelation); memberTableModel.applyToRelation(newRelation); List<RelationMember> newMembers = new ArrayList<>(); for (RelationMember rm: newRelation.getMembers()) { if (!rm.getMember().isDeleted()) { newMembers.add(rm); } } if (newRelation.getMembersCount() != newMembers.size()) { newRelation.setMembers(newMembers); String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" + "was open. They have been removed from the relation members list."); JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE); } // If the user wanted to create a new relation, but hasn't added any members or // tags, don't add an empty relation if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys()) return; Main.main.undoRedo.add(new AddCommand(layer, newRelation)); // make sure everybody is notified about the changes // layer.data.fireSelectionChanged(); editor.setRelation(newRelation); if (editor instanceof RelationEditor) { RelationDialogManager.getRelationDialogManager().updateContext( layer, editor.getRelation(), (RelationEditor) editor); } // Relation list gets update in EDT so selecting my be postponed to following EDT run SwingUtilities.invokeLater(() -> Main.map.relationListDialog.selectRelation(newRelation)); } /** * Apply the updates for an existing relation which has been changed outside of the relation editor. * @param tagEditorModel tag editor model */ protected void applyExistingConflictingRelation(TagEditorModel tagEditorModel) { Relation editedRelation = new Relation(editor.getRelation()); tagEditorModel.applyToPrimitive(editedRelation); memberTableModel.applyToRelation(editedRelation); Conflict<Relation> conflict = new Conflict<>(editor.getRelation(), editedRelation); Main.main.undoRedo.add(new ConflictAddCommand(layer, conflict)); } /** * Apply the updates for an existing relation which has not been changed outside of the relation editor. * @param tagEditorModel tag editor model */ protected void applyExistingNonConflictingRelation(TagEditorModel tagEditorModel) { Relation editedRelation = new Relation(editor.getRelation()); tagEditorModel.applyToPrimitive(editedRelation); memberTableModel.applyToRelation(editedRelation); if (!editedRelation.hasEqualSemanticAttributes(editor.getRelation(), false)) { Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation)); layer.data.fireSelectionChanged(); } } protected boolean confirmClosingBecauseOfDirtyState() { ButtonSpec[] options = new ButtonSpec[] { new ButtonSpec( tr("Yes, create a conflict and close"), ImageProvider.get("ok"), tr("Click to create a conflict and close this relation editor"), null /* no specific help topic */ ), new ButtonSpec( tr("No, continue editing"), ImageProvider.get("cancel"), tr("Click to return to the relation editor and to resume relation editing"), null /* no specific help topic */ ) }; int ret = HelpAwareOptionPane.showOptionDialog( Main.parent, tr("<html>This relation has been changed outside of the editor.<br>" + "You cannot apply your changes and continue editing.<br>" + "<br>" + "Do you want to create a conflict and close the editor?</html>"), tr("Conflict in data"), JOptionPane.WARNING_MESSAGE, null, options, options[0], // OK is default "/Dialog/RelationEditor#RelationChangedOutsideOfEditor" ); if (ret == 0) { Main.map.conflictDialog.unfurlDialog(); } return ret == 0; } protected void warnDoubleConflict() { JOptionPane.showMessageDialog( Main.parent, tr("<html>Layer ''{0}'' already has a conflict for object<br>" + "''{1}''.<br>" + "Please resolve this conflict first, then try again.</html>", Utils.escapeReservedCharactersHTML(layer.getName()), Utils.escapeReservedCharactersHTML(editor.getRelation().getDisplayName(DefaultNameFormatter.getInstance())) ), tr("Double conflict"), JOptionPane.WARNING_MESSAGE ); } @Override protected void updateEnabledState() { // Do nothing } protected boolean applyChanges() { if (editor.getRelation() == null) { applyNewRelation(tagModel); } else if (isEditorDirty()) { if (editor.isDirtyRelation()) { if (confirmClosingBecauseOfDirtyState()) { if (layer.getConflicts().hasConflictForMy(editor.getRelation())) { warnDoubleConflict(); return false; } applyExistingConflictingRelation(tagModel); hideEditor(); } else return false; } else { applyExistingNonConflictingRelation(tagModel); } } editor.setRelation(editor.getRelation()); return true; } protected void hideEditor() { if (editor instanceof Component) { ((Component) editor).setVisible(false); } } protected boolean isEditorDirty() { Relation snapshot = editor.getRelationSnapshot(); return (snapshot != null && !memberTableModel.hasSameMembersAs(snapshot)) || tagModel.isDirty(); } }