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

Last change on this file since 290 was 290, checked in by imi, 17 years ago
  • added support for multiple data layers
File size: 14.8 KB
Line 
1package org.openstreetmap.josm.gui.dialogs;
2
3import static org.openstreetmap.josm.tools.I18n.tr;
4import static org.openstreetmap.josm.tools.I18n.trn;
5import static org.xnap.commons.i18n.I18n.marktr;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.Font;
10import java.awt.GridBagLayout;
11import java.awt.GridLayout;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.FocusAdapter;
15import java.awt.event.FocusEvent;
16import java.awt.event.KeyEvent;
17import java.awt.event.MouseAdapter;
18import java.awt.event.MouseEvent;
19import java.util.Collection;
20import java.util.HashMap;
21import java.util.Map;
22import java.util.TreeMap;
23import java.util.TreeSet;
24import java.util.Vector;
25import java.util.Map.Entry;
26
27import javax.swing.Box;
28import javax.swing.DefaultComboBoxModel;
29import javax.swing.JButton;
30import javax.swing.JComboBox;
31import javax.swing.JDialog;
32import javax.swing.JLabel;
33import javax.swing.JOptionPane;
34import javax.swing.JPanel;
35import javax.swing.JScrollPane;
36import javax.swing.JTable;
37import javax.swing.JTextField;
38import javax.swing.ListSelectionModel;
39import javax.swing.table.DefaultTableCellRenderer;
40import javax.swing.table.DefaultTableModel;
41import javax.swing.text.JTextComponent;
42
43import org.openstreetmap.josm.Main;
44import org.openstreetmap.josm.command.ChangePropertyCommand;
45import org.openstreetmap.josm.command.SequenceCommand;
46import org.openstreetmap.josm.data.SelectionChangedListener;
47import org.openstreetmap.josm.data.osm.DataSet;
48import org.openstreetmap.josm.data.osm.OsmPrimitive;
49import org.openstreetmap.josm.gui.MapFrame;
50import org.openstreetmap.josm.gui.annotation.AnnotationCellRenderer;
51import org.openstreetmap.josm.gui.annotation.AnnotationPreset;
52import org.openstreetmap.josm.gui.annotation.ForwardActionListener;
53import org.openstreetmap.josm.gui.preferences.AnnotationPresetPreference;
54import org.openstreetmap.josm.tools.AutoCompleteComboBox;
55import org.openstreetmap.josm.tools.GBC;
56import org.openstreetmap.josm.tools.ImageProvider;
57
58/**
59 * This dialog displays the properties of the current selected primitives.
60 *
61 * If no object is selected, the dialog list is empty.
62 * If only one is selected, all properties of this object are selected.
63 * If more than one object are selected, the sum of all properties are displayed. If the
64 * different objects share the same property, the shared value is displayed. If they have
65 * different values, all of them are put in a combo box and the string "<different>"
66 * is displayed in italic.
67 *
68 * Below the list, the user can click on an add, modify and delete property button to
69 * edit the table selection value.
70 *
71 * The command is applied to all selected entries.
72 *
73 * @author imi
74 */
75public class PropertiesDialog extends ToggleDialog implements SelectionChangedListener {
76
77 /**
78 * Watches for double clicks and from editing or new property, depending on the
79 * location, the click was.
80 * @author imi
81 */
82 public class DblClickWatch extends MouseAdapter {
83 @Override public void mouseClicked(MouseEvent e) {
84 if (e.getClickCount() < 2)
85 return;
86 if (e.getSource() instanceof JScrollPane)
87 add();
88 else {
89 int row = propertyTable.rowAtPoint(e.getPoint());
90 edit(row);
91 }
92 }
93 }
94
95 /**
96 * Edit the value in the table row
97 * @param row The row of the table, from which the value is edited.
98 */
99 void edit(int row) {
100 String key = data.getValueAt(row, 0).toString();
101 Collection<OsmPrimitive> sel = Main.ds.getSelected();
102 if (sel.isEmpty()) {
103 JOptionPane.showMessageDialog(Main.parent, tr("Please select the objects you want to change properties for."));
104 return;
105 }
106 String msg = "<html>"+trn("This will change {0} object.", "This will change {0} objects.", sel.size(), sel.size())+"<br><br>("+tr("An empty value deletes the key.", key)+")</html>";
107
108 JPanel panel = new JPanel(new BorderLayout());
109 panel.add(new JLabel(msg), BorderLayout.NORTH);
110
111 JPanel p = new JPanel(new GridBagLayout());
112 panel.add(p, BorderLayout.CENTER);
113
114 final JTextField keyField = new JTextField(key);
115 p.add(new JLabel(tr("Key")), GBC.std());
116 p.add(Box.createHorizontalStrut(10), GBC.std());
117 p.add(keyField, GBC.eol().fill(GBC.HORIZONTAL));
118
119 final JComboBox combo = (JComboBox)data.getValueAt(row, 1);
120 p.add(new JLabel(tr("Value")), GBC.std());
121 p.add(Box.createHorizontalStrut(10), GBC.std());
122 p.add(combo, GBC.eol().fill(GBC.HORIZONTAL));
123
124 final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
125 @Override public void selectInitialValue() {
126 combo.requestFocusInWindow();
127 combo.getEditor().selectAll();
128 }
129 };
130 final JDialog dlg = optionPane.createDialog(Main.parent, tr("Change values?"));
131 combo.getEditor().addActionListener(new ActionListener(){
132 public void actionPerformed(ActionEvent e) {
133 optionPane.setValue(JOptionPane.OK_OPTION);
134 dlg.setVisible(false);
135 }
136 });
137 String oldComboEntry = combo.getEditor().getItem().toString();
138 dlg.setVisible(true);
139
140 Object answer = optionPane.getValue();
141 if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE ||
142 (answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) {
143 combo.getEditor().setItem(oldComboEntry);
144 return;
145 }
146
147 String value = combo.getEditor().getItem().toString();
148 if (value.equals(tr("<different>")))
149 return;
150 if (value.equals(""))
151 value = null; // delete the key
152 String newkey = keyField.getText();
153 if (newkey.equals("")) {
154 newkey = key;
155 value = null; // delete the key instead
156 }
157 if (key.equals(newkey) || value == null)
158 Main.main.editLayer().add(new ChangePropertyCommand(sel, newkey, value));
159 else {
160 Main.main.editLayer().add(new SequenceCommand(trn("Change properties of {0} object", "Change properties of {0} objects", sel.size(), sel.size()),
161 new ChangePropertyCommand(sel, key, null),
162 new ChangePropertyCommand(sel, newkey, value)));
163 }
164
165 if (!key.equals(newkey) || value == null)
166 selectionChanged(sel); // update whole table
167
168 Main.parent.repaint(); // repaint all - drawing could have been changed
169 }
170
171 /**
172 * Open the add selection dialog and add a new key/value to the table (and
173 * to the dataset, of course).
174 */
175 void add() {
176 Collection<OsmPrimitive> sel = Main.ds.getSelected();
177 if (sel.isEmpty()) {
178 JOptionPane.showMessageDialog(Main.parent, tr("Please select objects for which you want to change properties."));
179 return;
180 }
181
182 JPanel p = new JPanel(new BorderLayout());
183 p.add(new JLabel("<html>"+trn("This will change {0} object.","This will change {0} objects.", sel.size(),sel.size())+"<br><br>"+tr("Please select a key")),
184 BorderLayout.NORTH);
185 final TreeMap<String,TreeSet<String>> allData = new TreeMap<String,TreeSet<String>>();
186 for (OsmPrimitive osm : Main.ds.allNonDeletedPrimitives()) {
187 for (String key : osm.keySet()) {
188 TreeSet<String> values = null;
189 if (allData.containsKey(key))
190 values = allData.get(key);
191 else {
192 values = new TreeSet<String>();
193 allData.put(key, values);
194 }
195 values.add(osm.get(key));
196 }
197 }
198 for (int i = 0; i < data.getRowCount(); ++i)
199 allData.remove(data.getValueAt(i, 0));
200 final AutoCompleteComboBox keys = new AutoCompleteComboBox();
201 keys.setPossibleItems(allData.keySet());
202 keys.setEditable(true);
203
204 p.add(keys, BorderLayout.CENTER);
205
206 JPanel p2 = new JPanel(new BorderLayout());
207 p.add(p2, BorderLayout.SOUTH);
208 p2.add(new JLabel(tr("Please select a value")), BorderLayout.NORTH);
209 final AutoCompleteComboBox values = new AutoCompleteComboBox();
210 values.setEditable(true);
211 p2.add(values, BorderLayout.CENTER);
212
213 // get the combo box' editor component
214 JTextComponent editor = (JTextComponent) values.getEditor().getEditorComponent();
215 // Refresh the values model when focus is gained
216 editor.addFocusListener(new FocusAdapter() {
217 @Override public void focusGained(FocusEvent e) {
218 String key = keys.getEditor().getItem().toString();
219 if (allData.containsKey(key)) {
220 values.setPossibleItems(allData.get(key));
221 } else {
222 values.removeAllItems();
223 }
224 }
225 });
226
227 JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
228 @Override public void selectInitialValue() {
229 keys.requestFocusInWindow();
230 keys.getEditor().selectAll();
231 }
232 };
233 pane.createDialog(Main.parent, tr("Change values?")).setVisible(true);
234 if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
235 return;
236 String key = keys.getEditor().getItem().toString();
237 String value = values.getEditor().getItem().toString();
238 if (value.equals(""))
239 return;
240 Main.main.editLayer().add(new ChangePropertyCommand(sel, key, value));
241 selectionChanged(sel); // update table
242 Main.parent.repaint(); // repaint all - drawing could have been changed
243 }
244
245 /**
246 * Delete the keys from the given row.
247 * @param row The row, which key gets deleted from the dataset.
248 */
249 private void delete(int row) {
250 String key = data.getValueAt(row, 0).toString();
251 Collection<OsmPrimitive> sel = Main.ds.getSelected();
252 Main.main.editLayer().add(new ChangePropertyCommand(sel, key, null));
253 selectionChanged(sel); // update table
254 }
255
256 /**
257 * The property data.
258 */
259 private final DefaultTableModel data = new DefaultTableModel(){
260 @Override public boolean isCellEditable(int row, int column) {
261 return false;
262 }
263 @Override public Class<?> getColumnClass(int columnIndex) {
264 return columnIndex == 1 ? JComboBox.class : String.class;
265 }
266 };
267 /**
268 * The properties list.
269 */
270 private final JTable propertyTable = new JTable(data);
271 public JComboBox annotationPresets = new JComboBox();
272
273
274 /**
275 * Create a new PropertiesDialog
276 */
277 public PropertiesDialog(MapFrame mapFrame) {
278 super(tr("Properties"), "propertiesdialog", tr("Properties for selected objects."), KeyEvent.VK_P, 150);
279
280 if (AnnotationPresetPreference.annotationPresets.size() > 0) {
281 Vector<ActionListener> allPresets = new Vector<ActionListener>();
282 for (final AnnotationPreset p : AnnotationPresetPreference.annotationPresets)
283 allPresets.add(new ForwardActionListener(this, p));
284
285 allPresets.add(0, new ForwardActionListener(this, new AnnotationPreset()));
286 annotationPresets.setModel(new DefaultComboBoxModel(allPresets));
287 JPanel north = new JPanel(new GridBagLayout());
288 north.add(getComponent(0),GBC.eol().fill(GBC.HORIZONTAL));
289 north.add(annotationPresets,GBC.eol().fill(GBC.HORIZONTAL));
290 add(north, BorderLayout.NORTH);
291 }
292 annotationPresets.addActionListener(new ActionListener(){
293 public void actionPerformed(ActionEvent e) {
294 AnnotationPreset preset = ((ForwardActionListener)annotationPresets.getSelectedItem()).preset;
295 preset.actionPerformed(e);
296 annotationPresets.setSelectedItem(null);
297 }
298 });
299 annotationPresets.setRenderer(new AnnotationCellRenderer());
300
301 data.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
302 propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
303 propertyTable.setDefaultRenderer(JComboBox.class, new DefaultTableCellRenderer(){
304 @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
305 Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
306 if (c instanceof JLabel) {
307 String str = ((JComboBox)value).getEditor().getItem().toString();
308 ((JLabel)c).setText(str);
309 if (str.equals(tr("<different>")))
310 c.setFont(c.getFont().deriveFont(Font.ITALIC));
311 }
312 return c;
313 }
314 });
315 propertyTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer(){
316 @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
317 return super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
318 }
319 });
320 DblClickWatch dblClickWatch = new DblClickWatch();
321 propertyTable.addMouseListener(dblClickWatch);
322 JScrollPane scrollPane = new JScrollPane(propertyTable);
323 scrollPane.addMouseListener(dblClickWatch);
324 add(scrollPane, BorderLayout.CENTER);
325
326 JPanel buttonPanel = new JPanel(new GridLayout(1,3));
327 ActionListener buttonAction = new ActionListener(){
328 public void actionPerformed(ActionEvent e) {
329 int sel = propertyTable.getSelectedRow();
330 if (e.getActionCommand().equals("Add"))
331 add();
332 else if (e.getActionCommand().equals("Edit")) {
333 if (sel == -1)
334 JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to edit."));
335 else
336 edit(sel);
337 } else if (e.getActionCommand().equals("Delete")) {
338 if (sel == -1)
339 JOptionPane.showMessageDialog(Main.parent, tr("Please select the row to delete."));
340 else
341 delete(sel);
342 }
343 }
344 };
345 buttonPanel.add(createButton(marktr("Add"),tr("Add a new key/value pair to all objects"), KeyEvent.VK_A, buttonAction));
346 buttonPanel.add(createButton(marktr("Edit"),tr( "Edit the value of the selected key for all objects"), KeyEvent.VK_E, buttonAction));
347 buttonPanel.add(createButton(marktr("Delete"),tr("Delete the selected key in all objects"), KeyEvent.VK_D, buttonAction));
348 add(buttonPanel, BorderLayout.SOUTH);
349
350 DataSet.listeners.add(this);
351 }
352
353 private JButton createButton(String name, String tooltip, int mnemonic, ActionListener actionListener) {
354 JButton b = new JButton(tr(name), ImageProvider.get("dialogs", name.toLowerCase()));
355 b.setActionCommand(name);
356 b.addActionListener(actionListener);
357 b.setToolTipText(tooltip);
358 b.setMnemonic(mnemonic);
359 b.putClientProperty("help", "Dialog/Properties/"+name);
360 return b;
361 }
362
363 @Override public void setVisible(boolean b) {
364 super.setVisible(b);
365 if (b)
366 selectionChanged(Main.ds.getSelected());
367 }
368
369 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
370 if (!isVisible())
371 return;
372 if (propertyTable == null)
373 return; // selection changed may be received in base class constructor before init
374 if (propertyTable.getCellEditor() != null)
375 propertyTable.getCellEditor().cancelCellEditing();
376 data.setRowCount(0);
377
378 Map<String, Integer> valueCount = new HashMap<String, Integer>();
379 TreeMap<String, Collection<String>> props = new TreeMap<String, Collection<String>>();
380 for (OsmPrimitive osm : newSelection) {
381 for (Entry<String, String> e : osm.entrySet()) {
382 Collection<String> value = props.get(e.getKey());
383 if (value == null) {
384 value = new TreeSet<String>();
385 props.put(e.getKey(), value);
386 }
387 value.add(e.getValue());
388 valueCount.put(e.getKey(), valueCount.containsKey(e.getKey()) ? valueCount.get(e.getKey())+1 : 1);
389 }
390 }
391 for (Entry<String, Collection<String>> e : props.entrySet()) {
392 JComboBox value = new JComboBox(e.getValue().toArray());
393 value.setEditable(true);
394 value.getEditor().setItem(e.getValue().size() > 1 || valueCount.get(e.getKey()) != newSelection.size() ? tr("<different>") : e.getValue().iterator().next());
395 data.addRow(new Object[]{e.getKey(), value});
396 }
397 }
398}
Note: See TracBrowser for help on using the repository browser.