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

Last change on this file since 9136 was 8975, checked in by simon04, 8 years ago

fix #12041 - Wrong message in relation member deletion confirmation

  • Property svn:eol-style set to native
File size: 13.0 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 public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
62 this.source = source;
63 this.target = target;
64 }
65
66 /**
67 * Determines if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
68 * {@link OsmPrimitive}s of exactly one type
69 * @return true if the source for tag pasting is heterogeneous
70 */
71 protected boolean isHeteogeneousSource() {
72 int count = 0;
73 count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
74 count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
75 count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
76 return count > 1;
77 }
78
79 /**
80 * Replies all primitives of type <code>type</code> in the current selection.
81 *
82 * @param type the type
83 * @return all primitives of type <code>type</code> in the current selection.
84 */
85 protected Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
86 return PrimitiveData.getFilteredList(source, type);
87 }
88
89 /**
90 * Replies the collection of tags for all primitives of type <code>type</code> in the current
91 * selection
92 *
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 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 type the type
106 * @return true if there is at least one tag in the current selection for primitives of
107 * type <code>type</code>
108 */
109 protected boolean hasSourceTagsByType(OsmPrimitiveType type) {
110 return !getSourceTagsByType(type).isEmpty();
111 }
112
113 protected void buildTags(TagCollection tc) {
114 for (String key : tc.getKeys()) {
115 tags.add(new Tag(key, tc.getValues(key).iterator().next()));
116 }
117 }
118
119 protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
120 Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class);
121 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
122 if (!getSourceTagsByType(type).isEmpty()) {
123 ret.put(type, getSourcePrimitivesByType(type).size());
124 }
125 }
126 return ret;
127 }
128
129 protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
130 Map<OsmPrimitiveType, Integer> ret = new EnumMap<>(OsmPrimitiveType.class);
131 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
132 int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
133 if (count > 0) {
134 ret.put(type, count);
135 }
136 }
137 return ret;
138 }
139
140 /**
141 * Pastes the tags from a homogeneous source (the {@link Main#pasteBuffer}s selection consisting
142 * of one type of {@link OsmPrimitive}s only).
143 *
144 * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
145 * regardless of their type, receive the same tags.
146 */
147 protected void pasteFromHomogeneousSource() {
148 TagCollection tc = null;
149 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
150 TagCollection tc1 = getSourceTagsByType(type);
151 if (!tc1.isEmpty()) {
152 tc = tc1;
153 }
154 }
155 if (tc == null)
156 // no tags found to paste. Abort.
157 return;
158
159 if (!tc.isApplicableToPrimitive()) {
160 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
161 dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
162 dialog.setVisible(true);
163 if (dialog.isCanceled())
164 return;
165 buildTags(dialog.getResolution());
166 } else {
167 // no conflicts in the source tags to resolve. Just apply the tags
168 // to the target primitives
169 //
170 buildTags(tc);
171 }
172 }
173
174 /**
175 * Replies true if there is at least one primitive of type <code>type</code>
176 * is in the target collection
177 *
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 boolean hasTargetPrimitives(Class<? extends OsmPrimitive> 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 * @return true if this a heterogeneous source can be pasted without conflicts to targets
190 */
191 protected boolean canPasteFromHeterogeneousSourceWithoutConflict() {
192 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
193 if (hasTargetPrimitives(type.getOsmClass())) {
194 TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
195 if (!tc.isEmpty() && !tc.isApplicableToPrimitive())
196 return false;
197 }
198 }
199 return true;
200 }
201
202 /**
203 * Pastes the tags in the current selection of the paste buffer to a set of target primitives.
204 */
205 protected void pasteFromHeterogeneousSource() {
206 if (canPasteFromHeterogeneousSourceWithoutConflict()) {
207 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
208 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
209 buildTags(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 buildTags(dialog.getResolution(type));
227 }
228 }
229 }
230 }
231
232 public List<Tag> execute() {
233 tags.clear();
234 if (isHeteogeneousSource()) {
235 pasteFromHeterogeneousSource();
236 } else {
237 pasteFromHomogeneousSource();
238 }
239 return tags;
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(CopyAction.CLIPBOARD_REGEXP)) {
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 if (tags == null || tags.isEmpty()) {
266 TextTagParser.showBadBufferMessage(help);
267 return false;
268 }
269 if (!TextTagParser.validateTags(tags)) return false;
270
271 List<Command> commands = new ArrayList<>(tags.size());
272 for (Entry<String, String> entry: tags.entrySet()) {
273 String v = entry.getValue();
274 commands.add(new ChangePropertyCommand(selection, entry.getKey(), "".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<>();
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 the commands to perform in a sequential command
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 @I18n.QuirkyPluralString
306 final String title = title1 + ' ' + title2;
307 Main.main.undoRedo.add(
308 new SequenceCommand(
309 title,
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.