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

Last change on this file since 2906 was 2906, checked in by mjulius, 14 years ago

remove OsmPrimitive.entrySet()
using keySet()/get() or getKeys() instead

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