source: josm/trunk/src/org/openstreetmap/josm/actions/PasteTagsAction.java @ 5241

Revision 4982, 11.8 KB checked in by stoecker, 3 months ago (diff)

see #7226 - patch by akks (fixed a bit) - fix shortcut deprecations

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2// Author: David Earl
3package org.openstreetmap.josm.actions;
4
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.command.ChangePropertyCommand;
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.SequenceCommand;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
23import org.openstreetmap.josm.data.osm.PrimitiveData;
24import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
25import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
26import org.openstreetmap.josm.data.osm.Tag;
27import org.openstreetmap.josm.data.osm.TagCollection;
28import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
29import org.openstreetmap.josm.tools.Shortcut;
30
31public final class PasteTagsAction extends JosmAction implements PasteBufferChangedListener {
32
33    public PasteTagsAction() {
34        super(tr("Paste Tags"), "pastetags",
35                tr("Apply tags of contents of paste buffer to all selected items."),
36                Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")),
37                KeyEvent.VK_V, Shortcut.CTRL_SHIFT), true);
38        Main.pasteBuffer.addPasteBufferChangedListener(this);
39        putValue("help", ht("/Action/PasteTags"));
40    }
41
42    public static class TagPaster {
43
44        private final Collection<PrimitiveData> source;
45        private final Collection<OsmPrimitive> target;
46        private final List<Tag> commands = new ArrayList<Tag>();
47
48        public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
49            this.source = source;
50            this.target = target;
51        }
52
53        /**
54         * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
55         * {@see OsmPrimitive}s of exactly one type
56         *
57         * @return
58         */
59        protected boolean isHeteogeneousSource() {
60            int count = 0;
61            count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
62            count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
63            count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
64            return count > 1;
65        }
66
67        /**
68         * Replies all primitives of type <code>type</code> in the current selection.
69         *
70         * @param <T>
71         * @param type  the type
72         * @return all primitives of type <code>type</code> in the current selection.
73         */
74        protected <T extends PrimitiveData> Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
75            return PrimitiveData.getFilteredList(source, type);
76        }
77
78        /**
79         * Replies the collection of tags for all primitives of type <code>type</code> in the current
80         * selection
81         *
82         * @param <T>
83         * @param type  the type
84         * @return the collection of tags for all primitives of type <code>type</code> in the current
85         * selection
86         */
87        protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(OsmPrimitiveType type) {
88            return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
89        }
90
91        /**
92         * Replies true if there is at least one tag in the current selection for primitives of
93         * type <code>type</code>
94         *
95         * @param <T>
96         * @param type the type
97         * @return true if there is at least one tag in the current selection for primitives of
98         * type <code>type</code>
99         */
100        protected <T extends OsmPrimitive> boolean hasSourceTagsByType(OsmPrimitiveType type) {
101            return ! getSourceTagsByType(type).isEmpty();
102        }
103
104        protected void buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
105            for (String key : tc.getKeys()) {
106                commands.add(new Tag(key, tc.getValues(key).iterator().next()));
107            }
108        }
109
110        protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
111            HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
112            for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
113                if (!getSourceTagsByType(type).isEmpty()) {
114                    ret.put(type, getSourcePrimitivesByType(type).size());
115                }
116            }
117            return ret;
118        }
119
120        protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
121            HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
122            for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
123                int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
124                if (count > 0) {
125                    ret.put(type, count);
126                }
127            }
128            return ret;
129        }
130
131        /**
132         * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting
133         * of one type of {@see OsmPrimitive}s only.
134         *
135         * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
136         * regardless of their type, receive the same tags.
137         *
138         * @param targets the collection of target primitives
139         */
140        protected void pasteFromHomogeneousSource() {
141            TagCollection tc = null;
142            for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
143                TagCollection tc1 = getSourceTagsByType(type);
144                if (!tc1.isEmpty()) {
145                    tc = tc1;
146                }
147            }
148            if (tc == null)
149                // no tags found to paste. Abort.
150                return;
151
152            if (!tc.isApplicableToPrimitive()) {
153                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
154                dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
155                dialog.setVisible(true);
156                if (dialog.isCanceled())
157                    return;
158                buildChangeCommand(target, dialog.getResolution());
159            } else {
160                // no conflicts in the source tags to resolve. Just apply the tags
161                // to the target primitives
162                //
163                buildChangeCommand(target, tc);
164            }
165        }
166
167        /**
168         * Replies true if there is at least one primitive of type <code>type</code> in the collection
169         * <code>selection</code>
170         *
171         * @param <T>
172         * @param selection  the collection of primitives
173         * @param type  the type to look for
174         * @return true if there is at least one primitive of type <code>type</code> in the collection
175         * <code>selection</code>
176         */
177        protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Class<T> type) {
178            return !OsmPrimitive.getFilteredList(target, type).isEmpty();
179        }
180
181        /**
182         * Replies true if this a heterogeneous source can be pasted without conflict to targets
183         *
184         * @param targets the collection of target primitives
185         * @return true if this a heterogeneous source can be pasted without conflicts to targets
186         */
187        protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
188            for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
189                if (hasTargetPrimitives(type.getOsmClass())) {
190                    TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
191                    if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
192                        return false;
193                }
194            }
195            return true;
196        }
197
198        /**
199         * Pastes the tags in the current selection of the paste buffer to a set of target
200         * primitives.
201         *
202         * @param targets the collection of target primitives
203         */
204        protected void pasteFromHeterogeneousSource() {
205            if (canPasteFromHeterogeneousSourceWithoutConflict(target)) {
206                for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
207                    if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
208                        buildChangeCommand(target, getSourceTagsByType(type));
209                    }
210                }
211            } else {
212                PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
213                dialog.populate(
214                        getSourceTagsByType(OsmPrimitiveType.NODE),
215                        getSourceTagsByType(OsmPrimitiveType.WAY),
216                        getSourceTagsByType(OsmPrimitiveType.RELATION),
217                        getSourceStatistics(),
218                        getTargetStatistics()
219                );
220                dialog.setVisible(true);
221                if (dialog.isCanceled())
222                    return;
223                for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
224                    if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
225                        buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
226                    }
227                }
228            }
229        }
230
231        public List<Tag> execute() {
232            commands.clear();
233            if (isHeteogeneousSource()) {
234                pasteFromHeterogeneousSource();
235            } else {
236                pasteFromHomogeneousSource();
237            }
238            return commands;
239        }
240
241    }
242
243    public void actionPerformed(ActionEvent e) {
244        Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
245
246        if (selection.isEmpty())
247            return;
248
249        TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), selection);
250
251        List<Command> commands = new ArrayList<Command>();
252        for (Tag tag: tagPaster.execute()) {
253            commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue())?null:tag.getValue()));
254        }
255        if (!commands.isEmpty()) {
256            String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
257            String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
258            Main.main.undoRedo.add(
259                    new SequenceCommand(
260                            title1 + " " + title2,
261                            commands
262                    ));
263        }
264
265    }
266
267    @Override public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
268        updateEnabledState();
269    }
270
271    @Override
272    protected void updateEnabledState() {
273        if (getCurrentDataSet() == null || Main.pasteBuffer == null) {
274            setEnabled(false);
275            return;
276        }
277        setEnabled(
278                !getCurrentDataSet().getSelected().isEmpty()
279                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
280        );
281    }
282
283    @Override
284    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
285        setEnabled(
286                selection!= null && !selection.isEmpty()
287                && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
288        );
289    }
290}
Note: See TracBrowser for help on using the repository browser.