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

Last change on this file since 10734 was 10692, checked in by Don-vip, 8 years ago

fix javadoc warnings

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