source: josm/trunk/src/org/openstreetmap/josm/io/remotecontrol/AddTagsDialog.java@ 11184

Last change on this file since 11184 was 11184, checked in by Don-vip, 7 years ago

fix #13826 - AIOOBE in getToolTipText

  • Property svn:eol-style set to native
File size: 12.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.remotecontrol;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Font;
9import java.awt.GridBagLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseEvent;
13import java.util.Collection;
14import java.util.HashMap;
15import java.util.HashSet;
16import java.util.Map;
17import java.util.Map.Entry;
18import java.util.Set;
19
20import javax.swing.AbstractAction;
21import javax.swing.JCheckBox;
22import javax.swing.JPanel;
23import javax.swing.JTable;
24import javax.swing.KeyStroke;
25import javax.swing.table.DefaultTableModel;
26import javax.swing.table.TableCellEditor;
27import javax.swing.table.TableCellRenderer;
28import javax.swing.table.TableModel;
29
30import org.openstreetmap.josm.Main;
31import org.openstreetmap.josm.command.ChangePropertyCommand;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.gui.ExtendedDialog;
34import org.openstreetmap.josm.gui.util.GuiHelper;
35import org.openstreetmap.josm.gui.util.TableHelper;
36import org.openstreetmap.josm.tools.GBC;
37import org.openstreetmap.josm.tools.Utils;
38
39/**
40 * Dialog to add tags as part of the remotecontrol.
41 * Existing Keys get grey color and unchecked selectboxes so they will not overwrite the old Key-Value-Pairs by default.
42 * You can choose the tags you want to add by selectboxes. You can edit the tags before you apply them.
43 * @author master
44 * @since 3850
45 */
46public class AddTagsDialog extends ExtendedDialog {
47
48 private final JTable propertyTable;
49 private final transient Collection<? extends OsmPrimitive> sel;
50 private final int[] count;
51
52 private final String sender;
53 private static final Set<String> trustedSenders = new HashSet<>();
54
55 /**
56 * Class for displaying "delete from ... objects" in the table
57 */
58 static class DeleteTagMarker {
59 private final int num;
60
61 DeleteTagMarker(int num) {
62 this.num = num;
63 }
64
65 @Override
66 public String toString() {
67 return tr("<delete from {0} objects>", num);
68 }
69 }
70
71 /**
72 * Class for displaying list of existing tag values in the table
73 */
74 static class ExistingValues {
75 private final String tag;
76 private final Map<String, Integer> valueCount;
77
78 ExistingValues(String tag) {
79 this.tag = tag;
80 this.valueCount = new HashMap<>();
81 }
82
83 int addValue(String val) {
84 Integer c = valueCount.get(val);
85 int r = c == null ? 1 : (c.intValue()+1);
86 valueCount.put(val, r);
87 return r;
88 }
89
90 @Override
91 public String toString() {
92 StringBuilder sb = new StringBuilder();
93 for (String k: valueCount.keySet()) {
94 if (sb.length() > 0) sb.append(", ");
95 sb.append(k);
96 }
97 return sb.toString();
98 }
99
100 private String getToolTip() {
101 StringBuilder sb = new StringBuilder(64);
102 sb.append("<html>")
103 .append(tr("Old values of"))
104 .append(" <b>")
105 .append(tag)
106 .append("</b><br/>");
107 for (Entry<String, Integer> e : valueCount.entrySet()) {
108 sb.append("<b>")
109 .append(e.getValue())
110 .append(" x </b>")
111 .append(e.getKey())
112 .append("<br/>");
113 }
114 sb.append("</html>");
115 return sb.toString();
116 }
117 }
118
119 /**
120 * Constructs a new {@code AddTagsDialog}.
121 * @param tags tags to add
122 * @param senderName String for skipping confirmations. Use empty string for always confirmed adding.
123 * @param primitives OSM objects that will be modified
124 */
125 public AddTagsDialog(String[][] tags, String senderName, Collection<? extends OsmPrimitive> primitives) {
126 super(Main.parent, tr("Add tags to selected objects"), new String[] {tr("Add selected tags"), tr("Add all tags"), tr("Cancel")},
127 false,
128 true);
129 setToolTipTexts(new String[]{tr("Add checked tags to selected objects"), tr("Shift+Enter: Add all tags to selected objects"), ""});
130
131 this.sender = senderName;
132
133 final DefaultTableModel tm = new DefaultTableModel(new String[] {tr("Assume"), tr("Key"), tr("Value"), tr("Existing values")},
134 tags.length) {
135 private final Class<?>[] types = {Boolean.class, String.class, Object.class, ExistingValues.class};
136 @Override
137 public Class<?> getColumnClass(int c) {
138 return types[c];
139 }
140 };
141
142 sel = primitives;
143 count = new int[tags.length];
144
145 for (int i = 0; i < tags.length; i++) {
146 count[i] = 0;
147 String key = tags[i][0];
148 String value = tags[i][1], oldValue;
149 Boolean b = Boolean.TRUE;
150 ExistingValues old = new ExistingValues(key);
151 for (OsmPrimitive osm : sel) {
152 oldValue = osm.get(key);
153 if (oldValue != null) {
154 old.addValue(oldValue);
155 if (!oldValue.equals(value)) {
156 b = Boolean.FALSE;
157 count[i]++;
158 }
159 }
160 }
161 tm.setValueAt(b, i, 0);
162 tm.setValueAt(tags[i][0], i, 1);
163 tm.setValueAt(tags[i][1].isEmpty() ? new DeleteTagMarker(count[i]) : tags[i][1], i, 2);
164 tm.setValueAt(old, i, 3);
165 }
166
167 propertyTable = new JTable(tm) {
168
169 @Override
170 public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
171 Component c = super.prepareRenderer(renderer, row, column);
172 if (count[row] > 0) {
173 c.setFont(c.getFont().deriveFont(Font.ITALIC));
174 c.setForeground(new Color(100, 100, 100));
175 } else {
176 c.setFont(c.getFont().deriveFont(Font.PLAIN));
177 c.setForeground(new Color(0, 0, 0));
178 }
179 return c;
180 }
181
182 @Override
183 public TableCellEditor getCellEditor(int row, int column) {
184 Object value = getValueAt(row, column);
185 if (value instanceof DeleteTagMarker) return null;
186 if (value instanceof ExistingValues) return null;
187 return getDefaultEditor(value.getClass());
188 }
189
190 @Override
191 public String getToolTipText(MouseEvent event) {
192 int r = rowAtPoint(event.getPoint());
193 int c = columnAtPoint(event.getPoint());
194 if (r < 0 || c < 0) {
195 return getToolTipText();
196 }
197 Object o = getValueAt(r, c);
198 if (c == 1 || c == 2) return o.toString();
199 if (c == 3) return ((ExistingValues) o).getToolTip();
200 return tr("Enable the checkbox to accept the value");
201 }
202 };
203
204 propertyTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
205 // a checkbox has a size of 15 px
206 propertyTable.getColumnModel().getColumn(0).setMaxWidth(15);
207 TableHelper.adjustColumnWidth(propertyTable, 1, 150);
208 TableHelper.adjustColumnWidth(propertyTable, 2, 400);
209 TableHelper.adjustColumnWidth(propertyTable, 3, 300);
210 // get edit results if the table looses the focus, for example if a user clicks "add tags"
211 propertyTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
212 propertyTable.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.SHIFT_MASK), "shiftenter");
213 propertyTable.getActionMap().put("shiftenter", new AbstractAction() {
214 @Override public void actionPerformed(ActionEvent e) {
215 buttonAction(1, e); // add all tags on Shift-Enter
216 }
217 });
218
219 // set the content of this AddTagsDialog consisting of the tableHeader and the table itself.
220 JPanel tablePanel = new JPanel(new GridBagLayout());
221 tablePanel.add(propertyTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
222 tablePanel.add(propertyTable, GBC.eol().fill(GBC.BOTH));
223 if (!sender.isEmpty() && !trustedSenders.contains(sender)) {
224 final JCheckBox c = new JCheckBox();
225 c.setAction(new AbstractAction(tr("Accept all tags from {0} for this session", sender)) {
226 @Override public void actionPerformed(ActionEvent e) {
227 if (c.isSelected())
228 trustedSenders.add(sender);
229 else
230 trustedSenders.remove(sender);
231 }
232 });
233 tablePanel.add(c, GBC.eol().insets(20, 10, 0, 0));
234 }
235 setContent(tablePanel);
236 setDefaultButton(2);
237 }
238
239 /**
240 * If you click the "Add tags" button build a ChangePropertyCommand for every key that has a checked checkbox
241 * to apply the key value pair to all selected osm objects.
242 * You get a entry for every key in the command queue.
243 */
244 @Override
245 protected void buttonAction(int buttonIndex, ActionEvent evt) {
246 // if layer all layers were closed, ignore all actions
247 if (Main.getLayerManager().getEditDataSet() != null && buttonIndex != 2) {
248 TableModel tm = propertyTable.getModel();
249 for (int i = 0; i < tm.getRowCount(); i++) {
250 if (buttonIndex == 1 || (Boolean) tm.getValueAt(i, 0)) {
251 String key = (String) tm.getValueAt(i, 1);
252 Object value = tm.getValueAt(i, 2);
253 Main.main.undoRedo.add(new ChangePropertyCommand(sel,
254 key, value instanceof String ? (String) value : ""));
255 }
256 }
257 }
258 if (buttonIndex == 2) {
259 trustedSenders.remove(sender);
260 }
261 setVisible(false);
262 }
263
264 /**
265 * parse addtags parameters Example URL (part):
266 * addtags=wikipedia:de%3DResidenzschloss Dresden|name:en%3DDresden Castle
267 * @param args request arguments
268 * @param sender is a string for skipping confirmations. Use empty string for always confirmed adding.
269 * @param primitives OSM objects that will be modified
270 */
271 public static void addTags(final Map<String, String> args, final String sender, final Collection<? extends OsmPrimitive> primitives) {
272 if (args.containsKey("addtags")) {
273 GuiHelper.executeByMainWorkerInEDT(() -> {
274 Set<String> tagSet = new HashSet<>();
275 for (String tag1 : Utils.decodeUrl(args.get("addtags")).split("\\|")) {
276 if (!tag1.trim().isEmpty() && tag1.contains("=")) {
277 tagSet.add(tag1.trim());
278 }
279 }
280 if (!tagSet.isEmpty()) {
281 String[][] keyValue = new String[tagSet.size()][2];
282 int i = 0;
283 for (String tag2 : tagSet) {
284 // support a = b===c as "a"="b===c"
285 String[] pair = tag2.split("\\s*=\\s*", 2);
286 keyValue[i][0] = pair[0];
287 keyValue[i][1] = pair.length < 2 ? "" : pair[1];
288 i++;
289 }
290 addTags(keyValue, sender, primitives);
291 }
292 });
293 }
294 }
295
296 /**
297 * Ask user and add the tags he confirm.
298 * @param keyValue is a table or {{tag1,val1},{tag2,val2},...}
299 * @param sender is a string for skipping confirmations. Use empty string for always confirmed adding.
300 * @param primitives OSM objects that will be modified
301 * @since 7521
302 */
303 public static void addTags(String[][] keyValue, String sender, Collection<? extends OsmPrimitive> primitives) {
304 if (trustedSenders.contains(sender)) {
305 if (Main.getLayerManager().getEditDataSet() != null) {
306 for (String[] row : keyValue) {
307 Main.main.undoRedo.add(new ChangePropertyCommand(primitives, row[0], row[1]));
308 }
309 }
310 } else {
311 new AddTagsDialog(keyValue, sender, primitives).showDialog();
312 }
313 }
314}
Note: See TracBrowser for help on using the repository browser.