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

Last change on this file since 4102 was 4102, checked in by bastiK, 13 years ago

fixed #4662 (patch by Hojoe) - dialog box going out of bound when tag is too long

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