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

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

fix #5933 - tagging presets: allow to change the matching process (match=none|key|key!|keyvalue), remove delete_if_empty, default defaults to "", adapted comments in defaultpresets.xml, refactoring of the matching process (removes some duplicate code and some magical arithmetic)

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