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

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

fix #14374 - automatic tag conflict resolution of source for French cadastre and Canadian CanVec (patch from Tyndare, modified)

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