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

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

fix #12478, fix #12565, fix #11114 - Use ​Swing Copy/Paste instead of CopyAction/PasteAction with custom buffer (patch by michael2402, modified) - gsoc-core

  • Property svn:eol-style set to native
File size: 12.6 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 {@link Main#pasteBuffer}s 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
178 // to the target primitives
179 //
180 buildTags(tc);
181 }
182 }
183
184 /**
185 * Replies true if there is at least one primitive of type <code>type</code>
186 * is in the target collection
187 *
188 * @param type the type to look for
189 * @return true if there is at least one primitive of type <code>type</code> in the collection
190 * <code>selection</code>
191 */
192 protected boolean hasTargetPrimitives(Class<? extends OsmPrimitive> type) {
193 return !OsmPrimitive.getFilteredList(target, type).isEmpty();
194 }
195
196 /**
197 * Replies true if this a heterogeneous source can be pasted without conflict to targets
198 *
199 * @return true if this a heterogeneous source can be pasted without conflicts to targets
200 */
201 protected boolean canPasteFromHeterogeneousSourceWithoutConflict() {
202 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
203 if (hasTargetPrimitives(type.getOsmClass())) {
204 TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
205 if (!tc.isEmpty() && !tc.isApplicableToPrimitive())
206 return false;
207 }
208 }
209 return true;
210 }
211
212 /**
213 * Pastes the tags in the current selection of the paste buffer to a set of target primitives.
214 */
215 protected void pasteFromHeterogeneousSource() {
216 if (canPasteFromHeterogeneousSourceWithoutConflict()) {
217 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
218 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
219 buildTags(getSourceTagsByType(type));
220 }
221 }
222 } else {
223 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
224 dialog.populate(
225 getSourceTagsByType(OsmPrimitiveType.NODE),
226 getSourceTagsByType(OsmPrimitiveType.WAY),
227 getSourceTagsByType(OsmPrimitiveType.RELATION),
228 getSourceStatistics(),
229 getTargetStatistics()
230 );
231 dialog.setVisible(true);
232 if (dialog.isCanceled())
233 return;
234 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
235 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
236 buildTags(dialog.getResolution(type));
237 }
238 }
239 }
240 }
241
242 /**
243 * Performs the paste operation.
244 * @return list of tags
245 */
246 public List<Tag> execute() {
247 tags.clear();
248 if (isHeterogeneousSource()) {
249 pasteFromHeterogeneousSource();
250 } else {
251 pasteFromHomogeneousSource();
252 }
253 return tags;
254 }
255
256 }
257
258 @Override
259 public void actionPerformed(ActionEvent e) {
260 Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
261
262 if (selection.isEmpty())
263 return;
264
265 transferHandler.pasteTags(selection);
266 }
267
268 /**
269 * Paste tags from arbitrary text, not using JOSM buffer
270 * @param selection selected primitives
271 * @param text text containing tags
272 * @return true if action was successful
273 * @see TextTagParser#readTagsFromText
274 */
275 public static boolean pasteTagsFromText(Collection<OsmPrimitive> selection, String text) {
276 Map<String, String> tags = TextTagParser.readTagsFromText(text);
277 if (tags == null || tags.isEmpty()) {
278 TextTagParser.showBadBufferMessage(help);
279 return false;
280 }
281 if (!TextTagParser.validateTags(tags)) return false;
282
283 List<Command> commands = new ArrayList<>(tags.size());
284 for (Entry<String, String> entry: tags.entrySet()) {
285 String v = entry.getValue();
286 commands.add(new ChangePropertyCommand(selection, entry.getKey(), "".equals(v) ? null : v));
287 }
288 commitCommands(selection, commands);
289 return !commands.isEmpty();
290 }
291
292 /**
293 * Create and execute SequenceCommand with descriptive title
294 * @param selection selected primitives
295 * @param commands the commands to perform in a sequential command
296 */
297 private static void commitCommands(Collection<OsmPrimitive> selection, List<Command> commands) {
298 if (!commands.isEmpty()) {
299 String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
300 String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
301 @I18n.QuirkyPluralString
302 final String title = title1 + ' ' + title2;
303 Main.main.undoRedo.add(
304 new SequenceCommand(
305 title,
306 commands
307 ));
308 }
309 }
310
311 @Override
312 protected void updateEnabledState() {
313 DataSet ds = getLayerManager().getEditDataSet();
314 if (ds == null) {
315 setEnabled(false);
316 return;
317 }
318 // buffer listening slows down the program and is not very good for arbitrary text in buffer
319 setEnabled(!ds.selectionEmpty());
320 }
321
322 @Override
323 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
324 setEnabled(selection != null && !selection.isEmpty());
325 }
326}
Note: See TracBrowser for help on using the repository browser.