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

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

fix EDT violations observed while merging nodes

  • Property svn:eol-style set to native
File size: 25.2 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.size() > 0) {
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 {
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 public void actionPerformed(ActionEvent arg0) {
392 setCanceled(true);
393 setVisible(false);
394 }
395 }
396
397 class ApplyAction extends AbstractAction implements PropertyChangeListener {
398
399 public ApplyAction() {
400 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
401 putValue(Action.NAME, tr("Apply"));
402 putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
403 updateEnabledState();
404 }
405
406 public void actionPerformed(ActionEvent arg0) {
407 setVisible(false);
408 pnlTagConflictResolver.rememberPreferences();
409 }
410
411 protected void updateEnabledState() {
412 setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
413 && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
414 }
415
416 public void propertyChange(PropertyChangeEvent evt) {
417 if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
418 updateEnabledState();
419 }
420 if (evt.getPropertyName().equals(RelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
421 updateEnabledState();
422 }
423 }
424 }
425
426 class AdjustDividerLocationAction extends WindowAdapter {
427 @Override
428 public void windowOpened(WindowEvent e) {
429 int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
430 int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
431 if (numTagDecisions > 0 && numRelationDecisions > 0) {
432 spTagConflictTypes.setDividerLocation(0.5);
433 }
434 }
435 }
436
437 static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
438 private double dividerLocation;
439
440 public AutoAdjustingSplitPane(int newOrientation) {
441 super(newOrientation);
442 addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
443 addHierarchyBoundsListener(this);
444 }
445
446 public void ancestorResized(HierarchyEvent e) {
447 setDividerLocation((int) (dividerLocation * getHeight()));
448 }
449
450 public void ancestorMoved(HierarchyEvent e) {
451 // do nothing
452 }
453
454 public void propertyChange(PropertyChangeEvent evt) {
455 if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
456 int newVal = (Integer) evt.getNewValue();
457 if (getHeight() != 0) {
458 dividerLocation = (double) newVal / (double) getHeight();
459 }
460 }
461 }
462 }
463
464 /**
465 * Replies the list of {@link Command commands} needed to resolve specified conflicts,
466 * by displaying if necessary a {@link CombinePrimitiveResolverDialog} to the user.
467 * This dialog will allow the user to choose conflict resolution actions.
468 *
469 * Non-expert users are informed first of the meaning of these operations, allowing them to cancel.
470 *
471 * @param tagsOfPrimitives The tag collection of the primitives to be combined.
472 * Should generally be equal to {@code TagCollection.unionOfAllPrimitives(primitives)}
473 * @param primitives The primitives to be combined
474 * @param targetPrimitives The primitives the collection of primitives are merged or combined to.
475 * @return The list of {@link Command commands} needed to apply resolution actions.
476 * @throws UserCancelException If the user cancelled a dialog.
477 */
478 public static List<Command> launchIfNecessary(
479 final TagCollection tagsOfPrimitives,
480 final Collection<? extends OsmPrimitive> primitives,
481 final Collection<? extends OsmPrimitive> targetPrimitives) throws UserCancelException {
482
483 CheckParameterUtil.ensureParameterNotNull(tagsOfPrimitives, "tagsOfPrimitives");
484 CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
485 CheckParameterUtil.ensureParameterNotNull(targetPrimitives, "targetPrimitives");
486
487 final TagCollection completeWayTags = new TagCollection(tagsOfPrimitives);
488 TagConflictResolutionUtil.combineTigerTags(completeWayTags);
489 TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, primitives);
490 final TagCollection tagsToEdit = new TagCollection(completeWayTags);
491 TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
492
493 final Set<Relation> parentRelations = OsmPrimitive.getParentRelations(primitives);
494
495 // Show information dialogs about conflicts to non-experts
496 if (!ExpertToggleAction.isExpert()) {
497 // Tag conflicts
498 if (!completeWayTags.isApplicableToPrimitive()) {
499 informAboutTagConflicts(primitives, completeWayTags);
500 }
501 // Relation membership conflicts
502 if (!parentRelations.isEmpty()) {
503 informAboutRelationMembershipConflicts(primitives, parentRelations);
504 }
505 }
506
507 // Build conflict resolution dialog
508 final CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
509
510 dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
511 dialog.getRelationMemberConflictResolverModel().populate(parentRelations, primitives);
512 dialog.prepareDefaultDecisions();
513
514 // Ensure a proper title is displayed instead of a previous target (fix #7925)
515 if (targetPrimitives.size() == 1) {
516 dialog.setTargetPrimitive(targetPrimitives.iterator().next());
517 } else {
518 dialog.setTargetPrimitive(null);
519 }
520
521 // Resolve tag conflicts if necessary
522 if (!completeWayTags.isApplicableToPrimitive() || !parentRelations.isEmpty()) {
523 dialog.setVisible(true);
524 if (dialog.isCanceled()) {
525 throw new UserCancelException();
526 }
527 }
528 List<Command> cmds = new LinkedList<Command>();
529 for (OsmPrimitive i : targetPrimitives) {
530 dialog.setTargetPrimitive(i);
531 cmds.addAll(dialog.buildResolutionCommands());
532 }
533 return cmds;
534 }
535
536 /**
537 * Inform a non-expert user about what relation membership conflict resolution means.
538 * @param primitives The primitives to be combined
539 * @param parentRelations The parent relations of the primitives
540 * @throws UserCancelException If the user cancels the dialog.
541 */
542 protected static void informAboutRelationMembershipConflicts(
543 final Collection<? extends OsmPrimitive> primitives,
544 final Set<Relation> parentRelations) throws UserCancelException {
545 String msg = trn("You are about to combine {1} objects, "
546 + "which are part of {0} relation:<br/>{2}"
547 + "Combining these objects may break this relation. If you are unsure, please cancel this operation.<br/>"
548 + "If you want to continue, you are shown a dialog to decide how to adapt the relation.<br/><br/>"
549 + "Do you want to continue?",
550 "You are about to combine {1} objects, "
551 + "which are part of {0} relations:<br/>{2}"
552 + "Combining these objects may break these relations. If you are unsure, please cancel this operation.<br/>"
553 + "If you want to continue, you are shown a dialog to decide how to adapt the relations.<br/><br/>"
554 + "Do you want to continue?",
555 parentRelations.size(), parentRelations.size(), primitives.size(),
556 DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(parentRelations));
557
558 if (!ConditionalOptionPaneUtil.showConfirmationDialog(
559 "combine_tags",
560 Main.parent,
561 "<html>" + msg + "</html>",
562 tr("Combine confirmation"),
563 JOptionPane.YES_NO_OPTION,
564 JOptionPane.QUESTION_MESSAGE,
565 JOptionPane.YES_OPTION)) {
566 throw new UserCancelException();
567 }
568 }
569
570 /**
571 * Inform a non-expert user about what tag conflict resolution means.
572 * @param primitives The primitives to be combined
573 * @param normalizedTags The normalized tag collection of the primitives to be combined
574 * @throws UserCancelException If the user cancels the dialog.
575 */
576 protected static void informAboutTagConflicts(
577 final Collection<? extends OsmPrimitive> primitives,
578 final TagCollection normalizedTags) throws UserCancelException {
579 String conflicts = Utils.joinAsHtmlUnorderedList(Utils.transform(normalizedTags.getKeysWithMultipleValues(), new Function<String, String>() {
580
581 @Override
582 public String apply(String key) {
583 return tr("{0} ({1})", key, Utils.join(tr(", "), Utils.transform(normalizedTags.getValues(key), new Function<String, String>() {
584
585 @Override
586 public String apply(String x) {
587 return x == null || x.isEmpty() ? tr("<i>missing</i>") : x;
588 }
589 })));
590 }
591 }));
592 String msg = tr("You are about to combine {0} objects, "
593 + "but the following tags are used conflictingly:<br/>{1}"
594 + "If these objects are combined, the resulting object may have unwanted tags.<br/>"
595 + "If you want to continue, you are shown a dialog to fix the conflicting tags.<br/><br/>"
596 + "Do you want to continue?",
597 primitives.size(), conflicts);
598
599 if (!ConditionalOptionPaneUtil.showConfirmationDialog(
600 "combine_tags",
601 Main.parent,
602 "<html>" + msg + "</html>",
603 tr("Combine confirmation"),
604 JOptionPane.YES_NO_OPTION,
605 JOptionPane.QUESTION_MESSAGE,
606 JOptionPane.YES_OPTION)) {
607 throw new UserCancelException();
608 }
609 }
610}
Note: See TracBrowser for help on using the repository browser.