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

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

Moved layer listener management from Layer to MapView
Made sure that listeners also unregister when they register for layer change events.

This will certainly break plugins. Plugin updates will follow later.

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