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

Last change on this file since 5508 was 5275, checked in by bastiK, 12 years ago

doc improvements

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