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

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