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

Last change on this file since 19022 was 19022, checked in by GerdP, 2 months ago

see #23305 and #23555:
-revert most of the changes in r18988, it caused too many problems with plugins which relied on the old behaviour

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