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

Last change on this file since 3306 was 3306, checked in by stoecker, 14 years ago

improve member position display in properties

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