Ignore:
Timestamp:
30.08.2009 15:41:28 (3 years ago)
Author:
Gubaer
Message:

new: tag conflict resolution when pasting tags (only if necessary)
fixed #2611: cant copy'n'past tags from a relation to ways or other way round
fixed #3137: Paste Tags does not work for relations

File:
1 edited

Legend:

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

    r1924 r2008  
    44 
    55import static org.openstreetmap.josm.tools.I18n.tr; 
     6import static org.openstreetmap.josm.tools.I18n.trn; 
    67 
    78import java.awt.event.ActionEvent; 
    89import java.awt.event.KeyEvent; 
     10import java.util.ArrayList; 
    911import java.util.Collection; 
    1012import java.util.HashMap; 
    11 import java.util.Iterator; 
    12 import java.util.LinkedList; 
     13import java.util.List; 
    1314import java.util.Map; 
    1415 
     
    1819import org.openstreetmap.josm.command.SequenceCommand; 
    1920import org.openstreetmap.josm.data.osm.DataSet; 
    20 import org.openstreetmap.josm.data.osm.DataSource; 
     21import org.openstreetmap.josm.data.osm.Node; 
    2122import org.openstreetmap.josm.data.osm.OsmPrimitive; 
     23import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 
     24import org.openstreetmap.josm.data.osm.Relation; 
     25import org.openstreetmap.josm.data.osm.TagCollection; 
     26import org.openstreetmap.josm.data.osm.Way; 
     27import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog; 
    2228import org.openstreetmap.josm.tools.Shortcut; 
    2329 
     
    3137    } 
    3238 
    33     private void pasteKeys(Collection<Command> clist, Collection<? extends OsmPrimitive> pasteBufferSubset, Collection<OsmPrimitive> selectionSubset) { 
    34         /* scan the paste buffer, and add tags to each of the selected objects. 
    35          * If a tag already exists, it is overwritten */ 
    36         if (selectionSubset == null || selectionSubset.isEmpty()) 
     39    /** 
     40     * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of 
     41     * {@see OsmPrimitive}s of exactly one type 
     42     *  
     43     * @return 
     44     */ 
     45    protected boolean isHeteogeneousSource() { 
     46        int count = 0; 
     47        count = !getSourcePrimitivesByType(Node.class).isEmpty() ? count + 1 : count; 
     48        count = !getSourcePrimitivesByType(Way.class).isEmpty() ? count + 1 : count; 
     49        count = !getSourcePrimitivesByType(Relation.class).isEmpty() ? count + 1 : count; 
     50        return count > 1; 
     51    } 
     52 
     53    /** 
     54     * Replies the subset  of {@see OsmPrimitive}s of <code>type</code> from <code>superSet</code>. 
     55     *  
     56     * @param <T> 
     57     * @param superSet  the super set of primitives 
     58     * @param type  the type 
     59     * @return 
     60     */ 
     61    protected <T extends OsmPrimitive> Collection<? extends OsmPrimitive> getSubcollectionByType(Collection<? extends OsmPrimitive> superSet, Class<T> type) { 
     62        Collection<OsmPrimitive> ret = new ArrayList<OsmPrimitive>(); 
     63        for (OsmPrimitive p : superSet) { 
     64            if (type.isInstance(p)) { 
     65                ret.add(p); 
     66            } 
     67        } 
     68        return ret; 
     69    } 
     70 
     71    /** 
     72     * Replies all primitives of type <code>type</code> in the current selection. 
     73     *  
     74     * @param <T> 
     75     * @param type  the type 
     76     * @return all primitives of type <code>type</code> in the current selection. 
     77     */ 
     78    protected <T extends OsmPrimitive> Collection<? extends OsmPrimitive> getSourcePrimitivesByType(Class<T> type) { 
     79        return getSubcollectionByType(Main.pasteBuffer.getSelected(), type); 
     80    } 
     81 
     82    /** 
     83     * Replies the collection of tags for all primitives of type <code>type</code> in the current 
     84     * selection 
     85     *  
     86     * @param <T> 
     87     * @param type  the type 
     88     * @return the collection of tags for all primitives of type <code>type</code> in the current 
     89     * selection 
     90     */ 
     91    protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(Class<T> type) { 
     92        return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type)); 
     93    } 
     94 
     95    /** 
     96     * Replies true if there is at least one tag in the current selection for primitives of 
     97     * type <code>type</code> 
     98     *  
     99     * @param <T> 
     100     * @param type the type 
     101     * @return true if there is at least one tag in the current selection for primitives of 
     102     * type <code>type</code> 
     103     */ 
     104    protected <T extends OsmPrimitive> boolean hasSourceTagsByType(Class<T> type) { 
     105        return ! getSourceTagsByType(type).isEmpty(); 
     106    } 
     107 
     108    protected Command buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) { 
     109        List<Command> commands = new ArrayList<Command>(); 
     110        for (String key : tc.getKeys()) { 
     111            String value = tc.getValues(key).iterator().next(); 
     112            value = value.equals("") ? null : value; 
     113            commands.add(new ChangePropertyCommand(selection,key,value)); 
     114        } 
     115        if (!commands.isEmpty()) { 
     116            String title1 = trn("Pasting {0} tag", "Pasting {0} tags", tc.getKeys().size(), tc.getKeys().size()); 
     117            String title2 = trn("to {0} primitive", "to {0} primtives", selection.size(), selection.size()); 
     118            return new SequenceCommand( 
     119                    title1 + " " + title2, 
     120                    commands 
     121            ); 
     122        } 
     123        return null; 
     124    } 
     125 
     126    protected Map<OsmPrimitiveType, Integer> getSourceStatistics() { 
     127        HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>(); 
     128        for (Class type: new Class[] {Node.class, Way.class, Relation.class}) { 
     129            if (!getSourceTagsByType(type).isEmpty()) { 
     130                ret.put(OsmPrimitiveType.from(type), getSourcePrimitivesByType(type).size()); 
     131            } 
     132        } 
     133        return ret; 
     134    } 
     135 
     136    protected Map<OsmPrimitiveType, Integer> getTargetStatistics() { 
     137        HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>(); 
     138        for (Class type: new Class[] {Node.class, Way.class, Relation.class}) { 
     139            int count = getSubcollectionByType(getEditLayer().data.getSelected(), type).size(); 
     140            if (count > 0) { 
     141                ret.put(OsmPrimitiveType.from(type), count); 
     142            } 
     143        } 
     144        return ret; 
     145    } 
     146 
     147    /** 
     148     * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting 
     149     * of one type of {@see OsmPrimitive}s only. 
     150     *  
     151     * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives, 
     152     * regardless of their type, receive the same tags. 
     153     *  
     154     * @param targets the collection of target primitives 
     155     */ 
     156    protected void pasteFromHomogeneousSource(Collection<? extends OsmPrimitive> targets) { 
     157        TagCollection tc = null; 
     158        Class sourceType = null; 
     159        for (Class type : new Class[] {Node.class, Way.class, Relation.class}) { 
     160            TagCollection tc1 = getSourceTagsByType(type); 
     161            if (!tc1.isEmpty()) { 
     162                tc = tc1; 
     163                sourceType = type; 
     164            } 
     165        } 
     166        if (tc == null) 
     167            // no tags found to paste. Abort. 
    37168            return; 
    38169 
    39         for (Iterator<? extends OsmPrimitive> it = pasteBufferSubset.iterator(); it.hasNext();) { 
    40             OsmPrimitive osm = it.next(); 
    41  
    42             for (String key : osm.keySet()) { 
    43                 clist.add(new ChangePropertyCommand(selectionSubset, key, osm.get(key))); 
     170 
     171        if (!tc.isApplicableToPrimitive()) { 
     172            PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent); 
     173            dialog.populate(tc, getSourceStatistics(), getTargetStatistics()); 
     174            dialog.setVisible(true); 
     175            if (dialog.isCanceled()) 
     176                return; 
     177            Command cmd = buildChangeCommand(targets, dialog.getResolution()); 
     178            Main.main.undoRedo.add(cmd); 
     179        } else { 
     180            // no conflicts in the source tags to resolve. Just apply the tags 
     181            // to the target primitives 
     182            // 
     183            Command cmd = buildChangeCommand(targets, tc); 
     184            Main.main.undoRedo.add(cmd); 
     185        } 
     186    } 
     187 
     188    /** 
     189     * Replies true if there is at least one primitive of type <code>type</code> in the collection 
     190     * <code>selection</code> 
     191     *  
     192     * @param <T> 
     193     * @param selection  the collection of primitives 
     194     * @param type  the type to look for 
     195     * @return true if there is at least one primitive of type <code>type</code> in the collection 
     196     * <code>selection</code> 
     197     */ 
     198    protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Collection<? extends OsmPrimitive> selection, Class<T> type) { 
     199        return !getSubcollectionByType(selection, type).isEmpty(); 
     200    } 
     201 
     202    /** 
     203     * Replies true if this a heterogeneous source can be pasted without conflict to targets 
     204     *  
     205     * @param targets the collection of target primitives 
     206     * @return true if this a heterogeneous source can be pasted without conflicts to targets 
     207     */ 
     208    protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<? extends OsmPrimitive> targets) { 
     209        if (hasTargetPrimitives(targets, Node.class)) { 
     210            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Node.class)); 
     211            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive()) 
     212                return false; 
     213        } 
     214        if (hasTargetPrimitives(targets, Way.class)) { 
     215            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Way.class)); 
     216            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive()) 
     217                return false; 
     218        } 
     219        if (hasTargetPrimitives(targets, Relation.class)) { 
     220            TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(Relation.class)); 
     221            if (!tc.isEmpty() && ! tc.isApplicableToPrimitive()) 
     222                return false; 
     223        } 
     224        return true; 
     225    } 
     226 
     227    /** 
     228     * Pastes the tags in the current selection of the paste buffer to a set of target 
     229     * primitives. 
     230     *  
     231     * @param targets the collection of target primitives 
     232     */ 
     233    protected void pasteFromHeterogeneousSource(Collection<? extends OsmPrimitive> targets) { 
     234        if (canPasteFromHeterogeneousSourceWithoutConflict(targets)) { 
     235            if (hasSourceTagsByType(Node.class) && hasTargetPrimitives(targets, Node.class)) { 
     236                Command cmd = buildChangeCommand(targets, getSourceTagsByType(Node.class)); 
     237                Main.main.undoRedo.add(cmd); 
     238            } 
     239            if (hasSourceTagsByType(Way.class) && hasTargetPrimitives(targets, Way.class)) { 
     240                Command cmd = buildChangeCommand(targets, getSourceTagsByType(Way.class)); 
     241                Main.main.undoRedo.add(cmd); 
     242            } 
     243            if (hasSourceTagsByType(Relation.class) && hasTargetPrimitives(targets, Relation.class)) { 
     244                Command cmd = buildChangeCommand(targets,getSourceTagsByType(Relation.class)); 
     245                Main.main.undoRedo.add(cmd); 
     246            } 
     247        } else { 
     248            PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent); 
     249            dialog.populate( 
     250                    getSourceTagsByType(Node.class), 
     251                    getSourceTagsByType(Way.class), 
     252                    getSourceTagsByType(Relation.class), 
     253                    getSourceStatistics(), 
     254                    getTargetStatistics() 
     255            ); 
     256            dialog.setVisible(true); 
     257            if (dialog.isCanceled()) 
     258                return; 
     259            if (hasSourceTagsByType(Node.class) && hasTargetPrimitives(targets, Node.class)) { 
     260                Command cmd = buildChangeCommand(getSubcollectionByType(targets, Node.class), dialog.getResolution(OsmPrimitiveType.NODE)); 
     261                Main.main.undoRedo.add(cmd); 
     262            } 
     263            if (hasSourceTagsByType(Way.class) && hasTargetPrimitives(targets, Way.class)) { 
     264                Command cmd = buildChangeCommand(getSubcollectionByType(targets, Way.class), dialog.getResolution(OsmPrimitiveType.WAY)); 
     265                Main.main.undoRedo.add(cmd); 
     266            } 
     267            if (hasSourceTagsByType(Relation.class) && hasTargetPrimitives(targets, Relation.class)) { 
     268                Command cmd = buildChangeCommand(getSubcollectionByType(targets, Relation.class), dialog.getResolution(OsmPrimitiveType.RELATION)); 
     269                Main.main.undoRedo.add(cmd); 
    44270            } 
    45271        } 
     
    47273 
    48274    public void actionPerformed(ActionEvent e) { 
    49         Collection<Command> clist = new LinkedList<Command>(); 
    50         String pbSource = "Multiple Sources"; 
    51         if(Main.pasteBuffer.dataSources.size() == 1) { 
    52             pbSource = ((DataSource) Main.pasteBuffer.dataSources.toArray()[0]).origin; 
    53         } 
    54  
    55         boolean pbNodes = Main.pasteBuffer.nodes.size() > 0; 
    56         boolean pbWays  = Main.pasteBuffer.ways.size() > 0; 
    57  
    58         boolean seNodes = getCurrentDataSet().getSelectedNodes().size() > 0; 
    59         boolean seWays  = getCurrentDataSet().getSelectedWays().size() > 0; 
    60         boolean seRels  = getCurrentDataSet().getSelectedRelations().size() > 0; 
    61  
    62         if(!seNodes && seWays && !seRels && pbNodes && pbSource.equals("Copied Nodes")) { 
    63             // Copy from nodes to ways 
    64             pasteKeys(clist, Main.pasteBuffer.nodes, getCurrentDataSet().getSelectedWays()); 
    65         } else if(seNodes && !seWays && !seRels && pbWays && pbSource.equals("Copied Ways")) { 
    66             // Copy from ways to nodes 
    67             pasteKeys(clist, Main.pasteBuffer.ways, getCurrentDataSet().getSelectedNodes()); 
     275        if (getCurrentDataSet().getSelected().isEmpty()) 
     276            return; 
     277        if (isHeteogeneousSource()) { 
     278            pasteFromHeterogeneousSource(getCurrentDataSet().getSelected()); 
    68279        } else { 
    69             // Copy from equal to equal 
    70             pasteKeys(clist, Main.pasteBuffer.nodes, getCurrentDataSet().getSelectedNodes()); 
    71             pasteKeys(clist, Main.pasteBuffer.ways, getCurrentDataSet().getSelectedWays()); 
    72             pasteKeys(clist, Main.pasteBuffer.relations, getCurrentDataSet().getSelectedRelations()); 
    73         } 
    74         Main.main.undoRedo.add(new SequenceCommand(tr("Paste Tags"), clist)); 
    75         getCurrentDataSet().setSelected(getCurrentDataSet().getSelected()); // to force selection listeners, in particular the tag panel, to update 
    76         Main.map.mapView.repaint(); 
    77     } 
    78  
    79     private boolean containsSameKeysWithDifferentValues(Collection<? extends OsmPrimitive> osms) { 
    80         Map<String,String> kvSeen = new HashMap<String,String>(); 
    81         for (OsmPrimitive osm:osms) { 
    82             for (String key : osm.keySet()) { 
    83                 String value = osm.get(key); 
    84                 if (! kvSeen.containsKey(key)) { 
    85                     kvSeen.put(key, value); 
    86                 } else if (! kvSeen.get(key).equals(value)) 
    87                     return true; 
    88             } 
    89         } 
    90         return false; 
    91     } 
    92  
    93     /** 
    94      * Determines whether to enable the widget depending on the contents of the paste 
    95      * buffer and current selection 
    96      * @param pasteBuffer 
    97      */ 
    98     private void possiblyEnable(Collection<? extends OsmPrimitive> selection, DataSet pasteBuffer) { 
    99         /* only enable if there is something selected to paste into and 
    100             if we don't have conflicting keys in the pastebuffer */ 
    101         DataSet ds = getCurrentDataSet(); 
    102         if (ds == null || ds.getSelected().isEmpty() || pasteBuffer == null || pasteBuffer.allPrimitives().isEmpty()) { 
    103             setEnabled(false); 
    104             return; 
    105         } 
    106         setEnabled((!ds.getSelectedNodes().isEmpty() && ! containsSameKeysWithDifferentValues(pasteBuffer.nodes)) || 
    107                 (!ds.getSelectedWays().isEmpty() && ! containsSameKeysWithDifferentValues(pasteBuffer.ways)) || 
    108                 (! ds.getSelectedRelations().isEmpty() && ! containsSameKeysWithDifferentValues(pasteBuffer.relations))); 
     280            pasteFromHomogeneousSource(getCurrentDataSet().getSelected()); 
     281        } 
    109282    } 
    110283 
     
    119292            return; 
    120293        } 
    121         possiblyEnable(getCurrentDataSet().getSelected(), Main.pasteBuffer); 
     294        setEnabled( 
     295                !getCurrentDataSet().getSelected().isEmpty() 
     296                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getSelected()).isEmpty() 
     297        ); 
    122298    } 
    123299} 
Note: See TracChangeset for help on using the changeset viewer.