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

Last change on this file since 6588 was 6380, checked in by Don-vip, 10 years ago

update license/copyright information

  • Property svn:eol-style set to native
File size: 13.2 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[626]2package org.openstreetmap.josm.actions;
3
[3384]4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
[626]5import static org.openstreetmap.josm.tools.I18n.tr;
[2008]6import static org.openstreetmap.josm.tools.I18n.trn;
[626]7
8import java.awt.event.ActionEvent;
9import java.awt.event.KeyEvent;
[2008]10import java.util.ArrayList;
[626]11import java.util.Collection;
12import java.util.HashMap;
[2008]13import java.util.List;
[626]14import java.util.Map;
[6258]15import java.util.Map.Entry;
[626]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;
[2008]22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
[2305]23import org.openstreetmap.josm.data.osm.PrimitiveData;
[3640]24import org.openstreetmap.josm.data.osm.Tag;
[2008]25import org.openstreetmap.josm.data.osm.TagCollection;
26import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
[1084]27import org.openstreetmap.josm.tools.Shortcut;
[5738]28import org.openstreetmap.josm.tools.TextTagParser;
29import org.openstreetmap.josm.tools.Utils;
[626]30
[5275]31/**
32 * Action, to paste all tags from one primitive to another.
33 *
34 * It will take the primitive from the copy-paste buffer an apply all its tags
35 * to the selected primitive(s).
36 *
37 * @author David Earl
38 */
[5738]39public final class PasteTagsAction extends JosmAction {
[626]40
[5809]41 private static final String help = ht("/Action/PasteTags");
[6069]42
[6258]43 /**
44 * Constructs a new {@code PasteTagsAction}.
45 */
[3385]46 public PasteTagsAction() {
[1169]47 super(tr("Paste Tags"), "pastetags",
[1814]48 tr("Apply tags of contents of paste buffer to all selected items."),
[4943]49 Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")),
[4982]50 KeyEvent.VK_V, Shortcut.CTRL_SHIFT), true);
[5809]51 putValue("help", help);
[1169]52 }
[626]53
[2305]54 public static class TagPaster {
[2070]55
[2305]56 private final Collection<PrimitiveData> source;
57 private final Collection<OsmPrimitive> target;
[3640]58 private final List<Tag> commands = new ArrayList<Tag>();
[1514]59
[2305]60 public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
61 this.source = source;
62 this.target = target;
63 }
[2008]64
[2305]65 /**
66 * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
[5266]67 * {@link OsmPrimitive}s of exactly one type
[2305]68 */
69 protected boolean isHeteogeneousSource() {
70 int count = 0;
71 count = !getSourcePrimitivesByType(OsmPrimitiveType.NODE).isEmpty() ? count + 1 : count;
72 count = !getSourcePrimitivesByType(OsmPrimitiveType.WAY).isEmpty() ? count + 1 : count;
73 count = !getSourcePrimitivesByType(OsmPrimitiveType.RELATION).isEmpty() ? count + 1 : count;
74 return count > 1;
75 }
[2008]76
[2305]77 /**
78 * Replies all primitives of type <code>type</code> in the current selection.
79 *
80 * @param <T>
81 * @param type the type
82 * @return all primitives of type <code>type</code> in the current selection.
83 */
84 protected <T extends PrimitiveData> Collection<? extends PrimitiveData> getSourcePrimitivesByType(OsmPrimitiveType type) {
85 return PrimitiveData.getFilteredList(source, type);
86 }
[2008]87
[2305]88 /**
89 * Replies the collection of tags for all primitives of type <code>type</code> in the current
90 * selection
91 *
92 * @param <T>
93 * @param type the type
94 * @return the collection of tags for all primitives of type <code>type</code> in the current
95 * selection
96 */
97 protected <T extends OsmPrimitive> TagCollection getSourceTagsByType(OsmPrimitiveType type) {
98 return TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
[2008]99 }
[2305]100
101 /**
102 * Replies true if there is at least one tag in the current selection for primitives of
103 * type <code>type</code>
104 *
105 * @param <T>
106 * @param type the type
107 * @return true if there is at least one tag in the current selection for primitives of
108 * type <code>type</code>
109 */
110 protected <T extends OsmPrimitive> boolean hasSourceTagsByType(OsmPrimitiveType type) {
111 return ! getSourceTagsByType(type).isEmpty();
[2008]112 }
113
[3640]114 protected void buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
[2305]115 for (String key : tc.getKeys()) {
[3640]116 commands.add(new Tag(key, tc.getValues(key).iterator().next()));
[1169]117 }
118 }
[750]119
[2305]120 protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
121 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
[4387]122 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
[2305]123 if (!getSourceTagsByType(type).isEmpty()) {
124 ret.put(type, getSourcePrimitivesByType(type).size());
125 }
[2008]126 }
[2305]127 return ret;
[1814]128 }
[1514]129
[2305]130 protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
131 HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
[4387]132 for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
[2305]133 int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
134 if (count > 0) {
135 ret.put(type, count);
136 }
[2008]137 }
[2305]138 return ret;
[2008]139 }
[1514]140
[2305]141 /**
[5275]142 * Pastes the tags from a homogeneous source (the {@link Main#pasteBuffer}s selection consisting
143 * of one type of {@link OsmPrimitive}s only).
[2305]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 protected void pasteFromHomogeneousSource() {
149 TagCollection tc = null;
[4387]150 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]151 TagCollection tc1 = getSourceTagsByType(type);
152 if (!tc1.isEmpty()) {
153 tc = tc1;
154 }
155 }
156 if (tc == null)
157 // no tags found to paste. Abort.
[2008]158 return;
[2305]159
160 if (!tc.isApplicableToPrimitive()) {
161 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
162 dialog.populate(tc, getSourceStatistics(), getTargetStatistics());
163 dialog.setVisible(true);
164 if (dialog.isCanceled())
165 return;
[3640]166 buildChangeCommand(target, dialog.getResolution());
[2305]167 } else {
168 // no conflicts in the source tags to resolve. Just apply the tags
169 // to the target primitives
170 //
[3640]171 buildChangeCommand(target, tc);
[2305]172 }
[1514]173 }
[750]174
[2305]175 /**
[6069]176 * Replies true if there is at least one primitive of type <code>type</code>
[5275]177 * is in the target collection
[2305]178 *
179 * @param <T>
180 * @param type the type to look for
181 * @return true if there is at least one primitive of type <code>type</code> in the collection
182 * <code>selection</code>
183 */
184 protected <T extends OsmPrimitive> boolean hasTargetPrimitives(Class<T> type) {
185 return !OsmPrimitive.getFilteredList(target, type).isEmpty();
186 }
[2008]187
[2305]188 /**
189 * Replies true if this a heterogeneous source can be pasted without conflict to targets
190 *
191 * @param targets the collection of target primitives
192 * @return true if this a heterogeneous source can be pasted without conflicts to targets
193 */
194 protected boolean canPasteFromHeterogeneousSourceWithoutConflict(Collection<OsmPrimitive> targets) {
[4387]195 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]196 if (hasTargetPrimitives(type.getOsmClass())) {
197 TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
198 if (!tc.isEmpty() && ! tc.isApplicableToPrimitive())
199 return false;
200 }
201 }
202 return true;
[1169]203 }
[2305]204
205 /**
206 * Pastes the tags in the current selection of the paste buffer to a set of target
207 * primitives.
208 */
209 protected void pasteFromHeterogeneousSource() {
210 if (canPasteFromHeterogeneousSourceWithoutConflict(target)) {
[4387]211 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]212 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
[3640]213 buildChangeCommand(target, getSourceTagsByType(type));
[2305]214 }
215 }
216 } else {
217 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
218 dialog.populate(
219 getSourceTagsByType(OsmPrimitiveType.NODE),
220 getSourceTagsByType(OsmPrimitiveType.WAY),
221 getSourceTagsByType(OsmPrimitiveType.RELATION),
222 getSourceStatistics(),
223 getTargetStatistics()
224 );
225 dialog.setVisible(true);
226 if (dialog.isCanceled())
227 return;
[4387]228 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]229 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
[3640]230 buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
[2305]231 }
232 }
233 }
[2008]234 }
[750]235
[3640]236 public List<Tag> execute() {
[2305]237 commands.clear();
238 if (isHeteogeneousSource()) {
239 pasteFromHeterogeneousSource();
240 } else {
241 pasteFromHomogeneousSource();
[2008]242 }
[2305]243 return commands;
[2008]244 }
[2305]245
[2008]246 }
247
[5890]248 @Override
[2008]249 public void actionPerformed(ActionEvent e) {
[3640]250 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
251
252 if (selection.isEmpty())
[1847]253 return;
[6069]254
[5738]255 String buf = Utils.getClipboardContent();
[6321]256 if (buf == null || buf.isEmpty() || buf.matches(CopyAction.CLIPBOARD_REGEXP)) {
[5809]257 pasteTagsFromJOSMBuffer(selection);
[6069]258 } else {
[5809]259 // Paste tags from arbitrary text
260 pasteTagsFromText(selection, buf);
261 }
262 }
[3640]263
[5890]264 /** Paste tags from arbitrary text, not using JOSM buffer
265 * @return true if action was successful
[5809]266 */
267 public static boolean pasteTagsFromText(Collection<OsmPrimitive> selection, String text) {
268 Map<String, String> tags = TextTagParser.readTagsFromText(text);
269 if (tags==null || tags.isEmpty()) {
[5915]270 TextTagParser.showBadBufferMessage(help);
[5809]271 return false;
[5890]272 }
[5809]273 if (!TextTagParser.validateTags(tags)) return false;
[6106]274
275 List<Command> commands = new ArrayList<Command>(tags.size());
[6258]276 for (Entry<String, String> entry: tags.entrySet()) {
277 String v = entry.getValue();
278 commands.add(new ChangePropertyCommand(selection, entry.getKey(), "".equals(v)?null:v));
[1847]279 }
[5809]280 commitCommands(selection, commands);
281 return !commands.isEmpty();
282 }
[6069]283
[5809]284 /** Paste tags from JOSM buffer
[5890]285 * @param selection objects that will have the tags
[6069]286 * @return false if JOSM buffer was empty
[5809]287 */
288 public static boolean pasteTagsFromJOSMBuffer(Collection<OsmPrimitive> selection) {
289 List<PrimitiveData> directlyAdded = Main.pasteBuffer.getDirectlyAdded();
290 if (directlyAdded==null || directlyAdded.isEmpty()) return false;
291
292 PasteTagsAction.TagPaster tagPaster = new PasteTagsAction.TagPaster(directlyAdded, selection);
293 List<Command> commands = new ArrayList<Command>();
294 for (Tag tag : tagPaster.execute()) {
295 commands.add(new ChangePropertyCommand(selection, tag.getKey(), "".equals(tag.getValue()) ? null : tag.getValue()));
[5738]296 }
[5809]297 commitCommands(selection, commands);
298 return true;
299 }
300
301 /**
302 * Create and execute SequenceCommand with descriptive title
[6069]303 * @param commands
[5809]304 */
305 private static void commitCommands(Collection<OsmPrimitive> selection, List<Command> commands) {
[3640]306 if (!commands.isEmpty()) {
307 String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
[3995]308 String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
[3640]309 Main.main.undoRedo.add(
310 new SequenceCommand(
311 title1 + " " + title2,
312 commands
313 ));
314 }
[6069]315 }
316
[1820]317 @Override
318 protected void updateEnabledState() {
[5738]319 if (getCurrentDataSet() == null) {
[1820]320 setEnabled(false);
321 return;
322 }
[5738]323 // buffer listening slows down the program and is not very good for arbitrary text in buffer
324 setEnabled(!getCurrentDataSet().getSelected().isEmpty());
[1169]325 }
[2256]326
327 @Override
328 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
[5738]329 setEnabled(selection!= null && !selection.isEmpty());
[2256]330 }
[626]331}
Note: See TracBrowser for help on using the repository browser.