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

Last change on this file since 1925 was 1925, checked in by jttt, 15 years ago

Replaced Relation.members with Relation.getMembers()

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