package org.openstreetmap.josm.gui.dialogs.properties;
import static org.openstreetmap.josm.tools.I18n.tr;
import static org.openstreetmap.josm.tools.I18n.trn;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;
import java.util.Map.Entry;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.text.JTextComponent;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.command.ChangeCommand;
import org.openstreetmap.josm.command.ChangePropertyCommand;
import org.openstreetmap.josm.command.Command;
import org.openstreetmap.josm.command.SequenceCommand;
import org.openstreetmap.josm.data.SelectionChangedListener;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
import org.openstreetmap.josm.data.osm.event.DataSetListenerAdapter;
import org.openstreetmap.josm.data.osm.event.DatasetEventManager;
import org.openstreetmap.josm.data.osm.event.SelectionEventManager;
import org.openstreetmap.josm.data.osm.event.DatasetEventManager.FireMode;
import org.openstreetmap.josm.gui.DefaultNameFormatter;
import org.openstreetmap.josm.gui.ExtendedDialog;
import org.openstreetmap.josm.gui.MapFrame;
import org.openstreetmap.josm.gui.MapView;
import org.openstreetmap.josm.gui.SideButton;
import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
import org.openstreetmap.josm.gui.dialogs.relation.RelationEditor;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.preferences.TaggingPresetPreference;
import org.openstreetmap.josm.gui.tagging.TaggingPreset;
import org.openstreetmap.josm.gui.widgets.AutoCompleteComboBox;
import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.ImageProvider;
import org.openstreetmap.josm.tools.Shortcut;
/**
* This dialog displays the properties of the current selected primitives.
*
* If no object is selected, the dialog list is empty.
* If only one is selected, all properties of this object are selected.
* If more than one object are selected, the sum of all properties are displayed. If the
* different objects share the same property, the shared value is displayed. If they have
* different values, all of them are put in a combo box and the string "<different>"
* is displayed in italic.
*
* Below the list, the user can click on an add, modify and delete property button to
* edit the table selection value.
*
* The command is applied to all selected entries.
*
* @author imi
*/
public class PropertiesDialog extends ToggleDialog implements SelectionChangedListener, MapView.EditLayerChangeListener, DataSetListenerAdapter.Listener {
/**
* Watches for double clicks and from editing or new property, depending on the
* location, the click was.
* @author imi
*/
public class DblClickWatch extends MouseAdapter {
@Override public void mouseClicked(MouseEvent e) {
if (e.getClickCount() < 2)
{
if (e.getSource() == propertyTable) {
membershipTable.clearSelection();
} else if (e.getSource() == membershipTable) {
propertyTable.clearSelection();
}
}
else if (e.getSource() == propertyTable)
{
int row = propertyTable.rowAtPoint(e.getPoint());
if (row > -1) {
propertyEdit(row);
}
} else if (e.getSource() == membershipTable) {
int row = membershipTable.rowAtPoint(e.getPoint());
if (row > -1) {
membershipEdit(row);
}
}
else
{
add();
}
}
@Override public void mousePressed(MouseEvent e) {
if (e.getSource() == propertyTable) {
membershipTable.clearSelection();
} else if (e.getSource() == membershipTable) {
propertyTable.clearSelection();
}
}
}
private final Map<String, Map<String, Integer>> valueCount = new TreeMap<String, Map<String, Integer>>();
private final ListOfUsedTags listOfUsedTags = new ListOfUsedTags();
private DataSetListenerAdapter dataChangedAdapter = new DataSetListenerAdapter(this);
@Override
public void showNotify() {
DatasetEventManager.getInstance().addDatasetListener(listOfUsedTags, FireMode.IMMEDIATELY);
DatasetEventManager.getInstance().addDatasetListener(dataChangedAdapter, FireMode.IN_EDT_CONSOLIDATED);
listOfUsedTags.rebuildNecessary();
SelectionEventManager.getInstance().addSelectionListener(this, FireMode.IN_EDT_CONSOLIDATED);
MapView.addEditLayerChangeListener(this);
updateSelection();
}
@Override
public void hideNotify() {
DatasetEventManager.getInstance().removeDatasetListener(listOfUsedTags);
DatasetEventManager.getInstance().removeDatasetListener(dataChangedAdapter);
SelectionEventManager.getInstance().removeSelectionListener(this);
MapView.removeEditLayerChangeListener(this);
}
/**
* Edit the value in the properties table row
* @param row The row of the table from which the value is edited.
*/
@SuppressWarnings("unchecked")
void propertyEdit(int row) {
Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
if (sel.isEmpty()) return;
String key = propertyData.getValueAt(row, 0).toString();
objKey=key;
String msg = "<html>"+trn("This will change {0} object.",
"This will change up to {0} objects.", sel.size(), sel.size())
+"<br><br>("+tr("An empty value deletes the tag.", key)+")</html>";
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel(msg), BorderLayout.NORTH);
JPanel p = new JPanel(new GridBagLayout());
panel.add(p, BorderLayout.CENTER);
final AutoCompleteComboBox keys = new AutoCompleteComboBox();
keys.setPossibleItems(listOfUsedTags.getUsedKeys());
keys.setEditable(true);
keys.setSelectedItem(key);
p.add(new JLabel(tr("Key")), GBC.std());
p.add(Box.createHorizontalStrut(10), GBC.std());
p.add(keys, GBC.eol().fill(GBC.HORIZONTAL));
final AutoCompleteComboBox values = new AutoCompleteComboBox();
values.setRenderer(new DefaultListCellRenderer() {
@Override public Component getListCellRendererComponent(JList list,
Object value, int index, boolean isSelected, boolean cellHasFocus){
Component c = super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
if (c instanceof JLabel) {
String str = null;
str=(String) value;
if (valueCount.containsKey(objKey)){
Map<String, Integer> m=valueCount.get(objKey);
if (m.containsKey(str)) {
str+="("+m.get(str)+")";
c.setFont(c.getFont().deriveFont(Font.ITALIC+Font.BOLD));
}
}
((JLabel)c).setText(str);
}
return c;
}
});
values.setEditable(true);
values.setPossibleItems(listOfUsedTags.getUsedValues(key));
Map<String, Integer> m=(Map<String, Integer>)propertyData.getValueAt(row, 1);
final String selection= m.size()!=1?tr("<different>"):m.entrySet().iterator().next().getKey();
values.setSelectedItem(selection);
values.getEditor().setItem(selection);
p.add(new JLabel(tr("Value")), GBC.std());
p.add(Box.createHorizontalStrut(10), GBC.std());
p.add(values, GBC.eol().fill(GBC.HORIZONTAL));
addFocusAdapter(row, keys, values);
final JOptionPane optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION) {
@Override public void selectInitialValue() {
values.requestFocusInWindow();
values.getEditor().selectAll();
}
};
final JDialog dlg = optionPane.createDialog(Main.parent, tr("Change values?"));
values.getEditor().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dlg.setVisible(false);
optionPane.setValue(JOptionPane.OK_OPTION);
}
});
String oldValue = values.getEditor().getItem().toString();
dlg.setVisible(true);
Object answer = optionPane.getValue();
if (answer == null || answer == JOptionPane.UNINITIALIZED_VALUE ||
(answer instanceof Integer && (Integer)answer != JOptionPane.OK_OPTION)) {
values.getEditor().setItem(oldValue);
return;
}
String value = values.getEditor().getItem().toString().trim();
// is not Java 1.5
//value = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFC);
if (value.equals("")) {
value = null; // delete the key
}
String newkey = keys.getEditor().getItem().toString().trim();
//newkey = java.text.Normalizer.normalize(newkey, java.text.Normalizer.Form.NFC);
if (newkey.equals("")) {
newkey = key;
value = null; // delete the key instead
}
if (key.equals(newkey) && tr("<different>").equals(value))
return;
if (key.equals(newkey) || value == null) {
Main.main.undoRedo.add(new ChangePropertyCommand(sel, newkey, value));
} else {
Collection<Command> commands=new Vector<Command>();
commands.add(new ChangePropertyCommand(sel, key, null));
if (value.equals(tr("<different>"))) {
HashMap<String, Vector<OsmPrimitive>> map=new HashMap<String, Vector<OsmPrimitive>>();
for (OsmPrimitive osm: sel) {
String val=osm.get(key);
if(val != null)
{
if (map.containsKey(val)) {
map.get(val).add(osm);
} else {
Vector<OsmPrimitive> v = new Vector<OsmPrimitive>();
v.add(osm);
map.put(val, v);
}
}
}
for (Entry<String, Vector<OsmPrimitive>> e: map.entrySet()) {
commands.add(new ChangePropertyCommand(e.getValue(), newkey, e.getKey()));
}
} else {
commands.add(new ChangePropertyCommand(sel, newkey, value));
}
Main.main.undoRedo.add(new SequenceCommand(
trn("Change properties of up to {0} object",
"Change properties of up to {0} objects", sel.size(), sel.size()),
commands));
}
if(!key.equals(newkey)) {
for(int i=0; i < propertyTable.getRowCount(); i++)
if(propertyData.getValueAt(i, 0).toString() == newkey) {
row=i;
break;
}
}
propertyTable.changeSelection(row, 0, false, false);
}
/**
* This simply fires up an relation editor for the relation shown; everything else
* is the editor's business.
*
* @param row
*/
@SuppressWarnings("unchecked")
void membershipEdit(int row) {
Relation relation = (Relation)membershipData.getValueAt(row, 0);
Main.map.relationListDialog.selectRelation(relation);
RelationEditor.getEditor(
Main.map.mapView.getEditLayer(),
relation,
(Collection<RelationMember>) membershipData.getValueAt(row, 1) ).setVisible(true);
}
/**
* Open the add selection dialog and add a new key/value to the table (and
* to the dataset, of course).
*/
void add() {
Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
if (sel.isEmpty()) return;
JPanel p = new JPanel(new BorderLayout());
p.add(new JLabel("<html>"+trn("This will change up to {0} object.",
"This will change up to {0} objects.", sel.size(),sel.size())
+"<br><br>"+tr("Please select a key")), BorderLayout.NORTH);
final AutoCompleteComboBox keys = new AutoCompleteComboBox();
List<String> usedKeys = new ArrayList<String>(listOfUsedTags.getUsedKeys());
for (int i = 0; i < propertyData.getRowCount(); ++i) {
usedKeys.remove(propertyData.getValueAt(i, 0));
}
keys.setPossibleItems(usedKeys);
keys.setEditable(true);
p.add(keys, BorderLayout.CENTER);
JPanel p2 = new JPanel(new BorderLayout());
p.add(p2, BorderLayout.SOUTH);
p2.add(new JLabel(tr("Please select a value")), BorderLayout.NORTH);
final AutoCompleteComboBox values = new AutoCompleteComboBox();
values.setEditable(true);
p2.add(values, BorderLayout.CENTER);
addFocusAdapter(-1, keys, values);
JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){
@Override public void selectInitialValue() {
keys.requestFocusInWindow();
keys.getEditor().selectAll();
}
};
JDialog dialog = pane.createDialog(Main.parent, tr("Change values?"));
dialog.setVisible(true);
if (!Integer.valueOf(JOptionPane.OK_OPTION).equals(pane.getValue()))
return;
String key = keys.getEditor().getItem().toString().trim();
String value = values.getEditor().getItem().toString().trim();
if (value.equals(""))
return;
Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, value));
btnAdd.requestFocusInWindow();
}
/**
* @param allData
* @param keys
* @param values
*/
private void addFocusAdapter(final int row, final AutoCompleteComboBox keys, final AutoCompleteComboBox values) {
// get the combo box' editor component
JTextComponent editor = (JTextComponent)values.getEditor()
.getEditorComponent();
// Refresh the values model when focus is gained
editor.addFocusListener(new FocusAdapter() {
@Override public void focusGained(FocusEvent e) {
String key = keys.getEditor().getItem().toString();
values.setPossibleItems(listOfUsedTags.getUsedValues(key));
objKey=key;
}
});
}
private String objKey;
/**
* The property data.
*/
private final DefaultTableModel propertyData = new DefaultTableModel() {
@Override public boolean isCellEditable(int row, int column) {
return false;
}
@Override public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
};
/**
* The membership data.
*/
private final DefaultTableModel membershipData = new DefaultTableModel() {
@Override public boolean isCellEditable(int row, int column) {
return false;
}
@Override public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
};
/**
* The properties list.
*/
private final JTable propertyTable = new JTable(propertyData);
private final JTable membershipTable = new JTable(membershipData);
public JComboBox taggingPresets = new JComboBox();
/**
* The Add/Edit/Delete buttons (needed to be able to disable them)
*/
private final SideButton btnAdd;
private final SideButton btnEdit;
private final SideButton btnDel;
private final JPanel presets = new JPanel(new GridBagLayout());
private final JLabel selectSth = new JLabel("<html><p>"
+ tr("Please select the objects you want to change properties for.") + "</p></html>");
/**
* Create a new PropertiesDialog
*/
public PropertiesDialog(MapFrame mapFrame) {
super(tr("Properties/Memberships"), "propertiesdialog", tr("Properties for selected objects."),
Shortcut.registerShortcut("subwindow:properties", tr("Toggle: {0}", tr("Properties/Memberships")), KeyEvent.VK_P,
Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150, true);
// setting up the properties table
propertyData.setColumnIdentifiers(new String[]{tr("Key"),tr("Value")});
propertyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
propertyTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer(){
@Override public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
if (c instanceof JLabel) {
String str = null;
if (value instanceof String) {
str = (String) value;
} else if (value instanceof Map<?, ?>) {
Map<?, ?> v = (Map<?, ?>) value;
if (v.size() != 1) {
str=tr("<different>");
c.setFont(c.getFont().deriveFont(Font.ITALIC));
} else {
final Map.Entry<?, ?> entry = v.entrySet().iterator().next();
str = (String) entry.getKey();
}
}
((JLabel)c).setText(str);
}
return c;
}
});
// setting up the membership table
membershipData.setColumnIdentifiers(new String[]{tr("Member Of"),tr("Role")});
membershipTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
membershipTable.addMouseListener(new PopupMenuLauncher() {
@Override
public void launch(MouseEvent evt) {
Point p = evt.getPoint();
int row = membershipTable.rowAtPoint(p);
if (row > -1) {
JPopupMenu menu = new JPopupMenu();
Relation relation = (Relation)membershipData.getValueAt(row, 0);
menu.add(new SelectRelationAction(relation, true));
menu.add(new SelectRelationAction(relation, false));
menu.show(membershipTable, p.x, p.y-3);
}
}
});
membershipTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
@Override public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
if (c instanceof JLabel) {
((JLabel)c).setText(((Relation)value).getDisplayName(DefaultNameFormatter.getInstance()));
}
return c;
}
});
membershipTable.getColumnModel().getColumn(1).setCellRenderer(new DefaultTableCellRenderer() {
@SuppressWarnings("unchecked")
@Override public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
Component c = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
if (c instanceof JLabel) {
Collection<RelationMember> col = (Collection<RelationMember>) value;
String text = null;
for (RelationMember r : col) {
if (text == null) {
text = r.getRole();
}
else if (!text.equals(r.getRole())) {
text = tr("<different>");
break;
}
}
((JLabel)c).setText(text);
}
return c;
}
});
// combine both tables and wrap them in a scrollPane
JPanel bothTables = new JPanel();
boolean top = Main.pref.getBoolean("properties.presets.top", true);
bothTables.setLayout(new GridBagLayout());
if(top) {
bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2));
}
bothTables.add(selectSth, GBC.eol().fill().insets(10, 10, 10, 10));
bothTables.add(propertyTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
bothTables.add(propertyTable, GBC.eol().fill(GBC.BOTH));
bothTables.add(membershipTable.getTableHeader(), GBC.eol().fill(GBC.HORIZONTAL));
bothTables.add(membershipTable, GBC.eol().fill(GBC.BOTH));
if(!top) {
bothTables.add(presets, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 2, 5, 2));
}
DblClickWatch dblClickWatch = new DblClickWatch();
propertyTable.addMouseListener(dblClickWatch);
membershipTable.addMouseListener(dblClickWatch);
JScrollPane scrollPane = new JScrollPane(bothTables);
scrollPane.addMouseListener(dblClickWatch);
add(scrollPane, BorderLayout.CENTER);
selectSth.setPreferredSize(scrollPane.getSize());
presets.setSize(scrollPane.getSize());
JPanel buttonPanel = getButtonPanel(3);
// -- add action and shortcut
AddAction addAction = new AddAction();
this.btnAdd = new SideButton(addAction);
btnAdd.setFocusable(true);
buttonPanel.add(this.btnAdd);
btnAdd.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "onEnter");
btnAdd.getActionMap().put("onEnter", addAction);
Shortcut sc = Shortcut.registerShortcut("properties:add", tr("Add Properties"), KeyEvent.VK_B,
Shortcut.GROUP_MNEMONIC);
Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sc.getKeyStroke(), "properties:add");
Main.contentPane.getActionMap().put("properties:add", addAction);
// -- edit action
//
EditAction editAction = new EditAction();
propertyTable.getSelectionModel().addListSelectionListener(editAction);
membershipTable.getSelectionModel().addListSelectionListener(editAction);
this.btnEdit = new SideButton(editAction);
buttonPanel.add(this.btnEdit);
// -- delete action
//
DeleteAction deleteAction = new DeleteAction();
this.btnDel = new SideButton(deleteAction);
membershipTable.getSelectionModel().addListSelectionListener(deleteAction);
propertyTable.getSelectionModel().addListSelectionListener(deleteAction);
getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),"delete"
);
getActionMap().put("delete", deleteAction);
buttonPanel.add(this.btnDel);
add(buttonPanel, BorderLayout.SOUTH);
}
@Override public void setVisible(boolean b) {
super.setVisible(b);
if (b && Main.main.getCurrentDataSet() != null) {
selectionChanged(Main.main.getCurrentDataSet().getSelected());
}
}
private void checkPresets(int nodes, int ways, int relations, int closedways)
{
/**
* Small helper class that manages the highlighting of the label on hover as well as opening
* the corresponding preset when clicked
*/
class PresetLabelML implements MouseListener {
JLabel label;
Font bold;
Font normal;
TaggingPreset tag;
PresetLabelML(JLabel lbl, TaggingPreset t) {
super();
label = lbl;
lbl.setCursor(new Cursor(Cursor.HAND_CURSOR));
normal = label.getFont();
bold = normal.deriveFont(normal.getStyle() ^ Font.BOLD);
tag = t;
}
public void mouseClicked(MouseEvent arg0) {
tag.actionPerformed(null);
}
public void mouseEntered(MouseEvent arg0) {
label.setFont(bold);
}
public void mouseExited(MouseEvent arg0) {
label.setFont(normal);
}
public void mousePressed(MouseEvent arg0) {}
public void mouseReleased(MouseEvent arg0) {}
}
presets.removeAll();
int total = nodes+ways+relations+closedways;
if(total == 0) {
presets.setVisible(false);
return;
}
for(TaggingPreset t : TaggingPresetPreference.taggingPresets) {
if((t.types == null || !((relations > 0 && !t.types.contains("relation")) &&
(nodes > 0 && !t.types.contains("node")) &&
(ways+closedways > 0 && !t.types.contains("way")) &&
(closedways > 0 && !t.types.contains("closedway")))) && t.isShowable())
{
int found = 0;
for(TaggingPreset.Item i : t.data) {
if(!(i instanceof TaggingPreset.Key)) {
continue;
}
String val = ((TaggingPreset.Key)i).value;
String key = ((TaggingPreset.Key)i).key;
// we subtract 100 if not found and add 1 if found
found -= 100;
if(key == null || !valueCount.containsKey(key)) {
continue;
}
Map<String, Integer> v = valueCount.get(key);
if(v.size() == 1 && val != null && v.containsKey(val) && v.get(val) == total) {
found += 101;
}
}
if(found <= 0) {
continue;
}
JLabel lbl = new JLabel(t.getName());
lbl.addMouseListener(new PresetLabelML(lbl, t));
presets.add(lbl, GBC.eol().fill(GBC.HORIZONTAL));
}
}
if(presets.getComponentCount() > 0) {
presets.setVisible(true);
// This ensures the presets are exactly as high as needed.
int height = presets.getComponentCount() * presets.getComponent(0).getHeight();
Dimension size = new Dimension(presets.getWidth(), height);
presets.setMaximumSize(size);
presets.setMinimumSize(size);
} else {
presets.setVisible(false);
}
}
private int findRow(TableModel model, Object value) {
for (int i=0; i<model.getRowCount(); i++) {
if (model.getValueAt(i, 0).equals(value))
return i;
}
return -1;
}
public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
if (!isVisible())
return;
if (propertyTable == null)
return; // selection changed may be received in base class constructor before init
if (propertyTable.getCellEditor() != null) {
propertyTable.getCellEditor().cancelCellEditing();
}
String selectedTag = null;
Relation selectedRelation = null;
if (propertyTable.getSelectedRowCount() == 1) {
selectedTag = (String)propertyData.getValueAt(propertyTable.getSelectedRow(), 0);
}
if (membershipTable.getSelectedRowCount() == 1) {
selectedRelation = (Relation)membershipData.getValueAt(membershipTable.getSelectedRow(), 0);
}
// re-load property data
propertyData.setRowCount(0);
int nodes = 0;
int ways = 0;
int relations = 0;
int closedways = 0;
Map<String, Integer> keyCount = new HashMap<String, Integer>();
valueCount.clear();
for (OsmPrimitive osm : newSelection) {
if(osm instanceof Node) {
++nodes;
} else if(osm instanceof Relation) {
++relations;
} else if(((Way)osm).isClosed()) {
++closedways;
} else {
++ways;
}
for (String key: osm.keySet()) {
String value = osm.get(key);
keyCount.put(key, keyCount.containsKey(key) ? keyCount.get(key) + 1 : 1);
if (valueCount.containsKey(key)) {
Map<String, Integer> v = valueCount.get(key);
v.put(value, v.containsKey(value)? v.get(value) + 1 : 1 );
} else {
TreeMap<String,Integer> v = new TreeMap<String, Integer>();
v.put(value, 1);
valueCount.put(key, v);
}
}
}
for (Entry<String, Map<String, Integer>> e : valueCount.entrySet()) {
int count=0;
for (Entry<String, Integer> e1: e.getValue().entrySet()) {
count+=e1.getValue();
}
if (count < newSelection.size()) {
e.getValue().put("", newSelection.size()-count);
}
propertyData.addRow(new Object[]{e.getKey(), e.getValue()});
}
// re-load membership data
// this is rather expensive since we have to walk through all members of all existing relationships.
// could use back references here for speed if necessary.
membershipData.setRowCount(0);
Map<Relation, Collection<RelationMember>> roles = new HashMap<Relation, Collection<RelationMember>>();
for (OsmPrimitive primitive: newSelection) {
for (OsmPrimitive ref: primitive.getReferrers()) {
if (ref instanceof Relation && !ref.isFiltered() && !ref.isIncomplete() && !ref.isDeleted()) {
Relation r = (Relation) ref;
for (RelationMember m : r.getMembers()) {
if (m.getMember() == primitive) {
Collection<RelationMember> value = roles.get(r);
if (value == null) {
value = new HashSet<RelationMember>();
roles.put(r, value);
}
value.add(m);
}
}
}
}
}
for (Entry<Relation, Collection<RelationMember>> e : roles.entrySet()) {
membershipData.addRow(new Object[]{e.getKey(), e.getValue()});
}
checkPresets(nodes, ways, relations, closedways);
membershipTable.getTableHeader().setVisible(membershipData.getRowCount() > 0);
membershipTable.setVisible(membershipData.getRowCount() > 0);
boolean hasSelection = !newSelection.isEmpty();
boolean hasTags = hasSelection && propertyData.getRowCount() > 0;
boolean hasMemberships = hasSelection && membershipData.getRowCount() > 0;
btnAdd.setEnabled(hasSelection);
btnEdit.setEnabled(hasTags || hasMemberships);
btnDel.setEnabled(hasTags || hasMemberships);
propertyTable.setVisible(hasSelection);
propertyTable.getTableHeader().setVisible(hasSelection);
selectSth.setVisible(!hasSelection);
int selectedIndex;
if (selectedTag != null && (selectedIndex = findRow(propertyData, selectedTag)) != -1) {
propertyTable.changeSelection(selectedIndex, 0, false, false);
} else if (selectedRelation != null && (selectedIndex = findRow(membershipData, selectedRelation)) != -1) {
membershipTable.changeSelection(selectedIndex, 0, false, false);
} else if(hasTags) {
propertyTable.changeSelection(0, 0, false, false);
} else if(hasMemberships) {
membershipTable.changeSelection(0, 0, false, false);
}
if(propertyData.getRowCount() != 0 || membershipData.getRowCount() != 0) {
setTitle(tr("Properties: {0} / Memberships: {1}",
propertyData.getRowCount(), membershipData.getRowCount()));
} else {
setTitle(tr("Properties / Memberships"));
}
}
private void updateSelection() {
if (Main.main.getCurrentDataSet() == null) {
selectionChanged(Collections.<OsmPrimitive>emptyList());
} else {
selectionChanged(Main.main.getCurrentDataSet().getSelected());
}
}
/* ---------------------------------------------------------------------------------- */
/* EditLayerChangeListener */
/* ---------------------------------------------------------------------------------- */
public void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
updateSelection();
}
public void processDatasetEvent(AbstractDatasetChangedEvent event) {
updateSelection();
}
class DeleteAction extends AbstractAction implements ListSelectionListener {
protected void deleteProperty(int row){
String key = propertyData.getValueAt(row, 0).toString();
String nextKey = null;
int rowCount = propertyData.getRowCount();
if (rowCount > 1) {
nextKey = (String)propertyData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
}
Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
Main.main.undoRedo.add(new ChangePropertyCommand(sel, key, null));
membershipTable.clearSelection();
if (nextKey != null) {
propertyTable.changeSelection(findRow(propertyData, nextKey), 0, false, false);
}
}
protected void deleteFromRelation(int row) {
Relation cur = (Relation)membershipData.getValueAt(row, 0);
Relation nextRelation = null;
int rowCount = membershipTable.getRowCount();
if (rowCount > 1) {
nextRelation = (Relation)membershipData.getValueAt((row + 1 < rowCount ? row + 1 : row - 1), 0);
}
ExtendedDialog ed = new ExtendedDialog(Main.parent,
tr("Change relation"),
new String[] {tr("Delete from relation"), tr("Cancel")});
ed.setButtonIcons(new String[] {"dialogs/delete.png", "cancel.png"});
ed.setContent(tr("Really delete selection from relation {0}?", cur.getDisplayName(DefaultNameFormatter.getInstance())));
ed.showDialog();
if(ed.getValue() != 1)
return;
Relation rel = new Relation(cur);
Collection<OsmPrimitive> sel = Main.main.getCurrentDataSet().getSelected();
for (OsmPrimitive primitive: sel) {
rel.removeMembersFor(primitive);
}
Main.main.undoRedo.add(new ChangeCommand(cur, rel));
propertyTable.clearSelection();
if (nextRelation != null) {
membershipTable.changeSelection(findRow(membershipData, nextRelation), 0, false, false);
}
}
public DeleteAction() {
putValue(NAME, tr("Delete"));
putValue(SHORT_DESCRIPTION, tr("Delete the selected key in all objects"));
putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
Shortcut s = Shortcut.registerShortcut("properties:delete", tr("Delete Properties"), KeyEvent.VK_Q,
Shortcut.GROUP_MNEMONIC);
putValue(MNEMONIC_KEY, (int) KeyEvent.getKeyText(s.getAssignedKey()).charAt(0));
updateEnabledState();
}
public void actionPerformed(ActionEvent e) {
if (propertyTable.getSelectedRowCount() >0 ) {
int row = propertyTable.getSelectedRow();
deleteProperty(row);
} else if (membershipTable.getSelectedRowCount() > 0) {
int row = membershipTable.getSelectedRow();
deleteFromRelation(row);
}
}
protected void updateEnabledState() {
setEnabled(
PropertiesDialog.this.propertyTable.getSelectedRowCount() >0
|| PropertiesDialog.this.membershipTable.getSelectedRowCount() > 0
);
}
public void valueChanged(ListSelectionEvent e) {
updateEnabledState();
}
}
class AddAction extends AbstractAction {
public AddAction() {
putValue(NAME, tr("Add"));
putValue(SHORT_DESCRIPTION, tr("Add a new key/value pair to all objects"));
putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
}
public void actionPerformed(ActionEvent e) {
add();
}
}
class EditAction extends AbstractAction implements ListSelectionListener {
public EditAction() {
putValue(NAME, tr("Edit"));
putValue(SHORT_DESCRIPTION, tr("Edit the value of the selected key for all objects"));
putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
updateEnabledState();
}
public void actionPerformed(ActionEvent e) {
if (!isEnabled())
return;
if (propertyTable.getSelectedRowCount() == 1) {
int row = propertyTable.getSelectedRow();
propertyEdit(row);
} else if (membershipTable.getSelectedRowCount() == 1) {
int row = membershipTable.getSelectedRow();
membershipEdit(row);
}
}
protected void updateEnabledState() {
setEnabled(
propertyTable.getSelectedRowCount() == 1
^ membershipTable.getSelectedRowCount() == 1
);
}
public void valueChanged(ListSelectionEvent e) {
updateEnabledState();
}
}
static class SelectRelationAction extends AbstractAction {
boolean selectionmode;
Relation relation;
public SelectRelationAction(Relation r, boolean select) {
selectionmode = select;
relation = r;
if(select) {
putValue(NAME, tr("Select relation"));
putValue(SHORT_DESCRIPTION, tr("Select relation in main selection."));
} else {
putValue(NAME, tr("Select in relation list"));
putValue(SHORT_DESCRIPTION, tr("Select relation in relation list."));
}
}
public void actionPerformed(ActionEvent e) {
if(selectionmode) {
Main.map.mapView.getEditLayer().data.setSelected(relation);
} else {
Main.map.relationListDialog.selectRelation(relation);
}
}
}
}