| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package mergeoverlap;
|
|---|
| 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.trc;
|
|---|
| 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.GridBagConstraints;
|
|---|
| 13 | import java.awt.GridBagLayout;
|
|---|
| 14 | import java.awt.Insets;
|
|---|
| 15 | import java.awt.event.ActionEvent;
|
|---|
| 16 | import java.awt.event.FocusAdapter;
|
|---|
| 17 | import java.awt.event.FocusEvent;
|
|---|
| 18 | import java.awt.event.HierarchyBoundsListener;
|
|---|
| 19 | import java.awt.event.HierarchyEvent;
|
|---|
| 20 | import java.awt.event.KeyEvent;
|
|---|
| 21 | import java.awt.event.WindowAdapter;
|
|---|
| 22 | import java.awt.event.WindowEvent;
|
|---|
| 23 | import java.beans.PropertyChangeEvent;
|
|---|
| 24 | import java.beans.PropertyChangeListener;
|
|---|
| 25 | import java.beans.PropertyChangeSupport;
|
|---|
| 26 | import java.util.ArrayList;
|
|---|
| 27 | import java.util.Collection;
|
|---|
| 28 | import java.util.Collections;
|
|---|
| 29 | import java.util.Comparator;
|
|---|
| 30 | import java.util.HashMap;
|
|---|
| 31 | import java.util.HashSet;
|
|---|
| 32 | import java.util.LinkedList;
|
|---|
| 33 | import java.util.List;
|
|---|
| 34 | import java.util.Map;
|
|---|
| 35 | import java.util.Set;
|
|---|
| 36 |
|
|---|
| 37 | import javax.swing.AbstractAction;
|
|---|
| 38 | import javax.swing.AbstractButton;
|
|---|
| 39 | import javax.swing.Action;
|
|---|
| 40 | import javax.swing.BorderFactory;
|
|---|
| 41 | import javax.swing.BoxLayout;
|
|---|
| 42 | import javax.swing.ButtonModel;
|
|---|
| 43 | import javax.swing.JButton;
|
|---|
| 44 | import javax.swing.JCheckBox;
|
|---|
| 45 | import javax.swing.JComboBox;
|
|---|
| 46 | import javax.swing.JComponent;
|
|---|
| 47 | import javax.swing.JDialog;
|
|---|
| 48 | import javax.swing.JLabel;
|
|---|
| 49 | import javax.swing.JOptionPane;
|
|---|
| 50 | import javax.swing.JPanel;
|
|---|
| 51 | import javax.swing.JScrollPane;
|
|---|
| 52 | import javax.swing.JSplitPane;
|
|---|
| 53 | import javax.swing.JTable;
|
|---|
| 54 | import javax.swing.KeyStroke;
|
|---|
| 55 | import javax.swing.ListSelectionModel;
|
|---|
| 56 | import javax.swing.UIManager;
|
|---|
| 57 | import javax.swing.event.ChangeEvent;
|
|---|
| 58 | import javax.swing.event.ChangeListener;
|
|---|
| 59 | import javax.swing.table.DefaultTableModel;
|
|---|
| 60 |
|
|---|
| 61 | import org.openstreetmap.josm.Main;
|
|---|
| 62 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
|---|
| 63 | import org.openstreetmap.josm.command.Command;
|
|---|
| 64 | import org.openstreetmap.josm.data.osm.Node;
|
|---|
| 65 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
|---|
| 66 | import org.openstreetmap.josm.data.osm.Relation;
|
|---|
| 67 | import org.openstreetmap.josm.data.osm.RelationMember;
|
|---|
| 68 | import org.openstreetmap.josm.data.osm.RelationToChildReference;
|
|---|
| 69 | import org.openstreetmap.josm.data.osm.TagCollection;
|
|---|
| 70 | import org.openstreetmap.josm.data.osm.Way;
|
|---|
| 71 | import org.openstreetmap.josm.gui.DefaultNameFormatter;
|
|---|
| 72 | import org.openstreetmap.josm.gui.JMultilineLabel;
|
|---|
| 73 | import org.openstreetmap.josm.gui.SideButton;
|
|---|
| 74 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;
|
|---|
| 75 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;
|
|---|
| 76 | import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
|
|---|
| 77 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
|
|---|
| 78 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
|
|---|
| 79 | import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictResolverColumnModel;
|
|---|
| 80 | import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverColumnModel;
|
|---|
| 81 | import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
|
|---|
| 82 | import org.openstreetmap.josm.gui.help.HelpUtil;
|
|---|
| 83 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
|
|---|
| 84 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
|
|---|
| 85 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
|---|
| 86 | import org.openstreetmap.josm.tools.ImageProvider;
|
|---|
| 87 | import org.openstreetmap.josm.tools.WindowGeometry;
|
|---|
| 88 |
|
|---|
| 89 | /**
|
|---|
| 90 | * This dialog helps to resolve conflicts occurring when ways are combined or
|
|---|
| 91 | * nodes are merged.
|
|---|
| 92 | *
|
|---|
| 93 | * There is a singleton instance of this dialog which can be retrieved using
|
|---|
| 94 | * {@see #getInstance()}.
|
|---|
| 95 | *
|
|---|
| 96 | * The dialog uses two models: one for resolving tag conflicts, the other
|
|---|
| 97 | * for resolving conflicts in relation memberships. For both models there are accessors,
|
|---|
| 98 | * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
|
|---|
| 99 | *
|
|---|
| 100 | * Models have to be <strong>populated</strong> before the dialog is launched. Example:
|
|---|
| 101 | * <pre>
|
|---|
| 102 | * CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
|
|---|
| 103 | * dialog.getTagConflictResolverModel().populate(aTagCollection);
|
|---|
| 104 | * dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
|
|---|
| 105 | * dialog.prepareDefaultDecisions();
|
|---|
| 106 | * </pre>
|
|---|
| 107 | *
|
|---|
| 108 | * You should also set the target primitive which other primitives (ways or nodes) are
|
|---|
| 109 | * merged to, see {@see #setTargetPrimitive(OsmPrimitive)}.
|
|---|
| 110 | *
|
|---|
| 111 | * After the dialog is closed use {@see #isCancelled()} to check whether the user canceled
|
|---|
| 112 | * the dialog. If it wasn't canceled you may build a collection of {@see Command} objects
|
|---|
| 113 | * which reflect the conflict resolution decisions the user made in the dialog:
|
|---|
| 114 | * see {@see #buildResolutionCommands()}
|
|---|
| 115 | *
|
|---|
| 116 | *
|
|---|
| 117 | */
|
|---|
| 118 | public class MyCombinePrimitiveResolverDialog extends JDialog {
|
|---|
| 119 |
|
|---|
| 120 | /** the unique instance of the dialog */
|
|---|
| 121 | static private MyCombinePrimitiveResolverDialog instance;
|
|---|
| 122 |
|
|---|
| 123 | /**
|
|---|
| 124 | * Replies the unique instance of the dialog
|
|---|
| 125 | *
|
|---|
| 126 | * @return the unique instance of the dialog
|
|---|
| 127 | */
|
|---|
| 128 | public static MyCombinePrimitiveResolverDialog getInstance() {
|
|---|
| 129 | if (instance == null) {
|
|---|
| 130 | instance = new MyCombinePrimitiveResolverDialog(Main.parent);
|
|---|
| 131 | }
|
|---|
| 132 | return instance;
|
|---|
| 133 | }
|
|---|
| 134 |
|
|---|
| 135 | private AutoAdjustingSplitPane spTagConflictTypes;
|
|---|
| 136 | private MyTagConflictResolver pnlTagConflictResolver;
|
|---|
| 137 | private MyRelationMemberConflictResolver pnlRelationMemberConflictResolver;
|
|---|
| 138 | private boolean cancelled;
|
|---|
| 139 | private JPanel pnlButtons;
|
|---|
| 140 | private OsmPrimitive targetPrimitive;
|
|---|
| 141 |
|
|---|
| 142 | /** the private help action */
|
|---|
| 143 | private ContextSensitiveHelpAction helpAction;
|
|---|
| 144 | /** the apply button */
|
|---|
| 145 | private SideButton btnApply;
|
|---|
| 146 |
|
|---|
| 147 | /**
|
|---|
| 148 | * Replies the target primitive the collection of primitives is merged
|
|---|
| 149 | * or combined to.
|
|---|
| 150 | *
|
|---|
| 151 | * @return the target primitive
|
|---|
| 152 | */
|
|---|
| 153 | public OsmPrimitive getTargetPrimitmive() {
|
|---|
| 154 | return targetPrimitive;
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | /**
|
|---|
| 158 | * Sets the primitive the collection of primitives is merged or combined
|
|---|
| 159 | * to.
|
|---|
| 160 | *
|
|---|
| 161 | * @param primitive the target primitive
|
|---|
| 162 | */
|
|---|
| 163 | public void setTargetPrimitive(OsmPrimitive primitive) {
|
|---|
| 164 | this.targetPrimitive = primitive;
|
|---|
| 165 | updateTitle();
|
|---|
| 166 | if (primitive instanceof Way) {
|
|---|
| 167 | pnlRelationMemberConflictResolver.initForWayCombining();
|
|---|
| 168 | } else if (primitive instanceof Node) {
|
|---|
| 169 | pnlRelationMemberConflictResolver.initForNodeMerging();
|
|---|
| 170 | }
|
|---|
| 171 | }
|
|---|
| 172 |
|
|---|
| 173 | protected void updateTitle() {
|
|---|
| 174 | if (targetPrimitive == null) {
|
|---|
| 175 | setTitle(tr("Conflicts when combining primitives", null));
|
|---|
| 176 | return;
|
|---|
| 177 | }
|
|---|
| 178 | if (targetPrimitive instanceof Way) {
|
|---|
| 179 | setTitle(tr("Conflicts when combining ways - combined way is ''{0}''", targetPrimitive
|
|---|
| 180 | .getDisplayName(DefaultNameFormatter.getInstance())));
|
|---|
| 181 | helpAction.setHelpTopic(ht("/Action/CombineWay#ResolvingConflicts"));
|
|---|
| 182 | getRootPane().putClientProperty("help", ht("/Action/CombineWay#ResolvingConflicts"));
|
|---|
| 183 | } else if (targetPrimitive instanceof Node) {
|
|---|
| 184 | setTitle(tr("Conflicts when merging nodes - target node is ''{0}''", targetPrimitive
|
|---|
| 185 | .getDisplayName(DefaultNameFormatter.getInstance())));
|
|---|
| 186 | helpAction.setHelpTopic(ht("/Action/MergeNodes#ResolvingConflicts"));
|
|---|
| 187 | getRootPane().putClientProperty("help", ht("/Action/MergeNodes#ResolvingConflicts"));
|
|---|
| 188 | }
|
|---|
| 189 | }
|
|---|
| 190 |
|
|---|
| 191 | protected void build() {
|
|---|
| 192 | getContentPane().setLayout(new BorderLayout());
|
|---|
| 193 | updateTitle();
|
|---|
| 194 | spTagConflictTypes = new AutoAdjustingSplitPane(JSplitPane.VERTICAL_SPLIT);
|
|---|
| 195 | spTagConflictTypes.setTopComponent(buildTagConflictResolverPanel());
|
|---|
| 196 | spTagConflictTypes.setBottomComponent(buildRelationMemberConflictResolverPanel());
|
|---|
| 197 | getContentPane().add(pnlButtons = buildButtonPanel(), BorderLayout.SOUTH);
|
|---|
| 198 | addWindowListener(new AdjustDividerLocationAction());
|
|---|
| 199 | HelpUtil.setHelpContext(getRootPane(), ht("/"));
|
|---|
| 200 | }
|
|---|
| 201 |
|
|---|
| 202 | protected JPanel buildTagConflictResolverPanel() {
|
|---|
| 203 | pnlTagConflictResolver = new MyTagConflictResolver();
|
|---|
| 204 | return pnlTagConflictResolver;
|
|---|
| 205 | }
|
|---|
| 206 |
|
|---|
| 207 | protected JPanel buildRelationMemberConflictResolverPanel() {
|
|---|
| 208 | pnlRelationMemberConflictResolver = new MyRelationMemberConflictResolver();
|
|---|
| 209 | return pnlRelationMemberConflictResolver;
|
|---|
| 210 | }
|
|---|
| 211 |
|
|---|
| 212 | protected JPanel buildButtonPanel() {
|
|---|
| 213 | JPanel pnl = new JPanel();
|
|---|
| 214 | pnl.setLayout(new FlowLayout(FlowLayout.CENTER));
|
|---|
| 215 |
|
|---|
| 216 | // -- apply button
|
|---|
| 217 | ApplyAction applyAction = new ApplyAction();
|
|---|
| 218 | pnlTagConflictResolver.getModel().addPropertyChangeListener(applyAction);
|
|---|
| 219 | pnlRelationMemberConflictResolver.getModel().addPropertyChangeListener(applyAction);
|
|---|
| 220 | btnApply = new SideButton(applyAction);
|
|---|
| 221 | btnApply.setFocusable(true);
|
|---|
| 222 | pnl.add(btnApply);
|
|---|
| 223 |
|
|---|
| 224 | // -- cancel button
|
|---|
| 225 | CancelAction cancelAction = new CancelAction();
|
|---|
| 226 | pnl.add(new SideButton(cancelAction));
|
|---|
| 227 |
|
|---|
| 228 | // -- help button
|
|---|
| 229 | helpAction = new ContextSensitiveHelpAction();
|
|---|
| 230 | pnl.add(new SideButton(helpAction));
|
|---|
| 231 |
|
|---|
| 232 | return pnl;
|
|---|
| 233 | }
|
|---|
| 234 |
|
|---|
| 235 | public MyCombinePrimitiveResolverDialog(Component owner) {
|
|---|
| 236 | super(JOptionPane.getFrameForComponent(owner), ModalityType.DOCUMENT_MODAL);
|
|---|
| 237 | build();
|
|---|
| 238 | }
|
|---|
| 239 |
|
|---|
| 240 | public MyTagConflictResolverModel getTagConflictResolverModel() {
|
|---|
| 241 | return pnlTagConflictResolver.getModel();
|
|---|
| 242 | }
|
|---|
| 243 |
|
|---|
| 244 | public MyRelationMemberConflictResolverModel getRelationMemberConflictResolverModel() {
|
|---|
| 245 | return pnlRelationMemberConflictResolver.getModel();
|
|---|
| 246 | }
|
|---|
| 247 |
|
|---|
| 248 | protected List<Command> buildTagChangeCommand(OsmPrimitive primitive, TagCollection tc) {
|
|---|
| 249 | LinkedList<Command> cmds = new LinkedList<Command>();
|
|---|
| 250 | for (String key : tc.getKeys()) {
|
|---|
| 251 | if (tc.hasUniqueEmptyValue(key)) {
|
|---|
| 252 | if (primitive.get(key) != null) {
|
|---|
| 253 | cmds.add(new ChangePropertyCommand(primitive, key, null));
|
|---|
| 254 | }
|
|---|
| 255 | } else {
|
|---|
| 256 | String value = tc.getJoinedValues(key);
|
|---|
| 257 | if (!value.equals(primitive.get(key))) {
|
|---|
| 258 | cmds.add(new ChangePropertyCommand(primitive, key, value));
|
|---|
| 259 | }
|
|---|
| 260 | }
|
|---|
| 261 | }
|
|---|
| 262 | return cmds;
|
|---|
| 263 | }
|
|---|
| 264 |
|
|---|
| 265 | public List<Command> buildWayResolutionCommands() {
|
|---|
| 266 | List<Command> cmds = new LinkedList<Command>();
|
|---|
| 267 |
|
|---|
| 268 | TagCollection allResolutions = getTagConflictResolverModel().getAllResolutions();
|
|---|
| 269 | if (allResolutions.size() > 0) {
|
|---|
| 270 | cmds.addAll(buildTagChangeCommand(targetPrimitive, allResolutions));
|
|---|
| 271 | }
|
|---|
| 272 | if (targetPrimitive.get("created_by") != null) {
|
|---|
| 273 | cmds.add(new ChangePropertyCommand(targetPrimitive, "created_by", null));
|
|---|
| 274 | }
|
|---|
| 275 |
|
|---|
| 276 | Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(getRelationMemberConflictResolverModel()
|
|---|
| 277 | .getModifiedRelations(targetPrimitive));
|
|---|
| 278 | if (cmd != null) {
|
|---|
| 279 | cmds.add(cmd);
|
|---|
| 280 | }
|
|---|
| 281 | return cmds;
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | public void buildRelationCorrespondance(Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
|
|---|
| 285 | getRelationMemberConflictResolverModel().buildRelationCorrespondance(targetPrimitive, newRelations, oldWays);
|
|---|
| 286 | }
|
|---|
| 287 |
|
|---|
| 288 | protected void prepareDefaultTagDecisions() {
|
|---|
| 289 | MyTagConflictResolverModel model = getTagConflictResolverModel();
|
|---|
| 290 | for (int i = 0; i < model.getRowCount(); i++) {
|
|---|
| 291 | MultiValueResolutionDecision decision = model.getDecision(i);
|
|---|
| 292 | List<String> values = decision.getValues();
|
|---|
| 293 | values.remove("");
|
|---|
| 294 | if (values.size() == 1) {
|
|---|
| 295 | decision.keepOne(values.get(0));
|
|---|
| 296 | } else {
|
|---|
| 297 | decision.keepAll();
|
|---|
| 298 | }
|
|---|
| 299 | }
|
|---|
| 300 | model.rebuild();
|
|---|
| 301 | }
|
|---|
| 302 |
|
|---|
| 303 | protected void prepareDefaultRelationDecisions() {
|
|---|
| 304 | MyRelationMemberConflictResolverModel model = getRelationMemberConflictResolverModel();
|
|---|
| 305 | Set<Relation> relations = new HashSet<Relation>();
|
|---|
| 306 | for (int i = 0; i < model.getNumDecisions(); i++) {
|
|---|
| 307 | RelationMemberConflictDecision decision = model.getDecision(i);
|
|---|
| 308 | if (!relations.contains(decision.getRelation())) {
|
|---|
| 309 | decision.decide(RelationMemberConflictDecisionType.KEEP);
|
|---|
| 310 | relations.add(decision.getRelation());
|
|---|
| 311 | } else {
|
|---|
| 312 | decision.decide(RelationMemberConflictDecisionType.REMOVE);
|
|---|
| 313 | }
|
|---|
| 314 | }
|
|---|
| 315 | model.refresh();
|
|---|
| 316 | }
|
|---|
| 317 |
|
|---|
| 318 | public void prepareDefaultDecisions() {
|
|---|
| 319 | prepareDefaultTagDecisions();
|
|---|
| 320 | prepareDefaultRelationDecisions();
|
|---|
| 321 | }
|
|---|
| 322 |
|
|---|
| 323 | protected JPanel buildEmptyConflictsPanel() {
|
|---|
| 324 | JPanel pnl = new JPanel();
|
|---|
| 325 | pnl.setLayout(new BorderLayout());
|
|---|
| 326 | pnl.add(new JLabel(tr("No conflicts to resolve", null)));
|
|---|
| 327 | return pnl;
|
|---|
| 328 | }
|
|---|
| 329 |
|
|---|
| 330 | protected void prepareGUIBeforeConflictResolutionStarts() {
|
|---|
| 331 | MyRelationMemberConflictResolverModel relModel = getRelationMemberConflictResolverModel();
|
|---|
| 332 | MyTagConflictResolverModel tagModel = getTagConflictResolverModel();
|
|---|
| 333 | getContentPane().removeAll();
|
|---|
| 334 |
|
|---|
| 335 | if (relModel.getNumDecisions() > 0 && tagModel.getNumDecisions() > 0) {
|
|---|
| 336 | // display both, the dialog for resolving relation conflicts and for resolving
|
|---|
| 337 | // tag conflicts
|
|---|
| 338 | spTagConflictTypes.setTopComponent(pnlTagConflictResolver);
|
|---|
| 339 | spTagConflictTypes.setBottomComponent(pnlRelationMemberConflictResolver);
|
|---|
| 340 | getContentPane().add(spTagConflictTypes, BorderLayout.CENTER);
|
|---|
| 341 | } else if (relModel.getNumDecisions() > 0) {
|
|---|
| 342 | // relation conflicts only
|
|---|
| 343 | //
|
|---|
| 344 | getContentPane().add(pnlRelationMemberConflictResolver, BorderLayout.CENTER);
|
|---|
| 345 | } else if (tagModel.getNumDecisions() > 0) {
|
|---|
| 346 | // tag conflicts only
|
|---|
| 347 | //
|
|---|
| 348 | getContentPane().add(pnlTagConflictResolver, BorderLayout.CENTER);
|
|---|
| 349 | } else {
|
|---|
| 350 | getContentPane().add(buildEmptyConflictsPanel(), BorderLayout.CENTER);
|
|---|
| 351 | }
|
|---|
| 352 |
|
|---|
| 353 | getContentPane().add(pnlButtons, BorderLayout.SOUTH);
|
|---|
| 354 | validate();
|
|---|
| 355 | int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
|
|---|
| 356 | int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
|
|---|
| 357 | if (numTagDecisions > 0 && numRelationDecisions > 0) {
|
|---|
| 358 | spTagConflictTypes.setDividerLocation(0.5);
|
|---|
| 359 | }
|
|---|
| 360 | pnlRelationMemberConflictResolver.prepareForEditing();
|
|---|
| 361 | }
|
|---|
| 362 |
|
|---|
| 363 | protected void setCancelled(boolean cancelled) {
|
|---|
| 364 | this.cancelled = cancelled;
|
|---|
| 365 | }
|
|---|
| 366 |
|
|---|
| 367 | public boolean isCancelled() {
|
|---|
| 368 | return cancelled;
|
|---|
| 369 | }
|
|---|
| 370 |
|
|---|
| 371 | @Override
|
|---|
| 372 | public void setVisible(boolean visible) {
|
|---|
| 373 | if (visible) {
|
|---|
| 374 | prepareGUIBeforeConflictResolutionStarts();
|
|---|
| 375 | new WindowGeometry(getClass().getName() + ".geometry", WindowGeometry.centerInWindow(Main.parent,
|
|---|
| 376 | new Dimension(600, 400))).applySafe(this);
|
|---|
| 377 | setCancelled(false);
|
|---|
| 378 | btnApply.requestFocusInWindow();
|
|---|
| 379 | } else {
|
|---|
| 380 | new WindowGeometry(this).remember(getClass().getName() + ".geometry");
|
|---|
| 381 | }
|
|---|
| 382 | super.setVisible(visible);
|
|---|
| 383 | }
|
|---|
| 384 |
|
|---|
| 385 | class CancelAction extends AbstractAction {
|
|---|
| 386 |
|
|---|
| 387 | public CancelAction() {
|
|---|
| 388 | putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution", null));
|
|---|
| 389 | putValue(Action.NAME, tr("Cancel", null));
|
|---|
| 390 | putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel"));
|
|---|
| 391 | setEnabled(true);
|
|---|
| 392 | }
|
|---|
| 393 |
|
|---|
| 394 | public void actionPerformed(ActionEvent arg0) {
|
|---|
| 395 | setCancelled(true);
|
|---|
| 396 | setVisible(false);
|
|---|
| 397 | }
|
|---|
| 398 | }
|
|---|
| 399 |
|
|---|
| 400 | class ApplyAction extends AbstractAction implements PropertyChangeListener {
|
|---|
| 401 |
|
|---|
| 402 | public ApplyAction() {
|
|---|
| 403 | putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts", null));
|
|---|
| 404 | putValue(Action.NAME, tr("Apply", null));
|
|---|
| 405 | putValue(Action.SMALL_ICON, ImageProvider.get("ok"));
|
|---|
| 406 | updateEnabledState();
|
|---|
| 407 | }
|
|---|
| 408 |
|
|---|
| 409 | public void actionPerformed(ActionEvent arg0) {
|
|---|
| 410 | setVisible(false);
|
|---|
| 411 | pnlTagConflictResolver.rememberPreferences();
|
|---|
| 412 | }
|
|---|
| 413 |
|
|---|
| 414 | protected void updateEnabledState() {
|
|---|
| 415 | setEnabled(pnlTagConflictResolver.getModel().getNumConflicts() == 0
|
|---|
| 416 | && pnlRelationMemberConflictResolver.getModel().getNumConflicts() == 0);
|
|---|
| 417 | }
|
|---|
| 418 |
|
|---|
| 419 | public void propertyChange(PropertyChangeEvent evt) {
|
|---|
| 420 | if (evt.getPropertyName().equals(MyTagConflictResolverModel.NUM_CONFLICTS_PROP)) {
|
|---|
| 421 | updateEnabledState();
|
|---|
| 422 | }
|
|---|
| 423 | if (evt.getPropertyName().equals(MyRelationMemberConflictResolverModel.NUM_CONFLICTS_PROP)) {
|
|---|
| 424 | updateEnabledState();
|
|---|
| 425 | }
|
|---|
| 426 | }
|
|---|
| 427 | }
|
|---|
| 428 |
|
|---|
| 429 | class AdjustDividerLocationAction extends WindowAdapter {
|
|---|
| 430 | @Override
|
|---|
| 431 | public void windowOpened(WindowEvent e) {
|
|---|
| 432 | int numTagDecisions = getTagConflictResolverModel().getNumDecisions();
|
|---|
| 433 | int numRelationDecisions = getRelationMemberConflictResolverModel().getNumDecisions();
|
|---|
| 434 | if (numTagDecisions > 0 && numRelationDecisions > 0) {
|
|---|
| 435 | spTagConflictTypes.setDividerLocation(0.5);
|
|---|
| 436 | }
|
|---|
| 437 | }
|
|---|
| 438 | }
|
|---|
| 439 |
|
|---|
| 440 | static class AutoAdjustingSplitPane extends JSplitPane implements PropertyChangeListener, HierarchyBoundsListener {
|
|---|
| 441 | private double dividerLocation;
|
|---|
| 442 |
|
|---|
| 443 | public AutoAdjustingSplitPane(int newOrientation) {
|
|---|
| 444 | super(newOrientation);
|
|---|
| 445 | addPropertyChangeListener(JSplitPane.DIVIDER_LOCATION_PROPERTY, this);
|
|---|
| 446 | addHierarchyBoundsListener(this);
|
|---|
| 447 | }
|
|---|
| 448 |
|
|---|
| 449 | public void ancestorResized(HierarchyEvent e) {
|
|---|
| 450 | setDividerLocation((int) (dividerLocation * getHeight()));
|
|---|
| 451 | }
|
|---|
| 452 |
|
|---|
| 453 | public void ancestorMoved(HierarchyEvent e) {
|
|---|
| 454 | // do nothing
|
|---|
| 455 | }
|
|---|
| 456 |
|
|---|
| 457 | public void propertyChange(PropertyChangeEvent evt) {
|
|---|
| 458 | if (evt.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)) {
|
|---|
| 459 | int newVal = (Integer) evt.getNewValue();
|
|---|
| 460 | if (getHeight() != 0) {
|
|---|
| 461 | dividerLocation = (double) newVal / (double) getHeight();
|
|---|
| 462 | }
|
|---|
| 463 | }
|
|---|
| 464 | }
|
|---|
| 465 | }
|
|---|
| 466 |
|
|---|
| 467 | /**
|
|---|
| 468 | * This model manages a list of conflicting relation members.
|
|---|
| 469 | *
|
|---|
| 470 | * It can be used as {@see TableModel}.
|
|---|
| 471 | *
|
|---|
| 472 | *
|
|---|
| 473 | */
|
|---|
| 474 | public static class MyRelationMemberConflictResolverModel extends DefaultTableModel {
|
|---|
| 475 | /** the property name for the number conflicts managed by this model */
|
|---|
| 476 | static public final String NUM_CONFLICTS_PROP = MyRelationMemberConflictResolverModel.class.getName() + ".numConflicts";
|
|---|
| 477 |
|
|---|
| 478 | /** the list of conflict decisions */
|
|---|
| 479 | private List<RelationMemberConflictDecision> decisions;
|
|---|
| 480 | /** the collection of relations for which we manage conflicts */
|
|---|
| 481 | private Collection<Relation> relations;
|
|---|
| 482 | /** the number of conflicts */
|
|---|
| 483 | private int numConflicts;
|
|---|
| 484 | private PropertyChangeSupport support;
|
|---|
| 485 |
|
|---|
| 486 | /**
|
|---|
| 487 | * Replies the current number of conflicts
|
|---|
| 488 | *
|
|---|
| 489 | * @return the current number of conflicts
|
|---|
| 490 | */
|
|---|
| 491 | public int getNumConflicts() {
|
|---|
| 492 | return numConflicts;
|
|---|
| 493 | }
|
|---|
| 494 |
|
|---|
| 495 | /**
|
|---|
| 496 | * Updates the current number of conflicts from list of decisions and emits
|
|---|
| 497 | * a property change event if necessary.
|
|---|
| 498 | *
|
|---|
| 499 | */
|
|---|
| 500 | protected void updateNumConflicts() {
|
|---|
| 501 | int count = 0;
|
|---|
| 502 | for (RelationMemberConflictDecision decision: decisions) {
|
|---|
| 503 | if (!decision.isDecided()) {
|
|---|
| 504 | count++;
|
|---|
| 505 | }
|
|---|
| 506 | }
|
|---|
| 507 | int oldValue = numConflicts;
|
|---|
| 508 | numConflicts = count;
|
|---|
| 509 | if (numConflicts != oldValue) {
|
|---|
| 510 | support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts);
|
|---|
| 511 | }
|
|---|
| 512 | }
|
|---|
| 513 |
|
|---|
| 514 | public void addPropertyChangeListener(PropertyChangeListener l) {
|
|---|
| 515 | support.addPropertyChangeListener(l);
|
|---|
| 516 | }
|
|---|
| 517 |
|
|---|
| 518 | public void removePropertyChangeListener(PropertyChangeListener l) {
|
|---|
| 519 | support.removePropertyChangeListener(l);
|
|---|
| 520 | }
|
|---|
| 521 |
|
|---|
| 522 | public MyRelationMemberConflictResolverModel() {
|
|---|
| 523 | decisions = new ArrayList<RelationMemberConflictDecision>();
|
|---|
| 524 | support = new PropertyChangeSupport(this);
|
|---|
| 525 | }
|
|---|
| 526 |
|
|---|
| 527 | @Override
|
|---|
| 528 | public int getRowCount() {
|
|---|
| 529 | if (decisions == null) return 0;
|
|---|
| 530 | return decisions.size();
|
|---|
| 531 | }
|
|---|
| 532 |
|
|---|
| 533 | @Override
|
|---|
| 534 | public Object getValueAt(int row, int column) {
|
|---|
| 535 | if (decisions == null) return null;
|
|---|
| 536 |
|
|---|
| 537 | RelationMemberConflictDecision d = decisions.get(row);
|
|---|
| 538 | switch(column) {
|
|---|
| 539 | case 0: /* relation */ return d.getRelation();
|
|---|
| 540 | case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
|
|---|
| 541 | case 2: /* role */ return d.getRole();
|
|---|
| 542 | case 3: /* original */ return d.getOriginalPrimitive();
|
|---|
| 543 | case 4: /* decision */ return d.getDecision();
|
|---|
| 544 | }
|
|---|
| 545 | return null;
|
|---|
| 546 | }
|
|---|
| 547 |
|
|---|
| 548 | @Override
|
|---|
| 549 | public void setValueAt(Object value, int row, int column) {
|
|---|
| 550 | RelationMemberConflictDecision d = decisions.get(row);
|
|---|
| 551 | switch(column) {
|
|---|
| 552 | case 2: /* role */
|
|---|
| 553 | d.setRole((String)value);
|
|---|
| 554 | break;
|
|---|
| 555 | case 4: /* decision */
|
|---|
| 556 | d.decide((RelationMemberConflictDecisionType)value);
|
|---|
| 557 | refresh();
|
|---|
| 558 | break;
|
|---|
| 559 | }
|
|---|
| 560 | fireTableDataChanged();
|
|---|
| 561 | }
|
|---|
| 562 |
|
|---|
| 563 | /**
|
|---|
| 564 | * Populates the model with the members of the relation <code>relation</code>
|
|---|
| 565 | * referring to <code>primitive</code>.
|
|---|
| 566 | *
|
|---|
| 567 | * @param relation the parent relation
|
|---|
| 568 | * @param primitive the child primitive
|
|---|
| 569 | */
|
|---|
| 570 | protected void populate(Relation relation, OsmPrimitive primitive, Map<Way, Way> oldWays) {
|
|---|
| 571 | for (int i = 0; i<relation.getMembersCount(); i++) {
|
|---|
| 572 | if (MergeOverlapAction.getOld(relation.getMember(i).getWay(), oldWays) == MergeOverlapAction.getOld((Way)primitive, oldWays)) {
|
|---|
| 573 | decisions.add(new RelationMemberConflictDecision(relation, i));
|
|---|
| 574 | }
|
|---|
| 575 | }
|
|---|
| 576 | }
|
|---|
| 577 |
|
|---|
| 578 | /**
|
|---|
| 579 | * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
|
|---|
| 580 | * and referring to one of the primitives in <code>memberPrimitives</code>.
|
|---|
| 581 | *
|
|---|
| 582 | * @param relations the parent relations. Empty list assumed if null.
|
|---|
| 583 | * @param memberPrimitives the child primitives. Empty list assumed if null.
|
|---|
| 584 | */
|
|---|
| 585 | public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives, Map<Way, Way> oldWays) {
|
|---|
| 586 | decisions.clear();
|
|---|
| 587 |
|
|---|
| 588 | relations = relations == null ? new LinkedList<Relation>() : relations;
|
|---|
| 589 | memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
|
|---|
| 590 | for (Relation r : relations) {
|
|---|
| 591 | for (OsmPrimitive p: memberPrimitives) {
|
|---|
| 592 | populate(r, p, oldWays);
|
|---|
| 593 | }
|
|---|
| 594 | }
|
|---|
| 595 | this.relations = relations;
|
|---|
| 596 | refresh();
|
|---|
| 597 | }
|
|---|
| 598 |
|
|---|
| 599 | /**
|
|---|
| 600 | * Populates the model with the relation members represented as a collection of
|
|---|
| 601 | * {@see RelationToChildReference}s.
|
|---|
| 602 | *
|
|---|
| 603 | * @param references the references. Empty list assumed if null.
|
|---|
| 604 | */
|
|---|
| 605 | public void populate(Collection<RelationToChildReference> references) {
|
|---|
| 606 | references = references == null ? new LinkedList<RelationToChildReference>() : references;
|
|---|
| 607 | decisions.clear();
|
|---|
| 608 | this.relations = new HashSet<Relation>(references.size());
|
|---|
| 609 | for (RelationToChildReference reference: references) {
|
|---|
| 610 | decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
|
|---|
| 611 | relations.add(reference.getParent());
|
|---|
| 612 | }
|
|---|
| 613 | refresh();
|
|---|
| 614 | }
|
|---|
| 615 |
|
|---|
| 616 | /**
|
|---|
| 617 | * Replies the decision at position <code>row</code>
|
|---|
| 618 | *
|
|---|
| 619 | * @param row
|
|---|
| 620 | * @return the decision at position <code>row</code>
|
|---|
| 621 | */
|
|---|
| 622 | public RelationMemberConflictDecision getDecision(int row) {
|
|---|
| 623 | return decisions.get(row);
|
|---|
| 624 | }
|
|---|
| 625 |
|
|---|
| 626 | /**
|
|---|
| 627 | * Replies the number of decisions managed by this model
|
|---|
| 628 | *
|
|---|
| 629 | * @return the number of decisions managed by this model
|
|---|
| 630 | */
|
|---|
| 631 | public int getNumDecisions() {
|
|---|
| 632 | return getRowCount();
|
|---|
| 633 | }
|
|---|
| 634 |
|
|---|
| 635 | /**
|
|---|
| 636 | * Refreshes the model state. Invoke this method to trigger necessary change
|
|---|
| 637 | * events after an update of the model data.
|
|---|
| 638 | *
|
|---|
| 639 | */
|
|---|
| 640 | public void refresh() {
|
|---|
| 641 | updateNumConflicts();
|
|---|
| 642 | fireTableDataChanged();
|
|---|
| 643 | }
|
|---|
| 644 |
|
|---|
| 645 | /**
|
|---|
| 646 | * Apply a role to all member managed by this model.
|
|---|
| 647 | *
|
|---|
| 648 | * @param role the role. Empty string assumed if null.
|
|---|
| 649 | */
|
|---|
| 650 | public void applyRole(String role) {
|
|---|
| 651 | role = role == null ? "" : role;
|
|---|
| 652 | for (RelationMemberConflictDecision decision : decisions) {
|
|---|
| 653 | decision.setRole(role);
|
|---|
| 654 | }
|
|---|
| 655 | refresh();
|
|---|
| 656 | }
|
|---|
| 657 |
|
|---|
| 658 | protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
|
|---|
| 659 | for(RelationMemberConflictDecision decision: decisions) {
|
|---|
| 660 | if (decision.matches(relation, pos)) return decision;
|
|---|
| 661 | }
|
|---|
| 662 | return null;
|
|---|
| 663 | }
|
|---|
| 664 |
|
|---|
| 665 | protected void buildResolveCorrespondance(Relation relation, OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
|
|---|
| 666 |
|
|---|
| 667 | List<RelationMember> relationsMembers = relation.getMembers();
|
|---|
| 668 | Relation modifiedRelation = MergeOverlapAction.getNew(relation, newRelations);
|
|---|
| 669 | modifiedRelation.setMembers(null);
|
|---|
| 670 | // boolean isChanged = false;
|
|---|
| 671 | for (int i=0; i < relationsMembers.size(); i++) {
|
|---|
| 672 | RelationMember rm = relationsMembers.get(i);
|
|---|
| 673 | // RelationMember rm = relation.getMember(i);
|
|---|
| 674 | // RelationMember rmNew;
|
|---|
| 675 | RelationMemberConflictDecision decision = getDecision(relation, i);
|
|---|
| 676 | if (decision == null) {
|
|---|
| 677 | modifiedRelation.addMember(rm);
|
|---|
| 678 | } else {
|
|---|
| 679 | System.out.println(modifiedRelation);
|
|---|
| 680 | System.out.println(111);
|
|---|
| 681 | switch(decision.getDecision()) {
|
|---|
| 682 | case KEEP:
|
|---|
| 683 | // modifiedRelation.removeMembersFor(newPrimitive);
|
|---|
| 684 | System.out.println(222);
|
|---|
| 685 | if (newPrimitive instanceof Way) {
|
|---|
| 686 | modifiedRelation.addMember(new RelationMember(decision.getRole(), MergeOverlapAction.getOld((Way)newPrimitive, oldWays)));
|
|---|
| 687 | }
|
|---|
| 688 | else {
|
|---|
| 689 | modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
|
|---|
| 690 | }
|
|---|
| 691 | // modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
|
|---|
| 692 | break;
|
|---|
| 693 | case REMOVE:
|
|---|
| 694 | System.out.println(333);
|
|---|
| 695 | // modifiedRelation.removeMembersFor(rm.getMember());
|
|---|
| 696 | // isChanged = true;
|
|---|
| 697 | // do nothing
|
|---|
| 698 | break;
|
|---|
| 699 | case UNDECIDED:
|
|---|
| 700 | // FIXME: this is an error
|
|---|
| 701 | break;
|
|---|
| 702 | }
|
|---|
| 703 | }
|
|---|
| 704 | }
|
|---|
| 705 | }
|
|---|
| 706 |
|
|---|
| 707 | /**
|
|---|
| 708 | * Builds a collection of commands executing the decisions made in this model.
|
|---|
| 709 | *
|
|---|
| 710 | * @param newPrimitive the primitive which members shall refer to if the
|
|---|
| 711 | * decision is {@see RelationMemberConflictDecisionType#REPLACE}
|
|---|
| 712 | * @return a list of commands
|
|---|
| 713 | */
|
|---|
| 714 | public void buildRelationCorrespondance(OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
|
|---|
| 715 | for (Relation relation : relations) {
|
|---|
| 716 | buildResolveCorrespondance(relation, newPrimitive, newRelations, oldWays);
|
|---|
| 717 | }
|
|---|
| 718 | }
|
|---|
| 719 |
|
|---|
| 720 | protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
|
|---|
| 721 | for (int i=0; i < relation.getMembersCount(); i++) {
|
|---|
| 722 | RelationMemberConflictDecision decision = getDecision(relation, i);
|
|---|
| 723 | if (decision == null) {
|
|---|
| 724 | continue;
|
|---|
| 725 | }
|
|---|
| 726 | switch(decision.getDecision()) {
|
|---|
| 727 | case REMOVE: return true;
|
|---|
| 728 | case KEEP:
|
|---|
| 729 | if (!relation.getMember(i).getRole().equals(decision.getRole()))
|
|---|
| 730 | return true;
|
|---|
| 731 | if (relation.getMember(i).getMember() != newPrimitive)
|
|---|
| 732 | return true;
|
|---|
| 733 | case UNDECIDED:
|
|---|
| 734 | // FIXME: handle error
|
|---|
| 735 | }
|
|---|
| 736 | }
|
|---|
| 737 | return false;
|
|---|
| 738 | }
|
|---|
| 739 |
|
|---|
| 740 | /**
|
|---|
| 741 | * Replies the set of relations which have to be modified according
|
|---|
| 742 | * to the decisions managed by this model.
|
|---|
| 743 | *
|
|---|
| 744 | * @param newPrimitive the primitive which members shall refer to if the
|
|---|
| 745 | * decision is {@see RelationMemberConflictDecisionType#REPLACE}
|
|---|
| 746 | *
|
|---|
| 747 | * @return the set of relations which have to be modified according
|
|---|
| 748 | * to the decisions managed by this model
|
|---|
| 749 | */
|
|---|
| 750 | public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
|
|---|
| 751 | HashSet<Relation> ret = new HashSet<Relation>();
|
|---|
| 752 | for (Relation relation: relations) {
|
|---|
| 753 | if (isChanged(relation, newPrimitive)) {
|
|---|
| 754 | ret.add(relation);
|
|---|
| 755 | }
|
|---|
| 756 | }
|
|---|
| 757 | return ret;
|
|---|
| 758 | }
|
|---|
| 759 | }
|
|---|
| 760 |
|
|---|
| 761 | public class MyRelationMemberConflictResolver extends JPanel {
|
|---|
| 762 |
|
|---|
| 763 | private AutoCompletingTextField tfRole;
|
|---|
| 764 | private AutoCompletingTextField tfKey;
|
|---|
| 765 | private AutoCompletingTextField tfValue;
|
|---|
| 766 | private JCheckBox cbTagRelations;
|
|---|
| 767 | private MyRelationMemberConflictResolverModel model;
|
|---|
| 768 | private MyRelationMemberConflictResolverTable tblResolver;
|
|---|
| 769 | private JMultilineLabel lblHeader;
|
|---|
| 770 |
|
|---|
| 771 | protected void build() {
|
|---|
| 772 | setLayout(new GridBagLayout());
|
|---|
| 773 | JPanel pnl = new JPanel();
|
|---|
| 774 | pnl.setLayout(new BorderLayout());
|
|---|
| 775 | pnl.add(lblHeader = new JMultilineLabel(""));
|
|---|
| 776 | GridBagConstraints gc = new GridBagConstraints();
|
|---|
| 777 | gc.fill = GridBagConstraints.HORIZONTAL;
|
|---|
| 778 | gc.weighty = 0.0;
|
|---|
| 779 | gc.weightx = 1.0;
|
|---|
| 780 | gc.insets = new Insets(5,5,5,5);
|
|---|
| 781 | add(pnl, gc);
|
|---|
| 782 | model = new MyRelationMemberConflictResolverModel();
|
|---|
| 783 |
|
|---|
| 784 | gc.gridy = 1;
|
|---|
| 785 | gc.weighty = 1.0;
|
|---|
| 786 | gc.fill = GridBagConstraints.BOTH;
|
|---|
| 787 | gc.insets = new Insets(0,0,0,0);
|
|---|
| 788 | add(new JScrollPane(tblResolver = new MyRelationMemberConflictResolverTable(model)), gc);
|
|---|
| 789 | pnl = new JPanel();
|
|---|
| 790 | pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
|
|---|
| 791 | pnl.add(buildRoleEditingPanel());
|
|---|
| 792 | pnl.add(buildTagRelationsPanel());
|
|---|
| 793 | gc.gridy = 2;
|
|---|
| 794 | gc.weighty = 0.0;
|
|---|
| 795 | gc.fill = GridBagConstraints.HORIZONTAL;
|
|---|
| 796 | add(pnl,gc);
|
|---|
| 797 | }
|
|---|
| 798 |
|
|---|
| 799 | protected JPanel buildRoleEditingPanel() {
|
|---|
| 800 | JPanel pnl = new JPanel();
|
|---|
| 801 | pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
|
|---|
| 802 | pnl.add(new JLabel(tr("Role:", null)));
|
|---|
| 803 | pnl.add(tfRole = new AutoCompletingTextField(10));
|
|---|
| 804 | tfRole.setToolTipText(tr("Enter a role for all relation memberships", null));
|
|---|
| 805 | pnl.add(new JButton(new ApplyRoleAction()));
|
|---|
| 806 | tfRole.addActionListener(new ApplyRoleAction());
|
|---|
| 807 | tfRole.addFocusListener(
|
|---|
| 808 | new FocusAdapter() {
|
|---|
| 809 | @Override
|
|---|
| 810 | public void focusGained(FocusEvent e) {
|
|---|
| 811 | tfRole.selectAll();
|
|---|
| 812 | }
|
|---|
| 813 | }
|
|---|
| 814 | );
|
|---|
| 815 | return pnl;
|
|---|
| 816 | }
|
|---|
| 817 |
|
|---|
| 818 | protected JPanel buildTagRelationsPanel() {
|
|---|
| 819 | JPanel pnl = new JPanel();
|
|---|
| 820 | pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
|
|---|
| 821 | cbTagRelations = new JCheckBox(tr("Tag modified relations with ", null));
|
|---|
| 822 | cbTagRelations.addChangeListener(new ToggleTagRelationsAction());
|
|---|
| 823 | cbTagRelations.setToolTipText(
|
|---|
| 824 | tr("<html>Select to enable entering a tag which will be applied<br>"
|
|---|
| 825 | + "to all modified relations.</html>", null));
|
|---|
| 826 | pnl.add(cbTagRelations);
|
|---|
| 827 | pnl.add(new JLabel(trc("tag", "Key:")));
|
|---|
| 828 | pnl.add(tfKey = new AutoCompletingTextField(10));
|
|---|
| 829 | tfKey.setToolTipText(tr("<html>Enter a tag key, i.e. <strong><tt>fixme</tt></strong></html>", null));
|
|---|
| 830 | pnl.add(new JLabel(tr("Value:", null)));
|
|---|
| 831 | pnl.add(tfValue = new AutoCompletingTextField(10));
|
|---|
| 832 | tfValue.setToolTipText(tr("<html>Enter a tag value, i.e. <strong><tt>check members</tt></strong></html>", null));
|
|---|
| 833 | cbTagRelations.setSelected(false);
|
|---|
| 834 | tfKey.setEnabled(false);
|
|---|
| 835 | tfValue.setEnabled(false);
|
|---|
| 836 | return pnl;
|
|---|
| 837 | }
|
|---|
| 838 |
|
|---|
| 839 | public MyRelationMemberConflictResolver() {
|
|---|
| 840 | build();
|
|---|
| 841 | }
|
|---|
| 842 |
|
|---|
| 843 | public void initForWayCombining() {
|
|---|
| 844 | lblHeader.setText(tr("<html>The combined ways are members in one ore more relations. "
|
|---|
| 845 | + "Please decide whether you want to <strong>keep</strong> these memberships "
|
|---|
| 846 | + "for the combined way or whether you want to <strong>remove</strong> them.<br>"
|
|---|
| 847 | + "The default is to <strong>keep</strong> the first way and <strong>remove</strong> "
|
|---|
| 848 | + "the other ways that are members of the same relation: the combined way will "
|
|---|
| 849 | + "take the place of the original way in the relation."
|
|---|
| 850 | + "</html>", null));
|
|---|
| 851 | invalidate();
|
|---|
| 852 | }
|
|---|
| 853 |
|
|---|
| 854 | public void initForNodeMerging() {
|
|---|
| 855 | lblHeader.setText(tr("<html>The merged nodes are members in one ore more relations. "
|
|---|
| 856 | + "Please decide whether you want to <strong>keep</strong> these memberships "
|
|---|
| 857 | + "for the target node or whether you want to <strong>remove</strong> them.<br>"
|
|---|
| 858 | + "The default is to <strong>keep</strong> the first node and <strong>remove</strong> "
|
|---|
| 859 | + "the other nodes that are members of the same relation: the target node will "
|
|---|
| 860 | + "take the place of the original node in the relation."
|
|---|
| 861 | + "</html>", null));
|
|---|
| 862 | invalidate();
|
|---|
| 863 | }
|
|---|
| 864 |
|
|---|
| 865 | class ApplyRoleAction extends AbstractAction {
|
|---|
| 866 | public ApplyRoleAction() {
|
|---|
| 867 | putValue(NAME, tr("Apply", null));
|
|---|
| 868 | putValue(SMALL_ICON, ImageProvider.get("ok"));
|
|---|
| 869 | putValue(SHORT_DESCRIPTION, tr("Apply this role to all members", null));
|
|---|
| 870 | }
|
|---|
| 871 |
|
|---|
| 872 | public void actionPerformed(ActionEvent e) {
|
|---|
| 873 | model.applyRole(tfRole.getText());
|
|---|
| 874 | }
|
|---|
| 875 | }
|
|---|
| 876 |
|
|---|
| 877 | class ToggleTagRelationsAction implements ChangeListener {
|
|---|
| 878 | public void stateChanged(ChangeEvent e) {
|
|---|
| 879 | ButtonModel buttonModel = ((AbstractButton) e.getSource()).getModel();
|
|---|
| 880 | tfKey.setEnabled(buttonModel.isSelected());
|
|---|
| 881 | tfValue.setEnabled(buttonModel.isSelected());
|
|---|
| 882 | tfKey.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
|
|---|
| 883 | .getColor("Panel.background"));
|
|---|
| 884 | tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
|
|---|
| 885 | .getColor("Panel.background"));
|
|---|
| 886 | }
|
|---|
| 887 | }
|
|---|
| 888 |
|
|---|
| 889 | public MyRelationMemberConflictResolverModel getModel() {
|
|---|
| 890 | return model;
|
|---|
| 891 | }
|
|---|
| 892 |
|
|---|
| 893 | public Command buildTagApplyCommands(Collection<? extends OsmPrimitive> primitives) {
|
|---|
| 894 | if (!cbTagRelations.isSelected())
|
|---|
| 895 | return null;
|
|---|
| 896 | if (tfKey.getText().trim().equals(""))
|
|---|
| 897 | return null;
|
|---|
| 898 | if (tfValue.getText().trim().equals(""))
|
|---|
| 899 | return null;
|
|---|
| 900 | if (primitives == null || primitives.isEmpty())
|
|---|
| 901 | return null;
|
|---|
| 902 | return new ChangePropertyCommand(primitives, tfKey.getText(), tfValue.getText());
|
|---|
| 903 | }
|
|---|
| 904 |
|
|---|
| 905 | public void prepareForEditing() {
|
|---|
| 906 | AutoCompletionList acList = new AutoCompletionList();
|
|---|
| 907 | Main.main.getEditLayer().data.getAutoCompletionManager().populateWithMemberRoles(acList);
|
|---|
| 908 | tfRole.setAutoCompletionList(acList);
|
|---|
| 909 | AutoCompletingTextField editor = (AutoCompletingTextField) tblResolver.getColumnModel().getColumn(2).getCellEditor();
|
|---|
| 910 | if (editor != null) {
|
|---|
| 911 | editor.setAutoCompletionList(acList);
|
|---|
| 912 | }
|
|---|
| 913 | AutoCompletionList acList2 = new AutoCompletionList();
|
|---|
| 914 | Main.main.getEditLayer().data.getAutoCompletionManager().populateWithKeys(acList2);
|
|---|
| 915 | tfKey.setAutoCompletionList(acList2);
|
|---|
| 916 | }
|
|---|
| 917 | }
|
|---|
| 918 |
|
|---|
| 919 |
|
|---|
| 920 | public class MyRelationMemberConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
|
|---|
| 921 |
|
|---|
| 922 | private SelectNextColumnCellAction selectNextColumnCellAction;
|
|---|
| 923 | private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
|
|---|
| 924 |
|
|---|
| 925 | public MyRelationMemberConflictResolverTable(MyRelationMemberConflictResolverModel model) {
|
|---|
| 926 | super(model, new RelationMemberConflictResolverColumnModel());
|
|---|
| 927 | build();
|
|---|
| 928 | }
|
|---|
| 929 |
|
|---|
| 930 | protected void build() {
|
|---|
| 931 | setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
|---|
| 932 | setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|---|
| 933 | putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
|
|---|
| 934 |
|
|---|
| 935 | // make ENTER behave like TAB
|
|---|
| 936 | //
|
|---|
| 937 | getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
|
|---|
| 938 | KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
|
|---|
| 939 |
|
|---|
| 940 | // install custom navigation actions
|
|---|
| 941 | //
|
|---|
| 942 | selectNextColumnCellAction = new SelectNextColumnCellAction();
|
|---|
| 943 | selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
|
|---|
| 944 | getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
|
|---|
| 945 | getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
|
|---|
| 946 |
|
|---|
| 947 | setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
|
|---|
| 948 | }
|
|---|
| 949 |
|
|---|
| 950 | /**
|
|---|
| 951 | * Action to be run when the user navigates to the next cell in the table, for instance by
|
|---|
| 952 | * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
|
|---|
| 953 | * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
|
|---|
| 954 | * when the user leaves the last cell in the table</li> <ul>
|
|---|
| 955 | *
|
|---|
| 956 | *
|
|---|
| 957 | */
|
|---|
| 958 | class SelectNextColumnCellAction extends AbstractAction {
|
|---|
| 959 | public void actionPerformed(ActionEvent e) {
|
|---|
| 960 | run();
|
|---|
| 961 | }
|
|---|
| 962 |
|
|---|
| 963 | public void run() {
|
|---|
| 964 | int col = getSelectedColumn();
|
|---|
| 965 | int row = getSelectedRow();
|
|---|
| 966 | if (getCellEditor() != null) {
|
|---|
| 967 | getCellEditor().stopCellEditing();
|
|---|
| 968 | }
|
|---|
| 969 |
|
|---|
| 970 | if (col == 2 && row < getRowCount() - 1) {
|
|---|
| 971 | row++;
|
|---|
| 972 | } else if (row < getRowCount() - 1) {
|
|---|
| 973 | col = 2;
|
|---|
| 974 | row++;
|
|---|
| 975 | }
|
|---|
| 976 | changeSelection(row, col, false, false);
|
|---|
| 977 | editCellAt(getSelectedRow(), getSelectedColumn());
|
|---|
| 978 | getEditorComponent().requestFocusInWindow();
|
|---|
| 979 | }
|
|---|
| 980 | }
|
|---|
| 981 |
|
|---|
| 982 | /**
|
|---|
| 983 | * Action to be run when the user navigates to the previous cell in the table, for instance by
|
|---|
| 984 | * pressing Shift-TAB
|
|---|
| 985 | *
|
|---|
| 986 | */
|
|---|
| 987 | class SelectPreviousColumnCellAction extends AbstractAction {
|
|---|
| 988 |
|
|---|
| 989 | public void actionPerformed(ActionEvent e) {
|
|---|
| 990 | run();
|
|---|
| 991 | }
|
|---|
| 992 |
|
|---|
| 993 | public void run() {
|
|---|
| 994 | int col = getSelectedColumn();
|
|---|
| 995 | int row = getSelectedRow();
|
|---|
| 996 | if (getCellEditor() != null) {
|
|---|
| 997 | getCellEditor().stopCellEditing();
|
|---|
| 998 | }
|
|---|
| 999 |
|
|---|
| 1000 | if (col <= 0 && row <= 0) {
|
|---|
| 1001 | // change nothing
|
|---|
| 1002 | } else if (row > 0) {
|
|---|
| 1003 | col = 2;
|
|---|
| 1004 | row--;
|
|---|
| 1005 | }
|
|---|
| 1006 | changeSelection(row, col, false, false);
|
|---|
| 1007 | editCellAt(getSelectedRow(), getSelectedColumn());
|
|---|
| 1008 | getEditorComponent().requestFocusInWindow();
|
|---|
| 1009 | }
|
|---|
| 1010 | }
|
|---|
| 1011 |
|
|---|
| 1012 | public void gotoNextDecision() {
|
|---|
| 1013 | selectNextColumnCellAction.run();
|
|---|
| 1014 | }
|
|---|
| 1015 |
|
|---|
| 1016 | public void gotoPreviousDecision() {
|
|---|
| 1017 | selectPreviousColumnCellAction.run();
|
|---|
| 1018 | }
|
|---|
| 1019 | }
|
|---|
| 1020 |
|
|---|
| 1021 |
|
|---|
| 1022 | public static class MyTagConflictResolverModel extends DefaultTableModel {
|
|---|
| 1023 | static public final String NUM_CONFLICTS_PROP = MyTagConflictResolverModel.class.getName() + ".numConflicts";
|
|---|
| 1024 |
|
|---|
| 1025 | private TagCollection tags;
|
|---|
| 1026 | private List<String> displayedKeys;
|
|---|
| 1027 | private Set<String> keysWithConflicts;
|
|---|
| 1028 | private HashMap<String, MultiValueResolutionDecision> decisions;
|
|---|
| 1029 | private int numConflicts;
|
|---|
| 1030 | private PropertyChangeSupport support;
|
|---|
| 1031 | private boolean showTagsWithConflictsOnly = false;
|
|---|
| 1032 | private boolean showTagsWithMultiValuesOnly = false;
|
|---|
| 1033 |
|
|---|
| 1034 | public MyTagConflictResolverModel() {
|
|---|
| 1035 | numConflicts = 0;
|
|---|
| 1036 | support = new PropertyChangeSupport(this);
|
|---|
| 1037 | }
|
|---|
| 1038 |
|
|---|
| 1039 | public void addPropertyChangeListener(PropertyChangeListener listener) {
|
|---|
| 1040 | support.addPropertyChangeListener(listener);
|
|---|
| 1041 | }
|
|---|
| 1042 |
|
|---|
| 1043 | public void removePropertyChangeListener(PropertyChangeListener listener) {
|
|---|
| 1044 | support.removePropertyChangeListener(listener);
|
|---|
| 1045 | }
|
|---|
| 1046 |
|
|---|
| 1047 | protected void setNumConflicts(int numConflicts) {
|
|---|
| 1048 | int oldValue = this.numConflicts;
|
|---|
| 1049 | this.numConflicts = numConflicts;
|
|---|
| 1050 | if (oldValue != this.numConflicts) {
|
|---|
| 1051 | support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
|
|---|
| 1052 | }
|
|---|
| 1053 | }
|
|---|
| 1054 |
|
|---|
| 1055 | protected void refreshNumConflicts() {
|
|---|
| 1056 | int count = 0;
|
|---|
| 1057 | for (MultiValueResolutionDecision d : decisions.values()) {
|
|---|
| 1058 | if (!d.isDecided()) {
|
|---|
| 1059 | count++;
|
|---|
| 1060 | }
|
|---|
| 1061 | }
|
|---|
| 1062 | setNumConflicts(count);
|
|---|
| 1063 | }
|
|---|
| 1064 |
|
|---|
| 1065 | protected void sort() {
|
|---|
| 1066 | Collections.sort(
|
|---|
| 1067 | displayedKeys,
|
|---|
| 1068 | new Comparator<String>() {
|
|---|
| 1069 | public int compare(String key1, String key2) {
|
|---|
| 1070 | if (decisions.get(key1).isDecided() && ! decisions.get(key2).isDecided())
|
|---|
| 1071 | return 1;
|
|---|
| 1072 | else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
|
|---|
| 1073 | return -1;
|
|---|
| 1074 | return key1.compareTo(key2);
|
|---|
| 1075 | }
|
|---|
| 1076 | }
|
|---|
| 1077 | );
|
|---|
| 1078 | }
|
|---|
| 1079 |
|
|---|
| 1080 | /**
|
|---|
| 1081 | * initializes the model from the current tags
|
|---|
| 1082 | *
|
|---|
| 1083 | */
|
|---|
| 1084 | protected void rebuild() {
|
|---|
| 1085 | if (tags == null) return;
|
|---|
| 1086 | for(String key: tags.getKeys()) {
|
|---|
| 1087 | MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
|
|---|
| 1088 | if (decisions.get(key) == null) {
|
|---|
| 1089 | decisions.put(key,decision);
|
|---|
| 1090 | }
|
|---|
| 1091 | }
|
|---|
| 1092 | displayedKeys.clear();
|
|---|
| 1093 | Set<String> keys = tags.getKeys();
|
|---|
| 1094 | if (showTagsWithConflictsOnly) {
|
|---|
| 1095 | keys.retainAll(keysWithConflicts);
|
|---|
| 1096 | if (showTagsWithMultiValuesOnly) {
|
|---|
| 1097 | Set<String> keysWithMultiValues = new HashSet<String>();
|
|---|
| 1098 | for (String key: keys) {
|
|---|
| 1099 | if (decisions.get(key).canKeepAll()) {
|
|---|
| 1100 | keysWithMultiValues.add(key);
|
|---|
| 1101 | }
|
|---|
| 1102 | }
|
|---|
| 1103 | keys.retainAll(keysWithMultiValues);
|
|---|
| 1104 | }
|
|---|
| 1105 | for (String key: tags.getKeys()) {
|
|---|
| 1106 | if (!decisions.get(key).isDecided() && !keys.contains(key)) {
|
|---|
| 1107 | keys.add(key);
|
|---|
| 1108 | }
|
|---|
| 1109 | }
|
|---|
| 1110 | }
|
|---|
| 1111 | displayedKeys.addAll(keys);
|
|---|
| 1112 | refreshNumConflicts();
|
|---|
| 1113 | sort();
|
|---|
| 1114 | fireTableDataChanged();
|
|---|
| 1115 | }
|
|---|
| 1116 |
|
|---|
| 1117 | /**
|
|---|
| 1118 | * Populates the model with the tags for which conflicts are to be resolved.
|
|---|
| 1119 | *
|
|---|
| 1120 | * @param tags the tag collection with the tags. Must not be null.
|
|---|
| 1121 | * @param keysWithConflicts the set of tag keys with conflicts
|
|---|
| 1122 | * @throws IllegalArgumentException thrown if tags is null
|
|---|
| 1123 | */
|
|---|
| 1124 | public void populate(TagCollection tags, Set<String> keysWithConflicts) {
|
|---|
| 1125 | CheckParameterUtil.ensureParameterNotNull(tags, "tags");
|
|---|
| 1126 | this.tags = tags;
|
|---|
| 1127 | displayedKeys = new ArrayList<String>();
|
|---|
| 1128 | this.keysWithConflicts = keysWithConflicts == null ? new HashSet<String>() : keysWithConflicts;
|
|---|
| 1129 | decisions = new HashMap<String, MultiValueResolutionDecision>();
|
|---|
| 1130 | rebuild();
|
|---|
| 1131 | }
|
|---|
| 1132 |
|
|---|
| 1133 | @Override
|
|---|
| 1134 | public int getRowCount() {
|
|---|
| 1135 | if (displayedKeys == null) return 0;
|
|---|
| 1136 | return displayedKeys.size();
|
|---|
| 1137 | }
|
|---|
| 1138 |
|
|---|
| 1139 | @Override
|
|---|
| 1140 | public Object getValueAt(int row, int column) {
|
|---|
| 1141 | return decisions.get(displayedKeys.get(row));
|
|---|
| 1142 | }
|
|---|
| 1143 |
|
|---|
| 1144 | @Override
|
|---|
| 1145 | public boolean isCellEditable(int row, int column) {
|
|---|
| 1146 | return column == 2;
|
|---|
| 1147 | }
|
|---|
| 1148 |
|
|---|
| 1149 | @Override
|
|---|
| 1150 | public void setValueAt(Object value, int row, int column) {
|
|---|
| 1151 | MultiValueResolutionDecision decision = decisions.get(displayedKeys.get(row));
|
|---|
| 1152 | if (value instanceof String) {
|
|---|
| 1153 | decision.keepOne((String)value);
|
|---|
| 1154 | } else if (value instanceof MultiValueDecisionType) {
|
|---|
| 1155 | MultiValueDecisionType type = (MultiValueDecisionType)value;
|
|---|
| 1156 | switch(type) {
|
|---|
| 1157 | case KEEP_NONE:
|
|---|
| 1158 | decision.keepNone();
|
|---|
| 1159 | break;
|
|---|
| 1160 | case KEEP_ALL:
|
|---|
| 1161 | decision.keepAll();
|
|---|
| 1162 | break;
|
|---|
| 1163 | }
|
|---|
| 1164 | }
|
|---|
| 1165 | fireTableDataChanged();
|
|---|
| 1166 | refreshNumConflicts();
|
|---|
| 1167 | }
|
|---|
| 1168 |
|
|---|
| 1169 | /**
|
|---|
| 1170 | * Replies true if each {@see MultiValueResolutionDecision} is decided.
|
|---|
| 1171 | *
|
|---|
| 1172 | * @return true if each {@see MultiValueResolutionDecision} is decided; false
|
|---|
| 1173 | * otherwise
|
|---|
| 1174 | */
|
|---|
| 1175 | public boolean isResolvedCompletely() {
|
|---|
| 1176 | return numConflicts == 0;
|
|---|
| 1177 | }
|
|---|
| 1178 |
|
|---|
| 1179 | public int getNumConflicts() {
|
|---|
| 1180 | return numConflicts;
|
|---|
| 1181 | }
|
|---|
| 1182 |
|
|---|
| 1183 | public int getNumDecisions() {
|
|---|
| 1184 | return getRowCount();
|
|---|
| 1185 | }
|
|---|
| 1186 |
|
|---|
| 1187 | //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
|
|---|
| 1188 | //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
|
|---|
| 1189 | public TagCollection getResolution() {
|
|---|
| 1190 | TagCollection tc = new TagCollection();
|
|---|
| 1191 | for (String key: displayedKeys) {
|
|---|
| 1192 | tc.add(decisions.get(key).getResolution());
|
|---|
| 1193 | }
|
|---|
| 1194 | return tc;
|
|---|
| 1195 | }
|
|---|
| 1196 |
|
|---|
| 1197 | public TagCollection getAllResolutions() {
|
|---|
| 1198 | TagCollection tc = new TagCollection();
|
|---|
| 1199 | for (MultiValueResolutionDecision value: decisions.values()) {
|
|---|
| 1200 | tc.add(value.getResolution());
|
|---|
| 1201 | }
|
|---|
| 1202 | return tc;
|
|---|
| 1203 | }
|
|---|
| 1204 |
|
|---|
| 1205 | public MultiValueResolutionDecision getDecision(int row) {
|
|---|
| 1206 | return decisions.get(displayedKeys.get(row));
|
|---|
| 1207 | }
|
|---|
| 1208 |
|
|---|
| 1209 | /**
|
|---|
| 1210 | * Sets whether all tags or only tags with conflicts are displayed
|
|---|
| 1211 | *
|
|---|
| 1212 | * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
|
|---|
| 1213 | */
|
|---|
| 1214 | public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
|
|---|
| 1215 | this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
|
|---|
| 1216 | rebuild();
|
|---|
| 1217 | }
|
|---|
| 1218 |
|
|---|
| 1219 | /**
|
|---|
| 1220 | * Sets whether all conflicts or only conflicts with multiple values are displayed
|
|---|
| 1221 | *
|
|---|
| 1222 | * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
|
|---|
| 1223 | */
|
|---|
| 1224 | public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
|
|---|
| 1225 | this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
|
|---|
| 1226 | rebuild();
|
|---|
| 1227 | }
|
|---|
| 1228 |
|
|---|
| 1229 | /**
|
|---|
| 1230 | * Prepare the default decisions for the current model
|
|---|
| 1231 | *
|
|---|
| 1232 | */
|
|---|
| 1233 | public void prepareDefaultTagDecisions() {
|
|---|
| 1234 | for (MultiValueResolutionDecision decision: decisions.values()) {
|
|---|
| 1235 | List<String> values = decision.getValues();
|
|---|
| 1236 | values.remove("");
|
|---|
| 1237 | if (values.size() == 1) {
|
|---|
| 1238 | decision.keepOne(values.get(0));
|
|---|
| 1239 | } else {
|
|---|
| 1240 | decision.keepAll();
|
|---|
| 1241 | }
|
|---|
| 1242 | }
|
|---|
| 1243 | rebuild();
|
|---|
| 1244 | }
|
|---|
| 1245 |
|
|---|
| 1246 | }
|
|---|
| 1247 |
|
|---|
| 1248 |
|
|---|
| 1249 | /**
|
|---|
| 1250 | * This is a UI widget for resolving tag conflicts, i.e. differences of the tag values
|
|---|
| 1251 | * of multiple {@see OsmPrimitive}s.
|
|---|
| 1252 | *
|
|---|
| 1253 | *
|
|---|
| 1254 | */
|
|---|
| 1255 | public class MyTagConflictResolver extends JPanel {
|
|---|
| 1256 |
|
|---|
| 1257 | /** the model for the tag conflict resolver */
|
|---|
| 1258 | private MyTagConflictResolverModel model;
|
|---|
| 1259 | /** selects wheter only tags with conflicts are displayed */
|
|---|
| 1260 | private JCheckBox cbShowTagsWithConflictsOnly;
|
|---|
| 1261 | private JCheckBox cbShowTagsWithMultiValuesOnly;
|
|---|
| 1262 |
|
|---|
| 1263 | protected JPanel buildInfoPanel() {
|
|---|
| 1264 | JPanel pnl = new JPanel();
|
|---|
| 1265 | pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
|
|---|
| 1266 | pnl.setLayout(new GridBagLayout());
|
|---|
| 1267 | GridBagConstraints gc = new GridBagConstraints();
|
|---|
| 1268 | gc.fill = GridBagConstraints.BOTH;
|
|---|
| 1269 | gc.weighty = 1.0;
|
|---|
| 1270 | gc.weightx = 1.0;
|
|---|
| 1271 | gc.anchor = GridBagConstraints.LINE_START;
|
|---|
| 1272 | pnl.add(new JLabel(tr("<html>Please select the values to keep for the following tags.</html>", null)), gc);
|
|---|
| 1273 |
|
|---|
| 1274 | gc.gridy = 1;
|
|---|
| 1275 | gc.fill = GridBagConstraints.HORIZONTAL;
|
|---|
| 1276 | gc.weighty = 0.0;
|
|---|
| 1277 | pnl.add(cbShowTagsWithConflictsOnly = new JCheckBox(tr("Show tags with conflicts only", null)), gc);
|
|---|
| 1278 | pnl.add(cbShowTagsWithMultiValuesOnly = new JCheckBox(tr("Show tags with multiple values only", null)), gc);
|
|---|
| 1279 | cbShowTagsWithConflictsOnly.addChangeListener(
|
|---|
| 1280 | new ChangeListener() {
|
|---|
| 1281 | public void stateChanged(ChangeEvent e) {
|
|---|
| 1282 | model.setShowTagsWithConflictsOnly(cbShowTagsWithConflictsOnly.isSelected());
|
|---|
| 1283 | cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
|
|---|
| 1284 | }
|
|---|
| 1285 | }
|
|---|
| 1286 | );
|
|---|
| 1287 | cbShowTagsWithConflictsOnly.setSelected(
|
|---|
| 1288 | Main.pref.getBoolean(getClass().getName() + ".showTagsWithConflictsOnly", false)
|
|---|
| 1289 | );
|
|---|
| 1290 | cbShowTagsWithMultiValuesOnly.addChangeListener(
|
|---|
| 1291 | new ChangeListener() {
|
|---|
| 1292 | public void stateChanged(ChangeEvent e) {
|
|---|
| 1293 | model.setShowTagsWithMultiValuesOnly(cbShowTagsWithMultiValuesOnly.isSelected());
|
|---|
| 1294 | }
|
|---|
| 1295 | }
|
|---|
| 1296 | );
|
|---|
| 1297 | cbShowTagsWithMultiValuesOnly.setSelected(
|
|---|
| 1298 | Main.pref.getBoolean(getClass().getName() + ".showTagsWithMultiValuesOnly", false)
|
|---|
| 1299 | );
|
|---|
| 1300 | cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
|
|---|
| 1301 | return pnl;
|
|---|
| 1302 | }
|
|---|
| 1303 |
|
|---|
| 1304 | /**
|
|---|
| 1305 | * Remembers the current settings in the global preferences
|
|---|
| 1306 | *
|
|---|
| 1307 | */
|
|---|
| 1308 | public void rememberPreferences() {
|
|---|
| 1309 | Main.pref.put(getClass().getName() + ".showTagsWithConflictsOnly", cbShowTagsWithConflictsOnly.isSelected());
|
|---|
| 1310 | Main.pref.put(getClass().getName() + ".showTagsWithMultiValuesOnly", cbShowTagsWithMultiValuesOnly.isSelected());
|
|---|
| 1311 | }
|
|---|
| 1312 |
|
|---|
| 1313 | protected void build() {
|
|---|
| 1314 | setLayout(new BorderLayout());
|
|---|
| 1315 | add(buildInfoPanel(), BorderLayout.NORTH);
|
|---|
| 1316 | add(new JScrollPane(new MyTagConflictResolverTable(model)), BorderLayout.CENTER);
|
|---|
| 1317 | }
|
|---|
| 1318 |
|
|---|
| 1319 | public MyTagConflictResolver() {
|
|---|
| 1320 | this.model = new MyTagConflictResolverModel();
|
|---|
| 1321 | build();
|
|---|
| 1322 | }
|
|---|
| 1323 |
|
|---|
| 1324 | /**
|
|---|
| 1325 | * Replies the model used by this dialog
|
|---|
| 1326 | *
|
|---|
| 1327 | * @return the model
|
|---|
| 1328 | */
|
|---|
| 1329 | public MyTagConflictResolverModel getModel() {
|
|---|
| 1330 | return model;
|
|---|
| 1331 | }
|
|---|
| 1332 | }
|
|---|
| 1333 |
|
|---|
| 1334 | public class MyTagConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
|
|---|
| 1335 |
|
|---|
| 1336 | private SelectNextColumnCellAction selectNextColumnCellAction;
|
|---|
| 1337 | private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
|
|---|
| 1338 |
|
|---|
| 1339 | public MyTagConflictResolverTable(MyTagConflictResolverModel model) {
|
|---|
| 1340 | super(model, new TagConflictResolverColumnModel());
|
|---|
| 1341 | build();
|
|---|
| 1342 | }
|
|---|
| 1343 |
|
|---|
| 1344 | protected void build() {
|
|---|
| 1345 | setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
|---|
| 1346 | setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|---|
| 1347 | putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
|
|---|
| 1348 |
|
|---|
| 1349 | // make ENTER behave like TAB
|
|---|
| 1350 | //
|
|---|
| 1351 | getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
|
|---|
| 1352 | KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
|
|---|
| 1353 |
|
|---|
| 1354 | // install custom navigation actions
|
|---|
| 1355 | //
|
|---|
| 1356 | selectNextColumnCellAction = new SelectNextColumnCellAction();
|
|---|
| 1357 | selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
|
|---|
| 1358 | getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
|
|---|
| 1359 | getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
|
|---|
| 1360 |
|
|---|
| 1361 | ((MultiValueCellEditor)getColumnModel().getColumn(2).getCellEditor()).addNavigationListeners(this);
|
|---|
| 1362 |
|
|---|
| 1363 | setRowHeight((int)new JComboBox().getPreferredSize().getHeight());
|
|---|
| 1364 | }
|
|---|
| 1365 |
|
|---|
| 1366 | /**
|
|---|
| 1367 | * Action to be run when the user navigates to the next cell in the table, for instance by
|
|---|
| 1368 | * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
|
|---|
| 1369 | * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
|
|---|
| 1370 | * when the user leaves the last cell in the table</li> <ul>
|
|---|
| 1371 | *
|
|---|
| 1372 | *
|
|---|
| 1373 | */
|
|---|
| 1374 | class SelectNextColumnCellAction extends AbstractAction {
|
|---|
| 1375 | public void actionPerformed(ActionEvent e) {
|
|---|
| 1376 | run();
|
|---|
| 1377 | }
|
|---|
| 1378 |
|
|---|
| 1379 | public void run() {
|
|---|
| 1380 | int col = getSelectedColumn();
|
|---|
| 1381 | int row = getSelectedRow();
|
|---|
| 1382 | if (getCellEditor() != null) {
|
|---|
| 1383 | getCellEditor().stopCellEditing();
|
|---|
| 1384 | }
|
|---|
| 1385 |
|
|---|
| 1386 | if (col == 2 && row < getRowCount() - 1) {
|
|---|
| 1387 | row++;
|
|---|
| 1388 | } else if (row < getRowCount() - 1) {
|
|---|
| 1389 | col = 2;
|
|---|
| 1390 | row++;
|
|---|
| 1391 | }
|
|---|
| 1392 | changeSelection(row, col, false, false);
|
|---|
| 1393 | editCellAt(getSelectedRow(), getSelectedColumn());
|
|---|
| 1394 | getEditorComponent().requestFocusInWindow();
|
|---|
| 1395 | }
|
|---|
| 1396 | }
|
|---|
| 1397 |
|
|---|
| 1398 | /**
|
|---|
| 1399 | * Action to be run when the user navigates to the previous cell in the table, for instance by
|
|---|
| 1400 | * pressing Shift-TAB
|
|---|
| 1401 | *
|
|---|
| 1402 | */
|
|---|
| 1403 | class SelectPreviousColumnCellAction extends AbstractAction {
|
|---|
| 1404 |
|
|---|
| 1405 | public void actionPerformed(ActionEvent e) {
|
|---|
| 1406 | run();
|
|---|
| 1407 | }
|
|---|
| 1408 |
|
|---|
| 1409 | public void run() {
|
|---|
| 1410 | int col = getSelectedColumn();
|
|---|
| 1411 | int row = getSelectedRow();
|
|---|
| 1412 | if (getCellEditor() != null) {
|
|---|
| 1413 | getCellEditor().stopCellEditing();
|
|---|
| 1414 | }
|
|---|
| 1415 |
|
|---|
| 1416 | if (col <= 0 && row <= 0) {
|
|---|
| 1417 | // change nothing
|
|---|
| 1418 | } else if (row > 0) {
|
|---|
| 1419 | col = 2;
|
|---|
| 1420 | row--;
|
|---|
| 1421 | }
|
|---|
| 1422 | changeSelection(row, col, false, false);
|
|---|
| 1423 | editCellAt(getSelectedRow(), getSelectedColumn());
|
|---|
| 1424 | getEditorComponent().requestFocusInWindow();
|
|---|
| 1425 | }
|
|---|
| 1426 | }
|
|---|
| 1427 |
|
|---|
| 1428 | public void gotoNextDecision() {
|
|---|
| 1429 | selectNextColumnCellAction.run();
|
|---|
| 1430 | }
|
|---|
| 1431 |
|
|---|
| 1432 | public void gotoPreviousDecision() {
|
|---|
| 1433 | selectPreviousColumnCellAction.run();
|
|---|
| 1434 | }
|
|---|
| 1435 | }
|
|---|
| 1436 | }
|
|---|