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

Last change on this file since 12046 was 12046, checked in by michael2402, 7 years ago

In conflict resolution dialog, only scroll tab contents and prevent vertical scroll bar from appearing on small screens just because the horizontal scroll bar makes the dialog content smaller.

  • Property svn:eol-style set to native
File size: 15.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.JComponent;
15import javax.swing.JPanel;
16import javax.swing.JScrollPane;
17import javax.swing.JTabbedPane;
18
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.command.conflict.ModifiedConflictResolveCommand;
22import org.openstreetmap.josm.command.conflict.VersionConflictResolveCommand;
23import org.openstreetmap.josm.data.conflict.Conflict;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.Way;
28import org.openstreetmap.josm.gui.conflict.pair.nodes.NodeListMerger;
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.RelationMemberMerger;
32import org.openstreetmap.josm.gui.conflict.pair.tags.TagMergeModel;
33import org.openstreetmap.josm.gui.conflict.pair.tags.TagMerger;
34import 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 * @since 1622
49 */
50public 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 public static 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 public static 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 public static final String THEIR_PRIMITIVE_PROP = ConflictResolver.class.getName() + ".theirPrimitive";
68
69 private JTabbedPane tabbedPane;
70 private TagMerger tagMerger;
71 private NodeListMerger nodeListMerger;
72 private RelationMemberMerger relationMemberMerger;
73 private PropertiesMerger propertiesMerger;
74 private final transient List<IConflictResolver> conflictResolvers = new ArrayList<>();
75 private transient OsmPrimitive my;
76 private transient OsmPrimitive their;
77 private transient 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 final void loadIcons() {
89 mergeComplete = ImageProvider.get("dialogs", "valid");
90 mergeIncomplete = ImageProvider.get("dialogs/conflict", "mergeincomplete");
91 }
92
93 /**
94 * builds the UI
95 */
96 protected final void build() {
97 tabbedPane = new JTabbedPane();
98
99 propertiesMerger = new PropertiesMerger();
100 propertiesMerger.setName("panel.propertiesmerger");
101 propertiesMerger.getModel().addPropertyChangeListener(this);
102 addTab(tr("Properties"), propertiesMerger);
103
104 tagMerger = new TagMerger();
105 tagMerger.setName("panel.tagmerger");
106 tagMerger.getModel().addPropertyChangeListener(this);
107 addTab(tr("Tags"), tagMerger);
108
109 nodeListMerger = new NodeListMerger();
110 nodeListMerger.setName("panel.nodelistmerger");
111 nodeListMerger.getModel().addPropertyChangeListener(this);
112 addTab(tr("Nodes"), nodeListMerger);
113
114 relationMemberMerger = new RelationMemberMerger();
115 relationMemberMerger.setName("panel.relationmembermerger");
116 relationMemberMerger.getModel().addPropertyChangeListener(this);
117 addTab(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 private void addTab(String title, JComponent tabContent) {
129 JScrollPane scrollPanel = new JScrollPane(tabContent);
130 tabbedPane.add(title, scrollPanel);
131 }
132
133 /**
134 * constructor
135 */
136 public ConflictResolver() {
137 resolvedCompletely = false;
138 build();
139 loadIcons();
140 }
141
142 /**
143 * Sets the {@link OsmPrimitive} in the role "my"
144 *
145 * @param my the primitive in the role "my"
146 */
147 protected void setMy(OsmPrimitive my) {
148 OsmPrimitive old = this.my;
149 this.my = my;
150 if (old != this.my) {
151 firePropertyChange(MY_PRIMITIVE_PROP, old, this.my);
152 }
153 }
154
155 /**
156 * Sets the {@link OsmPrimitive} in the role "their".
157 *
158 * @param their the primitive in the role "their"
159 */
160 protected void setTheir(OsmPrimitive their) {
161 OsmPrimitive old = this.their;
162 this.their = their;
163 if (old != this.their) {
164 firePropertyChange(THEIR_PRIMITIVE_PROP, old, this.their);
165 }
166 }
167
168 /**
169 * handles property change events
170 * @param evt the event
171 * @see TagMergeModel
172 * @see AbstractListMergeModel
173 * @see PropertiesMergeModel
174 */
175 @Override
176 public void propertyChange(PropertyChangeEvent evt) {
177 if (evt.getPropertyName().equals(TagMergeModel.PROP_NUM_UNDECIDED_TAGS)) {
178 int newValue = (Integer) evt.getNewValue();
179 if (newValue == 0) {
180 tabbedPane.setTitleAt(1, tr("Tags"));
181 tabbedPane.setToolTipTextAt(1, tr("No pending tag conflicts to be resolved"));
182 tabbedPane.setIconAt(1, mergeComplete);
183 } else {
184 tabbedPane.setTitleAt(1, trn("Tags({0} conflict)", "Tags({0} conflicts)", newValue, newValue));
185 tabbedPane.setToolTipTextAt(1,
186 trn("{0} pending tag conflict to be resolved", "{0} pending tag conflicts to be resolved", newValue, newValue));
187 tabbedPane.setIconAt(1, mergeIncomplete);
188 }
189 updateResolvedCompletely();
190 } else if (evt.getPropertyName().equals(AbstractListMergeModel.FROZEN_PROP)) {
191 boolean frozen = (Boolean) evt.getNewValue();
192 if (evt.getSource() == nodeListMerger.getModel() && my instanceof Way) {
193 if (frozen) {
194 tabbedPane.setTitleAt(2, tr("Nodes(resolved)"));
195 tabbedPane.setToolTipTextAt(2, tr("Merged node list frozen. No pending conflicts in the node list of this way"));
196 tabbedPane.setIconAt(2, mergeComplete);
197 } else {
198 tabbedPane.setTitleAt(2, tr("Nodes(with conflicts)"));
199 tabbedPane.setToolTipTextAt(2, tr("Pending conflicts in the node list of this way"));
200 tabbedPane.setIconAt(2, mergeIncomplete);
201 }
202 } else if (evt.getSource() == relationMemberMerger.getModel() && my instanceof Relation) {
203 if (frozen) {
204 tabbedPane.setTitleAt(3, tr("Members(resolved)"));
205 tabbedPane.setToolTipTextAt(3, tr("Merged member list frozen. No pending conflicts in the member list of this relation"));
206 tabbedPane.setIconAt(3, mergeComplete);
207 } else {
208 tabbedPane.setTitleAt(3, tr("Members(with conflicts)"));
209 tabbedPane.setToolTipTextAt(3, tr("Pending conflicts in the member list of this relation"));
210 tabbedPane.setIconAt(3, mergeIncomplete);
211 }
212 }
213 updateResolvedCompletely();
214 } else if (evt.getPropertyName().equals(PropertiesMergeModel.RESOLVED_COMPLETELY_PROP)) {
215 boolean resolved = (Boolean) evt.getNewValue();
216 if (resolved) {
217 tabbedPane.setTitleAt(0, tr("Properties"));
218 tabbedPane.setToolTipTextAt(0, tr("No pending property conflicts"));
219 tabbedPane.setIconAt(0, mergeComplete);
220 } else {
221 tabbedPane.setTitleAt(0, tr("Properties(with conflicts)"));
222 tabbedPane.setToolTipTextAt(0, tr("Pending property conflicts to be resolved"));
223 tabbedPane.setIconAt(0, mergeIncomplete);
224 }
225 updateResolvedCompletely();
226 } else if (PropertiesMergeModel.DELETE_PRIMITIVE_PROP.equals(evt.getPropertyName())) {
227 for (IConflictResolver resolver: conflictResolvers) {
228 resolver.deletePrimitive((Boolean) evt.getNewValue());
229 }
230 }
231 }
232
233 /**
234 * populates the conflict resolver with the conflicts between my and their
235 *
236 * @param conflict the conflict data set
237 */
238 public void populate(Conflict<? extends OsmPrimitive> conflict) {
239 setMy(conflict.getMy());
240 setTheir(conflict.getTheir());
241 this.conflict = conflict;
242 propertiesMerger.populate(conflict);
243
244 tabbedPane.setEnabledAt(0, true);
245 tagMerger.populate(conflict);
246 tabbedPane.setEnabledAt(1, true);
247
248 if (my instanceof Node) {
249 tabbedPane.setEnabledAt(2, false);
250 tabbedPane.setEnabledAt(3, false);
251 } else if (my instanceof Way) {
252 nodeListMerger.populate(conflict);
253 tabbedPane.setEnabledAt(2, true);
254 tabbedPane.setEnabledAt(3, false);
255 tabbedPane.setTitleAt(3, tr("Members"));
256 tabbedPane.setIconAt(3, null);
257 } else if (my instanceof Relation) {
258 relationMemberMerger.populate(conflict);
259 tabbedPane.setEnabledAt(2, false);
260 tabbedPane.setTitleAt(2, tr("Nodes"));
261 tabbedPane.setIconAt(2, null);
262 tabbedPane.setEnabledAt(3, true);
263 }
264 updateResolvedCompletely();
265 selectFirstTabWithConflicts();
266 }
267
268 /**
269 * {@link JTabbedPane#setSelectedIndex(int) Selects} the first tab with conflicts
270 */
271 public void selectFirstTabWithConflicts() {
272 for (int i = 0; i < tabbedPane.getTabCount(); i++) {
273 if (tabbedPane.isEnabledAt(i) && mergeIncomplete.equals(tabbedPane.getIconAt(i))) {
274 tabbedPane.setSelectedIndex(i);
275 break;
276 }
277 }
278 }
279
280 /**
281 * Builds the resolution command(s) for the resolved conflicts in this ConflictResolver
282 *
283 * @return the resolution command
284 */
285 public Command buildResolveCommand() {
286 List<Command> commands = new ArrayList<>();
287
288 if (tagMerger.getModel().getNumResolvedConflicts() > 0) {
289 commands.add(tagMerger.getModel().buildResolveCommand(conflict));
290 }
291 commands.addAll(propertiesMerger.getModel().buildResolveCommand(conflict));
292 if (my instanceof Way && nodeListMerger.getModel().isFrozen()) {
293 commands.add(nodeListMerger.getModel().buildResolveCommand(conflict));
294 } else if (my instanceof Relation && relationMemberMerger.getModel().isFrozen()) {
295 commands.add(relationMemberMerger.getModel().buildResolveCommand(conflict));
296 }
297 if (isResolvedCompletely()) {
298 commands.add(new VersionConflictResolveCommand(conflict));
299 commands.add(new ModifiedConflictResolveCommand(conflict));
300 }
301 return new SequenceCommand(tr("Conflict Resolution"), commands);
302 }
303
304 /**
305 * Updates the state of the property {@link #RESOLVED_COMPLETELY_PROP}
306 *
307 */
308 protected void updateResolvedCompletely() {
309 boolean oldValueResolvedCompletely = resolvedCompletely;
310 if (my instanceof Node) {
311 // resolve the version conflict if this is a node and all tag
312 // conflicts have been resolved
313 //
314 this.resolvedCompletely =
315 tagMerger.getModel().isResolvedCompletely()
316 && propertiesMerger.getModel().isResolvedCompletely();
317 } else if (my instanceof Way) {
318 // resolve the version conflict if this is a way, all tag
319 // conflicts have been resolved, and conflicts in the node list
320 // have been resolved
321 //
322 this.resolvedCompletely =
323 tagMerger.getModel().isResolvedCompletely()
324 && propertiesMerger.getModel().isResolvedCompletely()
325 && nodeListMerger.getModel().isFrozen();
326 } else if (my instanceof Relation) {
327 // resolve the version conflict if this is a relation, all tag
328 // conflicts and all conflicts in the member list
329 // have been resolved
330 //
331 this.resolvedCompletely =
332 tagMerger.getModel().isResolvedCompletely()
333 && propertiesMerger.getModel().isResolvedCompletely()
334 && relationMemberMerger.getModel().isFrozen();
335 }
336 if (this.resolvedCompletely != oldValueResolvedCompletely) {
337 firePropertyChange(RESOLVED_COMPLETELY_PROP, oldValueResolvedCompletely, this.resolvedCompletely);
338 }
339 }
340
341 /**
342 * Replies true all differences in this conflicts are resolved
343 *
344 * @return true all differences in this conflicts are resolved
345 */
346 public boolean isResolvedCompletely() {
347 return resolvedCompletely;
348 }
349
350 /**
351 * Adds all registered listeners by this conflict resolver
352 * @see #unregisterListeners()
353 * @since 10454
354 */
355 public void registerListeners() {
356 nodeListMerger.registerListeners();
357 relationMemberMerger.registerListeners();
358 }
359
360 /**
361 * Removes all registered listeners by this conflict resolver
362 */
363 public void unregisterListeners() {
364 nodeListMerger.unregisterListeners();
365 relationMemberMerger.unregisterListeners();
366 }
367
368 /**
369 * {@link PropertiesMerger#decideRemaining(MergeDecisionType) Decides/resolves} undecided conflicts to the given decision type
370 * @param decision the decision to take for undecided conflicts
371 * @throws AssertionError if {@link #isResolvedCompletely()} does not hold after applying the decision
372 */
373 public void decideRemaining(MergeDecisionType decision) {
374 propertiesMerger.decideRemaining(decision);
375 tagMerger.decideRemaining(decision);
376 if (my instanceof Way) {
377 nodeListMerger.decideRemaining(decision);
378 } else if (my instanceof Relation) {
379 relationMemberMerger.decideRemaining(decision);
380 }
381 updateResolvedCompletely();
382 if (!isResolvedCompletely()) {
383 throw new AssertionError("The conflict could not be resolved completely!");
384 }
385 }
386}
Note: See TracBrowser for help on using the repository browser.