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

Last change on this file since 5278 was 5278, checked in by simon04, 12 years ago

fix #7617 - Properties/Memberships shortcuts fail after closing window

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