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

Last change on this file since 1970 was 1970, checked in by Gubaer, 15 years ago

fixed #3252: selected primitives after deleting a data layer

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