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

Last change on this file since 6033 was 5915, checked in by stoecker, 11 years ago

use 3 step wiki loading fallback, cleanup handling of language fallbacks

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