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

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

autocompletion cleanup - fixes #2729

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