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

Last change on this file since 2008 was 2008, checked in by Gubaer, 15 years ago

new: tag conflict resolution when pasting tags (only if necessary)
fixed #2611: cant copy'n'past tags from a relation to ways or other way round
fixed #3137: Paste Tags does not work for relations

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