source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/properties/PropertiesDialog.java@ 5425

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

see #7671, see #7951 - prevents unwanted line breaks with values composed of several words

  • Property svn:eol-style set to native
File size: 70.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs.properties;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Cursor;
10import java.awt.Dialog.ModalityType;
11import java.awt.Dimension;
12import java.awt.FlowLayout;
13import java.awt.Font;
14import java.awt.GridBagConstraints;
15import java.awt.GridBagLayout;
16import java.awt.Point;
17import java.awt.Toolkit;
18import java.awt.datatransfer.Clipboard;
19import java.awt.datatransfer.Transferable;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.FocusAdapter;
23import java.awt.event.FocusEvent;
24import java.awt.event.KeyEvent;
25import java.awt.event.MouseAdapter;
26import java.awt.event.MouseEvent;
27import java.awt.image.BufferedImage;
28import java.net.HttpURLConnection;
29import java.net.URI;
30import java.net.URLEncoder;
31import java.util.ArrayList;
32import java.util.Arrays;
33import java.util.Collection;
34import java.util.Collections;
35import java.util.Comparator;
36import java.util.EnumSet;
37import java.util.HashMap;
38import java.util.HashSet;
39import java.util.Iterator;
40import java.util.LinkedHashMap;
41import java.util.LinkedList;
42import java.util.List;
43import java.util.Map;
44import java.util.Map.Entry;
45import java.util.Set;
46import java.util.TreeMap;
47import java.util.TreeSet;
48import java.util.Vector;
49
50import javax.swing.AbstractAction;
51import javax.swing.Action;
52import javax.swing.Box;
53import javax.swing.DefaultListCellRenderer;
54import javax.swing.ImageIcon;
55import javax.swing.JComboBox;
56import javax.swing.JComponent;
57import javax.swing.JDialog;
58import javax.swing.JLabel;
59import javax.swing.JList;
60import javax.swing.JMenuItem;
61import javax.swing.JOptionPane;
62import javax.swing.JPanel;
63import javax.swing.JPopupMenu;
64import javax.swing.JScrollPane;
65import javax.swing.JTable;
66import javax.swing.KeyStroke;
67import javax.swing.ListSelectionModel;
68import javax.swing.event.ListSelectionEvent;
69import javax.swing.event.ListSelectionListener;
70import javax.swing.event.PopupMenuListener;
71import javax.swing.table.DefaultTableCellRenderer;
72import javax.swing.table.DefaultTableModel;
73import javax.swing.table.TableColumnModel;
74import javax.swing.table.TableModel;
75import javax.swing.text.JTextComponent;
76
77import org.openstreetmap.josm.Main;
78import org.openstreetmap.josm.actions.JosmAction;
79import org.openstreetmap.josm.actions.mapmode.DrawAction;
80import org.openstreetmap.josm.actions.search.SearchAction.SearchMode;
81import org.openstreetmap.josm.actions.search.SearchAction.SearchSetting;
82import org.openstreetmap.josm.command.ChangeCommand;
83import org.openstreetmap.josm.command.ChangePropertyCommand;
84import org.openstreetmap.josm.command.Command;
85import org.openstreetmap.josm.command.SequenceCommand;
86import org.openstreetmap.josm.data.SelectionChangedListener;
87import org.openstreetmap.josm.data.osm.DataSet;
88import org.openstreetmap.josm.data.osm.IRelation;
89import org.openstreetmap.josm.data.osm.Node;
90import org.openstreetmap.josm.data.osm.OsmPrimitive;
91import org.openstreetmap.josm.data.osm.Relation;
92import org.openstreetmap.josm.data.osm.RelationMember;
93import org.openstreetmap.josm.data.osm.Tag;
94import org.openstreetmap.josm.data.osm.Way;
95import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
96import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
97import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
98import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
99import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
100import org.openstreetmap.josm.gui.DefaultNameFormatter;
101import org.openstreetmap.josm.gui.ExtendedDialog;
102import org.openstreetmap.josm.gui.MapFrame;
103import org.openstreetmap.josm.gui.MapView;
104import org.openstreetmap.josm.gui.SideButton;
105import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
106import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel.PresetHandler;
107import org.openstreetmap.josm.gui.dialogs.relation.DownloadRelationMemberTask;
108import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
109import org.openstreetmap.josm.gui.layer.OsmDataLayer;
110import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
111import org.openstreetmap.josm.gui.tagging.TaggingPreset;
112import org.openstreetmap.josm.gui.tagging.TaggingPreset.PresetType;
113import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
114import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
115import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
116import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
117import org.openstreetmap.josm.tools.GBC;
118import org.openstreetmap.josm.tools.ImageProvider;
119import org.openstreetmap.josm.tools.InputMapUtils;
120import org.openstreetmap.josm.tools.LanguageInfo;
121import org.openstreetmap.josm.tools.OpenBrowser;
122import org.openstreetmap.josm.tools.Shortcut;
123import org.openstreetmap.josm.tools.Utils;
124
125/**
126 * This dialog displays the properties of the current selected primitives.
127 *
128 * If no object is selected, the dialog list is empty.
129 * If only one is selected, all properties of this object are selected.
130 * If more than one object are selected, the sum of all properties are displayed. If the
131 * different objects share the same property, the shared value is displayed. If they have
132 * different values, all of them are put in a combo box and the string "<different>"
133 * is displayed in italic.
134 *
135 * Below the list, the user can click on an add, modify and delete property button to
136 * edit the table selection value.
137 *
138 * The command is applied to all selected entries.
139 *
140 * @author imi
141 */
142public class PropertiesDialog extends ToggleDialog implements SelectionChangedListener, MapView.EditLayerChangeListener, DataSetListenerAdapter.Listener {
143 /**
144 * Watches for mouse clicks
145 * @author imi
146 */
147 public class MouseClickWatch extends MouseAdapter {
148 @Override public void mouseClicked(MouseEvent e) {
149 if (e.getClickCount() < 2)
150 {
151 // single click, clear selection in other table not clicked in
152 if (e.getSource() == propertyTable) {
153 membershipTable.clearSelection();
154 } else if (e.getSource() == membershipTable) {
155 propertyTable.clearSelection();
156 }
157 }
158 // double click, edit or add property
159 else if (e.getSource() == propertyTable)
160 {
161 int row = propertyTable.rowAtPoint(e.getPoint());
162 if (row > -1) {
163 editProperty(row);
164 } else {
165 addProperty();
166 }
167 } else if (e.getSource() == membershipTable) {
168 int row = membershipTable.rowAtPoint(e.getPoint());
169 if (row > -1) {
170 editMembership(row);
171 }
172 }
173 else
174 {
175 addProperty();
176 }
177 }
178 @Override public void mousePressed(MouseEvent e) {
179 if (e.getSource() == propertyTable) {
180 membershipTable.clearSelection();
181 } else if (e.getSource() == membershipTable) {
182 propertyTable.clearSelection();
183 }
184 }
185 }
186
187 // hook for roadsigns plugin to display a small
188 // button in the upper right corner of this dialog
189 public static final JPanel pluginHook = new JPanel();
190
191 private JPopupMenu propertyMenu;
192 private JPopupMenu membershipMenu;
193
194 private final Map<String, Map<String, Integer>> valueCount = new TreeMap<String, Map<String, Integer>>();
195
196 Comparator<AutoCompletionListItem> defaultACItemComparator = new Comparator<AutoCompletionListItem>() {
197 public int compare(AutoCompletionListItem o1, AutoCompletionListItem o2) {
198 return String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
199 }
200 };
201
202 private DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
203 private HelpAction helpAction = new HelpAction();
204 private CopyValueAction copyValueAction = new CopyValueAction();
205 private CopyKeyValueAction copyKeyValueAction = new CopyKeyValueAction();
206 private CopyAllKeyValueAction copyAllKeyValueAction = new CopyAllKeyValueAction();
207 private SearchAction searchActionSame = new SearchAction(true);
208 private SearchAction searchActionAny = new SearchAction(false);
209 private AddAction addAction = new AddAction();
210 private EditAction editAction = new EditAction();
211 private DeleteAction deleteAction = new DeleteAction();
212
213 @Override
214 public void showNotify() {
215 DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
216 SelectionEventManager.getInstance().addSelectionListener(this, FireMode.IN_EDT_CONSOLIDATED);
217 MapView.addEditLayerChangeListener(this);
218 Main.registerActionShortcut(addAction);
219 Main.registerActionShortcut(editAction);
220 Main.registerActionShortcut(deleteAction);
221 updateSelection();
222 }
223
224 @Override
225 public void hideNotify() {
226 DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
227 SelectionEventManager.getInstance().removeSelectionListener(this);
228 MapView.removeEditLayerChangeListener(this);
229 Main.unregisterActionShortcut(addAction);
230 Main.unregisterActionShortcut(editAction);
231 Main.unregisterActionShortcut(deleteAction);
232 }
233
234 /**
235 * Edit the value in the properties table row
236 * @param row The row of the table from which the value is edited.
237 */
238 @SuppressWarnings("unchecked")
239 private void editProperty(int row) {
240 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
241 if (sel.isEmpty()) return;
242
243 String key = propertyData.getValueAt(row, 0).toString();
244 objKey=key;
245
246 String msg = "<html>"+trn("This will change {0} object.",
247 "This will change up to {0} objects.", sel.size(), sel.size())
248 +"<br><br>("+tr("An empty value deletes the tag.", key)+")</html>";
249
250 JPanel panel = new JPanel(new BorderLayout());
251 panel.add(new JLabel(msg), BorderLayout.NORTH);
252
253 JPanel p = new JPanel(new GridBagLayout());
254 panel.add(p, BorderLayout.CENTER);
255
256 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
257 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
258 Collections.sort(keyList, defaultACItemComparator);
259
260 final AutoCompletingComboBox keys = new AutoCompletingComboBox();
261 keys.setPossibleACItems(keyList);
262 keys.setEditable(true);
263 keys.setSelectedItem(key);
264
265 p.add(new JLabel(tr("Key")), GBC.std());
266 p.add(Box.createHorizontalStrut(10), GBC.std());
267 p.add(keys, GBC.eol().fill(GBC.HORIZONTAL));
268
269 final AutoCompletingComboBox values = new AutoCompletingComboBox();
270 values.setRenderer(new DefaultListCellRenderer() {
271 @Override public Component getListCellRendererComponent(JList list,
272 Object value, int index, boolean isSelected, boolean cellHasFocus){
273 Component c = super.getListCellRendererComponent(list, value,
274 index, isSelected, cellHasFocus);
275 if (c instanceof JLabel) {
276 String str = ((AutoCompletionListItem) value).getValue();
277 if (valueCount.containsKey(objKey)) {
278 Map<String, Integer> m = valueCount.get(objKey);
279 if (m.containsKey(str)) {
280 str = tr("{0} ({1})", str, m.get(str));
281 c.setFont(c.getFont().deriveFont(Font.ITALIC + Font.BOLD));
282 }
283 }
284 ((JLabel) c).setText(str);
285 }
286 return c;
287 }
288 });
289 values.setEditable(true);
290
291 final Map<String, Integer> m = (Map<String, Integer>) propertyData.getValueAt(row, 1);
292
293 Comparator<AutoCompletionListItem> usedValuesAwareComparator = new Comparator<AutoCompletionListItem>() {
294
295 @Override
296 public int compare(AutoCompletionListItem o1, AutoCompletionListItem o2) {
297 boolean c1 = m.containsKey(o1.getValue());
298 boolean c2 = m.containsKey(o2.getValue());
299 if (c1 == c2)
300 return String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
301 else if (c1)
302 return -1;
303 else
304 return +1;
305 }
306 };
307
308 List<AutoCompletionListItem> valueList = autocomplete.getValues(getAutocompletionKeys(key));
309 Collections.sort(valueList, usedValuesAwareComparator);
310
311 values.setPossibleACItems(valueList);
312 final String selection= m.size()!=1?tr("<different>"):m.entrySet().iterator().next().getKey();
313 values.setSelectedItem(selection);
314 values.getEditor().setItem(selection);
315 p.add(new JLabel(tr("Value")), GBC.std());
316 p.add(Box.createHorizontalStrut(10), GBC.std());
317 p.add(values, GBC.eol().fill(GBC.HORIZONTAL));
318 addFocusAdapter(row, keys, values, autocomplete, usedValuesAwareComparator);
319
320 final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
321 @Override public void selectInitialValue() {
322 // save unix system selection (middle mouse paste)
323 Clipboard sysSel = Toolkit.getDefaultToolkit().getSystemSelection();
324 if(sysSel != null) {
325 Transferable old = sysSel.getContents(null);
326 values.requestFocusInWindow();
327 values.getEditor().selectAll();
328 sysSel.setContents(old, null);
329 } else {
330 values.requestFocusInWindow();
331 values.getEditor().selectAll();
332 }
333 }
334 };
335 final JDialog dlg = optionPane.createDialog(Main.parent, trn("Change value?", "Change values?", m.size()));
336 dlg.setModalityType(ModalityType.DOCUMENT_MODAL);
337 Dimension dlgSize = dlg.getSize();
338 if(dlgSize.width > Main.parent.getSize().width) {
339 dlgSize.width = Math.max(250, Main.parent.getSize().width);
340 dlg.setSize(dlgSize);
341 }
342 dlg.setLocationRelativeTo(Main.parent);
343 values.getEditor().addActionListener(new ActionListener() {
344 public void actionPerformed(ActionEvent e) {
345 dlg.setVisible(false);
346 optionPane.setValue(JOptionPane.OK_OPTION);
347 }
348 });
349
350 String oldValue = values.getEditor().getItem().toString();
351 dlg.setVisible(true);
352
353 Object answer = optionPane.getValue();
354 if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE ||
355 (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) {
356 values.getEditor().setItem(oldValue);
357 return;
358 }
359
360 String value = values.getEditor().getItem().toString().trim();
361 // is not Java 1.5
362 //value = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFC);
363 if (value.equals("")) {
364 value = null; // delete the key
365 }
366 String newkey = keys.getEditor().getItem().toString().trim();
367 //newkey = java.text.Normalizer.normalize(newkey, java.text.Normalizer.Form.NFC);
368 if (newkey.equals("")) {
369 newkey = key;
370 value = null; // delete the key instead
371 }
372 if (key.equals(newkey) && tr("<different>").equals(value))
373 return;
374 if (key.equals(newkey) || value == null) {
375 Main.main.undoRedo.add(new ChangePropertyCommand(sel, newkey, value));
376 } else {
377 for (OsmPrimitive osm: sel) {
378 if(osm.get(newkey) != null) {
379 ExtendedDialog ed = new ExtendedDialog(
380 Main.parent,
381 tr("Overwrite key"),
382 new String[]{tr("Replace"), tr("Cancel")});
383 ed.setButtonIcons(new String[]{"purge", "cancel"});
384 ed.setContent(tr("You changed the key from ''{0}'' to ''{1}''.\n"
385 + "The new key is already used, overwrite values?", key, newkey));
386 ed.setCancelButton(2);
387 ed.toggleEnable("overwriteEditKey");
388 ed.showDialog();
389
390 if (ed.getValue() != 1)
391 return;
392 break;
393 }
394 }
395 Collection<Command> commands=new Vector<Command>();
396 commands.add(new ChangePropertyCommand(sel, key, null));
397 if (value.equals(tr("<different>"))) {
398 HashMap<String, Vector<OsmPrimitive>> map=new HashMap<String, Vector<OsmPrimitive>>();
399 for (OsmPrimitive osm: sel) {
400 String val=osm.get(key);
401 if(val != null)
402 {
403 if (map.containsKey(val)) {
404 map.get(val).add(osm);
405 } else {
406 Vector<OsmPrimitive> v = new Vector<OsmPrimitive>();
407 v.add(osm);
408 map.put(val, v);
409 }
410 }
411 }
412 for (Entry<String, Vector<OsmPrimitive>> e: map.entrySet()) {
413 commands.add(new ChangePropertyCommand(e.getValue(), newkey, e.getKey()));
414 }
415 } else {
416 commands.add(new ChangePropertyCommand(sel, newkey, value));
417 }
418 Main.main.undoRedo.add(new SequenceCommand(
419 trn("Change properties of up to {0} object",
420 "Change properties of up to {0} objects", sel.size(), sel.size()),
421 commands));
422 }
423
424 if(!key.equals(newkey)) {
425 for(int i=0; i < propertyTable.getRowCount(); i++)
426 if(propertyData.getValueAt(i, 0).toString().equals(newkey)) {
427 row=i;
428 break;
429 }
430 }
431 propertyTable.changeSelection(row, 0, false, false);
432 }
433
434 /**
435 * For a given key k, return a list of keys which are used as keys for
436 * auto-completing values to increase the search space.
437 * @param key the key k
438 * @return a list of keys
439 */
440 private static List<String> getAutocompletionKeys(String key) {
441 if ("name".equals(key) || "addr:street".equals(key))
442 return Arrays.asList("addr:street", "name");
443 else
444 return Arrays.asList(key);
445 }
446
447 /**
448 * This simply fires up an relation editor for the relation shown; everything else
449 * is the editor's business.
450 *
451 * @param row
452 */
453 private void editMembership(int row) {
454 Relation relation = (Relation)membershipData.getValueAt(row, 0);
455 Main.map.relationListDialog.selectRelation(relation);
456 RelationEditor.getEditor(
457 Main.map.mapView.getEditLayer(),
458 relation,
459 ((MemberInfo) membershipData.getValueAt(row, 1)).role).setVisible(true);
460 }
461
462 private static String lastAddKey = null;
463 private static String lastAddValue = null;
464
465 public static final int DEFAULT_LRU_TAGS_NUMBER = 5;
466 public static final int MAX_LRU_TAGS_NUMBER = 9;
467
468 // LRU cache for recently added tags (http://java-planet.blogspot.com/2005/08/how-to-set-up-simple-lru-cache-using.html)
469 private static final Map<Tag, Void> recentTags = new LinkedHashMap<Tag, Void>(MAX_LRU_TAGS_NUMBER+1, 1.1f, true) {
470 @Override
471 protected boolean removeEldestEntry(Entry<Tag, Void> eldest) {
472 return size() > MAX_LRU_TAGS_NUMBER;
473 }
474 };
475
476 /**
477 * Open the add selection dialog and add a new key/value to the table (and
478 * to the dataset, of course).
479 */
480 private void addProperty() {
481 Collection<OsmPrimitive> sel;
482 if (Main.map.mapMode instanceof DrawAction) {
483 sel = ((DrawAction) Main.map.mapMode).getInProgressSelection();
484 } else {
485 DataSet ds = Main.main.getCurrentDataSet();
486 if (ds == null) return;
487 sel = ds.getSelected();
488 }
489 if (sel.isEmpty()) return;
490
491 JPanel p = new JPanel(new GridBagLayout());
492 p.add(new JLabel("<html>"+trn("This will change up to {0} object.",
493 "This will change up to {0} objects.", sel.size(),sel.size())
494 +"<br><br>"+tr("Please select a key")), GBC.eol().fill(GBC.HORIZONTAL));
495 final AutoCompletingComboBox keys = new AutoCompletingComboBox();
496 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
497 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
498
499 AutoCompletionListItem itemToSelect = null;
500 // remove the object's tag keys from the list
501 Iterator<AutoCompletionListItem> iter = keyList.iterator();
502 while (iter.hasNext()) {
503 AutoCompletionListItem item = iter.next();
504 if (item.getValue().equals(lastAddKey)) {
505 itemToSelect = item;
506 }
507 for (int i = 0; i < propertyData.getRowCount(); ++i) {
508 if (item.getValue().equals(propertyData.getValueAt(i, 0))) {
509 if (itemToSelect == item) {
510 itemToSelect = null;
511 }
512 iter.remove();
513 break;
514 }
515 }
516 }
517
518 Collections.sort(keyList, defaultACItemComparator);
519 keys.setPossibleACItems(keyList);
520 keys.setEditable(true);
521
522 p.add(keys, GBC.eop().fill());
523
524 p.add(new JLabel(tr("Please select a value")), GBC.eol());
525 final AutoCompletingComboBox values = new AutoCompletingComboBox();
526 values.setEditable(true);
527 p.add(values, GBC.eop().fill());
528 if (itemToSelect != null) {
529 keys.setSelectedItem(itemToSelect);
530 /* don't add single chars, as they are no properly selected */
531 if(lastAddValue != null && lastAddValue.length() > 1) {
532 values.setSelectedItem(lastAddValue);
533 }
534 }
535
536 FocusAdapter focus = addFocusAdapter(-1, keys, values, autocomplete, defaultACItemComparator);
537 // fire focus event in advance or otherwise the popup list will be too small at first
538 focus.focusGained(null);
539
540 int recentTagsToShow = Main.pref.getInteger("properties.recently-added-tags", DEFAULT_LRU_TAGS_NUMBER);
541 if (recentTagsToShow > MAX_LRU_TAGS_NUMBER) {
542 recentTagsToShow = MAX_LRU_TAGS_NUMBER;
543 }
544 List<JosmAction> recentTagsActions = new ArrayList<JosmAction>();
545 suggestRecentlyAddedTags(p, keys, values, recentTagsActions, recentTagsToShow, focus);
546
547 JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
548 @Override public void selectInitialValue() {
549 // save unix system selection (middle mouse paste)
550 Clipboard sysSel = Toolkit.getDefaultToolkit().getSystemSelection();
551 if(sysSel != null) {
552 Transferable old = sysSel.getContents(null);
553 keys.requestFocusInWindow();
554 keys.getEditor().selectAll();
555 sysSel.setContents(old, null);
556 } else {
557 keys.requestFocusInWindow();
558 keys.getEditor().selectAll();
559 }
560 }
561 };
562 JDialog dialog = pane.createDialog(Main.parent, tr("Add value?"));
563 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
564 dialog.setVisible(true);
565
566 for (JosmAction action : recentTagsActions) {
567 action.destroy();
568 }
569
570 if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
571 return;
572 String key = keys.getEditor().getItem().toString().trim();
573 String value = values.getEditor().getItem().toString().trim();
574 if (key.isEmpty() || value.isEmpty())
575 return;
576 lastAddKey = key;
577 lastAddValue = value;
578 recentTags.put(new Tag(key, value), null);
579 Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, value));
580 btnAdd.requestFocusInWindow();
581 }
582
583 private void suggestRecentlyAddedTags(JPanel p, final AutoCompletingComboBox keys, final AutoCompletingComboBox values, List<JosmAction> tagsActions, int tagsToShow, final FocusAdapter focus) {
584 if (tagsToShow > 0 && !recentTags.isEmpty()) {
585 p.add(new JLabel(tr("Recently added tags")), GBC.eol());
586
587 int count = 1;
588 // We store the maximum number (9) of recent tags to allow dynamic change of number of tags shown in the preferences.
589 // This implies to iterate in descending order, as the oldest elements will only be removed after we reach the maximum numbern and not the number of tags to show.
590 // However, as Set does not allow to iterate in descending order, we need to copy its elements into a List we can access in reverse order.
591 List<Tag> tags = new LinkedList<Tag>(recentTags.keySet());
592 for (int i = tags.size()-1; i >= 0 && count <= tagsToShow; i--, count++) {
593 final Tag t = tags.get(i);
594 // Find and display icon
595 ImageIcon icon = MapPaintStyles.getNodeIcon(t, false); // Filters deprecated icon
596 if (icon == null) {
597 icon = new ImageIcon(new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB));
598 }
599 GridBagConstraints gbc = new GridBagConstraints();
600 gbc.ipadx = 5;
601 p.add(new JLabel(icon), gbc);
602 // Create action for reusing the tag, with keyboard shortcut Ctrl+(1-5)
603 String actionShortcutKey = "properties:recent:"+count;
604 Shortcut sc = Shortcut.registerShortcut(actionShortcutKey, null, KeyEvent.VK_0+count, Shortcut.CTRL);
605 final JosmAction action = new JosmAction(actionShortcutKey, null, tr("Use this tag again"), sc, false) {
606 @Override
607 public void actionPerformed(ActionEvent e) {
608 keys.getEditor().setItem(t.getKey());
609 values.getEditor().setItem(t.getValue());
610 // Update list of values (fix #7951)
611 focus.focusGained(null);
612 }
613 };
614 p.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), actionShortcutKey);
615 p.getActionMap().put(actionShortcutKey, action);
616 tagsActions.add(action);
617 // Display clickable tag
618 final JLabel tagLabel = new JLabel("<html>"
619 + "<style>td{border:1px solid gray; font-weight:normal;}</style>"
620 + "<table><tr><td>" + t.toString() + "</td></tr></table></html>");
621 tagLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
622 tagLabel.setToolTipText((String) action.getValue(Action.SHORT_DESCRIPTION));
623 tagLabel.addMouseListener(new MouseAdapter() {
624 @Override
625 public void mouseClicked(MouseEvent e) {
626 action.actionPerformed(null);
627 }
628 });
629 JPanel tagPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
630 tagPanel.add(tagLabel);
631 p.add(tagPanel, GBC.eol());
632 }
633 }
634 }
635
636 /**
637 * @param allData
638 * @param keys
639 * @param values
640 */
641 private FocusAdapter addFocusAdapter(final int row,
642 final AutoCompletingComboBox keys, final AutoCompletingComboBox values,
643 final AutoCompletionManager autocomplete, final Comparator<AutoCompletionListItem> comparator) {
644 // get the combo box' editor component
645 JTextComponent editor = (JTextComponent)values.getEditor()
646 .getEditorComponent();
647 // Refresh the values model when focus is gained
648 FocusAdapter focus = new FocusAdapter() {
649 @Override public void focusGained(FocusEvent e) {
650 String key = keys.getEditor().getItem().toString();
651
652 List<AutoCompletionListItem> valueList = autocomplete.getValues(getAutocompletionKeys(key));
653 Collections.sort(valueList, comparator);
654
655 values.setPossibleACItems(valueList);
656 objKey=key;
657 }
658 };
659 editor.addFocusListener(focus);
660 return focus;
661 }
662 private String objKey;
663
664 /**
665 * The property data.
666 */
667 private final DefaultTableModel propertyData = new DefaultTableModel() {
668 @Override public boolean isCellEditable(int row, int column) {
669 return false;
670 }
671 @Override public Class<?> getColumnClass(int columnIndex) {
672 return String.class;
673 }
674 };
675
676 /**
677 * The membership data.
678 */
679 private final DefaultTableModel membershipData = new DefaultTableModel() {
680 @Override public boolean isCellEditable(int row, int column) {
681 return false;
682 }
683 @Override public Class<?> getColumnClass(int columnIndex) {
684 return String.class;
685 }
686 };
687
688 /**
689 * The properties list.
690 */
691 private final JTable propertyTable = new JTable(propertyData);
692 private final JTable membershipTable = new JTable(membershipData);
693
694 public JComboBox taggingPresets = new JComboBox();
695
696 /**
697 * The Add/Edit/Delete buttons (needed to be able to disable them)
698 */
699 private final SideButton btnAdd;
700 private final SideButton btnEdit;
701 private final SideButton btnDel;
702 private final PresetListPanel presets = new PresetListPanel();
703
704 private final JLabel selectSth = new JLabel("<html><p>"
705 + tr("Select objects for which to change properties.") + "</p></html>");
706
707 static class MemberInfo {
708 List<RelationMember> role = new ArrayList<RelationMember>();
709 List<Integer> position = new ArrayList<Integer>();
710 private String positionString = null;
711 void add(RelationMember r, Integer p) {
712 role.add(r);
713 position.add(p);
714 }
715 String getPositionString() {
716 if (positionString == null) {
717 Collections.sort(position);
718 positionString = String.valueOf(position.get(0));
719 int cnt = 0;
720 int last = position.get(0);
721 for (int i = 1; i < position.size(); ++i) {
722 int cur = position.get(i);
723 if (cur == last + 1) {
724 ++cnt;
725 } else if (cnt == 0) {
726 positionString += "," + String.valueOf(cur);
727 } else {
728 positionString += "-" + String.valueOf(last);
729 positionString += "," + String.valueOf(cur);
730 cnt = 0;
731 }
732 last = cur;
733 }
734 if (cnt >= 1) {
735 positionString += "-" + String.valueOf(last);
736 }
737 }
738 if (positionString.length() > 20) {
739 positionString = positionString.substring(0, 17) + "...";
740 }
741 return positionString;
742 }
743 }
744
745 /**
746 * Create a new PropertiesDialog
747 */
748 public PropertiesDialog(MapFrame mapFrame) {
749 super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
750 Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
751 Shortcut.ALT_SHIFT), 150, true);
752
753 // setting up the properties table
754 propertyMenu = new JPopupMenu();
755 propertyMenu.add(copyValueAction);
756 propertyMenu.add(copyKeyValueAction);
757 propertyMenu.add(copyAllKeyValueAction);
758 propertyMenu.addSeparator();
759 propertyMenu.add(searchActionAny);
760 propertyMenu.add(searchActionSame);
761 propertyMenu.addSeparator();
762 propertyMenu.add(helpAction);
763
764 propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
765 propertyTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
766 propertyTable.getTableHeader().setReorderingAllowed(false);
767 propertyTable.addMouseListener(new PopupMenuLauncher() {
768 @Override
769 public void launch(MouseEvent evt) {
770 Point p = evt.getPoint();
771 int row = propertyTable.rowAtPoint(p);
772 if (row > -1) {
773 propertyTable.changeSelection(row, 0, false, false);
774 propertyMenu.show(propertyTable, p.x, p.y-3);
775 }
776 }
777 });
778
779 propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer(){
780 @Override public Component getTableCellRendererComponent(JTable table, Object value,
781 boolean isSelected, boolean hasFocus, int row, int column) {
782 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
783 if (value == null)
784 return this;
785 if (c instanceof JLabel) {
786 String str = null;
787 if (value instanceof String) {
788 str = (String) value;
789 } else if (value instanceof Map<?, ?>) {
790 Map<?, ?> v = (Map<?, ?>) value;
791 if (v.size() != 1) {
792 str=tr("<different>");
793 c.setFont(c.getFont().deriveFont(Font.ITALIC));
794 } else {
795 final Map.Entry<?, ?> entry = v.entrySet().iterator().next();
796 str = (String) entry.getKey();
797 }
798 }
799 ((JLabel)c).setText(str);
800 }
801 return c;
802 }
803 });
804
805 // setting up the membership table
806 membershipMenu = new JPopupMenu();
807 membershipMenu.add(new SelectRelationAction(true));
808 membershipMenu.add(new SelectRelationAction(false));
809 membershipMenu.add(new SelectRelationMembersAction());
810 membershipMenu.add(new DownloadIncompleteMembersAction());
811 membershipMenu.addSeparator();
812 membershipMenu.add(helpAction);
813
814 membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role"),tr("Position")});
815 membershipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
816 membershipTable.addMouseListener(new PopupMenuLauncher() {
817 @Override
818 public void launch(MouseEvent evt) {
819 Point p = evt.getPoint();
820 int row = membershipTable.rowAtPoint(p);
821 if (row > -1) {
822 membershipTable.changeSelection(row, 0, false, false);
823 Relation relation = (Relation)membershipData.getValueAt(row, 0);
824 for (Component c : membershipMenu.getComponents()) {
825 if (c instanceof JMenuItem) {
826 Action action = ((JMenuItem) c).getAction();
827 if (action instanceof RelationRelated) {
828 ((RelationRelated)action).setRelation(relation);
829 }
830 }
831 }
832 membershipMenu.show(membershipTable, p.x, p.y-3);
833 }
834 }
835 });
836
837 TableColumnModel mod = membershipTable.getColumnModel();
838 membershipTable.getTableHeader().setReorderingAllowed(false);
839 mod.getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
840 @Override public Component getTableCellRendererComponent(JTable table, Object value,
841 boolean isSelected, boolean hasFocus, int row, int column) {
842 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
843 if (value == null)
844 return this;
845 if (c instanceof JLabel) {
846 JLabel label = (JLabel)c;
847 Relation r = (Relation)value;
848 label.setText(r.getDisplayName(DefaultNameFormatter.getInstance()));
849 if (r.isDisabledAndHidden()) {
850 label.setFont(label.getFont().deriveFont(Font.ITALIC));
851 }
852 }
853 return c;
854 }
855 });
856
857 mod.getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
858 @Override public Component getTableCellRendererComponent(JTable table, Object value,
859 boolean isSelected, boolean hasFocus, int row, int column) {
860 if (value == null)
861 return this;
862 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
863 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
864 if (c instanceof JLabel) {
865 JLabel label = (JLabel)c;
866 MemberInfo col = (MemberInfo) value;
867
868 String text = null;
869 for (RelationMember r : col.role) {
870 if (text == null) {
871 text = r.getRole();
872 }
873 else if (!text.equals(r.getRole())) {
874 text = tr("<different>");
875 break;
876 }
877 }
878
879 label.setText(text);
880 if (isDisabledAndHidden) {
881 label.setFont(label.getFont().deriveFont(Font.ITALIC));
882 }
883 }
884 return c;
885 }
886 });
887
888 mod.getColumn(2).setCellRenderer(new DefaultTableCellRenderer() {
889 @Override public Component getTableCellRendererComponent(JTable table, Object value,
890 boolean isSelected, boolean hasFocus, int row, int column) {
891 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
892 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
893 if (c instanceof JLabel) {
894 JLabel label = (JLabel)c;
895 label.setText(((MemberInfo) table.getValueAt(row, 1)).getPositionString());
896 if (isDisabledAndHidden) {
897 label.setFont(label.getFont().deriveFont(Font.ITALIC));
898 }
899 }
900 return c;
901 }
902 });
903 mod.getColumn(2).setPreferredWidth(20);
904 mod.getColumn(1).setPreferredWidth(40);
905 mod.getColumn(0).setPreferredWidth(200);
906
907 // combine both tables and wrap them in a scrollPane
908 JPanel bothTables = new JPanel();
909 boolean top = Main.pref.getBoolean("properties.presets.top", true);
910 bothTables.setLayout(new GridBagLayout());
911 if(top) {
912 bothTables.add(presets, GBC.std().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2).anchor(GBC.NORTHWEST));
913 double epsilon = Double.MIN_VALUE; // need to set a weight or else anchor value is ignored
914 bothTables.add(pluginHook, GBC.eol().insets(0,1,1,1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon));
915 }
916 bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10));
917 bothTables.add(propertyTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
918 bothTables.add(propertyTable, GBC.eol().fill(GBC.BOTH));
919 bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
920 bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
921 if(!top) {
922 bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2));
923 }
924
925 // Open edit dialog whe enter pressed in tables
926 propertyTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
927 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter");
928 propertyTable.getActionMap().put("onTableEnter",editAction);
929 membershipTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
930 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter");
931 membershipTable.getActionMap().put("onTableEnter",editAction);
932
933 // Open add property dialog when INS is pressed in tables
934 propertyTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
935 .put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0),"onTableInsert");
936 propertyTable.getActionMap().put("onTableInsert",addAction);
937
938 // unassign some standard shortcuts for JTable to allow upload / download
939 InputMapUtils.unassignCtrlShiftUpDown(propertyTable, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
940
941 // -- add action and shortcut
942 this.btnAdd = new SideButton(addAction);
943 InputMapUtils.enableEnter(this.btnAdd);
944
945 // -- edit action
946 //
947 propertyTable.getSelectionModel().addListSelectionListener(editAction);
948 membershipTable.getSelectionModel().addListSelectionListener(editAction);
949 this.btnEdit = new SideButton(editAction);
950
951 // -- delete action
952 //
953 this.btnDel = new SideButton(deleteAction);
954 membershipTable.getSelectionModel().addListSelectionListener(deleteAction);
955 propertyTable.getSelectionModel().addListSelectionListener(deleteAction);
956 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
957 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
958 );
959 getActionMap().put("delete", deleteAction);
960
961 JScrollPane scrollPane = (JScrollPane) createLayout(bothTables, true, Arrays.asList(new SideButton[] {
962 this.btnAdd, this.btnEdit, this.btnDel
963 }));
964
965 MouseClickWatch mouseClickWatch = new MouseClickWatch();
966 propertyTable.addMouseListener(mouseClickWatch);
967 membershipTable.addMouseListener(mouseClickWatch);
968 scrollPane.addMouseListener(mouseClickWatch);
969
970 selectSth.setPreferredSize(scrollPane.getSize());
971 presets.setSize(scrollPane.getSize());
972
973 // -- help action
974 //
975 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
976 KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "onHelp");
977 getActionMap().put("onHelp", helpAction);
978 }
979
980 @Override public void setVisible(boolean b) {
981 super.setVisible(b);
982 if (b && Main.main.getCurrentDataSet() != null) {
983 selectionChanged(Main.main.getCurrentDataSet().getSelected());
984 }
985 }
986
987 private int findRow(TableModel model, Object value) {
988 for (int i=0; i<model.getRowCount(); i++) {
989 if (model.getValueAt(i, 0).equals(value))
990 return i;
991 }
992 return -1;
993 }
994
995 private PresetHandler presetHandler = new PresetHandler() {
996
997 @Override
998 public void updateTags(List<Tag> tags) {
999 Command command = TaggingPreset.createCommand(getSelection(), tags);
1000 if (command != null) {
1001 Main.main.undoRedo.add(command);
1002 }
1003 }
1004
1005 @Override
1006 public Collection<OsmPrimitive> getSelection() {
1007 if (Main.main == null) return null;
1008 if (Main.main.getCurrentDataSet() == null) return null;
1009
1010 return Main.main.getCurrentDataSet().getSelected();
1011 }
1012 };
1013
1014 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1015 if (!isVisible())
1016 return;
1017 if (propertyTable == null)
1018 return; // selection changed may be received in base class constructor before init
1019 if (propertyTable.getCellEditor() != null) {
1020 propertyTable.getCellEditor().cancelCellEditing();
1021 }
1022
1023 String selectedTag = null;
1024 Relation selectedRelation = null;
1025 if (propertyTable.getSelectedRowCount() == 1) {
1026 selectedTag = (String)propertyData.getValueAt(propertyTable.getSelectedRow(), 0);
1027 }
1028 if (membershipTable.getSelectedRowCount() == 1) {
1029 selectedRelation = (Relation)membershipData.getValueAt(membershipTable.getSelectedRow(), 0);
1030 }
1031
1032 // re-load property data
1033 propertyData.setRowCount(0);
1034
1035 final Map<String, Integer> keyCount = new HashMap<String, Integer>();
1036 final Map<String, String> tags = new HashMap<String, String>();
1037 valueCount.clear();
1038 EnumSet<PresetType> types = EnumSet.noneOf(TaggingPreset.PresetType.class);
1039 for (OsmPrimitive osm : newSelection) {
1040 types.add(PresetType.forPrimitive(osm));
1041 for (String key : osm.keySet()) {
1042 String value = osm.get(key);
1043 keyCount.put(key, keyCount.containsKey(key) ? keyCount.get(key) + 1 : 1);
1044 if (valueCount.containsKey(key)) {
1045 Map<String, Integer> v = valueCount.get(key);
1046 v.put(value, v.containsKey(value) ? v.get(value) + 1 : 1);
1047 } else {
1048 TreeMap<String, Integer> v = new TreeMap<String, Integer>();
1049 v.put(value, 1);
1050 valueCount.put(key, v);
1051 }
1052 }
1053 }
1054 for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) {
1055 int count = 0;
1056 for (Entry<String, Integer> e1 : e.getValue().entrySet()) {
1057 count += e1.getValue();
1058 }
1059 if (count < newSelection.size()) {
1060 e.getValue().put("", newSelection.size() - count);
1061 }
1062 propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
1063 tags.put(e.getKey(), e.getValue().size() == 1
1064 ? e.getValue().keySet().iterator().next() : tr("<different>"));
1065 }
1066
1067 membershipData.setRowCount(0);
1068
1069 Map<Relation, MemberInfo> roles = new HashMap<Relation, MemberInfo>();
1070 for (OsmPrimitive primitive: newSelection) {
1071 for (OsmPrimitive ref: primitive.getReferrers()) {
1072 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
1073 Relation r = (Relation) ref;
1074 MemberInfo mi = roles.get(r);
1075 if(mi == null) {
1076 mi = new MemberInfo();
1077 }
1078 roles.put(r, mi);
1079 int i = 1;
1080 for (RelationMember m : r.getMembers()) {
1081 if (m.getMember() == primitive) {
1082 mi.add(m, i);
1083 }
1084 ++i;
1085 }
1086 }
1087 }
1088 }
1089
1090 List<Relation> sortedRelations = new ArrayList<Relation>(roles.keySet());
1091 Collections.sort(sortedRelations, new Comparator<Relation>() {
1092 public int compare(Relation o1, Relation o2) {
1093 int comp = Boolean.valueOf(o1.isDisabledAndHidden()).compareTo(o2.isDisabledAndHidden());
1094 if (comp == 0) {
1095 comp = o1.getDisplayName(DefaultNameFormatter.getInstance()).compareTo(o2.getDisplayName(DefaultNameFormatter.getInstance()));
1096 }
1097 return comp;
1098 }}
1099 );
1100
1101 for (Relation r: sortedRelations) {
1102 membershipData.addRow(new Object[]{r, roles.get(r)});
1103 }
1104
1105 presets.updatePresets(types, tags, presetHandler);
1106
1107 membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
1108 membershipTable.setVisible(membershipData.getRowCount() > 0);
1109
1110 boolean hasSelection = !newSelection.isEmpty();
1111 boolean hasTags = hasSelection && propertyData.getRowCount() > 0;
1112 boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0;
1113 btnAdd.setEnabled(hasSelection);
1114 btnEdit.setEnabled(hasTags || hasMemberships);
1115 btnDel.setEnabled(hasTags || hasMemberships);
1116 propertyTable.setVisible(hasTags);
1117 propertyTable.getTableHeader().setVisible(hasTags);
1118 selectSth.setVisible(!hasSelection);
1119 pluginHook.setVisible(hasSelection);
1120
1121 int selectedIndex;
1122 if (selectedTag != null && (selectedIndex = findRow(propertyData, selectedTag)) != -1) {
1123 propertyTable.changeSelection(selectedIndex, 0, false, false);
1124 } else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) {
1125 membershipTable.changeSelection(selectedIndex, 0, false, false);
1126 } else if(hasTags) {
1127 propertyTable.changeSelection(0, 0, false, false);
1128 } else if(hasMemberships) {
1129 membershipTable.changeSelection(0, 0, false, false);
1130 }
1131
1132 if(propertyData.getRowCount() != 0 || membershipData.getRowCount() != 0) {
1133 setTitle(tr("Properties: {0} / Memberships: {1}",
1134 propertyData.getRowCount(), membershipData.getRowCount()));
1135 } else {
1136 setTitle(tr("Properties / Memberships"));
1137 }
1138 }
1139
1140 private void updateSelection() {
1141 if (Main.main.getCurrentDataSet() == null) {
1142 selectionChanged(Collections.<OsmPrimitive>emptyList());
1143 } else {
1144 selectionChanged(Main.main.getCurrentDataSet().getSelected());
1145 }
1146 }
1147
1148 /* ---------------------------------------------------------------------------------- */
1149 /* EditLayerChangeListener */
1150 /* ---------------------------------------------------------------------------------- */
1151 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
1152 updateSelection();
1153 }
1154
1155 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
1156 updateSelection();
1157 }
1158
1159 class DeleteAction extends JosmAction implements ListSelectionListener {
1160
1161 public DeleteAction() {
1162 super(tr("Delete"), "dialogs/delete", tr("Delete the selected key in all objects"),
1163 Shortcut.registerShortcut("properties:delete", tr("Delete Properties"), KeyEvent.VK_D,
1164 Shortcut.ALT_CTRL_SHIFT), false);
1165 updateEnabledState();
1166 }
1167
1168 protected void deleteProperties(int[] rows){
1169 // convert list of rows to HashMap (and find gap for nextKey)
1170 HashMap<String, String> tags = new HashMap<String, String>(rows.length);
1171 int nextKeyIndex = rows[0];
1172 for (int row : rows) {
1173 String key = propertyData.getValueAt(row, 0).toString();
1174 if (row == nextKeyIndex + 1) {
1175 nextKeyIndex = row; // no gap yet
1176 }
1177 tags.put(key, null);
1178 }
1179
1180 // find key to select after deleting other properties
1181 String nextKey = null;
1182 int rowCount = propertyData.getRowCount();
1183 if (rowCount > rows.length) {
1184 if (nextKeyIndex == rows[rows.length-1]) {
1185 // no gap found, pick next or previous key in list
1186 nextKeyIndex = (nextKeyIndex + 1 < rowCount ? nextKeyIndex + 1 : rows[0] - 1);
1187 } else {
1188 // gap found
1189 nextKeyIndex++;
1190 }
1191 nextKey = (String)propertyData.getValueAt(nextKeyIndex, 0);
1192 }
1193
1194 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1195 Main.main.undoRedo.add(new ChangePropertyCommand(sel, tags));
1196
1197 membershipTable.clearSelection();
1198 if (nextKey != null) {
1199 propertyTable.changeSelection(findRow(propertyData, nextKey), 0, false, false);
1200 }
1201 }
1202
1203 protected void deleteFromRelation(int row) {
1204 Relation cur = (Relation)membershipData.getValueAt(row, 0);
1205
1206 Relation nextRelation = null;
1207 int rowCount = membershipTable.getRowCount();
1208 if (rowCount > 1) {
1209 nextRelation = (Relation)membershipData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
1210 }
1211
1212 ExtendedDialog ed = new ExtendedDialog(Main.parent,
1213 tr("Change relation"),
1214 new String[] {tr("Delete from relation"), tr("Cancel")});
1215 ed.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"});
1216 ed.setContent(tr("Really delete selection from relation {0}?", cur.getDisplayName(DefaultNameFormatter.getInstance())));
1217 ed.toggleEnable("delete_from_relation");
1218 ed.showDialog();
1219
1220 if(ed.getValue() != 1)
1221 return;
1222
1223 Relation rel = new Relation(cur);
1224 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1225 for (OsmPrimitive primitive: sel) {
1226 rel.removeMembersFor(primitive);
1227 }
1228 Main.main.undoRedo.add(new ChangeCommand(cur, rel));
1229
1230 propertyTable.clearSelection();
1231 if (nextRelation != null) {
1232 membershipTable.changeSelection(findRow(membershipData, nextRelation), 0, false, false);
1233 }
1234 }
1235
1236 @Override
1237 public void actionPerformed(ActionEvent e) {
1238 if (propertyTable.getSelectedRowCount() > 0) {
1239 int[] rows = propertyTable.getSelectedRows();
1240 deleteProperties(rows);
1241 } else if (membershipTable.getSelectedRowCount() > 0) {
1242 int row = membershipTable.getSelectedRow();
1243 deleteFromRelation(row);
1244 }
1245 }
1246
1247 @Override
1248 protected void updateEnabledState() {
1249 setEnabled(
1250 (propertyTable != null && propertyTable.getSelectedRowCount() >= 1)
1251 || (membershipTable != null && membershipTable.getSelectedRowCount() == 1)
1252 );
1253 }
1254
1255 @Override
1256 public void valueChanged(ListSelectionEvent e) {
1257 updateEnabledState();
1258 }
1259 }
1260
1261 class AddAction extends JosmAction {
1262 public AddAction() {
1263 super(tr("Add"), "dialogs/add", tr("Add a new key/value pair to all objects"),
1264 Shortcut.registerShortcut("properties:add", tr("Add Property"), KeyEvent.VK_A,
1265 Shortcut.ALT), false);
1266 }
1267
1268 @Override
1269 public void actionPerformed(ActionEvent e) {
1270 addProperty();
1271 }
1272 }
1273
1274 class EditAction extends JosmAction implements ListSelectionListener {
1275 public EditAction() {
1276 super(tr("Edit"), "dialogs/edit", tr("Edit the value of the selected key for all objects"),
1277 Shortcut.registerShortcut("properties:edit", tr("Edit Properties"), KeyEvent.VK_S,
1278 Shortcut.ALT), false);
1279 updateEnabledState();
1280 }
1281
1282 @Override
1283 public void actionPerformed(ActionEvent e) {
1284 if (!isEnabled())
1285 return;
1286 if (propertyTable.getSelectedRowCount() == 1) {
1287 int row = propertyTable.getSelectedRow();
1288 editProperty(row);
1289 } else if (membershipTable.getSelectedRowCount() == 1) {
1290 int row = membershipTable.getSelectedRow();
1291 editMembership(row);
1292 }
1293 }
1294
1295 @Override
1296 protected void updateEnabledState() {
1297 setEnabled(
1298 (propertyTable != null && propertyTable.getSelectedRowCount() == 1)
1299 ^ (membershipTable != null && membershipTable.getSelectedRowCount() == 1)
1300 );
1301 }
1302
1303 @Override
1304 public void valueChanged(ListSelectionEvent e) {
1305 updateEnabledState();
1306 }
1307 }
1308
1309 class HelpAction extends AbstractAction {
1310 public HelpAction() {
1311 putValue(NAME, tr("Go to OSM wiki for tag help (F1)"));
1312 putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help for selected object"));
1313 putValue(SMALL_ICON, ImageProvider.get("dialogs", "search"));
1314 }
1315
1316 public void actionPerformed(ActionEvent e) {
1317 try {
1318 String base = Main.pref.get("url.openstreetmap-wiki", "http://wiki.openstreetmap.org/wiki/");
1319 String lang = LanguageInfo.getWikiLanguagePrefix();
1320 final List<URI> uris = new ArrayList<URI>();
1321 int row;
1322 if (propertyTable.getSelectedRowCount() == 1) {
1323 row = propertyTable.getSelectedRow();
1324 String key = URLEncoder.encode(propertyData.getValueAt(row, 0).toString(), "UTF-8");
1325 String val = URLEncoder.encode(
1326 ((Map<String,Integer>)propertyData.getValueAt(row, 1))
1327 .entrySet().iterator().next().getKey(), "UTF-8"
1328 );
1329
1330 uris.add(new URI(String.format("%s%sTag:%s=%s", base, lang, key, val)));
1331 uris.add(new URI(String.format("%sTag:%s=%s", base, key, val)));
1332 uris.add(new URI(String.format("%s%sKey:%s", base, lang, key)));
1333 uris.add(new URI(String.format("%sKey:%s", base, key)));
1334 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1335 uris.add(new URI(String.format("%sMap_Features", base)));
1336 } else if (membershipTable.getSelectedRowCount() == 1) {
1337 row = membershipTable.getSelectedRow();
1338 String type = URLEncoder.encode(
1339 ((Relation)membershipData.getValueAt(row, 0)).get("type"), "UTF-8"
1340 );
1341
1342 if (type != null && !type.equals("")) {
1343 uris.add(new URI(String.format("%s%sRelation:%s", base, lang, type)));
1344 uris.add(new URI(String.format("%sRelation:%s", base, type)));
1345 }
1346
1347 uris.add(new URI(String.format("%s%sRelations", base, lang)));
1348 uris.add(new URI(String.format("%sRelations", base)));
1349 } else {
1350 // give the generic help page, if more than one element is selected
1351 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1352 uris.add(new URI(String.format("%sMap_Features", base)));
1353 }
1354
1355 Main.worker.execute(new Runnable(){
1356 public void run() {
1357 try {
1358 // find a page that actually exists in the wiki
1359 HttpURLConnection conn;
1360 for (URI u : uris) {
1361 conn = (HttpURLConnection) u.toURL().openConnection();
1362 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
1363
1364 if (conn.getResponseCode() != 200) {
1365 System.out.println("INFO: " + u + " does not exist");
1366 conn.disconnect();
1367 } else {
1368 int osize = conn.getContentLength();
1369 conn.disconnect();
1370
1371 conn = (HttpURLConnection) new URI(u.toString()
1372 .replace("=", "%3D") /* do not URLencode whole string! */
1373 .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=")
1374 ).toURL().openConnection();
1375 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
1376
1377 /* redirect pages have different content length, but retrieving a "nonredirect"
1378 * page using index.php and the direct-link method gives slightly different
1379 * content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better)
1380 */
1381 if (Math.abs(conn.getContentLength() - osize) > 200) {
1382 System.out.println("INFO: " + u + " is a mediawiki redirect");
1383 conn.disconnect();
1384 } else {
1385 System.out.println("INFO: browsing to " + u);
1386 conn.disconnect();
1387
1388 OpenBrowser.displayUrl(u.toString());
1389 break;
1390 }
1391 }
1392 }
1393 } catch (Exception e) {
1394 e.printStackTrace();
1395 }
1396 }
1397 });
1398 } catch (Exception e1) {
1399 e1.printStackTrace();
1400 }
1401 }
1402 }
1403
1404 public void addPropertyPopupMenuSeparator() {
1405 propertyMenu.addSeparator();
1406 }
1407
1408 public JMenuItem addPropertyPopupMenuAction(Action a) {
1409 return propertyMenu.add(a);
1410 }
1411
1412 public void addPropertyPopupMenuListener(PopupMenuListener l) {
1413 propertyMenu.addPopupMenuListener(l);
1414 }
1415
1416 public void removePropertyPopupMenuListener(PopupMenuListener l) {
1417 propertyMenu.addPopupMenuListener(l);
1418 }
1419
1420 @SuppressWarnings("unchecked")
1421 public Tag getSelectedProperty() {
1422 int row = propertyTable.getSelectedRow();
1423 if (row == -1) return null;
1424 TreeMap<String, Integer> map = (TreeMap<String, Integer>) propertyData.getValueAt(row, 1);
1425 return new Tag(
1426 propertyData.getValueAt(row, 0).toString(),
1427 map.size() > 1 ? "" : map.keySet().iterator().next());
1428 }
1429
1430 public void addMembershipPopupMenuSeparator() {
1431 membershipMenu.addSeparator();
1432 }
1433
1434 public JMenuItem addMembershipPopupMenuAction(Action a) {
1435 return membershipMenu.add(a);
1436 }
1437
1438 public void addMembershipPopupMenuListener(PopupMenuListener l) {
1439 membershipMenu.addPopupMenuListener(l);
1440 }
1441
1442 public void removeMembershipPopupMenuListener(PopupMenuListener l) {
1443 membershipMenu.addPopupMenuListener(l);
1444 }
1445
1446 public IRelation getSelectedMembershipRelation() {
1447 int row = membershipTable.getSelectedRow();
1448 return row > -1 ? (IRelation) membershipData.getValueAt(row, 0) : null;
1449 }
1450
1451 public static interface RelationRelated {
1452 public Relation getRelation();
1453 public void setRelation(Relation relation);
1454 }
1455
1456 static abstract class AbstractRelationAction extends AbstractAction implements RelationRelated {
1457 protected Relation relation;
1458 public Relation getRelation() {
1459 return this.relation;
1460 }
1461 public void setRelation(Relation relation) {
1462 this.relation = relation;
1463 }
1464 }
1465
1466 static class SelectRelationAction extends AbstractRelationAction {
1467 boolean selectionmode;
1468 public SelectRelationAction(boolean select) {
1469 selectionmode = select;
1470 if(select) {
1471 putValue(NAME, tr("Select relation"));
1472 putValue(SHORT_DESCRIPTION, tr("Select relation in main selection."));
1473 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
1474 } else {
1475 putValue(NAME, tr("Select in relation list"));
1476 putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
1477 putValue(SMALL_ICON, ImageProvider.get("dialogs", "relationlist"));
1478 }
1479 }
1480
1481 public void actionPerformed(ActionEvent e) {
1482 if(selectionmode) {
1483 Main.map.mapView.getEditLayer().data.setSelected(relation);
1484 } else {
1485 Main.map.relationListDialog.selectRelation(relation);
1486 Main.map.relationListDialog.unfurlDialog();
1487 }
1488 }
1489 }
1490
1491
1492 /**
1493 * Sets the current selection to the members of selected relation
1494 *
1495 */
1496 class SelectRelationMembersAction extends AbstractRelationAction {
1497 public SelectRelationMembersAction() {
1498 putValue(SHORT_DESCRIPTION,tr("Select the members of selected relation"));
1499 putValue(SMALL_ICON, ImageProvider.get("selectall"));
1500 putValue(NAME, tr("Select members"));
1501 }
1502
1503 public void actionPerformed(ActionEvent e) {
1504 HashSet<OsmPrimitive> members = new HashSet<OsmPrimitive>();
1505 members.addAll(relation.getMemberPrimitives());
1506 Main.map.mapView.getEditLayer().data.setSelected(members);
1507 }
1508
1509 }
1510
1511 /**
1512 * Action for downloading incomplete members of selected relation
1513 *
1514 */
1515 class DownloadIncompleteMembersAction extends AbstractRelationAction {
1516 public DownloadIncompleteMembersAction() {
1517 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
1518 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
1519 putValue(NAME, tr("Download incomplete members"));
1520 }
1521
1522 public Set<OsmPrimitive> buildSetOfIncompleteMembers(Relation r) {
1523 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
1524 ret.addAll(r.getIncompleteMembers());
1525 return ret;
1526 }
1527
1528 public void actionPerformed(ActionEvent e) {
1529 if (!relation.hasIncompleteMembers()) return;
1530 ArrayList<Relation> rels = new ArrayList<Relation>();
1531 rels.add(relation);
1532 Main.worker.submit(new DownloadRelationMemberTask(
1533 rels,
1534 buildSetOfIncompleteMembers(relation),
1535 Main.map.mapView.getEditLayer()
1536 ));
1537 }
1538 }
1539
1540 abstract class AbstractCopyAction extends AbstractAction {
1541
1542 protected abstract Collection<String> getString(OsmPrimitive p, String key);
1543
1544 @Override
1545 public void actionPerformed(ActionEvent ae) {
1546 if (propertyTable.getSelectedRowCount() != 1)
1547 return;
1548 String key = propertyData.getValueAt(propertyTable.getSelectedRow(), 0).toString();
1549 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1550 if (sel.isEmpty())
1551 return;
1552 Set<String> values = new TreeSet<String>();
1553 for (OsmPrimitive p : sel) {
1554 Collection<String> s = getString(p,key);
1555 if (s != null) {
1556 values.addAll(s);
1557 }
1558 }
1559 Utils.copyToClipboard(Utils.join("\n", values));
1560 }
1561 }
1562
1563 class CopyValueAction extends AbstractCopyAction {
1564
1565 public CopyValueAction() {
1566 putValue(NAME, tr("Copy Value"));
1567 putValue(SHORT_DESCRIPTION, tr("Copy the value of the selected tag to clipboard"));
1568 }
1569
1570 @Override
1571 protected Collection<String> getString(OsmPrimitive p, String key) {
1572 String v = p.get(key);
1573 return v == null ? null : Collections.singleton(v);
1574 }
1575 }
1576
1577 class CopyKeyValueAction extends AbstractCopyAction {
1578
1579 public CopyKeyValueAction() {
1580 putValue(NAME, tr("Copy Key/Value"));
1581 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the selected tag to clipboard"));
1582 }
1583
1584 @Override
1585 protected Collection<String> getString(OsmPrimitive p, String key) {
1586 String v = p.get(key);
1587 return v == null ? null : Collections.singleton(new Tag(key, v).toString());
1588 }
1589 }
1590
1591 class CopyAllKeyValueAction extends AbstractCopyAction {
1592
1593 public CopyAllKeyValueAction() {
1594 putValue(NAME, tr("Copy all Keys/Values"));
1595 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the all tags to clipboard"));
1596 }
1597
1598 @Override
1599 protected Collection<String> getString(OsmPrimitive p, String key) {
1600 List<String> r = new LinkedList<String>();
1601 for (Entry<String, String> kv : p.getKeys().entrySet()) {
1602 r.add(new Tag(kv.getKey(), kv.getValue()).toString());
1603 }
1604 return r;
1605 }
1606 }
1607
1608 class SearchAction extends AbstractAction {
1609 final boolean sameType;
1610
1611 public SearchAction(boolean sameType) {
1612 this.sameType = sameType;
1613 if (sameType) {
1614 putValue(NAME, tr("Search Key/Value/Type"));
1615 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag, restrict to type (i.e., node/way/relation)"));
1616 } else {
1617 putValue(NAME, tr("Search Key/Value"));
1618 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag"));
1619 }
1620 }
1621
1622 public void actionPerformed(ActionEvent e) {
1623 if (propertyTable.getSelectedRowCount() != 1)
1624 return;
1625 String key = propertyData.getValueAt(propertyTable.getSelectedRow(), 0).toString();
1626 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1627 if (sel.isEmpty())
1628 return;
1629 String sep = "";
1630 String s = "";
1631 for (OsmPrimitive p : sel) {
1632 String val = p.get(key);
1633 if (val == null) {
1634 continue;
1635 }
1636 String t = "";
1637 if (!sameType) {
1638 t = "";
1639 } else if (p instanceof Node) {
1640 t = "type:node ";
1641 } else if (p instanceof Way) {
1642 t = "type:way ";
1643 } else if (p instanceof Relation) {
1644 t = "type:relation ";
1645 }
1646 s += sep + "(" + t + "\"" +
1647 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(key) + "\"=\"" +
1648 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(val) + "\")";
1649 sep = " OR ";
1650 }
1651
1652 SearchSetting ss = new SearchSetting(s, SearchMode.replace, true, false, false);
1653 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(ss);
1654 }
1655 }
1656}
Note: See TracBrowser for help on using the repository browser.