Changeset 2095 in josm


Ignore:
Timestamp:
Sep 12, 2009 6:21:30 AM (4 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.