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

Last change on this file since 3592 was 3592, checked in by bastiK, 14 years ago

fixed #5465 (based on patch by cmuelle8) - properties dialog tag-wiki-lookup does not have timeout (potentially blocks)

  • Property svn:eol-style set to native
File size: 49.3 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.Font;
11import java.awt.GridBagLayout;
12import java.awt.Point;
13import java.awt.event.ActionEvent;
14import java.awt.event.ActionListener;
15import java.awt.event.FocusAdapter;
16import java.awt.event.FocusEvent;
17import java.awt.event.KeyEvent;
18import java.awt.event.MouseAdapter;
19import java.awt.event.MouseEvent;
20import java.net.HttpURLConnection;
21import java.net.URI;
22import java.net.URLEncoder;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Comparator;
27import java.util.HashMap;
28import java.util.Iterator;
29import java.util.List;
30import java.util.Map;
31import java.util.Map.Entry;
32import java.util.TreeMap;
33import java.util.Vector;
34
35import javax.swing.AbstractAction;
36import javax.swing.Box;
37import javax.swing.DefaultListCellRenderer;
38import javax.swing.JComboBox;
39import javax.swing.JComponent;
40import javax.swing.JDialog;
41import javax.swing.JLabel;
42import javax.swing.JList;
43import javax.swing.JOptionPane;
44import javax.swing.JPanel;
45import javax.swing.JPopupMenu;
46import javax.swing.JScrollPane;
47import javax.swing.JTable;
48import javax.swing.KeyStroke;
49import javax.swing.ListSelectionModel;
50import javax.swing.event.ListSelectionEvent;
51import javax.swing.event.ListSelectionListener;
52import javax.swing.table.DefaultTableCellRenderer;
53import javax.swing.table.DefaultTableModel;
54import javax.swing.table.TableColumnModel;
55import javax.swing.table.TableModel;
56import javax.swing.text.JTextComponent;
57
58import org.openstreetmap.josm.Main;
59import org.openstreetmap.josm.command.ChangeCommand;
60import org.openstreetmap.josm.command.ChangePropertyCommand;
61import org.openstreetmap.josm.command.Command;
62import org.openstreetmap.josm.command.SequenceCommand;
63import org.openstreetmap.josm.data.SelectionChangedListener;
64import org.openstreetmap.josm.data.osm.Node;
65import org.openstreetmap.josm.data.osm.OsmPrimitive;
66import org.openstreetmap.josm.data.osm.Relation;
67import org.openstreetmap.josm.data.osm.RelationMember;
68import org.openstreetmap.josm.data.osm.Tag;
69import org.openstreetmap.josm.data.osm.Way;
70import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
71import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
72import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
73import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
74import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
75import org.openstreetmap.josm.gui.DefaultNameFormatter;
76import org.openstreetmap.josm.gui.ExtendedDialog;
77import org.openstreetmap.josm.gui.MapFrame;
78import org.openstreetmap.josm.gui.MapView;
79import org.openstreetmap.josm.gui.SideButton;
80import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
81import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel.PresetHandler;
82import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
83import org.openstreetmap.josm.gui.layer.OsmDataLayer;
84import org.openstreetmap.josm.gui.tagging.TaggingPreset;
85import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingComboBox;
86import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
87import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
88import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
89import org.openstreetmap.josm.tools.GBC;
90import org.openstreetmap.josm.tools.ImageProvider;
91import org.openstreetmap.josm.tools.LanguageInfo;
92import org.openstreetmap.josm.tools.OpenBrowser;
93import org.openstreetmap.josm.tools.Shortcut;
94
95/**
96 * This dialog displays the properties of the current selected primitives.
97 *
98 * If no object is selected, the dialog list is empty.
99 * If only one is selected, all properties of this object are selected.
100 * If more than one object are selected, the sum of all properties are displayed. If the
101 * different objects share the same property, the shared value is displayed. If they have
102 * different values, all of them are put in a combo box and the string "<different>"
103 * is displayed in italic.
104 *
105 * Below the list, the user can click on an add, modify and delete property button to
106 * edit the table selection value.
107 *
108 * The command is applied to all selected entries.
109 *
110 * @author imi
111 */
112public class PropertiesDialog extends ToggleDialog implements SelectionChangedListener, MapView.EditLayerChangeListener, DataSetListenerAdapter.Listener {
113 /**
114 * Watches for double clicks and from editing or new property, depending on the
115 * location, the click was.
116 * @author imi
117 */
118 public class DblClickWatch extends MouseAdapter {
119 @Override public void mouseClicked(MouseEvent e) {
120 if (e.getClickCount() < 2)
121 {
122 if (e.getSource() == propertyTable) {
123 membershipTable.clearSelection();
124 } else if (e.getSource() == membershipTable) {
125 propertyTable.clearSelection();
126 }
127 }
128 else if (e.getSource() == propertyTable)
129 {
130 int row = propertyTable.rowAtPoint(e.getPoint());
131 if (row > -1) {
132 propertyEdit(row);
133 }
134 } else if (e.getSource() == membershipTable) {
135 int row = membershipTable.rowAtPoint(e.getPoint());
136 if (row > -1) {
137 membershipEdit(row);
138 }
139 }
140 else
141 {
142 add();
143 }
144 }
145 @Override public void mousePressed(MouseEvent e) {
146 if (e.getSource() == propertyTable) {
147 membershipTable.clearSelection();
148 } else if (e.getSource() == membershipTable) {
149 propertyTable.clearSelection();
150 }
151 }
152 }
153
154 // hook for roadsigns plugin to display a small
155 // button in the upper right corner of this dialog
156 public static JPanel pluginHook = new JPanel();
157
158 private final Map<String, Map<String, Integer>> valueCount = new TreeMap<String, Map<String, Integer>>();
159
160 Comparator<AutoCompletionListItem> defaultACItemComparator = new Comparator<AutoCompletionListItem>() {
161 public int compare(AutoCompletionListItem o1, AutoCompletionListItem o2) {
162 return String.CASE_INSENSITIVE_ORDER.compare(o1.getValue(), o2.getValue());
163 }
164 };
165
166 private DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
167 private HelpAction helpAction = new HelpAction();
168 private AddAction addAction = new AddAction();
169 private Shortcut addActionShortcut = Shortcut.registerShortcut("properties:add", tr("Add Properties"), KeyEvent.VK_B,
170 Shortcut.GROUP_MNEMONIC);
171
172 @Override
173 public void showNotify() {
174 DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
175 SelectionEventManager.getInstance().addSelectionListener(this, FireMode.IN_EDT_CONSOLIDATED);
176 MapView.addEditLayerChangeListener(this);
177 updateSelection();
178 Main.registerActionShortcut(addAction, addActionShortcut);
179 }
180
181 @Override
182 public void hideNotify() {
183 DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
184 SelectionEventManager.getInstance().removeSelectionListener(this);
185 MapView.removeEditLayerChangeListener(this);
186 Main.unregisterActionShortcut(addAction, addActionShortcut);
187 }
188
189 /**
190 * Edit the value in the properties table row
191 * @param row The row of the table from which the value is edited.
192 */
193 @SuppressWarnings("unchecked")
194 void propertyEdit(int row) {
195 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
196 if (sel.isEmpty()) return;
197
198 String key = propertyData.getValueAt(row, 0).toString();
199 objKey=key;
200
201 String msg = "<html>"+trn("This will change {0} object.",
202 "This will change up to {0} objects.", sel.size(), sel.size())
203 +"<br><br>("+tr("An empty value deletes the tag.", key)+")</html>";
204
205 JPanel panel = new JPanel(new BorderLayout());
206 panel.add(new JLabel(msg), BorderLayout.NORTH);
207
208 JPanel p = new JPanel(new GridBagLayout());
209 panel.add(p, BorderLayout.CENTER);
210
211 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
212 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
213 Collections.sort(keyList, defaultACItemComparator);
214
215 final AutoCompletingComboBox keys = new AutoCompletingComboBox();
216 keys.setPossibleACItems(keyList);
217 keys.setEditable(true);
218 keys.setSelectedItem(key);
219
220 p.add(new JLabel(tr("Key")), GBC.std());
221 p.add(Box.createHorizontalStrut(10), GBC.std());
222 p.add(keys, GBC.eol().fill(GBC.HORIZONTAL));
223
224 final AutoCompletingComboBox values = new AutoCompletingComboBox();
225 values.setRenderer(new DefaultListCellRenderer() {
226 @Override public Component getListCellRendererComponent(JList list,
227 Object value, int index, boolean isSelected, boolean cellHasFocus){
228 Component c = super.getListCellRendererComponent(list, value,
229 index, isSelected, cellHasFocus);
230 if (c instanceof JLabel) {
231 String str = null;
232 str=((AutoCompletionListItem) value).getValue();
233 if (valueCount.containsKey(objKey)){
234 Map<String, Integer> m=valueCount.get(objKey);
235 if (m.containsKey(str)) {
236 str+="("+m.get(str)+")";
237 c.setFont(c.getFont().deriveFont(Font.ITALIC+Font.BOLD));
238 }
239 }
240 ((JLabel)c).setText(str);
241 }
242 return c;
243 }
244 });
245 values.setEditable(true);
246
247 List<AutoCompletionListItem> valueList = autocomplete.getValues(key);
248 Collections.sort(valueList, defaultACItemComparator);
249
250 values.setPossibleACItems(valueList);
251 Map<String, Integer> m=(Map<String, Integer>)propertyData.getValueAt(row, 1);
252 final String selection= m.size()!=1?tr("<different>"):m.entrySet().iterator().next().getKey();
253 values.setSelectedItem(selection);
254 values.getEditor().setItem(selection);
255 p.add(new JLabel(tr("Value")), GBC.std());
256 p.add(Box.createHorizontalStrut(10), GBC.std());
257 p.add(values, GBC.eol().fill(GBC.HORIZONTAL));
258 addFocusAdapter(row, keys, values, autocomplete);
259
260 final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
261 @Override public void selectInitialValue() {
262 values.requestFocusInWindow();
263 values.getEditor().selectAll();
264 }
265 };
266 final JDialog dlg = optionPane.createDialog(Main.parent, tr("Change values?"));
267 dlg.setModalityType(ModalityType.DOCUMENT_MODAL);
268
269 values.getEditor().addActionListener(new ActionListener() {
270 public void actionPerformed(ActionEvent e) {
271 dlg.setVisible(false);
272 optionPane.setValue(JOptionPane.OK_OPTION);
273 }
274 });
275
276 String oldValue = values.getEditor().getItem().toString();
277 dlg.setVisible(true);
278
279 Object answer = optionPane.getValue();
280 if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE ||
281 (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) {
282 values.getEditor().setItem(oldValue);
283 return;
284 }
285
286 String value = values.getEditor().getItem().toString().trim();
287 // is not Java 1.5
288 //value = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFC);
289 if (value.equals("")) {
290 value = null; // delete the key
291 }
292 String newkey = keys.getEditor().getItem().toString().trim();
293 //newkey = java.text.Normalizer.normalize(newkey, java.text.Normalizer.Form.NFC);
294 if (newkey.equals("")) {
295 newkey = key;
296 value = null; // delete the key instead
297 }
298 if (key.equals(newkey) && tr("<different>").equals(value))
299 return;
300 if (key.equals(newkey) || value == null) {
301 Main.main.undoRedo.add(new ChangePropertyCommand(sel, newkey, value));
302 } else {
303 Collection<Command> commands=new Vector<Command>();
304 commands.add(new ChangePropertyCommand(sel, key, null));
305 if (value.equals(tr("<different>"))) {
306 HashMap<String, Vector<OsmPrimitive>> map=new HashMap<String, Vector<OsmPrimitive>>();
307 for (OsmPrimitive osm: sel) {
308 String val=osm.get(key);
309 if(val != null)
310 {
311 if (map.containsKey(val)) {
312 map.get(val).add(osm);
313 } else {
314 Vector<OsmPrimitive> v = new Vector<OsmPrimitive>();
315 v.add(osm);
316 map.put(val, v);
317 }
318 }
319 }
320 for (Entry<String, Vector<OsmPrimitive>> e: map.entrySet()) {
321 commands.add(new ChangePropertyCommand(e.getValue(), newkey, e.getKey()));
322 }
323 } else {
324 commands.add(new ChangePropertyCommand(sel, newkey, value));
325 }
326 Main.main.undoRedo.add(new SequenceCommand(
327 trn("Change properties of up to {0} object",
328 "Change properties of up to {0} objects", sel.size(), sel.size()),
329 commands));
330 }
331
332 if(!key.equals(newkey)) {
333 for(int i=0; i < propertyTable.getRowCount(); i++)
334 if(propertyData.getValueAt(i, 0).toString().equals(newkey)) {
335 row=i;
336 break;
337 }
338 }
339 propertyTable.changeSelection(row, 0, false, false);
340 }
341
342 /**
343 * This simply fires up an relation editor for the relation shown; everything else
344 * is the editor's business.
345 *
346 * @param row
347 */
348 void membershipEdit(int row) {
349 Relation relation = (Relation)membershipData.getValueAt(row, 0);
350 Main.map.relationListDialog.selectRelation(relation);
351 RelationEditor.getEditor(
352 Main.map.mapView.getEditLayer(),
353 relation,
354 ((MemberInfo) membershipData.getValueAt(row, 1)).role).setVisible(true);
355 }
356
357 /**
358 * Open the add selection dialog and add a new key/value to the table (and
359 * to the dataset, of course).
360 */
361 void add() {
362 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
363 if (sel.isEmpty()) return;
364
365 JPanel p = new JPanel(new BorderLayout());
366 p.add(new JLabel("<html>"+trn("This will change up to {0} object.",
367 "This will change up to {0} objects.", sel.size(),sel.size())
368 +"<br><br>"+tr("Please select a key")), BorderLayout.NORTH);
369 final AutoCompletingComboBox keys = new AutoCompletingComboBox();
370 AutoCompletionManager autocomplete = Main.main.getEditLayer().data.getAutoCompletionManager();
371 List<AutoCompletionListItem> keyList = autocomplete.getKeys();
372
373 // remove the object's tag keys from the list
374 Iterator<AutoCompletionListItem> iter = keyList.iterator();
375 while (iter.hasNext()) {
376 AutoCompletionListItem item = iter.next();
377 for (int i = 0; i < propertyData.getRowCount(); ++i) {
378 if (item.getValue().equals(propertyData.getValueAt(i, 0))) {
379 iter.remove();
380 break;
381 }
382 }
383 }
384
385 Collections.sort(keyList, defaultACItemComparator);
386 keys.setPossibleACItems(keyList);
387 keys.setEditable(true);
388
389 p.add(keys, BorderLayout.CENTER);
390
391 JPanel p2 = new JPanel(new BorderLayout());
392 p.add(p2, BorderLayout.SOUTH);
393 p2.add(new JLabel(tr("Please select a value")), BorderLayout.NORTH);
394 final AutoCompletingComboBox values = new AutoCompletingComboBox();
395 values.setEditable(true);
396 p2.add(values, BorderLayout.CENTER);
397
398 FocusAdapter focus = addFocusAdapter(-1, keys, values, autocomplete);
399 // fire focus event in advance or otherwise the popup list will be too small at first
400 focus.focusGained(null);
401
402 JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
403 @Override public void selectInitialValue() {
404 keys.requestFocusInWindow();
405 keys.getEditor().selectAll();
406 }
407 };
408 JDialog dialog = pane.createDialog(Main.parent, tr("Change values?"));
409 dialog.setModalityType(ModalityType.DOCUMENT_MODAL);
410 dialog.setVisible(true);
411
412 if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
413 return;
414 String key = keys.getEditor().getItem().toString().trim();
415 String value = values.getEditor().getItem().toString().trim();
416 if (value.equals(""))
417 return;
418 Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, value));
419 btnAdd.requestFocusInWindow();
420 }
421
422 /**
423 * @param allData
424 * @param keys
425 * @param values
426 */
427 private FocusAdapter addFocusAdapter(final int row, final AutoCompletingComboBox keys, final AutoCompletingComboBox values, final AutoCompletionManager autocomplete) {
428 // get the combo box' editor component
429 JTextComponent editor = (JTextComponent)values.getEditor()
430 .getEditorComponent();
431 // Refresh the values model when focus is gained
432 FocusAdapter focus = new FocusAdapter() {
433 @Override public void focusGained(FocusEvent e) {
434 String key = keys.getEditor().getItem().toString();
435
436 List<AutoCompletionListItem> valueList = autocomplete.getValues(key);
437 Collections.sort(valueList, defaultACItemComparator);
438
439 values.setPossibleACItems(valueList);
440 objKey=key;
441 }
442 };
443 editor.addFocusListener(focus);
444 return focus;
445 }
446 private String objKey;
447
448 /**
449 * The property data.
450 */
451 private final DefaultTableModel propertyData = new DefaultTableModel() {
452 @Override public boolean isCellEditable(int row, int column) {
453 return false;
454 }
455 @Override public Class<?> getColumnClass(int columnIndex) {
456 return String.class;
457 }
458 };
459
460 /**
461 * The membership data.
462 */
463 private final DefaultTableModel membershipData = new DefaultTableModel() {
464 @Override public boolean isCellEditable(int row, int column) {
465 return false;
466 }
467 @Override public Class<?> getColumnClass(int columnIndex) {
468 return String.class;
469 }
470 };
471
472 /**
473 * The properties list.
474 */
475 private final JTable propertyTable = new JTable(propertyData);
476 private final JTable membershipTable = new JTable(membershipData);
477
478 public JComboBox taggingPresets = new JComboBox();
479
480 /**
481 * The Add/Edit/Delete buttons (needed to be able to disable them)
482 */
483 private final SideButton btnAdd;
484 private final SideButton btnEdit;
485 private final SideButton btnDel;
486 private final PresetListPanel presets = new PresetListPanel();
487
488 private final JLabel selectSth = new JLabel("<html><p>"
489 + tr("Please select the objects you want to change properties for.") + "</p></html>");
490
491 static class MemberInfo {
492 List<RelationMember> role = new ArrayList<RelationMember>();
493 List<Integer> position = new ArrayList<Integer>();
494 private String positionString = null;
495 void add(RelationMember r, Integer p)
496 {
497 role.add(r);
498 position.add(p);
499 }
500 String getPositionString()
501 {
502 if(positionString == null)
503 {
504 Collections.sort(position);
505 positionString = String.valueOf(position.get(0));
506 int cnt = 0;
507 int last = position.get(0);
508 for(int i = 1; i < position.size(); ++i) {
509 int cur = position.get(i);
510 if(cur == last+1) {
511 ++cnt;
512 } else {
513 if(cnt == 1) {
514 positionString += ","+String.valueOf(last);
515 } else if(cnt > 1) {
516 positionString += "-"+String.valueOf(last);
517 }
518 positionString += "-"+String.valueOf(cur);
519 cnt = 0;
520 }
521 last = cur;
522 }
523 if(cnt == 1) {
524 positionString += ","+String.valueOf(last);
525 } else if(cnt > 1) {
526 positionString += "-"+String.valueOf(last);
527 }
528 }
529 if(positionString.length() > 20) {
530 positionString = positionString.substring(0,17)+"...";
531 }
532 return positionString;
533 }
534 }
535
536 /**
537 * Create a new PropertiesDialog
538 */
539 public PropertiesDialog(MapFrame mapFrame) {
540 super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
541 Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
542 Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
543
544 // setting up the properties table
545 propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
546 propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
547 propertyTable.addMouseListener(new PopupMenuLauncher() {
548 @Override
549 public void launch(MouseEvent evt) {
550 Point p = evt.getPoint();
551 int row = propertyTable.rowAtPoint(p);
552 if (row > -1) {
553 propertyTable.changeSelection(row, 0, false, false);
554 JPopupMenu menu = new JPopupMenu();
555 menu.add(helpAction);
556 menu.show(propertyTable, p.x, p.y-3);
557 }
558 }
559 });
560
561 propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer(){
562 @Override public Component getTableCellRendererComponent(JTable table, Object value,
563 boolean isSelected, boolean hasFocus, int row, int column) {
564 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
565 if (c instanceof JLabel) {
566 String str = null;
567 if (value instanceof String) {
568 str = (String) value;
569 } else if (value instanceof Map<?, ?>) {
570 Map<?, ?> v = (Map<?, ?>) value;
571 if (v.size() != 1) {
572 str=tr("<different>");
573 c.setFont(c.getFont().deriveFont(Font.ITALIC));
574 } else {
575 final Map.Entry<?, ?> entry = v.entrySet().iterator().next();
576 str = (String) entry.getKey();
577 }
578 }
579 ((JLabel)c).setText(str);
580 }
581 return c;
582 }
583 });
584
585 // setting up the membership table
586
587 membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role"),tr("Position")});
588 membershipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
589 membershipTable.addMouseListener(new PopupMenuLauncher() {
590 @Override
591 public void launch(MouseEvent evt) {
592 Point p = evt.getPoint();
593 int row = membershipTable.rowAtPoint(p);
594 if (row > -1) {
595 membershipTable.changeSelection(row, 0, false, false);
596 JPopupMenu menu = new JPopupMenu();
597 Relation relation = (Relation)membershipData.getValueAt(row, 0);
598 menu.add(new SelectRelationAction(relation, true));
599 menu.add(new SelectRelationAction(relation, false));
600 menu.addSeparator();
601 menu.add(helpAction);
602 menu.show(membershipTable, p.x, p.y-3);
603 }
604 }
605 });
606
607 TableColumnModel mod = membershipTable.getColumnModel();
608 mod.getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
609 @Override public Component getTableCellRendererComponent(JTable table, Object value,
610 boolean isSelected, boolean hasFocus, int row, int column) {
611 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
612 if (c instanceof JLabel) {
613 JLabel label = (JLabel)c;
614 Relation r = (Relation)value;
615 label.setText(r.getDisplayName(DefaultNameFormatter.getInstance()));
616 if (r.isDisabledAndHidden()) {
617 label.setFont(label.getFont().deriveFont(Font.ITALIC));
618 }
619 }
620 return c;
621 }
622 });
623
624 mod.getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
625 @Override public Component getTableCellRendererComponent(JTable table, Object value,
626 boolean isSelected, boolean hasFocus, int row, int column) {
627 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
628 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
629 if (c instanceof JLabel) {
630 JLabel label = (JLabel)c;
631 MemberInfo col = (MemberInfo) value;
632
633 String text = null;
634 for (RelationMember r : col.role) {
635 if (text == null) {
636 text = r.getRole();
637 }
638 else if (!text.equals(r.getRole())) {
639 text = tr("<different>");
640 break;
641 }
642 }
643
644 label.setText(text);
645 if (isDisabledAndHidden) {
646 label.setFont(label.getFont().deriveFont(Font.ITALIC));
647 }
648 }
649 return c;
650 }
651 });
652
653 mod.getColumn(2).setCellRenderer(new DefaultTableCellRenderer() {
654 @Override public Component getTableCellRendererComponent(JTable table, Object value,
655 boolean isSelected, boolean hasFocus, int row, int column) {
656 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
657 boolean isDisabledAndHidden = (((Relation)table.getValueAt(row, 0))).isDisabledAndHidden();
658 if (c instanceof JLabel) {
659 JLabel label = (JLabel)c;
660 label.setText(((MemberInfo) table.getValueAt(row, 1)).getPositionString());
661 if (isDisabledAndHidden) {
662 label.setFont(label.getFont().deriveFont(Font.ITALIC));
663 }
664 }
665 return c;
666 }
667 });
668 mod.getColumn(2).setPreferredWidth(20);
669 mod.getColumn(1).setPreferredWidth(40);
670 mod.getColumn(0).setPreferredWidth(200);
671
672 // combine both tables and wrap them in a scrollPane
673 JPanel bothTables = new JPanel();
674 boolean top = Main.pref.getBoolean("properties.presets.top", true);
675 bothTables.setLayout(new GridBagLayout());
676 if(top) {
677 bothTables.add(presets, GBC.std().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2).anchor(GBC.NORTHWEST));
678 double epsilon = Double.MIN_VALUE; // need to set a weight or else anchor value is ignored
679 bothTables.add(pluginHook, GBC.eol().insets(0,1,1,1).anchor(GBC.NORTHEAST).weight(epsilon, epsilon));
680 }
681 bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10));
682 bothTables.add(propertyTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
683 bothTables.add(propertyTable, GBC.eol().fill(GBC.BOTH));
684 bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
685 bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
686 if(!top) {
687 bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2));
688 }
689
690 DblClickWatch dblClickWatch = new DblClickWatch();
691 propertyTable.addMouseListener(dblClickWatch);
692 membershipTable.addMouseListener(dblClickWatch);
693 JScrollPane scrollPane = new JScrollPane(bothTables);
694 scrollPane.addMouseListener(dblClickWatch);
695 add(scrollPane, BorderLayout.CENTER);
696
697 selectSth.setPreferredSize(scrollPane.getSize());
698 presets.setSize(scrollPane.getSize());
699
700 JPanel buttonPanel = getButtonPanel(3);
701
702 // -- add action and shortcut
703 this.btnAdd = new SideButton(addAction);
704 btnAdd.setFocusable(true);
705 buttonPanel.add(this.btnAdd);
706 btnAdd.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "onEnter");
707 btnAdd.getActionMap().put("onEnter", addAction);
708
709 // -- edit action
710 //
711 EditAction editAction = new EditAction();
712 propertyTable.getSelectionModel().addListSelectionListener(editAction);
713 membershipTable.getSelectionModel().addListSelectionListener(editAction);
714 this.btnEdit = new SideButton(editAction);
715 buttonPanel.add(this.btnEdit);
716
717 // -- delete action
718 //
719 DeleteAction deleteAction = new DeleteAction();
720 this.btnDel = new SideButton(deleteAction);
721 membershipTable.getSelectionModel().addListSelectionListener(deleteAction);
722 propertyTable.getSelectionModel().addListSelectionListener(deleteAction);
723 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
724 KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
725 );
726 getActionMap().put("delete", deleteAction);
727 buttonPanel.add(this.btnDel);
728 add(buttonPanel, BorderLayout.SOUTH);
729
730 // -- help action
731 //
732 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
733 KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), "onHelp");
734 getActionMap().put("onHelp", helpAction);
735 }
736
737 @Override public void setVisible(boolean b) {
738 super.setVisible(b);
739 if (b && Main.main.getCurrentDataSet() != null) {
740 selectionChanged(Main.main.getCurrentDataSet().getSelected());
741 }
742 }
743
744 private int findRow(TableModel model, Object value) {
745 for (int i=0; i<model.getRowCount(); i++) {
746 if (model.getValueAt(i, 0).equals(value))
747 return i;
748 }
749 return -1;
750 }
751
752 private PresetHandler presetHandler = new PresetHandler() {
753
754 @Override
755 public void updateTags(List<Tag> tags) {
756 Command command = TaggingPreset.createCommand(getSelection(), tags);
757 if (command != null) {
758 Main.main.undoRedo.add(command);
759 }
760 }
761
762 @Override
763 public Collection<OsmPrimitive> getSelection() {
764 if (Main.main == null) return null;
765 if (Main.main.getCurrentDataSet() == null) return null;
766
767 return Main.main.getCurrentDataSet().getSelected();
768 }
769 };
770
771 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
772 if (!isVisible())
773 return;
774 if (propertyTable == null)
775 return; // selection changed may be received in base class constructor before init
776 if (propertyTable.getCellEditor() != null) {
777 propertyTable.getCellEditor().cancelCellEditing();
778 }
779
780 String selectedTag = null;
781 Relation selectedRelation = null;
782 if (propertyTable.getSelectedRowCount() == 1) {
783 selectedTag = (String)propertyData.getValueAt(propertyTable.getSelectedRow(), 0);
784 }
785 if (membershipTable.getSelectedRowCount() == 1) {
786 selectedRelation = (Relation)membershipData.getValueAt(membershipTable.getSelectedRow(), 0);
787 }
788
789 // re-load property data
790 propertyData.setRowCount(0);
791 int nodes = 0;
792 int ways = 0;
793 int relations = 0;
794 int closedways = 0;
795
796 Map<String, Integer> keyCount = new HashMap<String, Integer>();
797 valueCount.clear();
798 for (OsmPrimitive osm : newSelection) {
799 if(osm instanceof Node) {
800 ++nodes;
801 } else if(osm instanceof Relation) {
802 ++relations;
803 } else if(((Way)osm).isClosed()) {
804 ++closedways;
805 } else {
806 ++ways;
807 }
808 for (String key: osm.keySet()) {
809 String value = osm.get(key);
810 keyCount.put(key, keyCount.containsKey(key) ? keyCount.get(key) + 1 : 1);
811 if (valueCount.containsKey(key)) {
812 Map<String, Integer> v = valueCount.get(key);
813 v.put(value, v.containsKey(value)? v.get(value) + 1 : 1 );
814 } else {
815 TreeMap<String,Integer> v = new TreeMap<String, Integer>();
816 v.put(value, 1);
817 valueCount.put(key, v);
818 }
819 }
820 }
821 for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) {
822 int count=0;
823 for (Entry<String, Integer> e1: e.getValue().entrySet()) {
824 count+=e1.getValue();
825 }
826 if (count < newSelection.size()) {
827 e.getValue().put("", newSelection.size()-count);
828 }
829 propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
830 }
831
832 membershipData.setRowCount(0);
833
834 Map<Relation, MemberInfo> roles = new HashMap<Relation, MemberInfo>();
835 for (OsmPrimitive primitive: newSelection) {
836 for (OsmPrimitive ref: primitive.getReferrers()) {
837 if (ref instanceof Relation && !ref.isIncomplete() && !ref.isDeleted()) {
838 Relation r = (Relation) ref;
839 MemberInfo mi = roles.get(r);
840 if(mi == null) {
841 mi = new MemberInfo();
842 }
843 roles.put(r, mi);
844 int i = 1;
845 for (RelationMember m : r.getMembers()) {
846 if (m.getMember() == primitive) {
847 mi.add(m, i);
848 }
849 ++i;
850 }
851 }
852 }
853 }
854
855 List<Relation> sortedRelations = new ArrayList<Relation>(roles.keySet());
856 Collections.sort(sortedRelations, new Comparator<Relation>() {
857 public int compare(Relation o1, Relation o2) {
858 int comp = Boolean.valueOf(o1.isDisabledAndHidden()).compareTo(o2.isDisabledAndHidden());
859 if (comp == 0) {
860 comp = o1.getDisplayName(DefaultNameFormatter.getInstance()).compareTo(o2.getDisplayName(DefaultNameFormatter.getInstance()));
861 }
862 return comp;
863 }}
864 );
865
866 for (Relation r: sortedRelations) {
867 membershipData.addRow(new Object[]{r, roles.get(r)});
868 }
869
870 presets.updatePresets(nodes, ways, relations, closedways, valueCount, presetHandler);
871
872 membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
873 membershipTable.setVisible(membershipData.getRowCount() > 0);
874
875 boolean hasSelection = !newSelection.isEmpty();
876 boolean hasTags = hasSelection && propertyData.getRowCount() > 0;
877 boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0;
878 btnAdd.setEnabled(hasSelection);
879 btnEdit.setEnabled(hasTags || hasMemberships);
880 btnDel.setEnabled(hasTags || hasMemberships);
881 propertyTable.setVisible(hasTags);
882 propertyTable.getTableHeader().setVisible(hasTags);
883 selectSth.setVisible(!hasSelection);
884 pluginHook.setVisible(hasSelection);
885
886 int selectedIndex;
887 if (selectedTag != null && (selectedIndex = findRow(propertyData, selectedTag)) != -1) {
888 propertyTable.changeSelection(selectedIndex, 0, false, false);
889 } else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) {
890 membershipTable.changeSelection(selectedIndex, 0, false, false);
891 } else if(hasTags) {
892 propertyTable.changeSelection(0, 0, false, false);
893 } else if(hasMemberships) {
894 membershipTable.changeSelection(0, 0, false, false);
895 }
896
897 if(propertyData.getRowCount() != 0 || membershipData.getRowCount() != 0) {
898 setTitle(tr("Properties: {0} / Memberships: {1}",
899 propertyData.getRowCount(), membershipData.getRowCount()));
900 } else {
901 setTitle(tr("Properties / Memberships"));
902 }
903 }
904
905 private void updateSelection() {
906 if (Main.main.getCurrentDataSet() == null) {
907 selectionChanged(Collections.<OsmPrimitive>emptyList());
908 } else {
909 selectionChanged(Main.main.getCurrentDataSet().getSelected());
910 }
911 }
912
913 /* ---------------------------------------------------------------------------------- */
914 /* EditLayerChangeListener */
915 /* ---------------------------------------------------------------------------------- */
916 public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
917 updateSelection();
918 }
919
920 public void processDatasetEvent(AbstractDatasetChangedEvent event) {
921 updateSelection();
922 }
923
924 class DeleteAction extends AbstractAction implements ListSelectionListener {
925
926 protected void deleteProperty(int row){
927 String key = propertyData.getValueAt(row, 0).toString();
928
929 String nextKey = null;
930 int rowCount = propertyData.getRowCount();
931 if (rowCount > 1) {
932 nextKey = (String)propertyData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
933 }
934
935 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
936 Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, null));
937
938 membershipTable.clearSelection();
939 if (nextKey != null) {
940 propertyTable.changeSelection(findRow(propertyData, nextKey), 0, false, false);
941 }
942 }
943
944 protected void deleteFromRelation(int row) {
945 Relation cur = (Relation)membershipData.getValueAt(row, 0);
946
947 Relation nextRelation = null;
948 int rowCount = membershipTable.getRowCount();
949 if (rowCount > 1) {
950 nextRelation = (Relation)membershipData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
951 }
952
953 ExtendedDialog ed = new ExtendedDialog(Main.parent,
954 tr("Change relation"),
955 new String[] {tr("Delete from relation"), tr("Cancel")});
956 ed.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"});
957 ed.setContent(tr("Really delete selection from relation {0}?", cur.getDisplayName(DefaultNameFormatter.getInstance())));
958 ed.showDialog();
959
960 if(ed.getValue() != 1)
961 return;
962
963 Relation rel = new Relation(cur);
964 Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
965 for (OsmPrimitive primitive: sel) {
966 rel.removeMembersFor(primitive);
967 }
968 Main.main.undoRedo.add(new ChangeCommand(cur, rel));
969
970 propertyTable.clearSelection();
971 if (nextRelation != null) {
972 membershipTable.changeSelection(findRow(membershipData, nextRelation), 0, false, false);
973 }
974 }
975
976 public DeleteAction() {
977 putValue(NAME, tr("Delete"));
978 putValue(SHORT_DESCRIPTION, tr("Delete the selected key in all objects"));
979 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
980 Shortcut s = Shortcut.registerShortcut("properties:delete", tr("Delete Properties"), KeyEvent.VK_Q,
981 Shortcut.GROUP_MNEMONIC);
982 putValue(MNEMONIC_KEY, (int) KeyEvent.getKeyText(s.getAssignedKey()).charAt(0));
983 updateEnabledState();
984 }
985
986 public void actionPerformed(ActionEvent e) {
987 if (propertyTable.getSelectedRowCount() >0 ) {
988 int row = propertyTable.getSelectedRow();
989 deleteProperty(row);
990 } else if (membershipTable.getSelectedRowCount() > 0) {
991 int row = membershipTable.getSelectedRow();
992 deleteFromRelation(row);
993 }
994 }
995
996 protected void updateEnabledState() {
997 setEnabled(
998 PropertiesDialog.this.propertyTable.getSelectedRowCount() >0
999 || PropertiesDialog.this.membershipTable.getSelectedRowCount() > 0
1000 );
1001 }
1002
1003 public void valueChanged(ListSelectionEvent e) {
1004 updateEnabledState();
1005 }
1006 }
1007
1008 class AddAction extends AbstractAction {
1009 public AddAction() {
1010 putValue(NAME, tr("Add"));
1011 putValue(SHORT_DESCRIPTION, tr("Add a new key/value pair to all objects"));
1012 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1013 }
1014
1015 public void actionPerformed(ActionEvent e) {
1016 add();
1017 }
1018 }
1019
1020 class EditAction extends AbstractAction implements ListSelectionListener {
1021 public EditAction() {
1022 putValue(NAME, tr("Edit"));
1023 putValue(SHORT_DESCRIPTION, tr("Edit the value of the selected key for all objects"));
1024 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1025 updateEnabledState();
1026 }
1027
1028 public void actionPerformed(ActionEvent e) {
1029 if (!isEnabled())
1030 return;
1031 if (propertyTable.getSelectedRowCount() == 1) {
1032 int row = propertyTable.getSelectedRow();
1033 propertyEdit(row);
1034 } else if (membershipTable.getSelectedRowCount() == 1) {
1035 int row = membershipTable.getSelectedRow();
1036 membershipEdit(row);
1037 }
1038 }
1039
1040 protected void updateEnabledState() {
1041 setEnabled(
1042 propertyTable.getSelectedRowCount() == 1
1043 ^ membershipTable.getSelectedRowCount() == 1
1044 );
1045 }
1046
1047 public void valueChanged(ListSelectionEvent e) {
1048 updateEnabledState();
1049 }
1050 }
1051
1052 class HelpAction extends AbstractAction {
1053 public HelpAction() {
1054 putValue(NAME, tr("Go to OSM wiki for tag help (F1)"));
1055 putValue(SHORT_DESCRIPTION, tr("Launch browser with wiki help to selected object"));
1056 putValue(SMALL_ICON, ImageProvider.get("dialogs", "search"));
1057 }
1058
1059 public void actionPerformed(ActionEvent e) {
1060 try {
1061 String base = Main.pref.get("url.openstreetmap-wiki", "http://wiki.openstreetmap.org/wiki/");
1062 String lang = LanguageInfo.getWikiLanguagePrefix();
1063 final List<URI> uris = new ArrayList<URI>();
1064 int row;
1065 if (propertyTable.getSelectedRowCount() == 1) {
1066 row = propertyTable.getSelectedRow();
1067 String key = URLEncoder.encode(propertyData.getValueAt(row, 0).toString(), "UTF-8");
1068 String val = URLEncoder.encode(
1069 ((Map<String,Integer>)propertyData.getValueAt(row, 1))
1070 .entrySet().iterator().next().getKey(), "UTF-8"
1071 );
1072
1073 uris.add(new URI(String.format("%s%sTag:%s=%s", base, lang, key, val)));
1074 uris.add(new URI(String.format("%sTag:%s=%s", base, key, val)));
1075 uris.add(new URI(String.format("%s%sKey:%s", base, lang, key)));
1076 uris.add(new URI(String.format("%sKey:%s", base, key)));
1077 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1078 uris.add(new URI(String.format("%sMap_Features", base)));
1079 } else if (membershipTable.getSelectedRowCount() == 1) {
1080 row = membershipTable.getSelectedRow();
1081 String type = URLEncoder.encode(
1082 ((Relation)membershipData.getValueAt(row, 0)).get("type"), "UTF-8"
1083 );
1084
1085 if (type != null && !type.equals("")) {
1086 uris.add(new URI(String.format("%s%sRelation:%s", base, lang, type)));
1087 uris.add(new URI(String.format("%sRelation:%s", base, type)));
1088 }
1089
1090 uris.add(new URI(String.format("%s%sRelations", base, lang)));
1091 uris.add(new URI(String.format("%sRelations", base)));
1092 } else {
1093 // give the generic help page, if more than one element is selected
1094 uris.add(new URI(String.format("%s%sMap_Features", base, lang)));
1095 uris.add(new URI(String.format("%sMap_Features", base)));
1096 }
1097
1098 Main.worker.execute(new Runnable(){
1099 public void run() {
1100 try {
1101 // find a page that actually exists in the wiki
1102 HttpURLConnection conn;
1103 for (URI u : uris) {
1104 conn = (HttpURLConnection) u.toURL().openConnection();
1105 conn.setConnectTimeout(5000);
1106
1107 if (conn.getResponseCode() != 200) {
1108 System.out.println("INFO: " + u + " does not exist");
1109 conn.disconnect();
1110 } else {
1111 int osize = conn.getContentLength();
1112 conn.disconnect();
1113
1114 conn = (HttpURLConnection) new URI(u.toString()
1115 .replace("=", "%3D") /* do not URLencode whole string! */
1116 .replaceFirst("/wiki/", "/w/index.php?redirect=no&title=")
1117 ).toURL().openConnection();
1118 conn.setConnectTimeout(5000);
1119
1120 /* redirect pages have different content length, but retrieving a "nonredirect"
1121 * page using index.php and the direct-link method gives slightly different
1122 * content lengths, so we have to be fuzzy.. (this is UGLY, recode if u know better)
1123 */
1124 if (Math.abs(conn.getContentLength() - osize) > 200) {
1125 System.out.println("INFO: " + u + " is a mediawiki redirect");
1126 conn.disconnect();
1127 } else {
1128 System.out.println("INFO: browsing to " + u);
1129 conn.disconnect();
1130
1131 OpenBrowser.displayUrl(u.toString());
1132 break;
1133 }
1134 }
1135 }
1136 } catch (Exception e) {
1137 e.printStackTrace();
1138 }
1139 }
1140 });
1141 } catch (Exception e1) {
1142 e1.printStackTrace();
1143 }
1144 }
1145 }
1146
1147 static class SelectRelationAction extends AbstractAction {
1148 boolean selectionmode;
1149 Relation relation;
1150 public SelectRelationAction(Relation r, boolean select) {
1151 selectionmode = select;
1152 relation = r;
1153 if(select) {
1154 putValue(NAME, tr("Select relation"));
1155 putValue(SHORT_DESCRIPTION, tr("Select relation in main selection."));
1156 putValue(SMALL_ICON, ImageProvider.get("dialogs", "select"));
1157 } else {
1158 putValue(NAME, tr("Select in relation list"));
1159 putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
1160 putValue(SMALL_ICON, ImageProvider.get("dialogs", "relationlist"));
1161 }
1162 }
1163
1164 public void actionPerformed(ActionEvent e) {
1165 if(selectionmode) {
1166 Main.map.mapView.getEditLayer().data.setSelected(relation);
1167 } else {
1168 Main.map.relationListDialog.selectRelation(relation);
1169 }
1170 }
1171 }
1172}
Note: See TracBrowser for help on using the repository browser.