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

Last change on this file since 3219 was 3219, checked in by jttt, 14 years ago

Fix #4976 filter : membership not shown in tag-list

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