Changeset 2095 in josm for trunk


Ignore:
Timestamp:
2009-09-12T06:21:30+02:00 (15 years ago)
Author:
Gubaer
Message:

rewrite of MergeNodesAction
new: new conflict resolution dialog for conflicts during node merging. Can resolve conflicts in relation members too.

Location:
trunk/src/org/openstreetmap/josm
Files:
1 added
4 edited
1 moved

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/CombineWayAction.java

    r2079 r2095  
    3333import org.openstreetmap.josm.data.osm.Way;
    3434import org.openstreetmap.josm.gui.ExtendedDialog;
    35 import org.openstreetmap.josm.gui.conflict.tags.CombineWaysConflictResolverDialog;
     35import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
    3636import org.openstreetmap.josm.tools.Pair;
    3737import org.openstreetmap.josm.tools.Shortcut;
     
    166166        completeTagCollectionWithMissingTags(completeWayTags, ways);
    167167
    168         CombineWaysConflictResolverDialog dialog = CombineWaysConflictResolverDialog.getInstance();
     168        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
    169169        dialog.getTagConflictResolverModel().populate(completeWayTags);
    170         dialog.setTargetWay(targetWay);
     170        dialog.setTargetPrimitive(targetWay);
    171171        dialog.getRelationMemberConflictResolverModel().populate(
    172172                referringRelations.getRelations(),
     
    191191        cmds.add(new DeleteCommand(deletedWays));
    192192        cmds.add(new ChangeCommand(targetWay, modifiedTargetWay));
    193         cmds.addAll(dialog.buildResolutionCommands(targetWay));
     193        cmds.addAll(dialog.buildResolutionCommands());
    194194        final SequenceCommand sequenceCommand = new SequenceCommand(tr("Combine {0} ways", ways.size()), cmds);
    195195
  • trunk/src/org/openstreetmap/josm/actions/MergeNodesAction.java

    r2070 r2095  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    6 import java.awt.GridBagLayout;
    76import java.awt.event.ActionEvent;
    87import java.awt.event.KeyEvent;
    98import java.util.ArrayList;
    109import java.util.Collection;
    11 import java.util.HashMap;
    1210import java.util.HashSet;
    1311import java.util.LinkedList;
    14 import java.util.List;
    15 import java.util.Map;
    1612import java.util.Set;
    17 import java.util.TreeMap;
    18 import java.util.TreeSet;
    19 import java.util.Map.Entry;
    20 
    21 import javax.swing.Box;
    22 import javax.swing.JComboBox;
    23 import javax.swing.JLabel;
     13
    2414import javax.swing.JOptionPane;
    25 import javax.swing.JPanel;
    2615
    2716import org.openstreetmap.josm.Main;
     
    3019import org.openstreetmap.josm.command.DeleteCommand;
    3120import org.openstreetmap.josm.command.SequenceCommand;
     21import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
    3222import org.openstreetmap.josm.data.osm.Node;
    3323import org.openstreetmap.josm.data.osm.OsmPrimitive;
    34 import org.openstreetmap.josm.data.osm.Relation;
    35 import org.openstreetmap.josm.data.osm.RelationMember;
    36 import org.openstreetmap.josm.data.osm.TigerUtils;
     24import org.openstreetmap.josm.data.osm.Tag;
     25import org.openstreetmap.josm.data.osm.TagCollection;
    3726import org.openstreetmap.josm.data.osm.Way;
    38 import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
    39 import org.openstreetmap.josm.gui.ExtendedDialog;
    40 import org.openstreetmap.josm.tools.GBC;
    41 import org.openstreetmap.josm.tools.Pair;
     27import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
     28import org.openstreetmap.josm.gui.conflict.tags.CombinePrimitiveResolverDialog;
     29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4230import org.openstreetmap.josm.tools.Shortcut;
    4331
    4432
    4533/**
    46  * Merge two or more nodes into one node.
    47  * (based on Combine ways)
    48  *
    49  * @author Matthew Newton
    50  *
     34 * Merges a collection of nodes into one node.
     35 *
    5136 */
    5237public class MergeNodesAction extends JosmAction {
     
    6045        if (!isEnabled())
    6146            return;
    62 
    6347        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
    64         LinkedList<Node> selectedNodes = new LinkedList<Node>();
    65 
    66         // the selection check should stop this procedure starting if
    67         // nothing but node are selected - otherwise we don't care
    68         // anyway as long as we have at least two nodes
    69         for (OsmPrimitive osm : selection)
    70             if (osm instanceof Node) {
    71                 selectedNodes.add((Node)osm);
    72             }
    73 
     48        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
    7449        if (selectedNodes.size() < 2) {
    7550            JOptionPane.showMessageDialog(
     
    8257        }
    8358
     59
     60        Node targetNode = selectTargetNode(selectedNodes);
     61        Command cmd = mergeNodes(Main.main.getEditLayer(), selectedNodes, targetNode);
     62        if (cmd != null) {
     63            Main.main.undoRedo.add(cmd);
     64            Main.main.getEditLayer().data.setSelected(targetNode);
     65        }
     66    }
     67
     68    protected void completeTagCollectionWithMissingTags(TagCollection tc, Collection<Node> mergedNodes) {
     69        for (String key: tc.getKeys()) {
     70            // make sure the empty value is in the tag set if a tag is not present
     71            // on all merged nodes
     72            //
     73            for (Node n: mergedNodes) {
     74                if (n.get(key) == null) {
     75                    tc.add(new Tag(key)); // add a tag with key and empty value
     76                }
     77            }
     78        }
     79        // remove irrelevant tags
     80        //
     81        tc.removeByKey("created_by");
     82    }
     83
     84    protected void completeTagCollectionForEditing(TagCollection tc) {
     85        for (String key: tc.getKeys()) {
     86            // make sure the empty value is in the tag set such that we can delete the tag
     87            // in the conflict dialog if necessary
     88            //
     89            tc.add(new Tag(key,""));
     90        }
     91    }
     92
     93    /**
     94     * Selects a node out of a collection of candidate nodes. The selected
     95     * node will become the target node the remaining nodes are merged to.
     96     *
     97     * @param candidates the collection of candidate nodes
     98     * @return the selected target node
     99     */
     100    public Node selectTargetNode(Collection<Node> candidates) {
    84101        // Find which node to merge into (i.e. which one will be left)
    85102        // - this should be combined from two things:
     
    93110        // that the user doesn't know which node will be chosen (so
    94111        // (2) is not implemented yet.)  :-(
    95         Node useNode = null;
    96         for (Node n: selectedNodes) {
     112        Node targetNode = null;
     113        for (Node n: candidates) {
    97114            if (n.getId() > 0) {
    98                 useNode = n;
     115                targetNode = n;
    99116                break;
    100117            }
    101118        }
    102         if (useNode == null) {
    103             useNode = selectedNodes.iterator().next();
    104         }
    105 
    106         mergeNodes(selectedNodes, useNode);
    107     }
    108 
    109     /**
    110      * really do the merging - returns the node that is left
    111      */
    112     public Node mergeNodes(LinkedList<Node> allNodes, Node dest) {
    113         Node newNode = new Node(dest);
    114 
    115         // Check whether all ways have identical relationship membership. More
    116         // specifically: If one of the selected ways is a member of relation X
    117         // in role Y, then all selected ways must be members of X in role Y.
    118 
    119         // FIXME: In a later revision, we should display some sort of conflict
    120         // dialog like we do for tags, to let the user choose which relations
    121         // should be kept.
    122 
    123         // Step 1, iterate over all relations and figure out which of our
    124         // selected ways are members of a relation.
    125         HashMap<Pair<Relation,String>, HashSet<Node>> backlinks =
    126             new HashMap<Pair<Relation,String>, HashSet<Node>>();
    127         HashSet<Relation> relationsUsingNodes = new HashSet<Relation>();
    128         for (Relation r : getCurrentDataSet().relations) {
    129             if (r.isDeleted() || r.incomplete) {
    130                 continue;
    131             }
    132             for (RelationMember rm : r.getMembers()) {
    133                 if (rm.isNode()) {
    134                     for (Node n : allNodes) {
    135                         if (rm.getMember() == n) {
    136                             Pair<Relation,String> pair = new Pair<Relation,String>(r, rm.getRole());
    137                             HashSet<Node> nodelinks = new HashSet<Node>();
    138                             if (backlinks.containsKey(pair)) {
    139                                 nodelinks = backlinks.get(pair);
    140                             } else {
    141                                 nodelinks = new HashSet<Node>();
    142                                 backlinks.put(pair, nodelinks);
    143                             }
    144                             nodelinks.add(n);
    145 
    146                             // this is just a cache for later use
    147                             relationsUsingNodes.add(r);
    148                         }
    149                     }
     119        if (targetNode == null) {
     120            // an arbitrary node
     121            targetNode = candidates.iterator().next();
     122        }
     123        return targetNode;
     124    }
     125
     126
     127    /**
     128     * Merges the nodes in <code>node</code> onto one of the nodes. Uses the dataset
     129     * managed by <code>layer</code> as reference.
     130     *
     131     * @param layer the reference data layer. Must not be null.
     132     * @param nodes the collection of nodes. Ignored if null.
     133     * @param targetNode the target node the collection of nodes is merged to. Must not be null.
     134     * @throws IllegalArgumentException thrown if layer is null
     135     * @throws IllegalArgumentException thrown if targetNode is null
     136     *
     137     */
     138    public Command mergeNodes(OsmDataLayer layer, Collection<Node> nodes, Node targetNode) throws IllegalArgumentException{
     139        if (layer == null)
     140            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "nodes"));
     141        if (targetNode == null)
     142            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "targetNode"));
     143
     144        if (nodes == null)
     145            return null;
     146        nodes.remove(null); // just in case
     147        BackreferencedDataSet backreferences = new BackreferencedDataSet(layer.data);
     148        backreferences.build();
     149        return mergeNodes(layer,backreferences, nodes, targetNode);
     150    }
     151
     152    /**
     153     * Merges the nodes in <code>node</code> onto one of the nodes. Uses the dataset
     154     * managed by <code>layer</code> as reference. <code>backreferences</code> is precomputed
     155     * collection of all parent/child references in the dataset.
     156     *
     157     * @param layer layer the reference data layer. Must not be null.
     158     * @param backreferences if null, backreferneces are first computed from layer.data; otherwise
     159     *    backreferences.getSource() == layer.data must hold
     160     * @param nodes the collection of nodes. Ignored if null.
     161     * @param targetNode the target node the collection of nodes is merged to. Must not be null.
     162     * @throw IllegalArgumentException thrown if layer is null
     163     * @throw IllegalArgumentException thrown if  backreferences.getSource() != layer.data
     164     */
     165    public Command mergeNodes(OsmDataLayer layer, BackreferencedDataSet backreferences, Collection<Node> nodes, Node targetNode) {
     166        if (layer == null)
     167            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "nodes"));
     168        if (targetNode == null)
     169            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "targetNode"));
     170        if (nodes == null)
     171            return null;
     172        if (backreferences == null) {
     173            backreferences = new BackreferencedDataSet(layer.data);
     174            backreferences.build();
     175        }
     176
     177        Set<RelationToChildReference> relationToNodeReferences = backreferences.getRelationToChildReferences(nodes);
     178
     179        // build the tag collection
     180        //
     181        TagCollection nodeTags = TagCollection.unionOfAllPrimitives(nodes);
     182        completeTagCollectionWithMissingTags(nodeTags, nodes);
     183        TagCollection nodeTagsToEdit = new TagCollection(nodeTags);
     184        completeTagCollectionForEditing(nodeTagsToEdit);
     185
     186        // launch a conflict resolution dialog, if necessary
     187        //
     188        CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
     189        dialog.getTagConflictResolverModel().populate(nodeTagsToEdit);
     190        dialog.getRelationMemberConflictResolverModel().populate(relationToNodeReferences);
     191        dialog.setTargetPrimitive(targetNode);
     192        dialog.prepareDefaultDecisions();
     193        if (! nodeTags.isApplicableToPrimitive() || relationToNodeReferences.size() > 1) {
     194            dialog.setVisible(true);
     195            if (dialog.isCancelled())
     196                return null;
     197        }
     198        LinkedList<Command> cmds = new LinkedList<Command>();
     199
     200        // the nodes we will have to delete
     201        //
     202        Collection<OsmPrimitive> nodesToDelete = new HashSet<OsmPrimitive>(nodes);
     203        nodesToDelete.remove(targetNode);
     204
     205        // change the ways referring to at least one of the merge nodes
     206        //
     207        Collection<Way> waysToDelete= new HashSet<Way>();
     208        for (Way w : OsmPrimitive.getFilteredList(backreferences.getParents(nodesToDelete), Way.class)) {
     209            // OK - this way contains one or more nodes to change
     210            ArrayList<Node> newNodes = new ArrayList<Node>(w.getNodesCount());
     211            for (Node n: w.getNodes()) {
     212                if (! nodesToDelete.contains(n)) {
     213                    newNodes.add(n);
    150214                }
    151215            }
    152         }
    153 
    154         // Complain to the user if the ways don't have equal memberships.
    155         for (HashSet<Node> nodelinks : backlinks.values()) {
    156             if (!nodelinks.containsAll(allNodes)) {
    157                 ExtendedDialog ed = new ExtendedDialog(Main.parent,
    158                         tr("Merge nodes with different memberships?"),
    159                         new String[] {tr("Merge Anyway"), tr("Cancel")});
    160                 ed.setButtonIcons(new String[] {"mergenodes.png", "cancel.png"});
    161                 ed.setContent(tr("The selected nodes have differing relation memberships.  "
    162                         + "Do you still want to merge them?"));
    163                 ed.showDialog();
    164 
    165                 if (ed.getValue() == 1) {
    166                     break;
    167                 }
    168                 return null;
    169             }
    170         }
    171 
    172         // collect properties for later conflict resolving
    173         Map<String, Set<String>> props = new TreeMap<String, Set<String>>();
    174         for (Node n : allNodes) {
    175             for (Entry<String,String> e : n.entrySet()) {
    176                 if (!props.containsKey(e.getKey())) {
    177                     props.put(e.getKey(), new TreeSet<String>());
    178                 }
    179                 props.get(e.getKey()).add(e.getValue());
    180             }
    181         }
    182 
    183         // display conflict dialog
    184         Map<String, JComboBox> components = new HashMap<String, JComboBox>();
    185         JPanel p = new JPanel(new GridBagLayout());
    186         for (Entry<String, Set<String>> e : props.entrySet()) {
    187             if (TigerUtils.isTigerTag(e.getKey())) {
    188                 String combined = TigerUtils.combineTags(e.getKey(), e.getValue());
    189                 newNode.put(e.getKey(), combined);
    190             } else if (e.getValue().size() > 1) {
    191                 JComboBox c = new JComboBox(e.getValue().toArray());
    192                 c.setEditable(true);
    193                 p.add(new JLabel(e.getKey()), GBC.std());
    194                 p.add(Box.createHorizontalStrut(10), GBC.std());
    195                 p.add(c, GBC.eol());
    196                 components.put(e.getKey(), c);
    197             } else {
    198                 newNode.put(e.getKey(), e.getValue().iterator().next());
    199             }
    200         }
    201 
    202         if (!components.isEmpty()) {
    203             ExtendedDialog dialog = new ExtendedDialog(
    204                     Main.parent,
    205                     tr("Enter values for all conflicts."),
    206                     new String[] {tr("Solve Conflicts"), tr("Cancel")}
    207             );
    208             dialog.setButtonIcons(new String[] {"dialogs/conflict.png", "cancel.png"});
    209             dialog.setContent(p);
    210             dialog.showDialog();
    211             int answer = dialog.getValue();
    212 
    213             if (answer != 1)
    214                 return null;
    215             for (Entry<String, JComboBox> e : components.entrySet()) {
    216                 newNode.put(e.getKey(), e.getValue().getEditor().getItem().toString());
    217             }
    218         }
    219 
    220         LinkedList<Command> cmds = new LinkedList<Command>();
    221 
    222         if (!newNode.getKeys().equals(dest.getKeys())) {
    223             cmds.add(new ChangeCommand(dest, newNode));
    224         }
    225 
    226         Collection<OsmPrimitive> del = new HashSet<OsmPrimitive>();
    227 
    228         for (Way w : getCurrentDataSet().ways) {
    229             if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
    230                 continue;
    231             }
    232             boolean modify = false;
    233             for (Node sn : allNodes) {
    234                 if (sn == dest) {
    235                     continue;
    236                 }
    237                 if (w.containsNode(sn)) {
    238                     modify = true;
    239                 }
    240             }
    241             if (!modify) {
    242                 continue;
    243             }
    244             // OK - this way contains one or more nodes to change
    245             ArrayList<Node> nn = new ArrayList<Node>();
    246             Node lastNode = null;
    247             for (Node pushNode: w.getNodes()) {
    248                 if (allNodes.contains(pushNode)) {
    249                     pushNode = dest;
    250                 }
    251                 if (pushNode != lastNode) {
    252                     nn.add(pushNode);
    253                 }
    254                 lastNode = pushNode;
    255             }
    256             if (nn.size() < 2) {
    257                 CollectBackReferencesVisitor backRefs =
    258                     new CollectBackReferencesVisitor(getCurrentDataSet(), false);
    259                 w.visit(backRefs);
    260                 if (!backRefs.data.isEmpty()) {
     216            if (newNodes.size() < 2) {
     217                if (backreferences.getParents(w).isEmpty()) {
     218                    waysToDelete.add(w);
     219                } else {
    261220                    JOptionPane.showMessageDialog(
    262221                            Main.parent,
     
    268227                    return null;
    269228                }
    270                 del.add(w);
     229            } else if(newNodes.size() < 2 && backreferences.getParents(w).isEmpty()) {
     230                waysToDelete.add(w);
    271231            } else {
    272232                Way newWay = new Way(w);
    273                 newWay.setNodes(nn);
     233                newWay.setNodes(newNodes);
    274234                cmds.add(new ChangeCommand(w, newWay));
    275235            }
    276236        }
    277237
    278         // delete any merged nodes
    279         del.addAll(allNodes);
    280         del.remove(dest);
    281         if (!del.isEmpty()) {
    282             cmds.add(new DeleteCommand(del));
    283         }
    284 
    285         // modify all relations containing the now-deleted nodes
    286         for (Relation r : relationsUsingNodes) {
    287             List<RelationMember> newMembers = new ArrayList<RelationMember>();
    288             HashSet<String> rolesToReAdd = new HashSet<String>();
    289             for (RelationMember rm : r.getMembers()) {
    290                 // Don't copy the member if it points to one of our nodes,
    291                 // just keep a note to re-add it later on.
    292                 if (allNodes.contains(rm.getMember())) {
    293                     rolesToReAdd.add(rm.getRole());
    294                 } else {
    295                     newMembers.add(rm);
    296                 }
    297             }
    298             for (String role : rolesToReAdd) {
    299                 newMembers.add(new RelationMember(role, dest));
    300             }
    301             Relation newRel = new Relation(r);
    302             newRel.setMembers(newMembers);
    303             cmds.add(new ChangeCommand(r, newRel));
    304         }
    305 
    306         Main.main.undoRedo.add(new SequenceCommand(tr("Merge {0} nodes", allNodes.size()), cmds));
    307         getCurrentDataSet().setSelected(dest);
    308 
    309         return dest;
     238        // build the commands
     239        //
     240        if (!nodesToDelete.isEmpty()) {
     241            cmds.add(new DeleteCommand(nodesToDelete));
     242        }
     243        if (!waysToDelete.isEmpty()) {
     244            cmds.add(new DeleteCommand(waysToDelete));
     245        }
     246        cmds.addAll(dialog.buildResolutionCommands());
     247        Command cmd = new SequenceCommand(tr("Merge {0} nodes", nodes.size()), cmds);
     248        return cmd;
    310249    }
    311250
  • trunk/src/org/openstreetmap/josm/actions/mapmode/SelectAction.java

    r2031 r2095  
    1717import java.util.LinkedList;
    1818import java.util.List;
     19import java.util.Set;
    1920import java.util.TreeSet;
    2021import java.util.logging.Logger;
     
    4344import org.openstreetmap.josm.gui.SelectionManager;
    4445import org.openstreetmap.josm.gui.SelectionManager.SelectionEnded;
     46import org.openstreetmap.josm.gui.dialogs.LayerListDialog.MergeAction;
    4547import org.openstreetmap.josm.gui.layer.Layer;
    4648import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     
    489491                    if (nn != null) {
    490492                        Node n = nn.iterator().next();
    491                         LinkedList<Node> selNodes = new LinkedList<Node>();
    492                         for (OsmPrimitive osm : selection)
    493                             if (osm instanceof Node) {
    494                                 selNodes.add((Node)osm);
    495                             }
    496                         if (selNodes.size() > 0) {
    497                             selNodes.add(n);
    498                             new MergeNodesAction().mergeNodes(selNodes, n);
     493                        Set<Node> selectedNodes = OsmPrimitive.getFilteredSet(selection, Node.class);
     494                        if (!selectedNodes.isEmpty()) {
     495                            selectedNodes.add(n);
     496                            MergeNodesAction mergeAction = new MergeNodesAction();
     497                            Node targetNode = mergeAction.selectTargetNode(selectedNodes);
     498                            mergeAction.mergeNodes(Main.main.getEditLayer(),selectedNodes, targetNode);
    499499                        }
    500500                    }
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/CombinePrimitiveResolverDialog.java

    r2084 r2095  
    2626import org.openstreetmap.josm.command.ChangePropertyCommand;
    2727import org.openstreetmap.josm.command.Command;
     28import org.openstreetmap.josm.data.osm.Node;
    2829import org.openstreetmap.josm.data.osm.OsmPrimitive;
    2930import org.openstreetmap.josm.data.osm.TagCollection;
     
    3435import org.openstreetmap.josm.tools.WindowGeometry;
    3536
    36 public class CombineWaysConflictResolverDialog extends JDialog {
    37 
    38     static private CombineWaysConflictResolverDialog instance;
    39 
    40     public static CombineWaysConflictResolverDialog getInstance() {
     37/**
     38 * This dialog helps to resolve conflicts occuring when combining or merging primitives.
     39 *
     40 * There is a singleton instance of this dialog which can be retrieved using
     41 * {@see #getInstance()}.
     42 *
     43 * The dialog uses two model: one model for resolving tag conflicts, the other model
     44 * for resolving conflicts in relation membership. For both models there are accessors,
     45 * i.e {@see #getTagConflictResolverModel()} and {@see #getRelationMemberConflictResolverModel()}.
     46 *
     47 * Models have to be <strong>populated</strong> before the dialog is launched. Example:
     48 * <pre>
     49 *    CombinePrimitiveResolverDialog dialog = CombinePrimitiveResolverDialog.getInstance();
     50 *    dialog.getTagConflictResolverModel().populate(aTagCollection);
     51 *    dialog.getRelationMemberConflictResolverModel().populate(aRelationLinkCollection);
     52 *    dialog.prepareDefaultDecisions();
     53 * </pre>
     54 *
     55 * You should also set the target primitive which other primitives (ways or nodes) are
     56 * merged to. Use {@see #setTargetPrimitive(OsmPrimitive)}.
     57 *
     58 * After the dialog closed use {@see #isCancelled()} to check whether the user cancelled
     59 * the dialog. If it wasn't cancelled you may build a collection of {@see Command} objects
     60 * which reflect the conflict resolution decisions the user made in the dialog:
     61 * see {@see #buildResolutionCommands()}
     62 *
     63 *
     64 */
     65public class CombinePrimitiveResolverDialog extends JDialog {
     66
     67    static private CombinePrimitiveResolverDialog instance;
     68
     69    public static CombinePrimitiveResolverDialog getInstance() {
    4170        if (instance == null) {
    42             instance = new CombineWaysConflictResolverDialog(Main.parent);
     71            instance = new CombinePrimitiveResolverDialog(Main.parent);
    4372        }
    4473        return instance;
     
    5079    private boolean cancelled;
    5180    private JPanel pnlButtons;
    52     private Way targetWay;
    53 
    54 
    55 
    56     public Way getTargetWay() {
    57         return targetWay;
    58     }
    59 
    60     public void setTargetWay(Way targetWay) {
    61         this.targetWay = targetWay;
     81    private OsmPrimitive targetPrimitive;
     82
     83
     84
     85    public OsmPrimitive getTargetPrimitmive() {
     86        return targetPrimitive;
     87    }
     88
     89    public void setTargetPrimitive(OsmPrimitive primitive) {
     90        this.targetPrimitive = primitive;
    6291        updateTitle();
    6392    }
    6493
    6594    protected void updateTitle() {
    66         if (targetWay == null) {
    67             setTitle(tr("Conflicts when combining ways"));
     95        if (targetPrimitive == null) {
     96            setTitle(tr("Conflicts when combining primitives"));
    6897            return;
    6998        }
    70         setTitle(
    71                 tr(
    72                         "Conflicts when combining ways - combined way is ''{0}''",
    73                         targetWay.getDisplayName(DefaultNameFormatter.getInstance())
    74                 )
    75         );
     99        if (targetPrimitive instanceof Way) {
     100            setTitle(
     101                    tr(
     102                            "Conflicts when combining ways - combined way is ''{0}''",
     103                            targetPrimitive.getDisplayName(DefaultNameFormatter.getInstance())
     104                    )
     105            );
     106        } else if (targetPrimitive instanceof Node) {
     107            setTitle(
     108                    tr(
     109                            "Conflicts when merging nodes - merged node is ''{0}''",
     110                            targetPrimitive.getDisplayName(DefaultNameFormatter.getInstance())
     111                    )
     112            );
     113        }
    76114    }
    77115
     
    113151    }
    114152
    115     public CombineWaysConflictResolverDialog(Component owner) {
     153    public CombinePrimitiveResolverDialog(Component owner) {
    116154        super(JOptionPane.getFrameForComponent(owner),true /* modal */);
    117155        build();
     
    143181    }
    144182
    145     public List<Command> buildResolutionCommands(Way targetWay) {
     183    public List<Command> buildResolutionCommands() {
    146184        List<Command> cmds = new LinkedList<Command>();
    147185
    148186        if (getTagConflictResolverModel().getNumDecisions() >0) {
    149187            TagCollection tc = getTagConflictResolverModel().getResolution();
    150             cmds.addAll(buildTagChangeCommand(targetWay, tc));
     188            cmds.addAll(buildTagChangeCommand(targetPrimitive, tc));
    151189        }
    152190
    153191        if (getRelationMemberConflictResolverModel().getNumDecisions() >0) {
    154             cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetWay));
     192            cmds.addAll(getRelationMemberConflictResolverModel().buildResolutionCommands(targetPrimitive));
    155193        }
    156194
    157195        Command cmd = pnlRelationMemberConflictResolver.buildTagApplyCommands(
    158                 getRelationMemberConflictResolverModel().getModifiedRelations(targetWay)
     196                getRelationMemberConflictResolverModel().getModifiedRelations(targetPrimitive)
    159197        );
    160198        if (cmd != null) {
     
    242280                    )
    243281            ).applySafe(this);
     282            setCancelled(false);
    244283        } else {
    245284            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
  • trunk/src/org/openstreetmap/josm/gui/conflict/tags/RelationMemberConflictResolverModel.java

    r2070 r2095  
    1818import org.openstreetmap.josm.data.osm.Relation;
    1919import org.openstreetmap.josm.data.osm.RelationMember;
    20 
     20import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
     21
     22/**
     23 * This model manages a list of conflicting relation members.
     24 *
     25 * It can be used as {@see TableModel}.
     26 *
     27 *
     28 */
    2129public class RelationMemberConflictResolverModel extends DefaultTableModel {
     30    /** the property name for the number conflicts managed by this model */
    2231    static public final String NUM_CONFLICTS_PROP = RelationMemberConflictResolverModel.class.getName() + ".numConflicts";
    2332
     33    /** the list of conflict decisions */
    2434    private List<RelationMemberConflictDecision> decisions;
     35    /** the collection of relations for which we manage conflicts */
    2536    private Collection<Relation> relations;
     37    /** the number of conflicts */
    2638    private int numConflicts;
    2739    private PropertyChangeSupport support;
    2840
    2941
     42    /**
     43     * Replies the current number of conflicts
     44     *
     45     * @return the current number of conflicts
     46     */
    3047    public int getNumConflicts() {
    3148        return numConflicts;
    3249    }
    3350
     51    /**
     52     * Updates the current number of conflicts from list of decisions and emits
     53     * a property change event if necessary.
     54     *
     55     */
    3456    protected void updateNumConflicts() {
    3557        int count = 0;
     
    7193        RelationMemberConflictDecision d = decisions.get(row);
    7294        switch(column) {
    73             case 0: /* relation */ return d.getRelation();
    74             case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
    75             case 2: /* role */ return d.getRole();
    76             case 3: /* original */ return d.getOriginalPrimitive();
    77             case 4: /* decision */ return d.getDecision();
     95        case 0: /* relation */ return d.getRelation();
     96        case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
     97        case 2: /* role */ return d.getRole();
     98        case 3: /* original */ return d.getOriginalPrimitive();
     99        case 4: /* decision */ return d.getDecision();
    78100        }
    79101        return null;
     
    84106        RelationMemberConflictDecision d = decisions.get(row);
    85107        switch(column) {
    86             case 2: /* role */
    87                 d.setRole((String)value);
    88                 break;
    89             case 4: /* decision */
    90                 d.decide((RelationMemberConflictDecisionType)value);
    91                 refresh();
    92                 break;
     108        case 2: /* role */
     109            d.setRole((String)value);
     110            break;
     111        case 4: /* decision */
     112            d.decide((RelationMemberConflictDecisionType)value);
     113            refresh();
     114            break;
    93115        }
    94116        fireTableDataChanged();
    95117    }
    96118
     119    /**
     120     * Populates the model with the members of the relation <code>relation</code>
     121     * referring to <code>primitive</code>.
     122     *
     123     * @param relation the parent relation
     124     * @param primitive the child primitive
     125     */
    97126    protected void populate(Relation relation, OsmPrimitive primitive) {
     127        decisions.clear();
    98128        for (int i =0; i<relation.getMembersCount();i++) {
    99129            if (relation.getMember(i).refersTo(primitive)) {
     
    103133    }
    104134
     135    /**
     136     * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
     137     * and referring to one of the primitives in <code>memberPrimitives</code>.
     138     *
     139     * @param relations  the parent relations. Empty list assumed if null.
     140     * @param memberPrimitives the child primitives. Empty list assumed if null.
     141     */
    105142    public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives) {
    106143        decisions.clear();
     144        relations = relations == null ? new LinkedList<Relation>() : relations;
     145        memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
    107146        for (Relation r : relations) {
    108147            for (OsmPrimitive p: memberPrimitives) {
     
    114153    }
    115154
     155    /**
     156     * Populates the model with the relation members represented as a collection of
     157     * {@see RelationToChildReference}s.
     158     *
     159     * @param references the references. Empty list assumed if null.
     160     */
     161    public void populate(Collection<RelationToChildReference> references) {
     162        references = references == null ? new LinkedList<RelationToChildReference>() : references;
     163        if (references.isEmpty()) {
     164            this.relations = new HashSet<Relation>(references.size());
     165            return;
     166        }
     167        decisions.clear();
     168        this.relations = new HashSet<Relation>(references.size());
     169        for (RelationToChildReference reference: references) {
     170            decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
     171            relations.add(reference.getParent());
     172        }
     173        refresh();
     174    }
     175
     176    /**
     177     * Replies the decision at position <code>row</code>
     178     *
     179     * @param row
     180     * @return the decision at position <code>row</code>
     181     */
    116182    public RelationMemberConflictDecision getDecision(int row) {
    117183        return decisions.get(row);
    118184    }
    119185
     186    /**
     187     * Replies the number of decisions managed by this model
     188     *
     189     * @return the number of decisions managed by this model
     190     */
    120191    public int getNumDecisions() {
    121192        return  getRowCount();
    122193    }
    123194
     195    /**
     196     * Refreshes the model state. Invoke this method to trigger necessary change
     197     * events after an update of the model data.
     198     *
     199     */
    124200    public void refresh() {
    125201        updateNumConflicts();
     
    127203    }
    128204
     205    /**
     206     * Apply a role to all member managed by this model.
     207     *
     208     * @param role the role. Empty string assumed if null.
     209     */
    129210    public void applyRole(String role) {
    130211        role = role == null ? "" : role;
     
    154235            } else {
    155236                switch(decision.getDecision()) {
    156                     case REPLACE:
    157                         rmNew = new RelationMember(decision.getRole(),newPrimitive);
    158                         modifiedRelation.addMember(rmNew);
    159                         isChanged |= ! rm.equals(rmNew);
    160                         break;
    161                     case REMOVE:
    162                         isChanged = true;
    163                         // do nothing
    164                         break;
    165                     case UNDECIDED:
    166                         // FIXME: this is an error
    167                         break;
     237                case REPLACE:
     238                    rmNew = new RelationMember(decision.getRole(),newPrimitive);
     239                    modifiedRelation.addMember(rmNew);
     240                    isChanged |= ! rm.equals(rmNew);
     241                    break;
     242                case REMOVE:
     243                    isChanged = true;
     244                    // do nothing
     245                    break;
     246                case UNDECIDED:
     247                    // FIXME: this is an error
     248                    break;
    168249                }
    169250            }
     
    174255    }
    175256
     257    /**
     258     * Builds a collection of commands executing the decisions made in this model.
     259     *
     260     * @param newPrimitive the primitive which members shall refer to if the
     261     * decision is {@see RelationMemberConflictDecisionType#REPLACE}
     262     * @return a list of commands
     263     */
    176264    public List<Command> buildResolutionCommands(OsmPrimitive newPrimitive) {
    177265        List<Command> command = new LinkedList<Command>();
     
    192280            }
    193281            switch(decision.getDecision()) {
    194                 case REMOVE: return true;
    195                 case REPLACE:
    196                     if (!relation.getMember(i).getRole().equals(decision.getRole()))
    197                         return true;
    198                     if (relation.getMember(i).getMember() != newPrimitive)
    199                         return true;
    200                 case UNDECIDED:
    201                     // FIXME: handle error
     282            case REMOVE: return true;
     283            case REPLACE:
     284                if (!relation.getMember(i).getRole().equals(decision.getRole()))
     285                    return true;
     286                if (relation.getMember(i).getMember() != newPrimitive)
     287                    return true;
     288            case UNDECIDED:
     289                // FIXME: handle error
    202290            }
    203291        }
     
    205293    }
    206294
     295    /**
     296     * Replies the set of relations which have to be modified according
     297     * to the decisions managed by this model.
     298     *
     299     * @param newPrimitive the primitive which members shall refer to if the
     300     * decision is {@see RelationMemberConflictDecisionType#REPLACE}
     301     *
     302     * @return the set of relations which have to be modified according
     303     * to the decisions managed by this model
     304     */
    207305    public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
    208306        HashSet<Relation> ret = new HashSet<Relation>();
Note: See TracChangeset for help on using the changeset viewer.