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

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

fix javadoc warnings

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