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

Last change on this file since 2348 was 2348, checked in by Gubaer, 14 years ago

applied #3780: patch by hansendc: Shift selection is broken

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