| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.gui.conflict.tags;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 6 | import static org.openstreetmap.josm.tools.I18n.trn;
|
|---|
| 7 |
|
|---|
| 8 | import java.awt.BorderLayout;
|
|---|
| 9 | import java.awt.Component;
|
|---|
| 10 | import java.awt.Dimension;
|
|---|
| 11 | import java.awt.FlowLayout;
|
|---|
| 12 | import java.awt.GraphicsEnvironment;
|
|---|
| 13 | import java.awt.event.ActionEvent;
|
|---|
| 14 | import java.awt.event.HierarchyBoundsListener;
|
|---|
| 15 | import java.awt.event.HierarchyEvent;
|
|---|
| 16 | import java.awt.event.WindowAdapter;
|
|---|
| 17 | import java.awt.event.WindowEvent;
|
|---|
| 18 | import java.beans.PropertyChangeEvent;
|
|---|
| 19 | import java.beans.PropertyChangeListener;
|
|---|
| 20 | import java.util.Collection;
|
|---|
| 21 | import java.util.LinkedList;
|
|---|
| 22 | import java.util.List;
|
|---|
| 23 | import java.util.Set;
|
|---|
| 24 |
|
|---|
| 25 | import javax.swing.AbstractAction;
|
|---|
| 26 | import javax.swing.Action;
|
|---|
| 27 | import javax.swing.JDialog;
|
|---|
| 28 | import javax.swing.JLabel;
|
|---|
| 29 | import javax.swing.JOptionPane;
|
|---|
| 30 | import javax.swing.JPanel;
|
|---|
| 31 | import javax.swing.JSplitPane;
|
|---|
| 32 |
|
|---|
| 33 | import org.openstreetmap.josm.Main;
|
|---|
| 34 | import org.openstreetmap.josm.actions.ExpertToggleAction;
|
|---|
| 35 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
|---|
| 36 | import org.openstreetmap.josm.command.Command;
|
|---|
| 37 | import org.openstreetmap.josm.data.osm.Node;
|
|---|
| 38 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
|---|
| 39 | import org.openstreetmap.josm.data.osm.Relation;
|
|---|
| 40 | import org.openstreetmap.josm.data.osm.TagCollection;
|
|---|
| 41 | import org.openstreetmap.josm.data.osm.Way;
|
|---|
| 42 | import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
|
|---|
| 43 | import org.openstreetmap.josm.gui.DefaultNameFormatter;
|
|---|
| 44 | import org.openstreetmap.josm.gui.SideButton;
|
|---|
| 45 | import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
|
|---|
| 46 | import org.openstreetmap.josm.gui.help.HelpUtil;
|
|---|
| 47 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
|---|
| 48 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
|---|
| 49 | import org.openstreetmap.josm.tools.ImageProvider;
|
|---|
| 50 | import org.openstreetmap.josm.tools.UserCancelException;
|
|---|
| 51 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 52 | import org.openstreetmap.josm.tools.Utils.Function;
|
|---|
| 53 | import 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 #isApplied()} to check whether the dialog has been
|
|---|
| 82 | * applied. If it was applied 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 | */
|
|---|
| 86 | public class CombinePrimitiveResolverDialog extends JDialog {
|
|---|
| 87 |
|
|---|
| 88 | /** the unique instance of the dialog */
|
|---|
| 89 | private static 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 synchronized 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 | protected RelationMemberConflictResolver pnlRelationMemberConflictResolver;
|
|---|
| 112 | private boolean applied;
|
|---|
| 113 | private JPanel pnlButtons;
|
|---|
| 114 | protected transient 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 final 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(new RelationMemberConflictResolverModel());
|
|---|
| 186 | return pnlRelationMemberConflictResolver;
|
|---|
| 187 | }
|
|---|
| 188 |
|
|---|
| 189 | protected ApplyAction buildApplyAction() {
|
|---|
| 190 | return new ApplyAction();
|
|---|
| 191 | }
|
|---|
| 192 |
|
|---|
| 193 | protected JPanel buildButtonPanel() {
|
|---|
| 194 | JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
|---|
| 195 |
|
|---|
| 196 | // -- apply button
|
|---|
| 197 | ApplyAction applyAction = buildApplyAction();
|
|---|
| 198 | pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
|
|---|
| 199 | pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
|
|---|
| 200 | btnApply = new SideButton(applyAction);
|
|---|
| 201 | btnApply.setFocusable(true);
|
|---|
| 202 | pnl.add(btnApply);
|
|---|
| 203 |
|
|---|
| 204 | // -- cancel button
|
|---|
| 205 | CancelAction cancelAction = new CancelAction();
|
|---|
| 206 | pnl.add(new SideButton(cancelAction));
|
|---|
| 207 |
|
|---|
| 208 | // -- help button
|
|---|
| 209 | helpAction = new ContextSensitiveHelpAction();
|
|---|
| 210 | pnl.add(new SideButton(helpAction));
|
|---|
| 211 |
|
|---|
| 212 | return pnl;
|
|---|
| 213 | }
|
|---|
| 214 |
|
|---|
| 215 | /**
|
|---|
| 216 | * Constructs a new {@code CombinePrimitiveResolverDialog}.
|
|---|
| 217 | * @param parent The parent component in which this dialog will be displayed.
|
|---|
| 218 | */
|
|---|
| 219 | public CombinePrimitiveResolverDialog(Component parent) {
|
|---|
| 220 | super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
|
|---|
| 221 | build();
|
|---|
| 222 | }
|
|---|
| 223 |
|
|---|
| 224 | /**
|
|---|
| 225 | * Replies the tag conflict resolver model.
|
|---|
| 226 | * @return The tag conflict resolver model.
|
|---|
| 227 | */
|
|---|
| 228 | public TagConflictResolverModel getTagConflictResolverModel() {
|
|---|
| 229 | return pnlTagConflictResolver.getModel();
|
|---|
| 230 | }
|
|---|
| 231 |
|
|---|
| 232 | /**
|
|---|
| 233 | * Replies the relation membership conflict resolver model.
|
|---|
| 234 | * @return The relation membership conflict resolver model.
|
|---|
| 235 | */
|
|---|
| 236 | public RelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
|
|---|
| 237 | return pnlRelationMemberConflictResolver.getModel();
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | /**
|
|---|
| 241 | * Replies true if all tag and relation member conflicts have been decided.
|
|---|
| 242 | *
|
|---|
| 243 | * @return true if all tag and relation member conflicts have been decided; false otherwise
|
|---|
| 244 | */
|
|---|
| 245 | public boolean isResolvedCompletely() {
|
|---|
| 246 | return getTagConflictResolverModel().isResolvedCompletely()
|
|---|
| 247 | && getRelationMemberConflictResolverModel().isResolvedCompletely();
|
|---|
| 248 | }
|
|---|
| 249 |
|
|---|
| 250 | protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
|
|---|
| 251 | List<Command> cmds = new LinkedList<>();
|
|---|
| 252 | for (String key : tc.getKeys()) {
|
|---|
| 253 | if (tc.hasUniqueEmptyValue(key)) {
|
|---|
| 254 | if (primitive.get(key) != null) {
|
|---|
| 255 | cmds.add(new ChangePropertyCommand(primitive, key, null));
|
|---|
| 256 | }
|
|---|
| 257 | } else {
|
|---|
| 258 | String value = tc.getJoinedValues(key);
|
|---|
| 259 | if (!value.equals(primitive.get(key))) {
|
|---|
| 260 | cmds.add(new ChangePropertyCommand(primitive, key, value));
|
|---|
| 261 | }
|
|---|
| 262 | }
|
|---|
| 263 | }
|
|---|
| 264 | return cmds;
|
|---|
| 265 | }
|
|---|
| 266 |
|
|---|
| 267 | /**
|
|---|
| 268 | * Replies the list of {@link Command commands} needed to apply resolution choices.
|
|---|
| 269 | * @return The list of {@link Command commands} needed to apply resolution choices.
|
|---|
| 270 | */
|
|---|
| 271 | public List<Command> buildResolutionCommands() {
|
|---|
| 272 | List<Command> cmds = new LinkedList<>();
|
|---|
| 273 |
|
|---|
| 274 | TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
|
|---|
| 275 | if (!allResolutions.isEmpty()) {
|
|---|
| 276 | cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
|
|---|
| 277 | }
|
|---|
| 278 | for (String p : OsmPrimitive.getDiscardableKeys()) {
|
|---|
| 279 | if (targetPrimitive.get(p) != null) {
|
|---|
| 280 | cmds.add(new ChangePropertyCommand(targetPrimitive, p, null));
|
|---|
| 281 | }
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | if (getRelationMemberConflictResolverModel().getNumDecisions() > 0) {
|
|---|
| 285 | cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetPrimitive));
|
|---|
| 286 | }
|
|---|
| 287 |
|
|---|
| 288 | Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
|
|---|
| 289 | .getModifiedRelations(targetPrimitive));
|
|---|
| 290 | if (cmd != null) {
|
|---|
| 291 | cmds.add(cmd);
|
|---|
| 292 | }
|
|---|
| 293 | return cmds;
|
|---|
| 294 | }
|
|---|
| 295 |
|
|---|
| 296 | /**
|
|---|
| 297 | * Prepares the default decisions for populated tag and relation membership conflicts.
|
|---|
| 298 | */
|
|---|
| 299 | public void prepareDefaultDecisions() {
|
|---|
| 300 | getTagConflictResolverModel().prepareDefaultTagDecisions();
|
|---|
| 301 | getRelationMemberConflictResolverModel().prepareDefaultRelationDecisions();
|
|---|
| 302 | }
|
|---|
| 303 |
|
|---|
| 304 | protected JPanel buildEmptyConflictsPanel() {
|
|---|
| 305 | JPanel pnl = new JPanel(new BorderLayout());
|
|---|
| 306 | pnl.add(new JLabel(tr("No conflicts to resolve")));
|
|---|
| 307 | return pnl;
|
|---|
| 308 | }
|
|---|
| 309 |
|
|---|
| 310 | protected void prepareGUIBeforeConflictResolutionStarts() {
|
|---|
| 311 | RelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
|
|---|
| 312 | TagConflictResolverModel tagModel = getTagConflictResolverModel();
|
|---|
| 313 | getContentPane().removeAll();
|
|---|
| 314 |
|
|---|
| 315 | if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
|
|---|
| 316 | // display both, the dialog for resolving relation conflicts and for resolving tag conflicts
|
|---|
| 317 | spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
|
|---|
| 318 | spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
|
|---|
| 319 | getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
|
|---|
| 320 | } else if (relModel.getNumDecisions() > 0) {
|
|---|
| 321 | // relation conflicts only
|
|---|
| 322 | getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
|
|---|
| 323 | } else if (tagModel.getNumDecisions() > 0) {
|
|---|
| 324 | // tag conflicts only
|
|---|
| 325 | getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
|
|---|
| 326 | } else {
|
|---|
| 327 | getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | getContentPane().add(pnlButtons, BorderLayout.SOUTH);
|
|---|
| 331 | validate();
|
|---|
| 332 | adjustDividerLocation();
|
|---|
| 333 | pnlRelationMemberConflictResolver.prepareForEditing();
|
|---|
| 334 | }
|
|---|
| 335 |
|
|---|
| 336 | protected void setApplied(boolean applied) {
|
|---|
| 337 | this.applied = applied;
|
|---|
| 338 | }
|
|---|
| 339 |
|
|---|
| 340 | /**
|
|---|
| 341 | * Determines if this dialog has been closed with "Apply".
|
|---|
| 342 | * @return true if this dialog has been closed with "Apply", false otherwise.
|
|---|
| 343 | */
|
|---|
| 344 | public boolean isApplied() {
|
|---|
| 345 | return applied;
|
|---|
| 346 | }
|
|---|
| 347 |
|
|---|
| 348 | @Override
|
|---|
| 349 | public void setVisible(boolean visible) {
|
|---|
| 350 | if (visible) {
|
|---|
| 351 | prepareGUIBeforeConflictResolutionStarts();
|
|---|
| 352 | setMinimumSize(new Dimension(400, 400));
|
|---|
| 353 | new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
|
|---|
| 354 | new Dimension(800, 600))).applySafe(this);
|
|---|
| 355 | setApplied(false);
|
|---|
| 356 | btnApply.requestFocusInWindow();
|
|---|
| 357 | } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
|
|---|
| 358 | new WindowGeometry(this).remember(getClass().getName() + ".geometry");
|
|---|
| 359 | }
|
|---|
| 360 | super.setVisible(visible);
|
|---|
| 361 | }
|
|---|
| 362 |
|
|---|
| 363 | class CancelAction extends AbstractAction {
|
|---|
| 364 |
|
|---|
| 365 | CancelAction() {
|
|---|
| 366 | putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution"));
|
|---|
| 367 | putValue(Action.NAME, tr("Cancel"));
|
|---|
| 368 | putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
|
|---|
| 369 | setEnabled(true);
|
|---|
| 370 | }
|
|---|
| 371 |
|
|---|
| 372 | @Override
|
|---|
| 373 | public void actionPerformed(ActionEvent arg0) {
|
|---|
| 374 | setVisible(false);
|
|---|
| 375 | }
|
|---|
| 376 | }
|
|---|
| 377 |
|
|---|
| 378 | protected class ApplyAction extends AbstractAction implements PropertyChangeListener {
|
|---|
| 379 |
|
|---|
| 380 | public ApplyAction() {
|
|---|
| 381 | putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts"));
|
|---|
| 382 | putValue(Action.NAME, tr("Apply"));
|
|---|
| 383 | putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
|
|---|
| 384 | updateEnabledState();
|
|---|
| 385 | }
|
|---|
| 386 |
|
|---|
| 387 | @Override
|
|---|
| 388 | public void actionPerformed(ActionEvent arg0) {
|
|---|
| 389 | setApplied(true);
|
|---|
| 390 | setVisible(false);
|
|---|
| 391 | pnlTagConflictResolver.rememberPreferences();
|
|---|
| 392 | }
|
|---|
| 393 |
|
|---|
| 394 | protected final void updateEnabledState() {
|
|---|
| 395 | setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
|
|---|
| 396 | && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
|
|---|
| 397 | }
|
|---|
| 398 |
|
|---|
| 399 | @Override
|
|---|
| 400 | public void propertyChange(PropertyChangeEvent evt) {
|
|---|
| 401 | if (evt.getPropertyName().equals(TagConflictResolverModel.NUM_CONFLICTS_PROP)) {
|
|---|
| 402 | updateEnabledState();
|
|---|
| 403 | }
|
|---|
| 404 | if (evt.getPropertyName().equals(RelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
|
|---|
| 405 | updateEnabledState();
|
|---|
| 406 | }
|
|---|
| 407 | }
|
|---|
| 408 | }
|
|---|
| 409 |
|
|---|
| 410 | private void adjustDividerLocation() {
|
|---|
| 411 | int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
|
|---|
| 412 | int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
|
|---|
| 413 | if (numTagDecisions > 0 && numRelationDecisions > 0) {
|
|---|
| 414 | double nTop = 1.0 + numTagDecisions;
|
|---|
| 415 | double nBottom = 2.5 + numRelationDecisions;
|
|---|
| 416 | spTagConflictTypes.setDividerLocation(nTop/(nTop+nBottom));
|
|---|
| 417 | }
|
|---|
| 418 | }
|
|---|
| 419 |
|
|---|
| 420 | class AdjustDividerLocationAction extends WindowAdapter {
|
|---|
| 421 | @Override
|
|---|
| 422 | public void windowOpened(WindowEvent e) {
|
|---|
| 423 | adjustDividerLocation();
|
|---|
| 424 | }
|
|---|
| 425 | }
|
|---|
| 426 |
|
|---|
| 427 | static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
|
|---|
| 428 | private double dividerLocation;
|
|---|
| 429 |
|
|---|
| 430 | AutoAdjustingSplitPane(int newOrientation) {
|
|---|
| 431 | super(newOrientation);
|
|---|
| 432 | addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
|
|---|
| 433 | addHierarchyBoundsListener(this);
|
|---|
| 434 | }
|
|---|
| 435 |
|
|---|
| 436 | @Override
|
|---|
| 437 | public void ancestorResized(HierarchyEvent e) {
|
|---|
| 438 | setDividerLocation((int) (dividerLocation * getHeight()));
|
|---|
| 439 | }
|
|---|
| 440 |
|
|---|
| 441 | @Override
|
|---|
| 442 | public void ancestorMoved(HierarchyEvent e) {
|
|---|
| 443 | // do nothing
|
|---|
| 444 | }
|
|---|
| 445 |
|
|---|
| 446 | @Override
|
|---|
| 447 | public void propertyChange(PropertyChangeEvent evt) {
|
|---|
| 448 | if (JSplitPane.DIVIDER_LOCATION_PROPERTY.equals(evt.getPropertyName())) {
|
|---|
| 449 | int newVal = (Integer) evt.getNewValue();
|
|---|
| 450 | if (getHeight() != 0) {
|
|---|
| 451 | dividerLocation = (double) newVal / (double) getHeight();
|
|---|
| 452 | }
|
|---|
| 453 | }
|
|---|
| 454 | }
|
|---|
| 455 | }
|
|---|
| 456 |
|
|---|
| 457 | /**
|
|---|
| 458 | * Replies the list of {@link Command commands} needed to resolve specified conflicts,
|
|---|
| 459 | * by displaying if necessary a {@link CombinePrimitiveResolverDialog} to the user.
|
|---|
| 460 | * This dialog will allow the user to choose conflict resolution actions.
|
|---|
| 461 | *
|
|---|
| 462 | * Non-expert users are informed first of the meaning of these operations, allowing them to cancel.
|
|---|
| 463 | *
|
|---|
| 464 | * @param tagsOfPrimitives The tag collection of the primitives to be combined.
|
|---|
| 465 | * Should generally be equal to {@code TagCollection.unionOfAllPrimitives(primitives)}
|
|---|
| 466 | * @param primitives The primitives to be combined
|
|---|
| 467 | * @param targetPrimitives The primitives the collection of primitives are merged or combined to.
|
|---|
| 468 | * @return The list of {@link Command commands} needed to apply resolution actions.
|
|---|
| 469 | * @throws UserCancelException If the user cancelled a dialog.
|
|---|
| 470 | */
|
|---|
| 471 | public static List<Command> launchIfNecessary(
|
|---|
| 472 | final TagCollection tagsOfPrimitives,
|
|---|
| 473 | final Collection<? extends OsmPrimitive> primitives,
|
|---|
| 474 | final Collection<? extends OsmPrimitive> targetPrimitives) throws UserCancelException {
|
|---|
| 475 |
|
|---|
| 476 | CheckParameterUtil.ensureParameterNotNull(tagsOfPrimitives, "tagsOfPrimitives");
|
|---|
| 477 | CheckParameterUtil.ensureParameterNotNull(primitives, "primitives");
|
|---|
| 478 | CheckParameterUtil.ensureParameterNotNull(targetPrimitives, "targetPrimitives");
|
|---|
| 479 |
|
|---|
| 480 | final TagCollection completeWayTags = new TagCollection(tagsOfPrimitives);
|
|---|
| 481 | TagConflictResolutionUtil.combineTigerTags(completeWayTags);
|
|---|
| 482 | TagConflictResolutionUtil.normalizeTagCollectionBeforeEditing(completeWayTags, primitives);
|
|---|
| 483 | final TagCollection tagsToEdit = new TagCollection(completeWayTags);
|
|---|
| 484 | TagConflictResolutionUtil.completeTagCollectionForEditing(tagsToEdit);
|
|---|
| 485 |
|
|---|
| 486 | final Set<Relation> parentRelations = OsmPrimitive.getParentRelations(primitives);
|
|---|
| 487 |
|
|---|
| 488 | // Show information dialogs about conflicts to non-experts
|
|---|
| 489 | if (!ExpertToggleAction.isExpert()) {
|
|---|
| 490 | // Tag conflicts
|
|---|
| 491 | if (!completeWayTags.isApplicableToPrimitive()) {
|
|---|
| 492 | informAboutTagConflicts(primitives, completeWayTags);
|
|---|
| 493 | }
|
|---|
| 494 | // Relation membership conflicts
|
|---|
| 495 | if (!parentRelations.isEmpty()) {
|
|---|
| 496 | informAboutRelationMembershipConflicts(primitives, parentRelations);
|
|---|
| 497 | }
|
|---|
| 498 | }
|
|---|
| 499 |
|
|---|
| 500 | List<Command> cmds = new LinkedList<>();
|
|---|
| 501 |
|
|---|
| 502 | if (!GraphicsEnvironment.isHeadless()) {
|
|---|
| 503 | // Build conflict resolution dialog
|
|---|
| 504 | final CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
|
|---|
| 505 |
|
|---|
| 506 | dialog.getTagConflictResolverModel().populate(tagsToEdit, completeWayTags.getKeysWithMultipleValues());
|
|---|
| 507 | dialog.getRelationMemberConflictResolverModel().populate(parentRelations, primitives);
|
|---|
| 508 | dialog.prepareDefaultDecisions();
|
|---|
| 509 |
|
|---|
| 510 | // Ensure a proper title is displayed instead of a previous target (fix #7925)
|
|---|
| 511 | if (targetPrimitives.size() == 1) {
|
|---|
| 512 | dialog.setTargetPrimitive(targetPrimitives.iterator().next());
|
|---|
| 513 | } else {
|
|---|
| 514 | dialog.setTargetPrimitive(null);
|
|---|
| 515 | }
|
|---|
| 516 |
|
|---|
| 517 | // Resolve tag conflicts if necessary
|
|---|
| 518 | if (!dialog.isResolvedCompletely()) {
|
|---|
| 519 | dialog.setVisible(true);
|
|---|
| 520 | if (!dialog.isApplied()) {
|
|---|
| 521 | throw new UserCancelException();
|
|---|
| 522 | }
|
|---|
| 523 | }
|
|---|
| 524 | for (OsmPrimitive i : targetPrimitives) {
|
|---|
| 525 | dialog.setTargetPrimitive(i);
|
|---|
| 526 | cmds.addAll(dialog.buildResolutionCommands());
|
|---|
| 527 | }
|
|---|
| 528 | }
|
|---|
| 529 | return cmds;
|
|---|
| 530 | }
|
|---|
| 531 |
|
|---|
| 532 | /**
|
|---|
| 533 | * Inform a non-expert user about what relation membership conflict resolution means.
|
|---|
| 534 | * @param primitives The primitives to be combined
|
|---|
| 535 | * @param parentRelations The parent relations of the primitives
|
|---|
| 536 | * @throws UserCancelException If the user cancels the dialog.
|
|---|
| 537 | */
|
|---|
| 538 | protected static void informAboutRelationMembershipConflicts(
|
|---|
| 539 | final Collection<? extends OsmPrimitive> primitives,
|
|---|
| 540 | final Set<Relation> parentRelations) throws UserCancelException {
|
|---|
| 541 | /* I18n: object count < 2 is not possible */
|
|---|
| 542 | String msg = trn("You are about to combine {1} object, "
|
|---|
| 543 | + "which is part of {0} relation:<br/>{2}"
|
|---|
| 544 | + "Combining these objects may break this relation. If you are unsure, please cancel this operation.<br/>"
|
|---|
| 545 | + "If you want to continue, you are shown a dialog to decide how to adapt the relation.<br/><br/>"
|
|---|
| 546 | + "Do you want to continue?",
|
|---|
| 547 | "You are about to combine {1} objects, "
|
|---|
| 548 | + "which are part of {0} relations:<br/>{2}"
|
|---|
| 549 | + "Combining these objects may break these relations. If you are unsure, please cancel this operation.<br/>"
|
|---|
| 550 | + "If you want to continue, you are shown a dialog to decide how to adapt the relations.<br/><br/>"
|
|---|
| 551 | + "Do you want to continue?",
|
|---|
| 552 | parentRelations.size(), parentRelations.size(), primitives.size(),
|
|---|
| 553 | DefaultNameFormatter.getInstance().formatAsHtmlUnorderedList(parentRelations, 20));
|
|---|
| 554 |
|
|---|
| 555 | if (!ConditionalOptionPaneUtil.showConfirmationDialog(
|
|---|
| 556 | "combine_tags",
|
|---|
| 557 | Main.parent,
|
|---|
| 558 | "<html>" + msg + "</html>",
|
|---|
| 559 | tr("Combine confirmation"),
|
|---|
| 560 | JOptionPane.YES_NO_OPTION,
|
|---|
| 561 | JOptionPane.QUESTION_MESSAGE,
|
|---|
| 562 | JOptionPane.YES_OPTION)) {
|
|---|
| 563 | throw new UserCancelException();
|
|---|
| 564 | }
|
|---|
| 565 | }
|
|---|
| 566 |
|
|---|
| 567 | /**
|
|---|
| 568 | * Inform a non-expert user about what tag conflict resolution means.
|
|---|
| 569 | * @param primitives The primitives to be combined
|
|---|
| 570 | * @param normalizedTags The normalized tag collection of the primitives to be combined
|
|---|
| 571 | * @throws UserCancelException If the user cancels the dialog.
|
|---|
| 572 | */
|
|---|
| 573 | protected static void informAboutTagConflicts(
|
|---|
| 574 | final Collection<? extends OsmPrimitive> primitives,
|
|---|
| 575 | final TagCollection normalizedTags) throws UserCancelException {
|
|---|
| 576 | String conflicts = Utils.joinAsHtmlUnorderedList(Utils.transform(normalizedTags.getKeysWithMultipleValues(),
|
|---|
| 577 | new Function<String, String>() {
|
|---|
| 578 | @Override
|
|---|
| 579 | public String apply(String key) {
|
|---|
| 580 | return tr("{0} ({1})", key, Utils.join(tr(", "), Utils.transform(normalizedTags.getValues(key),
|
|---|
| 581 | new Function<String, String>() {
|
|---|
| 582 | @Override
|
|---|
| 583 | public String apply(String x) {
|
|---|
| 584 | return x == null || x.isEmpty() ? tr("<i>missing</i>") : x;
|
|---|
| 585 | }
|
|---|
| 586 | })));
|
|---|
| 587 | }
|
|---|
| 588 | }));
|
|---|
| 589 | String msg = /* for correct i18n of plural forms - see #9110 */ trn("You are about to combine {0} objects, "
|
|---|
| 590 | + "but the following tags are used conflictingly:<br/>{1}"
|
|---|
| 591 | + "If these objects are combined, the resulting object may have unwanted tags.<br/>"
|
|---|
| 592 | + "If you want to continue, you are shown a dialog to fix the conflicting tags.<br/><br/>"
|
|---|
| 593 | + "Do you want to continue?", "You are about to combine {0} objects, "
|
|---|
| 594 | + "but the following tags are used conflictingly:<br/>{1}"
|
|---|
| 595 | + "If these objects are combined, the resulting object may have unwanted tags.<br/>"
|
|---|
| 596 | + "If you want to continue, you are shown a dialog to fix the conflicting tags.<br/><br/>"
|
|---|
| 597 | + "Do you want to continue?",
|
|---|
| 598 | primitives.size(), primitives.size(), conflicts);
|
|---|
| 599 |
|
|---|
| 600 | if (!ConditionalOptionPaneUtil.showConfirmationDialog(
|
|---|
| 601 | "combine_tags",
|
|---|
| 602 | Main.parent,
|
|---|
| 603 | "<html>" + msg + "</html>",
|
|---|
| 604 | tr("Combine confirmation"),
|
|---|
| 605 | JOptionPane.YES_NO_OPTION,
|
|---|
| 606 | JOptionPane.QUESTION_MESSAGE,
|
|---|
| 607 | JOptionPane.YES_OPTION)) {
|
|---|
| 608 | throw new UserCancelException();
|
|---|
| 609 | }
|
|---|
| 610 | }
|
|---|
| 611 | }
|
|---|