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

Last change on this file since 8899 was 8855, checked in by Don-vip, 9 years ago

sonar - Unused private method should be removed
sonar - Unused protected methods should be removed
sonar - Sections of code should not be "commented out"
sonar - Empty statements should be removed
sonar - squid:S1172 - Unused method parameters should be removed
sonar - squid:S1481 - Unused local variables should be removed

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