001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.relation.actions; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.util.ArrayList; 008import java.util.List; 009 010import javax.swing.JOptionPane; 011import javax.swing.SwingUtilities; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.command.AddCommand; 015import org.openstreetmap.josm.command.ChangeCommand; 016import org.openstreetmap.josm.command.conflict.ConflictAddCommand; 017import org.openstreetmap.josm.data.conflict.Conflict; 018import org.openstreetmap.josm.data.osm.Relation; 019import org.openstreetmap.josm.data.osm.RelationMember; 020import org.openstreetmap.josm.gui.DefaultNameFormatter; 021import org.openstreetmap.josm.gui.HelpAwareOptionPane; 022import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 023import org.openstreetmap.josm.gui.dialogs.relation.MemberTable; 024import org.openstreetmap.josm.gui.dialogs.relation.MemberTableModel; 025import org.openstreetmap.josm.gui.dialogs.relation.IRelationEditor; 026import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager; 027import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor; 028import org.openstreetmap.josm.gui.layer.OsmDataLayer; 029import org.openstreetmap.josm.gui.tagging.TagEditorModel; 030import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField; 031import org.openstreetmap.josm.tools.ImageProvider; 032 033/** 034 * Abstract superclass of relation saving actions (OK, Apply, Cancel). 035 * @since 9496 036 */ 037abstract class SavingAction extends AbstractRelationEditorAction { 038 039 protected final TagEditorModel tagModel; 040 protected final AutoCompletingTextField tfRole; 041 042 protected SavingAction(MemberTable memberTable, MemberTableModel memberTableModel, TagEditorModel tagModel, OsmDataLayer layer, 043 IRelationEditor editor, AutoCompletingTextField tfRole) { 044 super(memberTable, memberTableModel, null, layer, editor); 045 this.tagModel = tagModel; 046 this.tfRole = tfRole; 047 } 048 049 /** 050 * apply updates to a new relation 051 * @param tagEditorModel tag editor model 052 */ 053 protected void applyNewRelation(TagEditorModel tagEditorModel) { 054 final Relation newRelation = new Relation(); 055 tagEditorModel.applyToPrimitive(newRelation); 056 memberTableModel.applyToRelation(newRelation); 057 List<RelationMember> newMembers = new ArrayList<>(); 058 for (RelationMember rm: newRelation.getMembers()) { 059 if (!rm.getMember().isDeleted()) { 060 newMembers.add(rm); 061 } 062 } 063 if (newRelation.getMembersCount() != newMembers.size()) { 064 newRelation.setMembers(newMembers); 065 String msg = tr("One or more members of this new relation have been deleted while the relation editor\n" + 066 "was open. They have been removed from the relation members list."); 067 JOptionPane.showMessageDialog(Main.parent, msg, tr("Warning"), JOptionPane.WARNING_MESSAGE); 068 } 069 // If the user wanted to create a new relation, but hasn't added any members or 070 // tags, don't add an empty relation 071 if (newRelation.getMembersCount() == 0 && !newRelation.hasKeys()) 072 return; 073 Main.main.undoRedo.add(new AddCommand(layer, newRelation)); 074 075 // make sure everybody is notified about the changes 076 // 077 layer.data.fireSelectionChanged(); 078 editor.setRelation(newRelation); 079 if (editor instanceof RelationEditor) { 080 RelationDialogManager.getRelationDialogManager().updateContext( 081 layer, editor.getRelation(), (RelationEditor) editor); 082 } 083 SwingUtilities.invokeLater(new Runnable() { 084 @Override 085 public void run() { 086 // Relation list gets update in EDT so selecting my be postponed to following EDT run 087 Main.map.relationListDialog.selectRelation(newRelation); 088 } 089 }); 090 } 091 092 /** 093 * Apply the updates for an existing relation which has been changed outside of the relation editor. 094 * @param tagEditorModel tag editor model 095 */ 096 protected void applyExistingConflictingRelation(TagEditorModel tagEditorModel) { 097 Relation editedRelation = new Relation(editor.getRelation()); 098 tagEditorModel.applyToPrimitive(editedRelation); 099 memberTableModel.applyToRelation(editedRelation); 100 Conflict<Relation> conflict = new Conflict<>(editor.getRelation(), editedRelation); 101 Main.main.undoRedo.add(new ConflictAddCommand(layer, conflict)); 102 } 103 104 /** 105 * Apply the updates for an existing relation which has not been changed outside of the relation editor. 106 * @param tagEditorModel tag editor model 107 */ 108 protected void applyExistingNonConflictingRelation(TagEditorModel tagEditorModel) { 109 Relation editedRelation = new Relation(editor.getRelation()); 110 tagEditorModel.applyToPrimitive(editedRelation); 111 memberTableModel.applyToRelation(editedRelation); 112 Main.main.undoRedo.add(new ChangeCommand(editor.getRelation(), editedRelation)); 113 layer.data.fireSelectionChanged(); 114 } 115 116 protected boolean confirmClosingBecauseOfDirtyState() { 117 ButtonSpec[] options = new ButtonSpec[] { 118 new ButtonSpec( 119 tr("Yes, create a conflict and close"), 120 ImageProvider.get("ok"), 121 tr("Click to create a conflict and close this relation editor"), 122 null /* no specific help topic */ 123 ), 124 new ButtonSpec( 125 tr("No, continue editing"), 126 ImageProvider.get("cancel"), 127 tr("Click to return to the relation editor and to resume relation editing"), 128 null /* no specific help topic */ 129 ) 130 }; 131 132 int ret = HelpAwareOptionPane.showOptionDialog( 133 Main.parent, 134 tr("<html>This relation has been changed outside of the editor.<br>" 135 + "You cannot apply your changes and continue editing.<br>" 136 + "<br>" 137 + "Do you want to create a conflict and close the editor?</html>"), 138 tr("Conflict in data"), 139 JOptionPane.WARNING_MESSAGE, 140 null, 141 options, 142 options[0], // OK is default 143 "/Dialog/RelationEditor#RelationChangedOutsideOfEditor" 144 ); 145 return ret == 0; 146 } 147 148 protected void warnDoubleConflict() { 149 JOptionPane.showMessageDialog( 150 Main.parent, 151 tr("<html>Layer ''{0}'' already has a conflict for object<br>" 152 + "''{1}''.<br>" 153 + "Please resolve this conflict first, then try again.</html>", 154 layer.getName(), 155 editor.getRelation().getDisplayName(DefaultNameFormatter.getInstance()) 156 ), 157 tr("Double conflict"), 158 JOptionPane.WARNING_MESSAGE 159 ); 160 } 161 162 @Override 163 protected void updateEnabledState() { 164 // Do nothing 165 } 166 167 protected boolean applyChanges() { 168 if (editor.getRelation() == null) { 169 applyNewRelation(tagModel); 170 } else if (isEditorDirty()) { 171 if (editor.isDirtyRelation()) { 172 if (confirmClosingBecauseOfDirtyState()) { 173 if (layer.getConflicts().hasConflictForMy(editor.getRelation())) { 174 warnDoubleConflict(); 175 return false; 176 } 177 applyExistingConflictingRelation(tagModel); 178 hideEditor(); 179 } else 180 return false; 181 } else { 182 applyExistingNonConflictingRelation(tagModel); 183 } 184 } 185 editor.setRelation(editor.getRelation()); 186 return true; 187 } 188 189 protected void hideEditor() { 190 if (editor instanceof Component) { 191 ((Component) editor).setVisible(false); 192 } 193 } 194 195 protected boolean isEditorDirty() { 196 Relation snapshot = editor.getRelationSnapshot(); 197 return (snapshot != null && !memberTableModel.hasSameMembersAs(snapshot)) || tagModel.isDirty(); 198 } 199}