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

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

changes needed for new RoadSigns plugin

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