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

Last change on this file since 6679 was 6679, checked in by stoecker, 10 years ago

see #9110 - fix singular forms, even if they are useless

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