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

Last change on this file since 7661 was 7661, checked in by Don-vip, 9 years ago

see #10668 - code refactoring to avoid hacks in merge-overlap plugin

  • Property svn:eol-style set to native
File size: 27.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.conflict.tags;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.BorderLayout;
9import java.awt.Component;
10import java.awt.Dimension;
11import java.awt.FlowLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.HierarchyBoundsListener;
14import java.awt.event.HierarchyEvent;
15import java.awt.event.WindowAdapter;
16import java.awt.event.WindowEvent;
17import java.beans.PropertyChangeEvent;
18import java.beans.PropertyChangeListener;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.HashMap;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25import java.util.Set;
26
27import javax.swing.AbstractAction;
28import javax.swing.Action;
29import javax.swing.JDialog;
30import javax.swing.JLabel;
31import javax.swing.JOptionPane;
32import javax.swing.JPanel;
33import javax.swing.JSplitPane;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.actions.ExpertToggleAction;
37import org.openstreetmap.josm.command.ChangePropertyCommand;
38import org.openstreetmap.josm.command.Command;
39import org.openstreetmap.josm.corrector.UserCancelException;
40import org.openstreetmap.josm.data.osm.Node;
41import org.openstreetmap.josm.data.osm.OsmPrimitive;
42import org.openstreetmap.josm.data.osm.Relation;
43import org.openstreetmap.josm.data.osm.TagCollection;
44import org.openstreetmap.josm.data.osm.Way;
45import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
46import org.openstreetmap.josm.gui.DefaultNameFormatter;
47import org.openstreetmap.josm.gui.SideButton;
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.CheckParameterUtil;
52import org.openstreetmap.josm.tools.ImageProvider;
53import org.openstreetmap.josm.tools.MultiMap;
54import org.openstreetmap.josm.tools.Predicates;
55import org.openstreetmap.josm.tools.Utils;
56import org.openstreetmap.josm.tools.Utils.Function;
57import org.openstreetmap.josm.tools.WindowGeometry;
58
59/**
60 * This dialog helps to resolve conflicts occurring when ways are combined or
61 * nodes are merged.
62 *
63 * Usage: {@link #launchIfNecessary} followed by {@link #buildResolutionCommands}.
64 *
65 * Prior to {@link #launchIfNecessary}, the following usage sequence was needed:
66 *
67 * There is a singleton instance of this dialog which can be retrieved using
68 * {@link #getInstance()}.
69 *
70 * The dialog uses two models: one for resolving tag conflicts, the other
71 * for resolving conflicts in relation memberships. For both models there are accessors,
72 * i.e {@link #getTagConflictResolverModel()} and {@link #getRelationMemberConflictResolverModel()}.
73 *
74 * Models have to be <strong>populated</strong> before the dialog is launched. Example:
75 * <pre>
76 * CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
77 * dialog.getTagConflictResolverModel().populate(aTagCollection);
78 * dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
79 * dialog.prepareDefaultDecisions();
80 * </pre>
81 *
82 * You should also set the target primitive which other primitives (ways or nodes) are
83 * merged to, see {@link #setTargetPrimitive(OsmPrimitive)}.
84 *
85 * After the dialog is closed use {@link #isCanceled()} to check whether the user canceled
86 * the dialog. If it wasn't canceled you may build a collection of {@link Command} objects
87 * which reflect the conflict resolution decisions the user made in the dialog:
88 * see {@link #buildResolutionCommands()}
89 */
90public class CombinePrimitiveResolverDialog extends JDialog {
91
92 /** the unique instance of the dialog */
93 private static CombinePrimitiveResolverDialog instance;
94
95 /**
96 * Replies the unique instance of the dialog
97 *
98 * @return the unique instance of the dialog
99 * @deprecated use {@link #launchIfNecessary} instead.
100 */
101 @Deprecated
102 public static CombinePrimitiveResolverDialog getInstance() {
103 if (instance == null) {
104 GuiHelper.runInEDTAndWait(new Runnable() {
105 @Override public void run() {
106 instance = new CombinePrimitiveResolverDialog(Main.parent);
107 }
108 });
109 }
110 return instance;
111 }
112
113 private AutoAdjustingSplitPane spTagConflictTypes;
114 private TagConflictResolver pnlTagConflictResolver;
115 private RelationMemberConflictResolver pnlRelationMemberConflictResolver;
116 private boolean canceled;
117 private JPanel pnlButtons;
118 private OsmPrimitive targetPrimitive;
119
120 /** the private help action */
121 private ContextSensitiveHelpAction helpAction;
122 /** the apply button */
123 private SideButton btnApply;
124
125 /**
126 * Replies the target primitive the collection of primitives is merged
127 * or combined to.
128 *
129 * @return the target primitive
130 */
131 public OsmPrimitive getTargetPrimitmive() {
132 return targetPrimitive;
133 }
134
135 /**
136 * Sets the primitive the collection of primitives is merged or combined to.
137 *
138 * @param primitive the target primitive
139 */
140 public void setTargetPrimitive(final OsmPrimitive primitive) {
141 this.targetPrimitive = primitive;
142 GuiHelper.runInEDTAndWait(new Runnable() {
143 @Override public void run() {
144 updateTitle();
145 if (primitive instanceof Way) {
146 pnlRelationMemberConflictResolver.initForWayCombining();
147 } else if (primitive instanceof Node) {
148 pnlRelationMemberConflictResolver.initForNodeMerging();
149 }
150 }
151 });
152 }
153
154 protected void updateTitle() {
155 if (targetPrimitive == null) {
156 setTitle(tr("Conflicts when combining primitives"));
157 return;
158 }
159 if (targetPrimitive instanceof Way) {
160 setTitle(tr("Conflicts when combining ways - combined way is ''{0}''", targetPrimitive
161 .getDisplayName(DefaultNameFormatter.getInstance())));
162 helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
163 getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
164 } else if (targetPrimitive instanceof Node) {
165 setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
166 .getDisplayName(DefaultNameFormatter.getInstance())));
167 helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
168 getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
169 }
170 }
171
172 protected final void build() {
173 getContentPane().setLayout(new BorderLayout());
174 updateTitle();
175 spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
176 spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
177 spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
178 getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
179 addWindowListener(new AdjustDividerLocationAction());
180 HelpUtil.setHelpContext(getRootPane(), ht("/"));
181 }
182
183 protected JPanel buildTagConflictResolverPanel() {
184 pnlTagConflictResolver = new TagConflictResolver();
185 return pnlTagConflictResolver;
186 }
187
188 protected JPanel buildRelationMemberConflictResolverPanel() {
189 pnlRelationMemberConflictResolver = new RelationMemberConflictResolver(new RelationMemberConflictResolverModel());
190 return pnlRelationMemberConflictResolver;
191 }
192
193 protected JPanel buildButtonPanel() {
194 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
195
196 // -- apply button
197 ApplyAction applyAction = new ApplyAction();
198 pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
199 pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
200 btnApply = new SideButton(applyAction);
201 btnApply.setFocusable(true);
202 pnl.add(btnApply);
203
204 // -- cancel button
205 CancelAction cancelAction = new CancelAction();
206 pnl.add(new SideButton(cancelAction));
207
208 // -- help button
209 helpAction = new ContextSensitiveHelpAction();
210 pnl.add(new SideButton(helpAction));
211
212 return pnl;
213 }
214
215 /**
216 * Constructs a new {@code CombinePrimitiveResolverDialog}.
217 * @param parent The parent component in which this dialog will be displayed.
218 */
219 public CombinePrimitiveResolverDialog(Component parent) {
220 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
221 build();
222 }
223
224 /**
225 * Replies the tag conflict resolver model.
226 * @return The tag conflict resolver model.
227 */
228 public TagConflictResolverModel getTagConflictResolverModel() {
229 return pnlTagConflictResolver.getModel();
230 }
231
232 /**
233 * Replies the relation membership conflict resolver model.
234 * @return The relation membership conflict resolver model.
235 */
236 public RelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
237 return pnlRelationMemberConflictResolver.getModel();
238 }
239
240 /**
241 * Replies true if all tag and relation member conflicts have been decided.
242 *
243 * @return true if all tag and relation member conflicts have been decided; false otherwise
244 */
245 public boolean isResolvedCompletely() {
246 return getTagConflictResolverModel().isResolvedCompletely()
247 && getRelationMemberConflictResolverModel().isResolvedCompletely();
248 }
249
250 protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
251 LinkedList<Command> cmds = new LinkedList<>();
252 for (String key : tc.getKeys()) {
253 if (tc.hasUniqueEmptyValue(key)) {
254 if (primitive.get(key) != null) {
255 cmds.add(new ChangePropertyCommand(primitive, key, null));
256 }
257 } else {
258 String value = tc.getJoinedValues(key);
259 if (!value.equals(primitive.get(key))) {
260 cmds.add(new ChangePropertyCommand(primitive, key, value));
261 }
262 }
263 }
264 return cmds;
265 }
266
267 /**
268 * Replies the list of {@link Command commands} needed to apply resolution choices.
269 * @return The list of {@link Command commands} needed to apply resolution choices.
270 */
271 public List<Command> buildResolutionCommands() {
272 List<Command> cmds = new LinkedList<>();
273
274 TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
275 if (!allResolutions.isEmpty()) {
276 cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
277 }
278 for(String p : OsmPrimitive.getDiscardableKeys()) {
279 if (targetPrimitive.get(p) != null) {
280 cmds.add(new ChangePropertyCommand(targetPrimitive, p, null));
281 }
282 }
283
284 if (getRelationMemberConflictResolverModel().getNumDecisions() > 0) {
285 cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetPrimitive));
286 }
287
288 Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
289 .getModifiedRelations(targetPrimitive));
290 if (cmd != null) {
291 cmds.add(cmd);
292 }
293 return cmds;
294 }
295
296 protected void prepareDefaultTagDecisions() {
297 TagConflictResolverModel model = getTagConflictResolverModel();
298 model.prepareDefaultTagDecisions();
299 model.rebuild();
300 }
301
302 protected void prepareDefaultRelationDecisions() {
303 final RelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
304 final Map<Relation, Integer> numberOfKeepResolutions = new HashMap<>();
305 final MultiMap<OsmPrimitive, Relation> resolvedRelationsPerPrimitive = new MultiMap<>();
306
307 for (int i = 0; i < model.getNumDecisions(); i++) {
308 final RelationMemberConflictDecision decision = model.getDecision(i);
309 final Relation r = decision.getRelation();
310 final OsmPrimitive p = decision.getOriginalPrimitive();
311 if (!numberOfKeepResolutions.containsKey(r)) {
312 decision.decide(RelationMemberConflictDecisionType.KEEP);
313 numberOfKeepResolutions.put(r, 1);
314 resolvedRelationsPerPrimitive.put(p, r);
315 continue;
316 }
317
318 final Integer keepResolutions = numberOfKeepResolutions.get(r);
319 final Collection<Relation> resolvedRelations = Utils.firstNonNull(resolvedRelationsPerPrimitive.get(p), Collections.<Relation>emptyList());
320 if (keepResolutions <= Utils.filter(resolvedRelations, Predicates.equalTo(r)).size()) {
321 // old relation contains one primitive more often than the current resolution => keep the current member
322 decision.decide(RelationMemberConflictDecisionType.KEEP);
323 numberOfKeepResolutions.put(r, keepResolutions + 1);
324 resolvedRelationsPerPrimitive.put(p, r);
325 } else {
326 decision.decide(RelationMemberConflictDecisionType.REMOVE);
327 resolvedRelationsPerPrimitive.put(p, r);
328 }
329 }
330 model.refresh();
331 }
332
333 /**
334 * Prepares the default decisions for populated tag and relation membership conflicts.
335 */
336 public void prepareDefaultDecisions() {
337 prepareDefaultTagDecisions();
338 prepareDefaultRelationDecisions();
339 }
340
341 protected JPanel buildEmptyConflictsPanel() {
342 JPanel pnl = new JPanel(new BorderLayout());
343 pnl.add(new JLabel(tr("No conflicts to resolve")));
344 return pnl;
345 }
346
347 protected void prepareGUIBeforeConflictResolutionStarts() {
348 RelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
349 TagConflictResolverModel tagModel = getTagConflictResolverModel();
350 getContentPane().removeAll();
351
352 if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
353 // display both, the dialog for resolving relation conflicts and for resolving tag conflicts
354 spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
355 spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
356 getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
357 } else if (relModel.getNumDecisions() > 0) {
358 // relation conflicts only
359 getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
360 } else if (tagModel.getNumDecisions() > 0) {
361 // tag conflicts only
362 getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
363 } else {
364 getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
365 }
366
367 getContentPane().add(pnlButtons, BorderLayout.SOUTH);
368 validate();
369 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
370 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
371 if (numTagDecisions > 0 && numRelationDecisions > 0) {
372 spTagConflictTypes.setDividerLocation(0.5);
373 }
374 pnlRelationMemberConflictResolver.prepareForEditing();
375 }
376
377 protected void setCanceled(boolean canceled) {
378 this.canceled = canceled;
379 }
380
381 /**
382 * Determines if this dialog has been cancelled.
383 * @return true if this dialog has been cancelled, false otherwise.
384 */
385 public boolean isCanceled() {
386 return canceled;
387 }
388
389 @Override
390 public void setVisible(boolean visible) {
391 if (visible) {
392 prepareGUIBeforeConflictResolutionStarts();
393 new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
394 new Dimension(600, 400))).applySafe(this);
395 setCanceled(false);
396 btnApply.requestFocusInWindow();
397 } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
398 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
399 }
400 super.setVisible(visible);
401 }
402
403 class CancelAction extends AbstractAction {
404
405 public CancelAction() {
406 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
407 putValue(Action.NAME, tr("Cancel"));
408 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
409 setEnabled(true);
410 }
411
412 @Override
413 public void actionPerformed(ActionEvent arg0) {
414 setCanceled(true);
415 setVisible(false);
416 }
417 }
418
419 class ApplyAction extends AbstractAction implements PropertyChangeListener {
420
421 public ApplyAction() {
422 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
423 putValue(Action.NAME, tr("Apply"));
424 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
425 updateEnabledState();
426 }
427
428 @Override
429 public void actionPerformed(ActionEvent arg0) {
430 setVisible(false);
431 pnlTagConflictResolver.rememberPreferences();
432 }
433
434 protected final void updateEnabledState() {
435 setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
436 && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
437 }
438
439 @Override
440 public void propertyChange(PropertyChangeEvent evt) {
441 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
442 updateEnabledState();
443 }
444 if (evt.getPropertyName().equals(RelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
445 updateEnabledState();
446 }
447 }
448 }
449
450 class AdjustDividerLocationAction extends WindowAdapter {
451 @Override
452 public void windowOpened(WindowEvent e) {
453 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
454 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
455 if (numTagDecisions > 0 && numRelationDecisions > 0) {
456 spTagConflictTypes.setDividerLocation(0.5);
457 }
458 }
459 }
460
461 static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
462 private double dividerLocation;
463
464 public AutoAdjustingSplitPane(int newOrientation) {
465 super(newOrientation);
466 addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
467 addHierarchyBoundsListener(this);
468 }
469
470 @Override
471 public void ancestorResized(HierarchyEvent e) {
472 setDividerLocation((int) (dividerLocation * getHeight()));
473 }
474
475 @Override
476 public void ancestorMoved(HierarchyEvent e) {
477 // do nothing
478 }
479
480 @Override
481 public void propertyChange(PropertyChangeEvent evt) {
482 if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
483 int newVal = (Integer) evt.getNewValue();
484 if (getHeight() != 0) {
485 dividerLocation = (double) newVal / (double) getHeight();
486 }
487 }
488 }
489 }
490
491 /**
492 * Replies the list of {@link Command commands} needed to resolve specified conflicts,
493 * by displaying if necessary a {@link CombinePrimitiveResolverDialog} to the user.
494 * This dialog will allow the user to choose conflict resolution actions.
495 *
496 * Non-expert users are informed first of the meaning of these operations, allowing them to cancel.
497 *
498 * @param tagsOfPrimitives The tag collection of the primitives to be combined.
499 * Should generally be equal to {@code TagCollection.unionOfAllPrimitives(primitives)}
500 * @param primitives The primitives to be combined
501 * @param targetPrimitives The primitives the collection of primitives are merged or combined to.
502 * @return The list of {@link Command commands} needed to apply resolution actions.
503 * @throws UserCancelException If the user cancelled a dialog.
504 */
505 public static List<Command> launchIfNecessary(
506 final TagCollection tagsOfPrimitives,
507 final Collection<? extends OsmPrimitive> primitives,
508 final Collection<? extends OsmPrimitive> targetPrimitives) throws UserCancelException {
509
510 CheckParameterUtil.ensureParameterNotNull(tagsOfPrimitives, "tagsOfPrimitives");
511 CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
512 CheckParameterUtil.ensureParameterNotNull(targetPrimitives, "targetPrimitives");
513
514 final TagCollection completeWayTags = new TagCollection(tagsOfPrimitives);
515 TagConflictResolutionUtil.combineTigerTags(completeWayTags);
516 TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, primitives);
517 final TagCollection tagsToEdit = new TagCollection(completeWayTags);
518 TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
519
520 final Set<Relation> parentRelations = OsmPrimitive.getParentRelations(primitives);
521
522 // Show information dialogs about conflicts to non-experts
523 if (!ExpertToggleAction.isExpert()) {
524 // Tag conflicts
525 if (!completeWayTags.isApplicableToPrimitive()) {
526 informAboutTagConflicts(primitives, completeWayTags);
527 }
528 // Relation membership conflicts
529 if (!parentRelations.isEmpty()) {
530 informAboutRelationMembershipConflicts(primitives, parentRelations);
531 }
532 }
533
534 // Build conflict resolution dialog
535 final CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
536
537 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
538 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, primitives);
539 dialog.prepareDefaultDecisions();
540
541 // Ensure a proper title is displayed instead of a previous target (fix #7925)
542 if (targetPrimitives.size() == 1) {
543 dialog.setTargetPrimitive(targetPrimitives.iterator().next());
544 } else {
545 dialog.setTargetPrimitive(null);
546 }
547
548 // Resolve tag conflicts if necessary
549 if (!dialog.isResolvedCompletely()) {
550 dialog.setVisible(true);
551 if (dialog.isCanceled()) {
552 throw new UserCancelException();
553 }
554 }
555 List<Command> cmds = new LinkedList<>();
556 for (OsmPrimitive i : targetPrimitives) {
557 dialog.setTargetPrimitive(i);
558 cmds.addAll(dialog.buildResolutionCommands());
559 }
560 return cmds;
561 }
562
563 /**
564 * Inform a non-expert user about what relation membership conflict resolution means.
565 * @param primitives The primitives to be combined
566 * @param parentRelations The parent relations of the primitives
567 * @throws UserCancelException If the user cancels the dialog.
568 */
569 protected static void informAboutRelationMembershipConflicts(
570 final Collection<? extends OsmPrimitive> primitives,
571 final Set<Relation> parentRelations) throws UserCancelException {
572 /* I18n: object count < 2 is not possible */
573 String msg = trn("You are about to combine {1} object, "
574 + "which is part of {0} relation:<br/>{2}"
575 + "Combining these objects may break this relation. If you are unsure, please cancel this operation.<br/>"
576 + "If you want to continue, you are shown a dialog to decide how to adapt the relation.<br/><br/>"
577 + "Do you want to continue?",
578 "You are about to combine {1} objects, "
579 + "which are part of {0} relations:<br/>{2}"
580 + "Combining these objects may break these relations. If you are unsure, please cancel this operation.<br/>"
581 + "If you want to continue, you are shown a dialog to decide how to adapt the relations.<br/><br/>"
582 + "Do you want to continue?",
583 parentRelations.size(), parentRelations.size(), primitives.size(),
584 DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(parentRelations));
585
586 if (!ConditionalOptionPaneUtil.showConfirmationDialog(
587 "combine_tags",
588 Main.parent,
589 "<html>" + msg + "</html>",
590 tr("Combine confirmation"),
591 JOptionPane.YES_NO_OPTION,
592 JOptionPane.QUESTION_MESSAGE,
593 JOptionPane.YES_OPTION)) {
594 throw new UserCancelException();
595 }
596 }
597
598 /**
599 * Inform a non-expert user about what tag conflict resolution means.
600 * @param primitives The primitives to be combined
601 * @param normalizedTags The normalized tag collection of the primitives to be combined
602 * @throws UserCancelException If the user cancels the dialog.
603 */
604 protected static void informAboutTagConflicts(
605 final Collection<? extends OsmPrimitive> primitives,
606 final TagCollection normalizedTags) throws UserCancelException {
607 String conflicts = Utils.joinAsHtmlUnorderedList(Utils.transform(normalizedTags.getKeysWithMultipleValues(), new Function<String, String>() {
608
609 @Override
610 public String apply(String key) {
611 return tr("{0} ({1})", key, Utils.join(tr(", "), Utils.transform(normalizedTags.getValues(key), new Function<String, String>() {
612
613 @Override
614 public String apply(String x) {
615 return x == null || x.isEmpty() ? tr("<i>missing</i>") : x;
616 }
617 })));
618 }
619 }));
620 String msg = /* for correct i18n of plural forms - see #9110 */ trn("You are about to combine {0} objects, "
621 + "but the following tags are used conflictingly:<br/>{1}"
622 + "If these objects are combined, the resulting object may have unwanted tags.<br/>"
623 + "If you want to continue, you are shown a dialog to fix the conflicting tags.<br/><br/>"
624 + "Do you want to continue?", "You are about to combine {0} objects, "
625 + "but the following tags are used conflictingly:<br/>{1}"
626 + "If these objects are combined, the resulting object may have unwanted tags.<br/>"
627 + "If you want to continue, you are shown a dialog to fix the conflicting tags.<br/><br/>"
628 + "Do you want to continue?",
629 primitives.size(), primitives.size(), conflicts);
630
631 if (!ConditionalOptionPaneUtil.showConfirmationDialog(
632 "combine_tags",
633 Main.parent,
634 "<html>" + msg + "</html>",
635 tr("Combine confirmation"),
636 JOptionPane.YES_NO_OPTION,
637 JOptionPane.QUESTION_MESSAGE,
638 JOptionPane.YES_OPTION)) {
639 throw new UserCancelException();
640 }
641 }
642}
Note: See TracBrowser for help on using the repository browser.