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

Last change on this file since 3718 was 3640, checked in by jttt, 13 years ago

Fix #5579 allow paste tags in the relation editor

  • Property svn:eol-style set to native
File size: 11.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2// Author: David Earl
3package org.openstreetmap.josm.actions;
4
5import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.event.ActionEvent;
10import java.awt.event.KeyEvent;
11import java.util.ArrayList;
12import java.util.Collection;
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
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.PrimitiveDeepCopy;
25import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
26import org.openstreetmap.josm.data.osm.Tag;
27import org.openstreetmap.josm.data.osm.TagCollection;
28import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
29import org.openstreetmap.josm.tools.Shortcut;
30
31public final class PasteTagsAction extends JosmAction implements PasteBufferChangedListener {
32
33 public PasteTagsAction() {
34 super(tr("Paste Tags"), "pastetags",
35 tr("Apply tags of contents of paste buffer to all selected items."),
36 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")), KeyEvent.VK_V, Shortcut.GROUP_MENU, Shortcut.SHIFT_DEFAULT), true);
37 Main.pasteBuffer.addPasteBufferChangedListener(this);
38 putValue("help", ht("/Action/PasteTags"));
39 }
40
41 public static class TagPaster {
42
43 private final Collection<PrimitiveData> source;
44 private final Collection<OsmPrimitive> target;
45 private final List<Tag> commands = new ArrayList<Tag>();
46
47 public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
48 this.source = source;
49 this.target = target;
50 }
51
52 /**
53 * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
54 * {@see OsmPrimitive}s of exactly one type
55 *
56 * @return
57 */
58 protected boolean isHeteogeneousSource() {
59 int count = 0;
60 count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
61 count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
62 count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
63 return count > 1;
64 }
65
66 /**
67 * Replies all primitives of type <code>type</code> in the current selection.
68 *
69 * @param <T>
70 * @param type the type
71 * @return all primitives of type <code>type</code> in the current selection.
72 */
73 protected <T extends PrimitiveData> Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
74 return PrimitiveData.getFilteredList(source, type);
75 }
76
77 /**
78 * Replies the collection of tags for all primitives of type <code>type</code> in the current
79 * selection
80 *
81 * @param <T>
82 * @param type the type
83 * @return the collection of tags for all primitives of type <code>type</code> in the current
84 * selection
85 */
86 protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(OsmPrimitiveType type) {
87 return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
88 }
89
90 /**
91 * Replies true if there is at least one tag in the current selection for primitives of
92 * type <code>type</code>
93 *
94 * @param <T>
95 * @param type the type
96 * @return true if there is at least one tag in the current selection for primitives of
97 * type <code>type</code>
98 */
99 protected <T extends OsmPrimitive> boolean hasSourceTagsByType(OsmPrimitiveType type) {
100 return ! getSourceTagsByType(type).isEmpty();
101 }
102
103 protected void buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
104 for (String key : tc.getKeys()) {
105 commands.add(new Tag(key, tc.getValues(key).iterator().next()));
106 }
107 }
108
109 protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
110 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
111 for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
112 if (!getSourceTagsByType(type).isEmpty()) {
113 ret.put(type, getSourcePrimitivesByType(type).size());
114 }
115 }
116 return ret;
117 }
118
119 protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
120 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
121 for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
122 int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
123 if (count > 0) {
124 ret.put(type, count);
125 }
126 }
127 return ret;
128 }
129
130 /**
131 * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting
132 * of one type of {@see OsmPrimitive}s only.
133 *
134 * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
135 * regardless of their type, receive the same tags.
136 *
137 * @param targets the collection of target primitives
138 */
139 protected void pasteFromHomogeneousSource() {
140 TagCollection tc = null;
141 for (OsmPrimitiveType type : OsmPrimitiveType.values()) {
142 TagCollection tc1 = getSourceTagsByType(type);
143 if (!tc1.isEmpty()) {
144 tc = tc1;
145 }
146 }
147 if (tc == null)
148 // no tags found to paste. Abort.
149 return;
150
151 if (!tc.isApplicableToPrimitive()) {
152 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
153 dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
154 dialog.setVisible(true);
155 if (dialog.isCanceled())
156 return;
157 buildChangeCommand(target, dialog.getResolution());
158 } else {
159 // no conflicts in the source tags to resolve. Just apply the tags
160 // to the target primitives
161 //
162 buildChangeCommand(target, tc);
163 }
164 }
165
166 /**
167 * Replies true if there is at least one primitive of type <code>type</code> in the collection
168 * <code>selection</code>
169 *
170 * @param <T>
171 * @param selection the collection of primitives
172 * @param type the type to look for
173 * @return true if there is at least one primitive of type <code>type</code> in the collection
174 * <code>selection</code>
175 */
176 protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Class<T> type) {
177 return !OsmPrimitive.getFilteredList(target, type).isEmpty();
178 }
179
180 /**
181 * Replies true if this a heterogeneous source can be pasted without conflict to targets
182 *
183 * @param targets the collection of target primitives
184 * @return true if this a heterogeneous source can be pasted without conflicts to targets
185 */
186 protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
187 for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
188 if (hasTargetPrimitives(type.getOsmClass())) {
189 TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
190 if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
191 return false;
192 }
193 }
194 return true;
195 }
196
197 /**
198 * Pastes the tags in the current selection of the paste buffer to a set of target
199 * primitives.
200 *
201 * @param targets the collection of target primitives
202 */
203 protected void pasteFromHeterogeneousSource() {
204 if (canPasteFromHeterogeneousSourceWithoutConflict(target)) {
205 for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
206 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
207 buildChangeCommand(target, 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.values()) {
223 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
224 buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
225 }
226 }
227 }
228 }
229
230 public List<Tag> execute() {
231 commands.clear();
232 if (isHeteogeneousSource()) {
233 pasteFromHeterogeneousSource();
234 } else {
235 pasteFromHomogeneousSource();
236 }
237 return commands;
238 }
239
240 }
241
242 public void actionPerformed(ActionEvent e) {
243 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
244
245 if (selection.isEmpty())
246 return;
247
248 TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), selection);
249
250 List<Command> commands = new ArrayList<Command>();
251 for (Tag tag: tagPaster.execute()) {
252 commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue())?null:tag.getValue()));
253 }
254 if (!commands.isEmpty()) {
255 String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
256 String title2 = trn("to {0} primitive", "to {0} primtives", selection.size(), selection.size());
257 Main.main.undoRedo.add(
258 new SequenceCommand(
259 title1 + " " + title2,
260 commands
261 ));
262 }
263
264 }
265
266 @Override public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
267 updateEnabledState();
268 }
269
270 @Override
271 protected void updateEnabledState() {
272 if (getCurrentDataSet() == null || Main.pasteBuffer == null) {
273 setEnabled(false);
274 return;
275 }
276 setEnabled(
277 !getCurrentDataSet().getSelected().isEmpty()
278 && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
279 );
280 }
281
282 @Override
283 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
284 setEnabled(
285 selection!= null && !selection.isEmpty()
286 && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
287 );
288 }
289}
Note: See TracBrowser for help on using the repository browser.