source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/pair/ConflictResolver.java@ 3034

Last change on this file since 3034 was 3034, checked in by jttt, 14 years ago

Fix #4467 Don't silently drop locally deleted member primitives from downloaded ways and relation (fix the issue when deleted primitive is referenced)

File size: 14.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.pair;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.beans.PropertyChangeEvent;
9import java.beans.PropertyChangeListener;
10import java.util.ArrayList;
11import java.util.List;
12
13import javax.swing.ImageIcon;
14import javax.swing.JPanel;
15import javax.swing.JTabbedPane;
16
17import org.openstreetmap.josm.command.Command;
18import org.openstreetmap.josm.command.ModifiedConflictResolveCommand;
19import org.openstreetmap.josm.command.SequenceCommand;
20import org.openstreetmap.josm.command.VersionConflictResolveCommand;
21import org.openstreetmap.josm.data.conflict.Conflict;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.Way;
26import org.openstreetmap.josm.gui.conflict.pair.nodes.NodeListMergeModel;
27import org.openstreetmap.josm.gui.conflict.pair.nodes.NodeListMerger;
28import org.openstreetmap.josm.gui.conflict.pair.properties.OperationCancelledException;
29import org.openstreetmap.josm.gui.conflict.pair.properties.PropertiesMergeModel;
30import org.openstreetmap.josm.gui.conflict.pair.properties.PropertiesMerger;
31import org.openstreetmap.josm.gui.conflict.pair.relation.RelationMemberListMergeModel;
32import org.openstreetmap.josm.gui.conflict.pair.relation.RelationMemberMerger;
33import org.openstreetmap.josm.gui.conflict.pair.tags.TagMergeModel;
34import org.openstreetmap.josm.gui.conflict.pair.tags.TagMerger;
35import org.openstreetmap.josm.tools.ImageProvider;
36
37/**
38 * An UI component for resolving conflicts between two {@see OsmPrimitive}s.
39 *
40 * This component emits {@see PropertyChangeEvent}s for three properties:
41 * <ul>
42 * <li>{@see #RESOLVED_COMPLETELY_PROP} - new value is <code>true</code>, if the conflict is
43 * completely resolved</li>
44 * <li>{@see #MY_PRIMITIVE_PROP} - new value is the {@see OsmPrimitive} in the role of
45 * my primitive</li>
46 * <li>{@see #THEIR_PRIMITIVE_PROP} - new value is the {@see OsmPrimitive} in the role of
47 * their primitive</li>
48 * </ul>
49 *
50 */
51public class ConflictResolver extends JPanel implements PropertyChangeListener {
52
53 /* -------------------------------------------------------------------------------------- */
54 /* Property names */
55 /* -------------------------------------------------------------------------------------- */
56 /** name of the property indicating whether all conflicts are resolved,
57 * {@see #isResolvedCompletely()}
58 */
59 static public final String RESOLVED_COMPLETELY_PROP = ConflictResolver.class.getName() + ".resolvedCompletely";
60 /**
61 * name of the property for the {@see OsmPrimitive} in the role "my"
62 */
63 static public final String MY_PRIMITIVE_PROP = ConflictResolver.class.getName() + ".myPrimitive";
64
65 /**
66 * name of the property for the {@see OsmPrimitive} in the role "my"
67 */
68 static public final String THEIR_PRIMITIVE_PROP = ConflictResolver.class.getName() + ".theirPrimitive";
69
70 //private static final Logger logger = Logger.getLogger(ConflictResolver.class.getName());
71
72 private JTabbedPane tabbedPane = null;
73 private TagMerger tagMerger;
74 private NodeListMerger nodeListMerger;
75 private RelationMemberMerger relationMemberMerger;
76 private PropertiesMerger propertiesMerger;
77 private final List<IConflictResolver> conflictResolvers = new ArrayList<IConflictResolver>();
78 private OsmPrimitive my;
79 private OsmPrimitive their;
80 private Conflict<? extends OsmPrimitive> conflict;
81
82 private ImageIcon mergeComplete;
83 private ImageIcon mergeIncomplete;
84
85 /** indicates whether the current conflict is resolved completely */
86 private boolean resolvedCompletely;
87
88 /**
89 * loads the required icons
90 */
91 protected void loadIcons() {
92 mergeComplete = ImageProvider.get("dialogs/conflict","mergecomplete.png" );
93 mergeIncomplete = ImageProvider.get("dialogs/conflict","mergeincomplete.png" );
94 }
95
96 /**
97 * builds the UI
98 */
99 protected void build() {
100 tabbedPane = new JTabbedPane();
101
102 propertiesMerger = new PropertiesMerger();
103 propertiesMerger.setName("panel.propertiesmerger");
104 propertiesMerger.getModel().addPropertyChangeListener(this);
105 tabbedPane.add(tr("Properties"), propertiesMerger);
106
107 tagMerger = new TagMerger();
108 tagMerger.setName("panel.tagmerger");
109 tagMerger.getModel().addPropertyChangeListener(this);
110 tabbedPane.add(tr("Tags"), tagMerger);
111
112 nodeListMerger = new NodeListMerger();
113 nodeListMerger.setName("panel.nodelistmerger");
114 nodeListMerger.getModel().addPropertyChangeListener(this);
115 tabbedPane.add(tr("Nodes"), nodeListMerger);
116
117 relationMemberMerger = new RelationMemberMerger();
118 relationMemberMerger.setName("panel.relationmembermerger");
119 relationMemberMerger.getModel().addPropertyChangeListener(this);
120 tabbedPane.add(tr("Members"), relationMemberMerger);
121
122 setLayout(new BorderLayout());
123 add(tabbedPane, BorderLayout.CENTER);
124
125 conflictResolvers.add(propertiesMerger);
126 conflictResolvers.add(tagMerger);
127 conflictResolvers.add(nodeListMerger);
128 conflictResolvers.add(relationMemberMerger);
129 }
130
131 /**
132 * constructor
133 */
134 public ConflictResolver() {
135 resolvedCompletely = false;
136 build();
137 loadIcons();
138 }
139
140 /**
141 * Sets the {@see OsmPrimitive} in the role "my"
142 *
143 * @param my the primitive in the role "my"
144 */
145 protected void setMy(OsmPrimitive my) {
146 OsmPrimitive old = this.my;
147 this.my = my;
148 if (old != this.my) {
149 firePropertyChange(MY_PRIMITIVE_PROP, old, this.my);
150 }
151 }
152
153 /**
154 * Sets the {@see OsmPrimitive} in the role "their".
155 *
156 * @param their the primitive in the role "their"
157 */
158 protected void setTheir(OsmPrimitive their) {
159 OsmPrimitive old = this.their;
160 this.their = their;
161 if (old != this.their) {
162 firePropertyChange(THEIR_PRIMITIVE_PROP, old, this.their);
163 }
164 }
165
166 /**
167 * handles property change events
168 */
169 public void propertyChange(PropertyChangeEvent evt) {
170 if (evt.getPropertyName().equals(TagMergeModel.PROP_NUM_UNDECIDED_TAGS)) {
171 int newValue = (Integer)evt.getNewValue();
172 if (newValue == 0) {
173 tabbedPane.setTitleAt(1, tr("Tags"));
174 tabbedPane.setToolTipTextAt(1, tr("No pending tag conflicts to be resolved"));
175 tabbedPane.setIconAt(1, mergeComplete);
176 } else {
177 tabbedPane.setTitleAt(1, trn("Tags({0} conflict)", "Tags({0} conflicts)", newValue, newValue));
178 tabbedPane.setToolTipTextAt(1, trn("{0} pending tag conflict to be resolved", "{0} pending tag conflicts to be resolved", newValue, newValue));
179 tabbedPane.setIconAt(1, mergeIncomplete);
180 }
181 updateResolvedCompletely();
182 } else if (evt.getPropertyName().equals(ListMergeModel.FROZEN_PROP)) {
183 boolean frozen = (Boolean)evt.getNewValue();
184 if (evt.getSource() == nodeListMerger.getModel() && my instanceof Way) {
185 if (frozen) {
186 tabbedPane.setTitleAt(2, tr("Nodes(resolved)"));
187 tabbedPane.setToolTipTextAt(2, tr("Merged node list frozen. No pending conflicts in the node list of this way"));
188 tabbedPane.setIconAt(2, mergeComplete);
189 } else {
190 tabbedPane.setTitleAt(2, tr("Nodes(with conflicts)"));
191 tabbedPane.setToolTipTextAt(2,tr("Pending conflicts in the node list of this way"));
192 tabbedPane.setIconAt(2, mergeIncomplete);
193 }
194 } else if (evt.getSource() == relationMemberMerger.getModel() && my instanceof Relation) {
195 if (frozen) {
196 tabbedPane.setTitleAt(3, tr("Members(resolved)"));
197 tabbedPane.setToolTipTextAt(3, tr("Merged member list frozen. No pending conflicts in the member list of this relation"));
198 tabbedPane.setIconAt(3, mergeComplete);
199 } else {
200 tabbedPane.setTitleAt(3, tr("Members(with conflicts)"));
201 tabbedPane.setToolTipTextAt(3, tr("Pending conflicts in the member list of this relation"));
202 tabbedPane.setIconAt(3, mergeIncomplete);
203 }
204 }
205 updateResolvedCompletely();
206 } else if (evt.getPropertyName().equals(PropertiesMergeModel.RESOLVED_COMPLETELY_PROP)) {
207 boolean resolved = (Boolean)evt.getNewValue();
208 if (resolved) {
209 tabbedPane.setTitleAt(0, tr("Properties"));
210 tabbedPane.setToolTipTextAt(0, tr("No pending property conflicts"));
211 tabbedPane.setIconAt(0, mergeComplete);
212 } else {
213 tabbedPane.setTitleAt(0, tr("Properties(with conflicts)"));
214 tabbedPane.setToolTipTextAt(0, tr("Pending property conflicts to be resolved"));
215 tabbedPane.setIconAt(0, mergeIncomplete);
216 }
217 updateResolvedCompletely();
218 } else if (PropertiesMergeModel.DELETE_PRIMITIVE_PROP.equals(evt.getPropertyName())) {
219 for (IConflictResolver resolver: conflictResolvers) {
220 resolver.deletePrimitive((Boolean) evt.getNewValue());
221 }
222 }
223 }
224
225 /**
226 * populates the conflict resolver with the conflicts between my and their
227 *
228 * @param my my primitive (i.e. the primitive in the local dataset)
229 * @param their their primitive (i.e. the primitive in the server dataset)
230 *
231 */
232 public void populate(Conflict<? extends OsmPrimitive> conflict) {
233 setMy(conflict.getMy());
234 setTheir(conflict.getTheir());
235 this.conflict = conflict;
236 propertiesMerger.populate(conflict);
237 if (propertiesMerger.getModel().hasVisibleStateConflict()) {
238 tabbedPane.setEnabledAt(1, false);
239 tabbedPane.setEnabledAt(2, false);
240 tabbedPane.setEnabledAt(3, false);
241 return;
242 }
243 tabbedPane.setEnabledAt(0, true);
244 tagMerger.populate(conflict);
245 tabbedPane.setEnabledAt(1, true);
246
247 if (my instanceof Node) {
248 tabbedPane.setEnabledAt(2,false);
249 tabbedPane.setEnabledAt(3,false);
250 } else if (my instanceof Way) {
251 nodeListMerger.populate(conflict);
252 tabbedPane.setEnabledAt(2, true);
253 tabbedPane.setEnabledAt(3, false);
254 tabbedPane.setTitleAt(3,tr("Members"));
255 tabbedPane.setIconAt(3, null);
256 } else if (my instanceof Relation) {
257 relationMemberMerger.populate(conflict);
258 tabbedPane.setEnabledAt(2, false);
259 tabbedPane.setTitleAt(2,tr("Nodes"));
260 tabbedPane.setIconAt(2, null);
261 tabbedPane.setEnabledAt(3, true);
262 }
263 updateResolvedCompletely();
264 }
265
266 /**
267 * Builds the resolution command(s) for the resolved conflicts in this
268 * ConflictResolver
269 *
270 * @return the resolution command
271 */
272 public Command buildResolveCommand() throws OperationCancelledException {
273 ArrayList<Command> commands = new ArrayList<Command>();
274 if (propertiesMerger.getModel().hasVisibleStateConflict()) {
275 if (propertiesMerger.getModel().isDecidedVisibleState()) {
276 commands.addAll(propertiesMerger.getModel().buildResolveCommand(conflict));
277 }
278 } else {
279 if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
280 commands.add(tagMerger.getModel().buildResolveCommand(conflict));
281 }
282 commands.addAll(propertiesMerger.getModel().buildResolveCommand(conflict));
283 if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
284 NodeListMergeModel model =(NodeListMergeModel)nodeListMerger.getModel();
285 commands.add(model.buildResolveCommand(conflict));
286 } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) {
287 RelationMemberListMergeModel model =(RelationMemberListMergeModel)relationMemberMerger.getModel();
288 commands.add(model.buildResolveCommand((Relation)my, (Relation)their));
289 }
290 if (isResolvedCompletely()) {
291 commands.add(new VersionConflictResolveCommand(conflict));
292 commands.add(new ModifiedConflictResolveCommand(conflict));
293 }
294 }
295 return new SequenceCommand(tr("Conflict Resolution"), commands);
296 }
297
298 /**
299 * Updates the state of the property {@see #RESOLVED_COMPLETELY_PROP}
300 *
301 */
302 protected void updateResolvedCompletely() {
303 boolean oldValueResolvedCompletely = resolvedCompletely;
304 if (my instanceof Node) {
305 // resolve the version conflict if this is a node and all tag
306 // conflicts have been resolved
307 //
308 this.resolvedCompletely =
309 tagMerger.getModel().isResolvedCompletely()
310 && propertiesMerger.getModel().isResolvedCompletely();
311 } else if (my instanceof Way) {
312 // resolve the version conflict if this is a way, all tag
313 // conflicts have been resolved, and conflicts in the node list
314 // have been resolved
315 //
316 this.resolvedCompletely =
317 tagMerger.getModel().isResolvedCompletely()
318 && propertiesMerger.getModel().isResolvedCompletely()
319 && nodeListMerger.getModel().isFrozen();
320 } else if (my instanceof Relation) {
321 // resolve the version conflict if this is a relation, all tag
322 // conflicts and all conflicts in the member list
323 // have been resolved
324 //
325 this.resolvedCompletely =
326 tagMerger.getModel().isResolvedCompletely()
327 && propertiesMerger.getModel().isResolvedCompletely()
328 && relationMemberMerger.getModel().isFrozen();
329 }
330 if (this.resolvedCompletely != oldValueResolvedCompletely) {
331 firePropertyChange(RESOLVED_COMPLETELY_PROP, oldValueResolvedCompletely, this.resolvedCompletely);
332 }
333 }
334
335 /**
336 * Replies true all differences in this conflicts are resolved
337 *
338 * @return true all differences in this conflicts are resolved
339 */
340 public boolean isResolvedCompletely() {
341 return resolvedCompletely;
342 }
343}
Note: See TracBrowser for help on using the repository browser.