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

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

Forgot this in previous commit, sorry

  • Property svn:eol-style set to native
File size: 71.5 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.setSelectedItem(t.getKey());
609 values.setSelectedItem(t.getValue());
610 // Update list of values (fix #7951)
611 focus.focusGained(null);
612 }
613 };
614 tagsActions.add(action);
615 // Disable action if its key is already set on the object (the key being absent from the keys list for this reason
616 // performing this action leads to autocomplete to the next key (see #7671 comments)
617 for (int j = 0; j < propertyData.getRowCount(); ++j) {
618 System.out.println(propertyData.getValueAt(j, 0));
619 if (t.getKey().equals(propertyData.getValueAt(j, 0))) {
620 action.setEnabled(false);
621 break;
622 }
623 }
624 // Create tag label
625 final JLabel tagLabel = new JLabel("<html>"
626 + "<style>td{border:1px solid gray; font-weight:normal;}</style>"
627 + "<table><tr><td>" + t.toString() + "</td></tr></table></html>");
628 if (action.isEnabled()) {
629 // Register action
630 p.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), actionShortcutKey);
631 p.getActionMap().put(actionShortcutKey, action);
632 // Make the tag label clickable and set tooltip to the action description (this displays also the keyboard shortcut)
633 tagLabel.setToolTipText((String) action.getValue(Action.SHORT_DESCRIPTION));
634 tagLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
635 tagLabel.addMouseListener(new MouseAdapter() {
636 @Override
637 public void mouseClicked(MouseEvent e) {
638 action.actionPerformed(null);
639 }
640 });
641 } else {
642 // Disable tag label
643 tagLabel.setEnabled(false);
644 // Explain in the tooltip why
645 tagLabel.setToolTipText(tr("The key ''{0}'' is already used", t.getKey()));
646 }
647 // Finally add label to the resulting panel
648 JPanel tagPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
649 tagPanel.add(tagLabel);
650 p.add(tagPanel, GBC.eol());
651 }
652 }
653 }
654
655 /**
656 * @param allData
657 * @param keys
658 * @param values
659 */
660 private FocusAdapter addFocusAdapter(final int row,
661 final AutoCompletingComboBox keys, final AutoCompletingComboBox values,
662 final AutoCompletionManager autocomplete, final Comparator<AutoCompletionListItem> comparator) {
663 // get the combo box' editor component
664 JTextComponent editor = (JTextComponent)values.getEditor()
665 .getEditorComponent();
666 // Refresh the values model when focus is gained
667 FocusAdapter focus = new FocusAdapter() {
668 @Override public void focusGained(FocusEvent e) {
669 String key = keys.getEditor().getItem().toString();
670
671 List<AutoCompletionListItem> valueList = autocomplete.getValues(getAutocompletionKeys(key));
672 Collections.sort(valueList, comparator);
673
674 values.setPossibleACItems(valueList);
675 objKey=key;
676 }
677 };
678 editor.addFocusListener(focus);
679 return focus;
680 }
681 private String objKey;
682
683 /**
684 * The property data.
685 */
686 private final DefaultTableModel propertyData = new DefaultTableModel() {
687 @Override public boolean isCellEditable(int row, int column) {
688 return false;
689 }
690 @Override public Class<?> getColumnClass(int columnIndex) {
691 return String.class;
692 }
693 };
694
695 /**
696 * The membership data.
697 */
698 private final DefaultTableModel membershipData = new DefaultTableModel() {
699 @Override public boolean isCellEditable(int row, int column) {
700 return false;
701 }
702 @Override public Class<?> getColumnClass(int columnIndex) {
703 return String.class;
704 }
705 };
706
707 /**
708 * The properties list.
709 */
710 private final JTable propertyTable = new JTable(propertyData);
711 private final JTable membershipTable = new JTable(membershipData);
712
713 public JComboBox taggingPresets = new JComboBox();
714
715 /**
716 * The Add/Edit/Delete buttons (needed to be able to disable them)
717 */
718 private final SideButton btnAdd;
719 private final SideButton btnEdit;
720 private final SideButton btnDel;
721 private final PresetListPanel presets = new PresetListPanel();
722
723 private final JLabel selectSth = new JLabel("<html><p>"
724 + tr("Select objects for which to change properties.") + "</p></html>");
725
726 static class MemberInfo {
727 List<RelationMember> role = new ArrayList<RelationMember>();
728 List<Integer> position = new ArrayList<Integer>();
729 private String positionString = null;
730 void add(RelationMember r, Integer p) {
731 role.add(r);
732 position.add(p);
733 }
734 String getPositionString() {
735 if (positionString == null) {
736 Collections.sort(position);
737 positionString = String.valueOf(position.get(0));
738 int cnt = 0;
739 int last = position.get(0);
740 for (int i = 1; i < position.size(); ++i) {
741 int cur = position.get(i);
742 if (cur == last + 1) {
743 ++cnt;
744 } else if (cnt == 0) {
745 positionString += "," + String.valueOf(cur);
746 } else {
747 positionString += "-" + String.valueOf(last);
748 positionString += "," + String.valueOf(cur);
749 cnt = 0;
750 }
751 last = cur;
752 }
753 if (cnt >= 1) {
754 positionString += "-" + String.valueOf(last);
755 }
756 }
757 if (positionString.length() > 20) {
758 positionString = positionString.substring(0, 17) + "...";
759 }
760 return positionString;
761 }
762 }
763
764 /**
765 * Create a new PropertiesDialog
766 */
767 public PropertiesDialog(MapFrame mapFrame) {
768 super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
769 Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
770 Shortcut.ALT_SHIFT), 150, true);
771
772 // setting up the properties table
773 propertyMenu = new JPopupMenu();
774 propertyMenu.add(copyValueAction);
775 propertyMenu.add(copyKeyValueAction);
776 propertyMenu.add(copyAllKeyValueAction);
777 propertyMenu.addSeparator();
778 propertyMenu.add(searchActionAny);
779 propertyMenu.add(searchActionSame);
780 propertyMenu.addSeparator();
781 propertyMenu.add(helpAction);
782
783 propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
784 propertyTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
785 propertyTable.getTableHeader().setReorderingAllowed(false);
786 propertyTable.addMouseListener(new PopupMenuLauncher() {
787 @Override
788 public void launch(MouseEvent evt) {
789 Point p = evt.getPoint();
790 int row = propertyTable.rowAtPoint(p);
791 if (row > -1) {
792 propertyTable.changeSelection(row, 0, false, false);
793 propertyMenu.show(propertyTable, p.x, p.y-3);
794 }
795 }
796 });
797
798 propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer(){
799 @Override public Component getTableCellRendererComponent(JTable table, Object value,
800 boolean isSelected, boolean hasFocus, int row, int column) {
801 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
802 if (value == null)
803 return this;
804 if (c instanceof JLabel) {
805 String str = null;
806 if (value instanceof String) {
807 str = (String) value;
808 } else if (value instanceof Map<?, ?>) {
809 Map<?, ?> v = (Map<?, ?>) value;
810 if (v.size() != 1) {
811 str=tr("<different>");
812 c.setFont(c.getFont().deriveFont(Font.ITALIC));
813 } else {
814 final Map.Entry<?, ?> entry = v.entrySet().iterator().next();
815 str = (String) entry.getKey();
816 }
817 }
818 ((JLabel)c).setText(str);
819 }
820 return c;
821 }
822 });
823
824 // setting up the membership table
825 membershipMenu = new JPopupMenu();
826 membershipMenu.add(new SelectRelationAction(true));
827 membershipMenu.add(new SelectRelationAction(false));
828 membershipMenu.add(new SelectRelationMembersAction());
829 membershipMenu.add(new DownloadIncompleteMembersAction());
830 membershipMenu.addSeparator();
831 membershipMenu.add(helpAction);
832
833 membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role"),tr("Position")});
834 membershipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
835 membershipTable.addMouseListener(new PopupMenuLauncher() {
836 @Override
837 public void launch(MouseEvent evt) {
838 Point p = evt.getPoint();
839 int row = membershipTable.rowAtPoint(p);
840 if (row > -1) {
841 membershipTable.changeSelection(row, 0, false, false);
842 Relation relation = (Relation)membershipData.getValueAt(row, 0);
843 for (Component c : membershipMenu.getComponents()) {
844 if (c instanceof JMenuItem) {
845 Action action = ((JMenuItem) c).getAction();
846 if (action instanceof RelationRelated) {
847 ((RelationRelated)action).setRelation(relation);
848 }
849 }
850 }
851 membershipMenu.show(membershipTable, p.x, p.y-3);
852 }
853 }
854 });
855
856 TableColumnModel mod = membershipTable.getColumnModel();
857 membershipTable.getTableHeader().setReorderingAllowed(false);
858 mod.getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
859 @Override public Component getTableCellRendererComponent(JTable table, Object value,
860 boolean isSelected, boolean hasFocus, int row, int column) {
861 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
862 if (value == null)
863 return this;
864 if (c instanceof JLabel) {
865 JLabel label = (JLabel)c;
866 Relation r = (Relation)value;
867 label.setText(r.getDisplayName(DefaultNameFormatter.getInstance()));
868 if (r.isDisabledAndHidden()) {
869 label.setFont(label.getFont().deriveFont(Font.ITALIC));
870 }
871 }
872 return c;
873 }
874 });
875
876 mod.getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
877 @Override public Component getTableCellRendererComponent(JTable table, Object value,
878 boolean isSelected, boolean hasFocus, int row, int column) {
879 if (value == null)
880 return this;
881 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
882 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
883 if (c instanceof JLabel) {
884 JLabel label = (JLabel)c;
885 MemberInfo col = (MemberInfo) value;
886
887 String text = null;
888 for (RelationMember r : col.role) {
889 if (text == null) {
890 text = r.getRole();
891 }
892 else if (!text.equals(r.getRole())) {
893 text = tr("<different>");
894 break;
895 }
896 }
897
898 label.setText(text);
899 if (isDisabledAndHidden) {
900 label.setFont(label.getFont().deriveFont(Font.ITALIC));
901 }
902 }
903 return c;
904 }
905 });
906
907 mod.getColumn(2).setCellRenderer(new DefaultTableCellRenderer() {
908 @Override public Component getTableCellRendererComponent(JTable table, Object value,
909 boolean isSelected, boolean hasFocus, int row, int column) {
910 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
911 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
912 if (c instanceof JLabel) {
913 JLabel label = (JLabel)c;
914 label.setText(((MemberInfo) table.getValueAt(row, 1)).getPositionString());
915 if (isDisabledAndHidden) {
916 label.setFont(label.getFont().deriveFont(Font.ITALIC));
917 }
918 }
919 return c;
920 }
921 });
922 mod.getColumn(2).setPreferredWidth(20);
923 mod.getColumn(1).setPreferredWidth(40);
924 mod.getColumn(0).setPreferredWidth(200);
925
926 // combine both tables and wrap them in a scrollPane
927 JPanel bothTables = new JPanel();
928 boolean top = Main.pref.getBoolean("properties.presets.top", true);
929 bothTables.setLayout(new GridBagLayout());
930 if(top) {
931 bothTables.add(presets, GBC.std().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2).anchor(GBC.NORTHWEST));
932 double epsilon = Double.MIN_VALUE; // need to set a weight or else anchor value is ignored
933 bothTables.add(pluginHook, GBC.eol().insets(0,1,1,1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon));
934 }
935 bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10));
936 bothTables.add(propertyTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
937 bothTables.add(propertyTable, GBC.eol().fill(GBC.BOTH));
938 bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
939 bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
940 if(!top) {
941 bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2));
942 }
943
944 // Open edit dialog whe enter pressed in tables
945 propertyTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
946 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter");
947 propertyTable.getActionMap().put("onTableEnter",editAction);
948 membershipTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
949 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"onTableEnter");
950 membershipTable.getActionMap().put("onTableEnter",editAction);
951
952 // Open add property dialog when INS is pressed in tables
953 propertyTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
954 .put(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0),"onTableInsert");
955 propertyTable.getActionMap().put("onTableInsert",addAction);
956
957 // unassign some standard shortcuts for JTable to allow upload / download
958 InputMapUtils.unassignCtrlShiftUpDown(propertyTable, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
959
960 // -- add action and shortcut
961 this.btnAdd = new SideButton(addAction);
962 InputMapUtils.enableEnter(this.btnAdd);
963
964 // -- edit action
965 //
966 propertyTable.getSelectionModel().addListSelectionListener(editAction);
967 membershipTable.getSelectionModel().addListSelectionListener(editAction);
968 this.btnEdit = new SideButton(editAction);
969
970 // -- delete action
971 //
972 this.btnDel = new SideButton(deleteAction);
973 membershipTable.getSelectionModel().addListSelectionListener(deleteAction);
974 propertyTable.getSelectionModel().addListSelectionListener(deleteAction);
975 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
976 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
977 );
978 getActionMap().put("delete", deleteAction);
979
980 JScrollPane scrollPane = (JScrollPane) createLayout(bothTables, true, Arrays.asList(new SideButton[] {
981 this.btnAdd, this.btnEdit, this.btnDel
982 }));
983
984 MouseClickWatch mouseClickWatch = new MouseClickWatch();
985 propertyTable.addMouseListener(mouseClickWatch);
986 membershipTable.addMouseListener(mouseClickWatch);
987 scrollPane.addMouseListener(mouseClickWatch);
988
989 selectSth.setPreferredSize(scrollPane.getSize());
990 presets.setSize(scrollPane.getSize());
991
992 // -- help action
993 //
994 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
995 KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "onHelp");
996 getActionMap().put("onHelp", helpAction);
997 }
998
999 @Override public void setVisible(boolean b) {
1000 super.setVisible(b);
1001 if (b && Main.main.getCurrentDataSet() != null) {
1002 selectionChanged(Main.main.getCurrentDataSet().getSelected());
1003 }
1004 }
1005
1006 private int findRow(TableModel model, Object value) {
1007 for (int i=0; i<model.getRowCount(); i++) {
1008 if (model.getValueAt(i, 0).equals(value))
1009 return i;
1010 }
1011 return -1;
1012 }
1013
1014 private PresetHandler presetHandler = new PresetHandler() {
1015
1016 @Override
1017 public void updateTags(List<Tag> tags) {
1018 Command command = TaggingPreset.createCommand(getSelection(), tags);
1019 if (command != null) {
1020 Main.main.undoRedo.add(command);
1021 }
1022 }
1023
1024 @Override
1025 public Collection<OsmPrimitive> getSelection() {
1026 if (Main.main == null) return null;
1027 if (Main.main.getCurrentDataSet() == null) return null;
1028
1029 return Main.main.getCurrentDataSet().getSelected();
1030 }
1031 };
1032
1033 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1034 if (!isVisible())
1035 return;
1036 if (propertyTable == null)
1037 return; // selection changed may be received in base class constructor before init
1038 if (propertyTable.getCellEditor() != null) {
1039 propertyTable.getCellEditor().cancelCellEditing();
1040 }
1041
1042 String selectedTag = null;
1043 Relation selectedRelation = null;
1044 if (propertyTable.getSelectedRowCount() == 1) {
1045 selectedTag = (String)propertyData.getValueAt(propertyTable.getSelectedRow(), 0);
1046 }
1047 if (membershipTable.getSelectedRowCount() == 1) {
1048 selectedRelation = (Relation)membershipData.getValueAt(membershipTable.getSelectedRow(), 0);
1049 }
1050
1051 // re-load property data
1052 propertyData.setRowCount(0);
1053
1054 final Map<String, Integer> keyCount = new HashMap<String, Integer>();
1055 final Map<String, String> tags = new HashMap<String, String>();
1056 valueCount.clear();
1057 EnumSet<PresetType> types = EnumSet.noneOf(TaggingPreset.PresetType.class);
1058 for (OsmPrimitive osm : newSelection) {
1059 types.add(PresetType.forPrimitive(osm));
1060 for (String key : osm.keySet()) {
1061 String value = osm.get(key);
1062 keyCount.put(key, keyCount.containsKey(key) ? keyCount.get(key) + 1 : 1);
1063 if (valueCount.containsKey(key)) {
1064 Map<String, Integer> v = valueCount.get(key);
1065 v.put(value, v.containsKey(value) ? v.get(value) + 1 : 1);
1066 } else {
1067 TreeMap<String, Integer> v = new TreeMap<String, Integer>();
1068 v.put(value, 1);
1069 valueCount.put(key, v);
1070 }
1071 }
1072 }
1073 for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) {
1074 int count = 0;
1075 for (Entry<String, Integer> e1 : e.getValue().entrySet()) {
1076 count += e1.getValue();
1077 }
1078 if (count < newSelection.size()) {
1079 e.getValue().put("", newSelection.size() - count);
1080 }
1081 propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
1082 tags.put(e.getKey(), e.getValue().size() == 1
1083 ? e.getValue().keySet().iterator().next() : tr("<different>"));
1084 }
1085
1086 membershipData.setRowCount(0);
1087
1088 Map<Relation, MemberInfo> roles = new HashMap<Relation, MemberInfo>();
1089 for (OsmPrimitive primitive: newSelection) {
1090 for (OsmPrimitive ref: primitive.getReferrers()) {
1091 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
1092 Relation r = (Relation) ref;
1093 MemberInfo mi = roles.get(r);
1094 if(mi == null) {
1095 mi = new MemberInfo();
1096 }
1097 roles.put(r, mi);
1098 int i = 1;
1099 for (RelationMember m : r.getMembers()) {
1100 if (m.getMember() == primitive) {
1101 mi.add(m, i);
1102 }
1103 ++i;
1104 }
1105 }
1106 }
1107 }
1108
1109 List<Relation> sortedRelations = new ArrayList<Relation>(roles.keySet());
1110 Collections.sort(sortedRelations, new Comparator<Relation>() {
1111 public int compare(Relation o1, Relation o2) {
1112 int comp = Boolean.valueOf(o1.isDisabledAndHidden()).compareTo(o2.isDisabledAndHidden());
1113 if (comp == 0) {
1114 comp = o1.getDisplayName(DefaultNameFormatter.getInstance()).compareTo(o2.getDisplayName(DefaultNameFormatter.getInstance()));
1115 }
1116 return comp;
1117 }}
1118 );
1119
1120 for (Relation r: sortedRelations) {
1121 membershipData.addRow(new Object[]{r, roles.get(r)});
1122 }
1123
1124 presets.updatePresets(types, tags, presetHandler);
1125
1126 membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
1127 membershipTable.setVisible(membershipData.getRowCount() > 0);
1128
1129 boolean hasSelection = !newSelection.isEmpty();
1130 boolean hasTags = hasSelection && propertyData.getRowCount() > 0;
1131 boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0;
1132 btnAdd.setEnabled(hasSelection);
1133 btnEdit.setEnabled(hasTags || hasMemberships);
1134 btnDel.setEnabled(hasTags || hasMemberships);
1135 propertyTable.setVisible(hasTags);
1136 propertyTable.getTableHeader().setVisible(hasTags);
1137 selectSth.setVisible(!hasSelection);
1138 pluginHook.setVisible(hasSelection);
1139
1140 int selectedIndex;
1141 if (selectedTag != null && (selectedIndex = findRow(propertyData, selectedTag)) != -1) {
1142 propertyTable.changeSelection(selectedIndex, 0, false, false);
1143 } else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) {
1144 membershipTable.changeSelection(selectedIndex, 0, false, false);
1145 } else if(hasTags) {
1146 propertyTable.changeSelection(0, 0, false, false);
1147 } else if(hasMemberships) {
1148 membershipTable.changeSelection(0, 0, false, false);
1149 }
1150
1151 if(propertyData.getRowCount() != 0 || membershipData.getRowCount() != 0) {
1152 setTitle(tr("Properties: {0} / Memberships: {1}",
1153 propertyData.getRowCount(), membershipData.getRowCount()));
1154 } else {
1155 setTitle(tr("Properties / Memberships"));
1156 }
1157 }
1158
1159 private void updateSelection() {
1160 if (Main.main.getCurrentDataSet() == null) {
1161 selectionChanged(Collections.<OsmPrimitive>emptyList());
1162 } else {
1163 selectionChanged(Main.main.getCurrentDataSet().getSelected());
1164 }
1165 }
1166
1167 /* ---------------------------------------------------------------------------------- */
1168 /* EditLayerChangeListener */
1169 /* ---------------------------------------------------------------------------------- */
1170 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
1171 updateSelection();
1172 }
1173
1174 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
1175 updateSelection();
1176 }
1177
1178 class DeleteAction extends JosmAction implements ListSelectionListener {
1179
1180 public DeleteAction() {
1181 super(tr("Delete"), "dialogs/delete", tr("Delete the selected key in all objects"),
1182 Shortcut.registerShortcut("properties:delete", tr("Delete Properties"), KeyEvent.VK_D,
1183 Shortcut.ALT_CTRL_SHIFT), false);
1184 updateEnabledState();
1185 }
1186
1187 protected void deleteProperties(int[] rows){
1188 // convert list of rows to HashMap (and find gap for nextKey)
1189 HashMap<String, String> tags = new HashMap<String, String>(rows.length);
1190 int nextKeyIndex = rows[0];
1191 for (int row : rows) {
1192 String key = propertyData.getValueAt(row, 0).toString();
1193 if (row == nextKeyIndex + 1) {
1194 nextKeyIndex = row; // no gap yet
1195 }
1196 tags.put(key, null);
1197 }
1198
1199 // find key to select after deleting other properties
1200 String nextKey = null;
1201 int rowCount = propertyData.getRowCount();
1202 if (rowCount > rows.length) {
1203 if (nextKeyIndex == rows[rows.length-1]) {
1204 // no gap found, pick next or previous key in list
1205 nextKeyIndex = (nextKeyIndex + 1 < rowCount ? nextKeyIndex + 1 : rows[0] - 1);
1206 } else {
1207 // gap found
1208 nextKeyIndex++;
1209 }
1210 nextKey = (String)propertyData.getValueAt(nextKeyIndex, 0);
1211 }
1212
1213 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1214 Main.main.undoRedo.add(new ChangePropertyCommand(sel, tags));
1215
1216 membershipTable.clearSelection();
1217 if (nextKey != null) {
1218 propertyTable.changeSelection(findRow(propertyData, nextKey), 0, false, false);
1219 }
1220 }
1221
1222 protected void deleteFromRelation(int row) {
1223 Relation cur = (Relation)membershipData.getValueAt(row, 0);
1224
1225 Relation nextRelation = null;
1226 int rowCount = membershipTable.getRowCount();
1227 if (rowCount > 1) {
1228 nextRelation = (Relation)membershipData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
1229 }
1230
1231 ExtendedDialog ed = new ExtendedDialog(Main.parent,
1232 tr("Change relation"),
1233 new String[] {tr("Delete from relation"), tr("Cancel")});
1234 ed.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"});
1235 ed.setContent(tr("Really delete selection from relation {0}?", cur.getDisplayName(DefaultNameFormatter.getInstance())));
1236 ed.toggleEnable("delete_from_relation");
1237 ed.showDialog();
1238
1239 if(ed.getValue() != 1)
1240 return;
1241
1242 Relation rel = new Relation(cur);
1243 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1244 for (OsmPrimitive primitive: sel) {
1245 rel.removeMembersFor(primitive);
1246 }
1247 Main.main.undoRedo.add(new ChangeCommand(cur, rel));
1248
1249 propertyTable.clearSelection();
1250 if (nextRelation != null) {
1251 membershipTable.changeSelection(findRow(membershipData, nextRelation), 0, false, false);
1252 }
1253 }
1254
1255 @Override
1256 public void actionPerformed(ActionEvent e) {
1257 if (propertyTable.getSelectedRowCount() > 0) {
1258 int[] rows = propertyTable.getSelectedRows();
1259 deleteProperties(rows);
1260 } else if (membershipTable.getSelectedRowCount() > 0) {
1261 int row = membershipTable.getSelectedRow();
1262 deleteFromRelation(row);
1263 }
1264 }
1265
1266 @Override
1267 protected void updateEnabledState() {
1268 setEnabled(
1269 (propertyTable != null && propertyTable.getSelectedRowCount() >= 1)
1270 || (membershipTable != null && membershipTable.getSelectedRowCount() == 1)
1271 );
1272 }
1273
1274 @Override
1275 public void valueChanged(ListSelectionEvent e) {
1276 updateEnabledState();
1277 }
1278 }
1279
1280 class AddAction extends JosmAction {
1281 public AddAction() {
1282 super(tr("Add"), "dialogs/add", tr("Add a new key/value pair to all objects"),
1283 Shortcut.registerShortcut("properties:add", tr("Add Property"), KeyEvent.VK_A,
1284 Shortcut.ALT), false);
1285 }
1286
1287 @Override
1288 public void actionPerformed(ActionEvent e) {
1289 addProperty();
1290 }
1291 }
1292
1293 class EditAction extends JosmAction implements ListSelectionListener {
1294 public EditAction() {
1295 super(tr("Edit"), "dialogs/edit", tr("Edit the value of the selected key for all objects"),
1296 Shortcut.registerShortcut("properties:edit", tr("Edit Properties"), KeyEvent.VK_S,
1297 Shortcut.ALT), false);
1298 updateEnabledState();
1299 }
1300
1301 @Override
1302 public void actionPerformed(ActionEvent e) {
1303 if (!isEnabled())
1304 return;
1305 if (propertyTable.getSelectedRowCount() == 1) {
1306 int row = propertyTable.getSelectedRow();
1307 editProperty(row);
1308 } else if (membershipTable.getSelectedRowCount() == 1) {
1309 int row = membershipTable.getSelectedRow();
1310 editMembership(row);
1311 }
1312 }
1313
1314 @Override
1315 protected void updateEnabledState() {
1316 setEnabled(
1317 (propertyTable != null && propertyTable.getSelectedRowCount() == 1)
1318 ^ (membershipTable != null && membershipTable.getSelectedRowCount() == 1)
1319 );
1320 }
1321
1322 @Override
1323 public void valueChanged(ListSelectionEvent e) {
1324 updateEnabledState();
1325 }
1326 }
1327
1328 class HelpAction extends AbstractAction {
1329 public HelpAction() {
1330 putValue(NAME, tr("Go to OSM wiki for tag help (F1)"));
1331 putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help for selected object"));
1332 putValue(SMALL_ICON, ImageProvider.get("dialogs", "search"));
1333 }
1334
1335 public void actionPerformed(ActionEvent e) {
1336 try {
1337 String base = Main.pref.get("url.openstreetmap-wiki", "http://wiki.openstreetmap.org/wiki/");
1338 String lang = LanguageInfo.getWikiLanguagePrefix();
1339 final List<URI> uris = new ArrayList<URI>();
1340 int row;
1341 if (propertyTable.getSelectedRowCount() == 1) {
1342 row = propertyTable.getSelectedRow();
1343 String key = URLEncoder.encode(propertyData.getValueAt(row, 0).toString(), "UTF-8");
1344 String val = URLEncoder.encode(
1345 ((Map<String,Integer>)propertyData.getValueAt(row, 1))
1346 .entrySet().iterator().next().getKey(), "UTF-8"
1347 );
1348
1349 uris.add(new URI(String.format("%s%sTag:%s=%s", base, lang, key, val)));
1350 uris.add(new URI(String.format("%sTag:%s=%s", base, key, val)));
1351 uris.add(new URI(String.format("%s%sKey:%s", base, lang, key)));
1352 uris.add(new URI(String.format("%sKey:%s", base, key)));
1353 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1354 uris.add(new URI(String.format("%sMap_Features", base)));
1355 } else if (membershipTable.getSelectedRowCount() == 1) {
1356 row = membershipTable.getSelectedRow();
1357 String type = URLEncoder.encode(
1358 ((Relation)membershipData.getValueAt(row, 0)).get("type"), "UTF-8"
1359 );
1360
1361 if (type != null && !type.equals("")) {
1362 uris.add(new URI(String.format("%s%sRelation:%s", base, lang, type)));
1363 uris.add(new URI(String.format("%sRelation:%s", base, type)));
1364 }
1365
1366 uris.add(new URI(String.format("%s%sRelations", base, lang)));
1367 uris.add(new URI(String.format("%sRelations", base)));
1368 } else {
1369 // give the generic help page, if more than one element is selected
1370 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1371 uris.add(new URI(String.format("%sMap_Features", base)));
1372 }
1373
1374 Main.worker.execute(new Runnable(){
1375 public void run() {
1376 try {
1377 // find a page that actually exists in the wiki
1378 HttpURLConnection conn;
1379 for (URI u : uris) {
1380 conn = (HttpURLConnection) u.toURL().openConnection();
1381 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
1382
1383 if (conn.getResponseCode() != 200) {
1384 System.out.println("INFO: " + u + " does not exist");
1385 conn.disconnect();
1386 } else {
1387 int osize = conn.getContentLength();
1388 conn.disconnect();
1389
1390 conn = (HttpURLConnection) new URI(u.toString()
1391 .replace("=", "%3D") /* do not URLencode whole string! */
1392 .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=")
1393 ).toURL().openConnection();
1394 conn.setConnectTimeout(Main.pref.getInteger("socket.timeout.connect",15)*1000);
1395
1396 /* redirect pages have different content length, but retrieving a "nonredirect"
1397 * page using index.php and the direct-link method gives slightly different
1398 * content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better)
1399 */
1400 if (Math.abs(conn.getContentLength() - osize) > 200) {
1401 System.out.println("INFO: " + u + " is a mediawiki redirect");
1402 conn.disconnect();
1403 } else {
1404 System.out.println("INFO: browsing to " + u);
1405 conn.disconnect();
1406
1407 OpenBrowser.displayUrl(u.toString());
1408 break;
1409 }
1410 }
1411 }
1412 } catch (Exception e) {
1413 e.printStackTrace();
1414 }
1415 }
1416 });
1417 } catch (Exception e1) {
1418 e1.printStackTrace();
1419 }
1420 }
1421 }
1422
1423 public void addPropertyPopupMenuSeparator() {
1424 propertyMenu.addSeparator();
1425 }
1426
1427 public JMenuItem addPropertyPopupMenuAction(Action a) {
1428 return propertyMenu.add(a);
1429 }
1430
1431 public void addPropertyPopupMenuListener(PopupMenuListener l) {
1432 propertyMenu.addPopupMenuListener(l);
1433 }
1434
1435 public void removePropertyPopupMenuListener(PopupMenuListener l) {
1436 propertyMenu.addPopupMenuListener(l);
1437 }
1438
1439 @SuppressWarnings("unchecked")
1440 public Tag getSelectedProperty() {
1441 int row = propertyTable.getSelectedRow();
1442 if (row == -1) return null;
1443 TreeMap<String, Integer> map = (TreeMap<String, Integer>) propertyData.getValueAt(row, 1);
1444 return new Tag(
1445 propertyData.getValueAt(row, 0).toString(),
1446 map.size() > 1 ? "" : map.keySet().iterator().next());
1447 }
1448
1449 public void addMembershipPopupMenuSeparator() {
1450 membershipMenu.addSeparator();
1451 }
1452
1453 public JMenuItem addMembershipPopupMenuAction(Action a) {
1454 return membershipMenu.add(a);
1455 }
1456
1457 public void addMembershipPopupMenuListener(PopupMenuListener l) {
1458 membershipMenu.addPopupMenuListener(l);
1459 }
1460
1461 public void removeMembershipPopupMenuListener(PopupMenuListener l) {
1462 membershipMenu.addPopupMenuListener(l);
1463 }
1464
1465 public IRelation getSelectedMembershipRelation() {
1466 int row = membershipTable.getSelectedRow();
1467 return row > -1 ? (IRelation) membershipData.getValueAt(row, 0) : null;
1468 }
1469
1470 public static interface RelationRelated {
1471 public Relation getRelation();
1472 public void setRelation(Relation relation);
1473 }
1474
1475 static abstract class AbstractRelationAction extends AbstractAction implements RelationRelated {
1476 protected Relation relation;
1477 public Relation getRelation() {
1478 return this.relation;
1479 }
1480 public void setRelation(Relation relation) {
1481 this.relation = relation;
1482 }
1483 }
1484
1485 static class SelectRelationAction extends AbstractRelationAction {
1486 boolean selectionmode;
1487 public SelectRelationAction(boolean select) {
1488 selectionmode = select;
1489 if(select) {
1490 putValue(NAME, tr("Select relation"));
1491 putValue(SHORT_DESCRIPTION, tr("Select relation in main selection."));
1492 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
1493 } else {
1494 putValue(NAME, tr("Select in relation list"));
1495 putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
1496 putValue(SMALL_ICON, ImageProvider.get("dialogs", "relationlist"));
1497 }
1498 }
1499
1500 public void actionPerformed(ActionEvent e) {
1501 if(selectionmode) {
1502 Main.map.mapView.getEditLayer().data.setSelected(relation);
1503 } else {
1504 Main.map.relationListDialog.selectRelation(relation);
1505 Main.map.relationListDialog.unfurlDialog();
1506 }
1507 }
1508 }
1509
1510
1511 /**
1512 * Sets the current selection to the members of selected relation
1513 *
1514 */
1515 class SelectRelationMembersAction extends AbstractRelationAction {
1516 public SelectRelationMembersAction() {
1517 putValue(SHORT_DESCRIPTION,tr("Select the members of selected relation"));
1518 putValue(SMALL_ICON, ImageProvider.get("selectall"));
1519 putValue(NAME, tr("Select members"));
1520 }
1521
1522 public void actionPerformed(ActionEvent e) {
1523 HashSet<OsmPrimitive> members = new HashSet<OsmPrimitive>();
1524 members.addAll(relation.getMemberPrimitives());
1525 Main.map.mapView.getEditLayer().data.setSelected(members);
1526 }
1527
1528 }
1529
1530 /**
1531 * Action for downloading incomplete members of selected relation
1532 *
1533 */
1534 class DownloadIncompleteMembersAction extends AbstractRelationAction {
1535 public DownloadIncompleteMembersAction() {
1536 putValue(SHORT_DESCRIPTION, tr("Download incomplete members of selected relations"));
1537 putValue(SMALL_ICON, ImageProvider.get("dialogs/relation", "downloadincompleteselected"));
1538 putValue(NAME, tr("Download incomplete members"));
1539 }
1540
1541 public Set<OsmPrimitive> buildSetOfIncompleteMembers(Relation r) {
1542 Set<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
1543 ret.addAll(r.getIncompleteMembers());
1544 return ret;
1545 }
1546
1547 public void actionPerformed(ActionEvent e) {
1548 if (!relation.hasIncompleteMembers()) return;
1549 ArrayList<Relation> rels = new ArrayList<Relation>();
1550 rels.add(relation);
1551 Main.worker.submit(new DownloadRelationMemberTask(
1552 rels,
1553 buildSetOfIncompleteMembers(relation),
1554 Main.map.mapView.getEditLayer()
1555 ));
1556 }
1557 }
1558
1559 abstract class AbstractCopyAction extends AbstractAction {
1560
1561 protected abstract Collection<String> getString(OsmPrimitive p, String key);
1562
1563 @Override
1564 public void actionPerformed(ActionEvent ae) {
1565 if (propertyTable.getSelectedRowCount() != 1)
1566 return;
1567 String key = propertyData.getValueAt(propertyTable.getSelectedRow(), 0).toString();
1568 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1569 if (sel.isEmpty())
1570 return;
1571 Set<String> values = new TreeSet<String>();
1572 for (OsmPrimitive p : sel) {
1573 Collection<String> s = getString(p,key);
1574 if (s != null) {
1575 values.addAll(s);
1576 }
1577 }
1578 Utils.copyToClipboard(Utils.join("\n", values));
1579 }
1580 }
1581
1582 class CopyValueAction extends AbstractCopyAction {
1583
1584 public CopyValueAction() {
1585 putValue(NAME, tr("Copy Value"));
1586 putValue(SHORT_DESCRIPTION, tr("Copy the value of the selected tag to clipboard"));
1587 }
1588
1589 @Override
1590 protected Collection<String> getString(OsmPrimitive p, String key) {
1591 String v = p.get(key);
1592 return v == null ? null : Collections.singleton(v);
1593 }
1594 }
1595
1596 class CopyKeyValueAction extends AbstractCopyAction {
1597
1598 public CopyKeyValueAction() {
1599 putValue(NAME, tr("Copy Key/Value"));
1600 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the selected tag to clipboard"));
1601 }
1602
1603 @Override
1604 protected Collection<String> getString(OsmPrimitive p, String key) {
1605 String v = p.get(key);
1606 return v == null ? null : Collections.singleton(new Tag(key, v).toString());
1607 }
1608 }
1609
1610 class CopyAllKeyValueAction extends AbstractCopyAction {
1611
1612 public CopyAllKeyValueAction() {
1613 putValue(NAME, tr("Copy all Keys/Values"));
1614 putValue(SHORT_DESCRIPTION, tr("Copy the key and value of the all tags to clipboard"));
1615 }
1616
1617 @Override
1618 protected Collection<String> getString(OsmPrimitive p, String key) {
1619 List<String> r = new LinkedList<String>();
1620 for (Entry<String, String> kv : p.getKeys().entrySet()) {
1621 r.add(new Tag(kv.getKey(), kv.getValue()).toString());
1622 }
1623 return r;
1624 }
1625 }
1626
1627 class SearchAction extends AbstractAction {
1628 final boolean sameType;
1629
1630 public SearchAction(boolean sameType) {
1631 this.sameType = sameType;
1632 if (sameType) {
1633 putValue(NAME, tr("Search Key/Value/Type"));
1634 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag, restrict to type (i.e., node/way/relation)"));
1635 } else {
1636 putValue(NAME, tr("Search Key/Value"));
1637 putValue(SHORT_DESCRIPTION, tr("Search with the key and value of the selected tag"));
1638 }
1639 }
1640
1641 public void actionPerformed(ActionEvent e) {
1642 if (propertyTable.getSelectedRowCount() != 1)
1643 return;
1644 String key = propertyData.getValueAt(propertyTable.getSelectedRow(), 0).toString();
1645 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
1646 if (sel.isEmpty())
1647 return;
1648 String sep = "";
1649 String s = "";
1650 for (OsmPrimitive p : sel) {
1651 String val = p.get(key);
1652 if (val == null) {
1653 continue;
1654 }
1655 String t = "";
1656 if (!sameType) {
1657 t = "";
1658 } else if (p instanceof Node) {
1659 t = "type:node ";
1660 } else if (p instanceof Way) {
1661 t = "type:way ";
1662 } else if (p instanceof Relation) {
1663 t = "type:relation ";
1664 }
1665 s += sep + "(" + t + "\"" +
1666 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(key) + "\"=\"" +
1667 org.openstreetmap.josm.actions.search.SearchAction.escapeStringForSearch(val) + "\")";
1668 sep = " OR ";
1669 }
1670
1671 SearchSetting ss = new SearchSetting(s, SearchMode.replace, true, false, false);
1672 org.openstreetmap.josm.actions.search.SearchAction.searchWithoutHistory(ss);
1673 }
1674 }
1675}
Note: See TracBrowser for help on using the repository browser.