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

Revision 4387, 11.8 KB checked in by bastiK, 6 months ago (diff)

fixed #6746 - paste properties in new layer results in nullpointer exception

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