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

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

fixed #3556: False positive conflicts when merging nodes
fixed #3460: show only conflicting tags at conflict resolution dialog
fixed #3103: resolve conflicts after combining just one conflict

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