[626] | 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others
|
---|
| 2 | package org.openstreetmap.josm.actions;
|
---|
| 3 |
|
---|
[3384] | 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
[626] | 5 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
[2008] | 6 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
[626] | 7 |
|
---|
| 8 | import java.awt.event.ActionEvent;
|
---|
| 9 | import java.awt.event.KeyEvent;
|
---|
[2008] | 10 | import java.util.ArrayList;
|
---|
[626] | 11 | import java.util.Collection;
|
---|
| 12 | import java.util.HashMap;
|
---|
[2008] | 13 | import java.util.List;
|
---|
[626] | 14 | import java.util.Map;
|
---|
| 15 |
|
---|
| 16 | import org.openstreetmap.josm.Main;
|
---|
| 17 | import org.openstreetmap.josm.command.ChangePropertyCommand;
|
---|
| 18 | import org.openstreetmap.josm.command.Command;
|
---|
| 19 | import org.openstreetmap.josm.command.SequenceCommand;
|
---|
| 20 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[2008] | 21 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
|
---|
[2305] | 22 | import org.openstreetmap.josm.data.osm.PrimitiveData;
|
---|
| 23 | import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy;
|
---|
[3640] | 24 | import org.openstreetmap.josm.data.osm.PrimitiveDeepCopy.PasteBufferChangedListener;
|
---|
| 25 | import org.openstreetmap.josm.data.osm.Tag;
|
---|
[2008] | 26 | import org.openstreetmap.josm.data.osm.TagCollection;
|
---|
| 27 | import org.openstreetmap.josm.gui.conflict.tags.PasteTagsConflictResolverDialog;
|
---|
[1084] | 28 | import org.openstreetmap.josm.tools.Shortcut;
|
---|
[626] | 29 |
|
---|
[5275] | 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 | */
|
---|
[3384] | 38 | public final class PasteTagsAction extends JosmAction implements PasteBufferChangedListener {
|
---|
[626] | 39 |
|
---|
[3385] | 40 | public PasteTagsAction() {
|
---|
[1169] | 41 | super(tr("Paste Tags"), "pastetags",
|
---|
[1814] | 42 | tr("Apply tags of contents of paste buffer to all selected items."),
|
---|
[4943] | 43 | Shortcut.registerShortcut("system:pastestyle", tr("Edit: {0}", tr("Paste Tags")),
|
---|
[4982] | 44 | KeyEvent.VK_V, Shortcut.CTRL_SHIFT), true);
|
---|
[3384] | 45 | Main.pasteBuffer.addPasteBufferChangedListener(this);
|
---|
[2323] | 46 | putValue("help", ht("/Action/PasteTags"));
|
---|
[1169] | 47 | }
|
---|
[626] | 48 |
|
---|
[2305] | 49 | public static class TagPaster {
|
---|
[2070] | 50 |
|
---|
[2305] | 51 | private final Collection<PrimitiveData> source;
|
---|
| 52 | private final Collection<OsmPrimitive> target;
|
---|
[3640] | 53 | private final List<Tag> commands = new ArrayList<Tag>();
|
---|
[1514] | 54 |
|
---|
[2305] | 55 | public TagPaster(Collection<PrimitiveData> source, Collection<OsmPrimitive> target) {
|
---|
| 56 | this.source = source;
|
---|
| 57 | this.target = target;
|
---|
| 58 | }
|
---|
[2008] | 59 |
|
---|
[2305] | 60 | /**
|
---|
| 61 | * Replies true if the source for tag pasting is heterogeneous, i.e. if it doesn't consist of
|
---|
[5266] | 62 | * {@link OsmPrimitive}s of exactly one type
|
---|
[2305] | 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 | }
|
---|
[2008] | 71 |
|
---|
[2305] | 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 | }
|
---|
[2008] | 82 |
|
---|
[2305] | 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));
|
---|
[2008] | 94 | }
|
---|
[2305] | 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();
|
---|
[2008] | 107 | }
|
---|
| 108 |
|
---|
[3640] | 109 | protected void buildChangeCommand(Collection<? extends OsmPrimitive> selection, TagCollection tc) {
|
---|
[2305] | 110 | for (String key : tc.getKeys()) {
|
---|
[3640] | 111 | commands.add(new Tag(key, tc.getValues(key).iterator().next()));
|
---|
[1169] | 112 | }
|
---|
| 113 | }
|
---|
[750] | 114 |
|
---|
[2305] | 115 | protected Map<OsmPrimitiveType, Integer> getSourceStatistics() {
|
---|
| 116 | HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
|
---|
[4387] | 117 | for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 118 | if (!getSourceTagsByType(type).isEmpty()) {
|
---|
| 119 | ret.put(type, getSourcePrimitivesByType(type).size());
|
---|
| 120 | }
|
---|
[2008] | 121 | }
|
---|
[2305] | 122 | return ret;
|
---|
[1814] | 123 | }
|
---|
[1514] | 124 |
|
---|
[2305] | 125 | protected Map<OsmPrimitiveType, Integer> getTargetStatistics() {
|
---|
| 126 | HashMap<OsmPrimitiveType, Integer> ret = new HashMap<OsmPrimitiveType, Integer>();
|
---|
[4387] | 127 | for (OsmPrimitiveType type: OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 128 | int count = OsmPrimitive.getFilteredList(target, type.getOsmClass()).size();
|
---|
| 129 | if (count > 0) {
|
---|
| 130 | ret.put(type, count);
|
---|
| 131 | }
|
---|
[2008] | 132 | }
|
---|
[2305] | 133 | return ret;
|
---|
[2008] | 134 | }
|
---|
[1514] | 135 |
|
---|
[2305] | 136 | /**
|
---|
[5275] | 137 | * Pastes the tags from a homogeneous source (the {@link Main#pasteBuffer}s selection consisting
|
---|
| 138 | * of one type of {@link OsmPrimitive}s only).
|
---|
[2305] | 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;
|
---|
[4387] | 145 | for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 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.
|
---|
[2008] | 153 | return;
|
---|
[2305] | 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;
|
---|
[3640] | 161 | buildChangeCommand(target, dialog.getResolution());
|
---|
[2305] | 162 | } else {
|
---|
| 163 | // no conflicts in the source tags to resolve. Just apply the tags
|
---|
| 164 | // to the target primitives
|
---|
| 165 | //
|
---|
[3640] | 166 | buildChangeCommand(target, tc);
|
---|
[2305] | 167 | }
|
---|
[1514] | 168 | }
|
---|
[750] | 169 |
|
---|
[2305] | 170 | /**
|
---|
[5275] | 171 | * Replies true if there is at least one primitive of type <code>type</code>
|
---|
| 172 | * is in the target collection
|
---|
[2305] | 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 | }
|
---|
[2008] | 182 |
|
---|
[2305] | 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) {
|
---|
[4387] | 190 | for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 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;
|
---|
[1169] | 198 | }
|
---|
[2305] | 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)) {
|
---|
[4387] | 206 | for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 207 | if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
|
---|
[3640] | 208 | buildChangeCommand(target, getSourceTagsByType(type));
|
---|
[2305] | 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;
|
---|
[4387] | 223 | for (OsmPrimitiveType type : OsmPrimitiveType.dataValues()) {
|
---|
[2305] | 224 | if (hasSourceTagsByType(type) && hasTargetPrimitives(type.getOsmClass())) {
|
---|
[3640] | 225 | buildChangeCommand(OsmPrimitive.getFilteredList(target, type.getOsmClass()), dialog.getResolution(type));
|
---|
[2305] | 226 | }
|
---|
| 227 | }
|
---|
| 228 | }
|
---|
[2008] | 229 | }
|
---|
[750] | 230 |
|
---|
[3640] | 231 | public List<Tag> execute() {
|
---|
[2305] | 232 | commands.clear();
|
---|
| 233 | if (isHeteogeneousSource()) {
|
---|
| 234 | pasteFromHeterogeneousSource();
|
---|
| 235 | } else {
|
---|
| 236 | pasteFromHomogeneousSource();
|
---|
[2008] | 237 | }
|
---|
[2305] | 238 | return commands;
|
---|
[2008] | 239 | }
|
---|
[2305] | 240 |
|
---|
[2008] | 241 | }
|
---|
| 242 |
|
---|
| 243 | public void actionPerformed(ActionEvent e) {
|
---|
[3640] | 244 | Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
|
---|
| 245 |
|
---|
| 246 | if (selection.isEmpty())
|
---|
[1847] | 247 | return;
|
---|
[3640] | 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()));
|
---|
[1847] | 254 | }
|
---|
[3640] | 255 | if (!commands.isEmpty()) {
|
---|
| 256 | String title1 = trn("Pasting {0} tag", "Pasting {0} tags", commands.size(), commands.size());
|
---|
[3995] | 257 | String title2 = trn("to {0} object", "to {0} objects", selection.size(), selection.size());
|
---|
[3640] | 258 | Main.main.undoRedo.add(
|
---|
| 259 | new SequenceCommand(
|
---|
| 260 | title1 + " " + title2,
|
---|
| 261 | commands
|
---|
| 262 | ));
|
---|
| 263 | }
|
---|
| 264 |
|
---|
[1169] | 265 | }
|
---|
[750] | 266 |
|
---|
[2305] | 267 | @Override public void pasteBufferChanged(PrimitiveDeepCopy newPasteBuffer) {
|
---|
[1847] | 268 | updateEnabledState();
|
---|
[1169] | 269 | }
|
---|
[750] | 270 |
|
---|
[1820] | 271 | @Override
|
---|
| 272 | protected void updateEnabledState() {
|
---|
| 273 | if (getCurrentDataSet() == null || Main.pasteBuffer == null) {
|
---|
| 274 | setEnabled(false);
|
---|
| 275 | return;
|
---|
| 276 | }
|
---|
[2008] | 277 | setEnabled(
|
---|
| 278 | !getCurrentDataSet().getSelected().isEmpty()
|
---|
[2305] | 279 | && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
|
---|
[2008] | 280 | );
|
---|
[1169] | 281 | }
|
---|
[2256] | 282 |
|
---|
| 283 | @Override
|
---|
| 284 | protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
|
---|
| 285 | setEnabled(
|
---|
| 286 | selection!= null && !selection.isEmpty()
|
---|
[2305] | 287 | && !TagCollection.unionOfAllPrimitives(Main.pasteBuffer.getDirectlyAdded()).isEmpty()
|
---|
[2256] | 288 | );
|
---|
| 289 | }
|
---|
[626] | 290 | }
|
---|