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