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

Last change on this file since 7937 was 7005, checked in by Don-vip, 10 years ago

see #8465 - use diamond operator where applicable

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