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

Last change on this file since 3384 was 3384, checked in by jttt, 14 years ago

Support Copy&Paste in relation editor

  • Property svn:eol-style set to native
File size: 12.0 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.TagCollection;
26import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
27import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
28import org.openstreetmap.josm.tools.Shortcut;
29
30public final class PasteTagsAction extends JosmAction implements PasteBufferChangedListener {
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 Main.pasteBuffer.addPasteBufferChangedListener(this);
37 putValue("help", ht("/Action/PasteTags"));
38 }
39
40 public static class TagPaster {
41
42 private final Collection<PrimitiveData> source;
43 private final Collection<OsmPrimitive> target;
44 private final List<Command> commands = new ArrayList<Command>();
45
46 public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
47 this.source = source;
48 this.target = target;
49 }
50
51 /**
52 * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
53 * {@see OsmPrimitive}s of exactly one type
54 *
55 * @return
56 */
57 protected boolean isHeteogeneousSource() {
58 int count = 0;
59 count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
60 count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
61 count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
62 return count > 1;
63 }
64
65 /**
66 * Replies all primitives of type <code>type</code> in the current selection.
67 *
68 * @param <T>
69 * @param type the type
70 * @return all primitives of type <code>type</code> in the current selection.
71 */
72 protected <T extends PrimitiveData> Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
73 return PrimitiveData.getFilteredList(source, type);
74 }
75
76 /**
77 * Replies the collection of tags for all primitives of type <code>type</code> in the current
78 * selection
79 *
80 * @param <T>
81 * @param type the type
82 * @return the collection of tags for all primitives of type <code>type</code> in the current
83 * selection
84 */
85 protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(OsmPrimitiveType type) {
86 return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
87 }
88
89 /**
90 * Replies true if there is at least one tag in the current selection for primitives of
91 * type <code>type</code>
92 *
93 * @param <T>
94 * @param type the type
95 * @return true if there is at least one tag in the current selection for primitives of
96 * type <code>type</code>
97 */
98 protected <T extends OsmPrimitive> boolean hasSourceTagsByType(OsmPrimitiveType type) {
99 return ! getSourceTagsByType(type).isEmpty();
100 }
101
102 protected Command buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
103 List<Command> commands = new ArrayList<Command>();
104 for (String key : tc.getKeys()) {
105 String value = tc.getValues(key).iterator().next();
106 value = value.equals("") ? null : value;
107 commands.add(new ChangePropertyCommand(selection,key,value));
108 }
109 if (!commands.isEmpty()) {
110 String title1 = trn("Pasting {0} tag", "Pasting {0} tags", tc.getKeys().size(), tc.getKeys().size());
111 String title2 = trn("to {0} primitive", "to {0} primtives", selection.size(), selection.size());
112 return new SequenceCommand(
113 title1 + " " + title2,
114 commands
115 );
116 }
117 return null;
118 }
119
120 protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
121 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
122 for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
123 if (!getSourceTagsByType(type).isEmpty()) {
124 ret.put(type, getSourcePrimitivesByType(type).size());
125 }
126 }
127 return ret;
128 }
129
130 protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
131 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
132 for (OsmPrimitiveType type: OsmPrimitiveType.values()) {
133 int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
134 if (count > 0) {
135 ret.put(type, count);
136 }
137 }
138 return ret;
139 }
140
141 /**
142 * Pastes the tags from a homogeneous source (i.e. the {@see Main#pasteBuffer}s selection consisting
143 * of one type of {@see OsmPrimitive}s only.
144 *
145 * Tags from a homogeneous source can be pasted to a heterogeneous target. All target primitives,
146 * regardless of their type, receive the same tags.
147 *
148 * @param targets the collection of target primitives
149 */
150 protected void pasteFromHomogeneousSource() {
151 TagCollection tc = null;
152 for (OsmPrimitiveType type : OsmPrimitiveType.values()) {
153 TagCollection tc1 = getSourceTagsByType(type);
154 if (!tc1.isEmpty()) {
155 tc = tc1;
156 }
157 }
158 if (tc == null)
159 // no tags found to paste. Abort.
160 return;
161
162 if (!tc.isApplicableToPrimitive()) {
163 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
164 dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
165 dialog.setVisible(true);
166 if (dialog.isCanceled())
167 return;
168 Command cmd = buildChangeCommand(target, dialog.getResolution());
169 commands.add(cmd);
170 } else {
171 // no conflicts in the source tags to resolve. Just apply the tags
172 // to the target primitives
173 //
174 Command cmd = buildChangeCommand(target, tc);
175 commands.add(cmd);
176 }
177 }
178
179 /**
180 * Replies true if there is at least one primitive of type <code>type</code> in the collection
181 * <code>selection</code>
182 *
183 * @param <T>
184 * @param selection the collection of primitives
185 * @param type the type to look for
186 * @return true if there is at least one primitive of type <code>type</code> in the collection
187 * <code>selection</code>
188 */
189 protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Class<T> type) {
190 return !OsmPrimitive.getFilteredList(target, type).isEmpty();
191 }
192
193 /**
194 * Replies true if this a heterogeneous source can be pasted without conflict to targets
195 *
196 * @param targets the collection of target primitives
197 * @return true if this a heterogeneous source can be pasted without conflicts to targets
198 */
199 protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
200 for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
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
212 * primitives.
213 *
214 * @param targets the collection of target primitives
215 */
216 protected void pasteFromHeterogeneousSource() {
217 if (canPasteFromHeterogeneousSourceWithoutConflict(target)) {
218 for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
219 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
220 Command cmd = buildChangeCommand(target, getSourceTagsByType(type));
221 commands.add(cmd);
222 }
223 }
224 } else {
225 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
226 dialog.populate(
227 getSourceTagsByType(OsmPrimitiveType.NODE),
228 getSourceTagsByType(OsmPrimitiveType.WAY),
229 getSourceTagsByType(OsmPrimitiveType.RELATION),
230 getSourceStatistics(),
231 getTargetStatistics()
232 );
233 dialog.setVisible(true);
234 if (dialog.isCanceled())
235 return;
236 for (OsmPrimitiveType type:OsmPrimitiveType.values()) {
237 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
238 Command cmd = buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
239 commands.add(cmd);
240 }
241 }
242 }
243 }
244
245 public List<Command> execute() {
246 commands.clear();
247 if (isHeteogeneousSource()) {
248 pasteFromHeterogeneousSource();
249 } else {
250 pasteFromHomogeneousSource();
251 }
252 return commands;
253 }
254
255 }
256
257 public void actionPerformed(ActionEvent e) {
258 if (getCurrentDataSet().getSelected().isEmpty())
259 return;
260 TagPaster tagPaster = new TagPaster(Main.pasteBuffer.getDirectlyAdded(), getCurrentDataSet().getSelected());
261 for (Command c:tagPaster.execute()) {
262 Main.main.undoRedo.add(c);
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.