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

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

Replace some occurrences of RelationMember.member with getters

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