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

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

fix #12478, fix #12565, fix #11114 - Use ​Swing Copy/Paste instead of CopyAction/PasteAction with custom buffer (patch by michael2402, modified) - gsoc-core

  • Property svn:eol-style set to native
File size: 12.6 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 /**
[5275]151 * Pastes the tags from a homogeneous source (the {@link Main#pasteBuffer}s selection consisting
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 {
177 // no conflicts in the source tags to resolve. Just apply the tags
178 // to the target primitives
179 //
[8855]180 buildTags(tc);
[2305]181 }
[1514]182 }
[750]183
[2305]184 /**
[6069]185 * Replies true if there is at least one primitive of type <code>type</code>
[5275]186 * is in the target collection
[2305]187 *
188 * @param type the type to look for
189 * @return true if there is at least one primitive of type <code>type</code> in the collection
190 * <code>selection</code>
191 */
[8470]192 protected boolean hasTargetPrimitives(Class<? extends OsmPrimitive> type) {
[2305]193 return !OsmPrimitive.getFilteredList(target, type).isEmpty();
194 }
[2008]195
[2305]196 /**
197 * Replies true if this a heterogeneous source can be pasted without conflict to targets
198 *
199 * @return true if this a heterogeneous source can be pasted without conflicts to targets
200 */
[8855]201 protected boolean canPasteFromHeterogeneousSourceWithoutConflict() {
[4387]202 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]203 if (hasTargetPrimitives(type.getOsmClass())) {
204 TagCollection tc = TagCollection.unionOfAllPrimitives(getSourcePrimitivesByType(type));
[8443]205 if (!tc.isEmpty() && !tc.isApplicableToPrimitive())
[2305]206 return false;
207 }
208 }
209 return true;
[1169]210 }
[2305]211
212 /**
[8855]213 * Pastes the tags in the current selection of the paste buffer to a set of target primitives.
[2305]214 */
215 protected void pasteFromHeterogeneousSource() {
[8855]216 if (canPasteFromHeterogeneousSourceWithoutConflict()) {
[4387]217 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]218 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
[8855]219 buildTags(getSourceTagsByType(type));
[2305]220 }
221 }
222 } else {
223 PasteTagsConflictResolverDialog dialog = new PasteTagsConflictResolverDialog(Main.parent);
224 dialog.populate(
225 getSourceTagsByType(OsmPrimitiveType.NODE),
226 getSourceTagsByType(OsmPrimitiveType.WAY),
227 getSourceTagsByType(OsmPrimitiveType.RELATION),
228 getSourceStatistics(),
229 getTargetStatistics()
230 );
231 dialog.setVisible(true);
232 if (dialog.isCanceled())
233 return;
[4387]234 for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
[2305]235 if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
[8855]236 buildTags(dialog.getResolution(type));
[2305]237 }
238 }
239 }
[2008]240 }
[750]241
[9230]242 /**
243 * Performs the paste operation.
244 * @return list of tags
245 */
[3640]246 public List<Tag> execute() {
[8855]247 tags.clear();
[9230]248 if (isHeterogeneousSource()) {
[2305]249 pasteFromHeterogeneousSource();
250 } else {
251 pasteFromHomogeneousSource();
[2008]252 }
[8855]253 return tags;
[2008]254 }
[2305]255
[2008]256 }
257
[5890]258 @Override
[2008]259 public void actionPerformed(ActionEvent e) {
[10382]260 Collection<OsmPrimitive> selection = getLayerManager().getEditDataSet().getSelected();
[3640]261
262 if (selection.isEmpty())
[1847]263 return;
[6069]264
[10604]265 transferHandler.pasteTags(selection);
[5809]266 }
[3640]267
[9230]268 /**
269 * Paste tags from arbitrary text, not using JOSM buffer
270 * @param selection selected primitives
271 * @param text text containing tags
[5890]272 * @return true if action was successful
[9230]273 * @see TextTagParser#readTagsFromText
[5809]274 */
275 public static boolean pasteTagsFromText(Collection<OsmPrimitive> selection, String text) {
276 Map<String, String> tags = TextTagParser.readTagsFromText(text);
[8510]277 if (tags == null || tags.isEmpty()) {
[5915]278 TextTagParser.showBadBufferMessage(help);
[5809]279 return false;
[5890]280 }
[5809]281 if (!TextTagParser.validateTags(tags)) return false;
[6106]282
[7005]283 List<Command> commands = new ArrayList<>(tags.size());
[6258]284 for (Entry<String, String> entry: tags.entrySet()) {
285 String v = entry.getValue();
[8510]286 commands.add(new ChangePropertyCommand(selection, entry.getKey(), "".equals(v) ? null : v));
[1847]287 }
[5809]288 commitCommands(selection, commands);
289 return !commands.isEmpty();
290 }
[6069]291
[9230]292 /**
[5809]293 * Create and execute SequenceCommand with descriptive title
[9230]294 * @param selection selected primitives
[8470]295 * @param commands the commands to perform in a sequential command
[5809]296 */
297 private static void commitCommands(Collection<OsmPrimitive> selection, List<Command> commands) {
[3640]298 if (!commands.isEmpty()) {
299 String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
[3995]300 String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
[8975]301 @I18n.QuirkyPluralString
302 final String title = title1 + ' ' + title2;
[3640]303 Main.main.undoRedo.add(
304 new SequenceCommand(
[8975]305 title,
[3640]306 commands
307 ));
308 }
[6069]309 }
310
[1820]311 @Override
312 protected void updateEnabledState() {
[10382]313 DataSet ds = getLayerManager().getEditDataSet();
314 if (ds == null) {
[1820]315 setEnabled(false);
316 return;
317 }
[5738]318 // buffer listening slows down the program and is not very good for arbitrary text in buffer
[10383]319 setEnabled(!ds.selectionEmpty());
[1169]320 }
[2256]321
322 @Override
323 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
[8510]324 setEnabled(selection != null && !selection.isEmpty());
[2256]325 }
[626]326}
Note: See TracBrowser for help on using the repository browser.