source: josm/trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java@ 2626

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

Fixed some of the warnings found by FindBugs

File size: 15.8 KB
Line 
1package org.openstreetmap.josm.gui.conflict.tags;
2
3import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.HierarchyBoundsListener;
12import java.awt.event.HierarchyEvent;
13import java.awt.event.WindowAdapter;
14import java.awt.event.WindowEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17import java.util.LinkedList;
18import java.util.List;
19
20import javax.swing.AbstractAction;
21import javax.swing.Action;
22import javax.swing.JDialog;
23import javax.swing.JLabel;
24import javax.swing.JOptionPane;
25import javax.swing.JPanel;
26import javax.swing.JSplitPane;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.command.ChangePropertyCommand;
30import org.openstreetmap.josm.command.Command;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.TagCollection;
34import org.openstreetmap.josm.data.osm.Way;
35import org.openstreetmap.josm.gui.DefaultNameFormatter;
36import org.openstreetmap.josm.gui.SideButton;
37import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
38import org.openstreetmap.josm.gui.help.HelpUtil;
39import org.openstreetmap.josm.tools.ImageProvider;
40import org.openstreetmap.josm.tools.WindowGeometry;
41
42/**
43 * This dialog helps to resolve conflicts occurring when ways are combined or
44 * nodes are merged.
45 *
46 * There is a singleton instance of this dialog which can be retrieved using
47 * {@see #getInstance()}.
48 *
49 * The dialog uses two models: one for resolving tag conflicts, the other
50 * for resolving conflicts in relation memberships. For both models there are accessors,
51 * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
52 *
53 * Models have to be <strong>populated</strong> before the dialog is launched. Example:
54 * <pre>
55 * CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
56 * dialog.getTagConflictResolverModel().populate(aTagCollection);
57 * dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
58 * dialog.prepareDefaultDecisions();
59 * </pre>
60 *
61 * You should also set the target primitive which other primitives (ways or nodes) are
62 * merged to, see {@see #setTargetPrimitive(OsmPrimitive)}.
63 *
64 * After the dialog is closed use {@see #isCancelled()} to check whether the user canceled
65 * the dialog. If it wasn't canceled you may build a collection of {@see Command} objects
66 * which reflect the conflict resolution decisions the user made in the dialog:
67 * see {@see #buildResolutionCommands()}
68 *
69 *
70 */
71public class CombinePrimitiveResolverDialog extends JDialog {
72
73 /** the unique instance of the dialog */
74 static private CombinePrimitiveResolverDialog instance;
75
76 /**
77 * Replies the unique instance of the dialog
78 *
79 * @return the unique instance of the dialog
80 */
81 public static CombinePrimitiveResolverDialog getInstance() {
82 if (instance == null) {
83 instance = new CombinePrimitiveResolverDialog(Main.parent);
84 }
85 return instance;
86 }
87
88 private AutoAdjustingSplitPane spTagConflictTypes;
89 private TagConflictResolver pnlTagConflictResolver;
90 private RelationMemberConflictResolver pnlRelationMemberConflictResolver;
91 private boolean cancelled;
92 private JPanel pnlButtons;
93 private OsmPrimitive targetPrimitive;
94
95 /** the private help action */
96 private ContextSensitiveHelpAction helpAction;
97 /** the apply button */
98 private SideButton btnApply;
99
100 /**
101 * Replies the target primitive the collection of primitives is merged
102 * or combined to.
103 *
104 * @return the target primitive
105 */
106 public OsmPrimitive getTargetPrimitmive() {
107 return targetPrimitive;
108 }
109
110 /**
111 * Sets the primitive the collection of primitives is merged or combined
112 * to.
113 *
114 * @param primitive the target primitive
115 */
116 public void setTargetPrimitive(OsmPrimitive primitive) {
117 this.targetPrimitive = primitive;
118 updateTitle();
119 if (primitive instanceof Way) {
120 pnlRelationMemberConflictResolver.initForWayCombining();
121 } else if (primitive instanceof Node) {
122 pnlRelationMemberConflictResolver.initForNodeMerging();
123 }
124 }
125
126 protected void updateTitle() {
127 if (targetPrimitive == null) {
128 setTitle(tr("Conflicts when combining primitives"));
129 return;
130 }
131 if (targetPrimitive instanceof Way) {
132 setTitle(
133 tr(
134 "Conflicts when combining ways - combined way is ''{0}''",
135 targetPrimitive.getDisplayName(DefaultNameFormatter.getInstance())
136 )
137 );
138 helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
139 getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
140 } else if (targetPrimitive instanceof Node) {
141 setTitle(
142 tr(
143 "Conflicts when merging nodes - target node is ''{0}''",
144 targetPrimitive.getDisplayName(DefaultNameFormatter.getInstance())
145 )
146 );
147 helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
148 getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
149 }
150 }
151
152 protected void build() {
153 getContentPane().setLayout(new BorderLayout());
154 updateTitle();
155 spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
156 spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
157 spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
158 getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
159 addWindowListener(new AdjustDividerLocationAction());
160 HelpUtil.setHelpContext(getRootPane(), ht("/"));
161 }
162
163 protected JPanel buildTagConflictResolverPanel() {
164 pnlTagConflictResolver = new TagConflictResolver();
165 return pnlTagConflictResolver;
166 }
167
168 protected JPanel buildRelationMemberConflictResolverPanel() {
169 pnlRelationMemberConflictResolver = new RelationMemberConflictResolver();
170 return pnlRelationMemberConflictResolver;
171 }
172
173 protected JPanel buildButtonPanel() {
174 JPanel pnl = new JPanel();
175 pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
176
177 // -- apply button
178 ApplyAction applyAction = new ApplyAction();
179 pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
180 pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
181 btnApply = new SideButton(applyAction);
182 btnApply.setFocusable(true);
183 pnl.add(btnApply);
184
185 // -- cancel button
186 CancelAction cancelAction = new CancelAction();
187 pnl.add(new SideButton(cancelAction));
188
189 // -- help button
190 helpAction = new ContextSensitiveHelpAction();
191 pnl.add(new SideButton(helpAction));
192
193 return pnl;
194 }
195
196 public CombinePrimitiveResolverDialog(Component owner) {
197 super(JOptionPane.getFrameForComponent(owner),true /* modal */);
198 build();
199 }
200
201 public TagConflictResolverModel getTagConflictResolverModel() {
202 return pnlTagConflictResolver.getModel();
203 }
204
205 public RelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
206 return pnlRelationMemberConflictResolver.getModel();
207 }
208
209 protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
210 LinkedList<Command> cmds = new LinkedList<Command>();
211 for (String key : tc.getKeys()) {
212 if (tc.hasUniqueEmptyValue(key)) {
213 if (primitive.get(key) != null) {
214 cmds.add(new ChangePropertyCommand(primitive, key, null));
215 }
216 } else {
217 String value = tc.getJoinedValues(key);
218 if (!value.equals(primitive.get(key))) {
219 cmds.add(new ChangePropertyCommand(primitive, key, value));
220 }
221 }
222 }
223 return cmds;
224 }
225
226 public List<Command> buildResolutionCommands() {
227 List<Command> cmds = new LinkedList<Command>();
228
229 if (getTagConflictResolverModel().getNumDecisions() >0) {
230 TagCollection tc = getTagConflictResolverModel().getResolution();
231 cmds.addAll(buildTagChangeCommand(targetPrimitive, tc));
232 }
233 if (targetPrimitive.get("created_by") != null) {
234 cmds.add(new ChangePropertyCommand(targetPrimitive, "created_by", null));
235 }
236
237 if (getRelationMemberConflictResolverModel().getNumDecisions() >0) {
238 cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetPrimitive));
239 }
240
241 Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(
242 getRelationMemberConflictResolverModel().getModifiedRelations(targetPrimitive)
243 );
244 if (cmd != null) {
245 cmds.add(cmd);
246 }
247 return cmds;
248 }
249
250 protected void prepareDefaultTagDecisions() {
251 TagConflictResolverModel model = getTagConflictResolverModel();
252 for (int i =0; i< model.getRowCount(); i++) {
253 MultiValueResolutionDecision decision = model.getDecision(i);
254 List<String> values = decision.getValues();
255 values.remove("");
256 if (values.size() == 1) {
257 decision.keepOne(values.get(0));
258 } else {
259 decision.keepAll();
260 }
261 }
262 model.rebuild();
263 }
264
265 protected void prepareDefaultRelationDecisions() {
266 RelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
267 for (int i=0; i < model.getNumDecisions(); i++) {
268 model.getDecision(i).decide(RelationMemberConflictDecisionType.KEEP);
269 }
270 model.refresh();
271 }
272
273 public void prepareDefaultDecisions() {
274 prepareDefaultTagDecisions();
275 prepareDefaultRelationDecisions();
276 }
277
278 protected JPanel buildEmptyConflictsPanel() {
279 JPanel pnl = new JPanel();
280 pnl.setLayout(new BorderLayout());
281 pnl.add(new JLabel(tr("No conflicts to resolve")));
282 return pnl;
283 }
284
285 protected void prepareGUIBeforeConflictResolutionStarts() {
286 RelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
287 TagConflictResolverModel tagModel = getTagConflictResolverModel();
288 getContentPane().removeAll();
289
290 if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
291 // display both, the dialog for resolving relation conflicts and for resolving
292 // tag conflicts
293 spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
294 spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
295 getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
296 } else if (relModel.getNumDecisions() > 0) {
297 // relation conflicts only
298 //
299 getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
300 } else if (tagModel.getNumDecisions() >0) {
301 // tag conflicts only
302 //
303 getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
304 } else {
305 getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
306 }
307
308 getContentPane().add(pnlButtons, BorderLayout.SOUTH);
309 validate();
310 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
311 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
312 if (numTagDecisions > 0 && numRelationDecisions > 0) {
313 spTagConflictTypes.setDividerLocation(0.5);
314 }
315 pnlRelationMemberConflictResolver.prepareForEditing();
316 }
317
318 protected void setCancelled(boolean cancelled) {
319 this.cancelled = cancelled;
320 }
321
322 public boolean isCancelled() {
323 return cancelled;
324 }
325
326 @Override
327 public void setVisible(boolean visible) {
328 if (visible) {
329 prepareGUIBeforeConflictResolutionStarts();
330 new WindowGeometry(
331 getClass().getName() + ".geometry",
332 WindowGeometry.centerInWindow(
333 Main.parent,
334 new Dimension(600,400)
335 )
336 ).applySafe(this);
337 setCancelled(false);
338 btnApply.requestFocusInWindow();
339 } else {
340 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
341 }
342 super.setVisible(visible);
343 }
344
345 class CancelAction extends AbstractAction {
346
347 public CancelAction() {
348 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
349 putValue(Action.NAME, tr("Cancel"));
350 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
351 setEnabled(true);
352 }
353
354 public void actionPerformed(ActionEvent arg0) {
355 setCancelled(true);
356 setVisible(false);
357 }
358 }
359
360 class ApplyAction extends AbstractAction implements PropertyChangeListener {
361
362 public ApplyAction() {
363 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
364 putValue(Action.NAME, tr("Apply"));
365 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
366 updateEnabledState();
367 }
368
369 public void actionPerformed(ActionEvent arg0) {
370 setVisible(false);
371 }
372
373 protected void updateEnabledState() {
374 setEnabled(
375 pnlTagConflictResolver.getModel().getNumConflicts() == 0
376 && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0
377 );
378 }
379
380 public void propertyChange(PropertyChangeEvent evt) {
381 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
382 updateEnabledState();
383 }
384 if (evt.getPropertyName().equals(RelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
385 updateEnabledState();
386 }
387 }
388 }
389
390 class AdjustDividerLocationAction extends WindowAdapter {
391 @Override
392 public void windowOpened(WindowEvent e) {
393 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
394 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
395 if (numTagDecisions > 0 && numRelationDecisions > 0) {
396 spTagConflictTypes.setDividerLocation(0.5);
397 }
398 }
399 }
400
401 static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
402 private double dividerLocation;
403
404 public AutoAdjustingSplitPane(int newOrientation) {
405 super(newOrientation);
406 addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY,this);
407 addHierarchyBoundsListener(this);
408 }
409
410 public void ancestorResized(HierarchyEvent e) {
411 setDividerLocation((int)(dividerLocation * getHeight()));
412 }
413
414 public void ancestorMoved(HierarchyEvent e) {
415 // do nothing
416 }
417
418 public void propertyChange(PropertyChangeEvent evt) {
419 if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
420 int newVal = (Integer)evt.getNewValue();
421 if (getHeight() != 0) {
422 dividerLocation = (double)newVal / (double)getHeight();
423 }
424 }
425 }
426 }
427}
Note: See TracBrowser for help on using the repository browser.