Ignore:
Timestamp:
2009-08-30T15:41:28+02:00 (15 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.