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

Last change on this file since 5809 was 5809, checked in by akks, 11 years ago

fix #8500, see #8384: added "Clear buffer" button to the messages shown for incorrect buffer (clipboard)
(next Ctrl-Shift-V will paste tags from JOSM-copied objects, if available)

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