source: osm/applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/hack/MyCombinePrimitiveResolverDialog.java@ 30766

Last change on this file since 30766 was 30766, checked in by donvip, 10 years ago

[josm-merge_overlap] fix #josm10668 - ClassCastException while merging ways

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