// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.tageditor.preset.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 java.util.Collection; import javax.swing.AbstractAction; import javax.swing.Action; 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.gui.tagging.presets.TaggingPreset; import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; public class TabularPresetSelector extends JPanel { private PresetsTable presetsTable = null; private JTextField tfFilter = null; private final ArrayList<IPresetSelectorListener> listeners = new ArrayList<>(); private JScrollPane scrollPane; private JButton btnApply; 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() { presetsTable = new PresetsTable(new PresetsTableModel(), new PresetsTableColumnModel()); scrollPane = new JScrollPane(presetsTable); 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(); presetsTable.adjustColumnWidth(d.width); } } ); // add the double click listener // presetsTable.addMouseListener(new DoubleClickAdapter()); // replace Enter action. apply the current preset on enter // presetsTable.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)); ActionListener enterAction = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { int rowNum = presetsTable.getSelectedRow(); if (rowNum >= 0) { fireItemSelected(getModel().getVisibleItem(rowNum)); } } }; presetsTable.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 = presetsTable.getSelectedRow(); if (row >= 0) { fireItemSelected(getModel().getVisibleItem(row)); } } } ); 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 preset 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 // presetsTable.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { btnApply.setEnabled(presetsTable.getSelectedRowCount() != 0); } } ); // load the set of presets and bind them to the preset table // bindTo(TaggingPresets.getTaggingPresets()); presetsTable.getSelectionModel().clearSelection(); btnApply.setEnabled(false); } public void bindTo(Collection<TaggingPreset> presets) { PresetsTableModel model = (PresetsTableModel) presetsTable.getModel(); model.setPresets(presets); } public TabularPresetSelector() { build(); } public void addPresetSelectorListener(IPresetSelectorListener listener) { synchronized (this.listeners) { if (listener != null && !listeners.contains(listener)) { listeners.add(listener); } } } public void removePresetSelectorListener(IPresetSelectorListener listener) { synchronized (this.listeners) { if (listener != null) { listeners.remove(listener); } } } protected void fireItemSelected(TaggingPreset item) { synchronized (this.listeners) { for (IPresetSelectorListener listener: listeners) { listener.itemSelected(item); } } } private class DoubleClickAdapter extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int rowNum = presetsTable.rowAtPoint(e.getPoint()); fireItemSelected(getModel().getVisibleItem(rowNum)); } } } public void filter(String filter) { presetsTable.getSelectionModel().clearSelection(); getModel().filter(filter); presetsTable.scrollRectToVisible(presetsTable.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 = presetsTable; while (c != null) { c.doLayout(); c.repaint(); c = c.getParent(); } } protected PresetsTableModel getModel() { return (PresetsTableModel) presetsTable.getModel(); } public void installKeyAction(Action a) { getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( (KeyStroke) a.getValue(AbstractAction.ACCELERATOR_KEY), a.getValue(AbstractAction.NAME)); getActionMap().put(a.getValue(AbstractAction.NAME), a); } }