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

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

replace .get*Selected().isEmpty() by .selectionEmpty()

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