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

Last change on this file since 4773 was 4773, checked in by bastiK, 12 years ago

applied #6883 - property toggle dialog: possibility to select and perform actions on several entries at once. (patch by joshdoe)

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