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

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

see #15182 - move NameFormatter* from gui to data.osm

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