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

Last change on this file since 1606 was 1606, checked in by stoecker, 15 years ago

close #2226 - patch by xeen - make presets in right menu clickable

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