// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.tageditor.tagspec.ui;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.openstreetmap.josm.data.osm.Tag;
public class TabularTagSelector extends JPanel {
private TagsTable tagsTable;
private JTextField tfFilter;
private JButton btnApply;
private JScrollPane scrollPane;
private final ArrayList<ITagSelectorListener> listeners = new ArrayList<>();
protected JPanel buildFilterPanel() {
JPanel pnl = new JPanel();
JLabel lbl = new JLabel(tr("Search: "));
pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
tfFilter = new JTextField(20);
pnl.add(lbl);
pnl.add(tfFilter, BorderLayout.CENTER);
JButton btn = new JButton(tr("Clear"));
pnl.add(btn);
btn.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tfFilter.setText("");
tfFilter.requestFocus();
}
}
);
return pnl;
}
protected JScrollPane buildPresetGrid() {
tagsTable = new TagsTable(new TagsTableModel(), new TagsTableColumnModel());
getModel().initFromTagSpecifications();
scrollPane = new JScrollPane(tagsTable);
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
// this adapters ensures that the width of the tag table columns is adjusted
// to the width of the scroll pane viewport. Also tried to overwrite
// getPreferredViewportSize() in JTable, but did not work.
//
scrollPane.addComponentListener(
new ComponentAdapter() {
@Override public void componentResized(ComponentEvent e) {
super.componentResized(e);
Dimension d = scrollPane.getViewport().getExtentSize();
tagsTable.adjustColumnWidth(d.width);
}
}
);
// add the double click listener
//
tagsTable.addMouseListener(new DoubleClickAdapter());
// replace Enter action. apply the current preset on enter
//
tagsTable.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
ActionListener enterAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int rowNum = tagsTable.getSelectedRow();
if (rowNum >= 0) {
Tag item = getModel().getVisibleItem(rowNum);
fireItemSelected(item);
}
}
};
tagsTable.registerKeyboardAction(
enterAction,
"Enter",
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
JComponent.WHEN_FOCUSED
);
return scrollPane;
}
protected JPanel buildControlButtonPanel() {
JPanel pnl = new JPanel();
pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
btnApply = new JButton(tr("Apply"));
pnl.add(btnApply);
btnApply.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
int row = tagsTable.getSelectedRow();
if (row >= 0) {
Tag item = getModel().getVisibleItem(row);
fireItemSelected(item);
}
}
}
);
return pnl;
}
protected void build() {
setLayout(new BorderLayout());
add(buildFilterPanel(), BorderLayout.NORTH);
add(buildPresetGrid(), BorderLayout.CENTER);
add(buildControlButtonPanel(), BorderLayout.SOUTH);
// wire the text field for filter expressions to the prests
// table
//
tfFilter.getDocument().addDocumentListener(
new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent arg0) {
onUpdate();
}
@Override
public void insertUpdate(DocumentEvent arg0) {
onUpdate();
}
@Override
public void removeUpdate(DocumentEvent arg0) {
onUpdate();
}
protected void onUpdate() {
filter(tfFilter.getText());
}
}
);
tfFilter.addActionListener(
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
filter(tfFilter.getText());
}
}
);
// wire the apply button to the selection model of the preset table
//
tagsTable.getSelectionModel().addListSelectionListener(
new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
btnApply.setEnabled(tagsTable.getSelectedRowCount() != 0);
}
}
);
// load the set of presets and bind them to the preset table
//
tagsTable.getSelectionModel().clearSelection();
btnApply.setEnabled(false);
}
public TabularTagSelector() {
build();
}
public void filter(String filter) {
tagsTable.getSelectionModel().clearSelection();
getModel().filter(filter);
tagsTable.scrollRectToVisible(tagsTable.getCellRect(0, 0, false));
// we change the number of rows by applying a filter condition. Because
// the table is embedded in a JScrollPane which again may be embedded in
// other JScrollPanes or JSplitPanes it seems that we have to recalculate
// the layout and repaint the component tree. Maybe there is a more efficient way
// to keep the GUI in sync with the number of rows in table. By trial
// and error I ended up with the following lines.
//
Component c = tagsTable;
while (c != null) {
c.doLayout();
c.repaint();
c = c.getParent();
}
}
protected TagsTableModel getModel() {
return (TagsTableModel) tagsTable.getModel();
}
public void addTagSelectorListener(ITagSelectorListener listener) {
synchronized (this.listeners) {
if (listener != null && !listeners.contains(listener)) {
listeners.add(listener);
}
}
}
public void removeTagSelectorListener(ITagSelectorListener listener) {
synchronized (this.listeners) {
if (listener != null) {
listeners.remove(listener);
}
}
}
protected void fireItemSelected(Tag pair) {
synchronized (this.listeners) {
for (ITagSelectorListener listener: listeners) {
listener.itemSelected(pair);
}
}
}
private class DoubleClickAdapter extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
int rowNum = tagsTable.rowAtPoint(e.getPoint());
Tag pair = getModel().getVisibleItem(rowNum);
fireItemSelected(pair);
}
}
}
}