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