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

Last change on this file since 9881 was 9230, checked in by Don-vip, 8 years ago

fix javadoc errors/warnings

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