source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/actions/SavingAction.java@ 17140

Last change on this file since 17140 was 17140, checked in by GerdP, 4 years ago

see #19885: memory leak with "temporary" objects in validator and actions
Reduce number of memory leaks in RelationEditor when nothing is changed.

File size: 7.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.relation.actions;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7
8import javax.swing.JOptionPane;
9import javax.swing.SwingUtilities;
10
11import org.openstreetmap.josm.command.AddCommand;
12import org.openstreetmap.josm.command.ChangeCommand;
13import org.openstreetmap.josm.command.conflict.ConflictAddCommand;
14import org.openstreetmap.josm.data.UndoRedoHandler;
15import org.openstreetmap.josm.data.conflict.Conflict;
16import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
17import org.openstreetmap.josm.data.osm.Relation;
18import org.openstreetmap.josm.gui.HelpAwareOptionPane;
19import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
20import org.openstreetmap.josm.gui.MainApplication;
21import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
22import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
23import org.openstreetmap.josm.gui.tagging.TagEditorModel;
24import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
25import org.openstreetmap.josm.tools.ImageProvider;
26import org.openstreetmap.josm.tools.Utils;
27
28/**
29 * Abstract superclass of relation saving actions (OK, Apply, Cancel).
30 * @since 9496
31 */
32abstract class SavingAction extends AbstractRelationEditorAction {
33 private static final long serialVersionUID = 1L;
34
35 protected final AutoCompletingTextField tfRole;
36
37 protected SavingAction(IRelationEditorActionAccess editorAccess, IRelationEditorUpdateOn... updateOn) {
38 super(editorAccess, updateOn);
39 this.tfRole = editorAccess.getTextFieldRole();
40 }
41
42 /**
43 * apply updates to a new relation
44 * @param tagEditorModel tag editor model
45 */
46 protected void applyNewRelation(TagEditorModel tagEditorModel) {
47 final Relation newRelation = new Relation();
48 tagEditorModel.applyToPrimitive(newRelation);
49 getMemberTableModel().applyToRelation(newRelation);
50 // If the user wanted to create a new relation, but hasn't added any members or
51 // tags, don't add an empty relation
52 if (newRelation.isEmpty() && !newRelation.hasKeys())
53 return;
54 UndoRedoHandler.getInstance().add(new AddCommand(getLayer().getDataSet(), newRelation));
55
56 // make sure everybody is notified about the changes
57 //
58 getEditor().setRelation(newRelation);
59 if (getEditor() instanceof RelationEditor) {
60 RelationDialogManager.getRelationDialogManager().updateContext(
61 getLayer(), getEditor().getRelation(), (RelationEditor) getEditor());
62 }
63 // Relation list gets update in EDT so selecting my be postponed to following EDT run
64 SwingUtilities.invokeLater(() -> MainApplication.getMap().relationListDialog.selectRelation(newRelation));
65 }
66
67 /**
68 * Apply the updates for an existing relation which has been changed outside of the relation editor.
69 * @param tagEditorModel tag editor model
70 */
71 protected void applyExistingConflictingRelation(TagEditorModel tagEditorModel) {
72 Relation editedRelation = new Relation(editorAccess.getEditor().getRelation());
73 tagEditorModel.applyToPrimitive(editedRelation);
74 editorAccess.getMemberTableModel().applyToRelation(editedRelation);
75 Conflict<Relation> conflict = new Conflict<>(editorAccess.getEditor().getRelation(), editedRelation);
76 UndoRedoHandler.getInstance().add(new ConflictAddCommand(getLayer().getDataSet(), conflict));
77 }
78
79 /**
80 * Apply the updates for an existing relation which has not been changed outside of the relation editor.
81 * @param tagEditorModel tag editor model
82 */
83 protected void applyExistingNonConflictingRelation(TagEditorModel tagEditorModel) {
84 Relation originRelation = editorAccess.getEditor().getRelation();
85 Relation editedRelation = new Relation(originRelation);
86 tagEditorModel.applyToPrimitive(editedRelation);
87 getMemberTableModel().applyToRelation(editedRelation);
88 if (!editedRelation.hasEqualSemanticAttributes(originRelation, false)) {
89 UndoRedoHandler.getInstance().add(new ChangeCommand(originRelation, editedRelation));
90 } else {
91 editedRelation.setMembers(null); // see #19885
92 }
93 }
94
95 protected boolean confirmClosingBecauseOfDirtyState() {
96 ButtonSpec[] options = {
97 new ButtonSpec(
98 tr("Yes, create a conflict and close"),
99 new ImageProvider("ok"),
100 tr("Click to create a conflict and close this relation editor"),
101 null /* no specific help topic */
102 ),
103 new ButtonSpec(
104 tr("No, continue editing"),
105 new ImageProvider("cancel"),
106 tr("Click to return to the relation editor and to resume relation editing"),
107 null /* no specific help topic */
108 )
109 };
110
111 int ret = HelpAwareOptionPane.showOptionDialog(
112 MainApplication.getMainFrame(),
113 tr("<html>This relation has been changed outside of the editor.<br>"
114 + "You cannot apply your changes and continue editing.<br>"
115 + "<br>"
116 + "Do you want to create a conflict and close the editor?</html>"),
117 tr("Conflict in data"),
118 JOptionPane.WARNING_MESSAGE,
119 null,
120 options,
121 options[0], // OK is default
122 "/Dialog/RelationEditor#RelationChangedOutsideOfEditor"
123 );
124 if (ret == 0) {
125 MainApplication.getMap().conflictDialog.unfurlDialog();
126 }
127 return ret == 0;
128 }
129
130 protected void warnDoubleConflict() {
131 JOptionPane.showMessageDialog(
132 MainApplication.getMainFrame(),
133 tr("<html>Layer ''{0}'' already has a conflict for object<br>"
134 + "''{1}''.<br>"
135 + "Please resolve this conflict first, then try again.</html>",
136 Utils.escapeReservedCharactersHTML(getLayer().getName()),
137 Utils.escapeReservedCharactersHTML(getEditor().getRelation().getDisplayName(DefaultNameFormatter.getInstance()))
138 ),
139 tr("Double conflict"),
140 JOptionPane.WARNING_MESSAGE
141 );
142 }
143
144 @Override
145 protected void updateEnabledState() {
146 // Do nothing
147 }
148
149 protected boolean applyChanges() {
150 if (editorAccess.getEditor().getRelation() == null) {
151 applyNewRelation(getTagModel());
152 } else if (isEditorDirty()) {
153 if (editorAccess.getEditor().isDirtyRelation()) {
154 if (confirmClosingBecauseOfDirtyState()) {
155 if (getLayer().getConflicts().hasConflictForMy(editorAccess.getEditor().getRelation())) {
156 warnDoubleConflict();
157 return false;
158 }
159 applyExistingConflictingRelation(getTagModel());
160 hideEditor();
161 } else
162 return false;
163 } else {
164 applyExistingNonConflictingRelation(getTagModel());
165 }
166 }
167 editorAccess.getEditor().setRelation(editorAccess.getEditor().getRelation());
168 return true;
169 }
170
171 protected void hideEditor() {
172 if (editorAccess.getEditor() instanceof Component) {
173 ((Component) editorAccess.getEditor()).setVisible(false);
174 }
175 }
176
177 protected boolean isEditorDirty() {
178 Relation snapshot = editorAccess.getEditor().getRelationSnapshot();
179 return (snapshot != null && !getMemberTableModel().hasSameMembersAs(snapshot)) || getTagModel().isDirty();
180 }
181}
Note: See TracBrowser for help on using the repository browser.