001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import java.awt.BorderLayout;
005import java.awt.event.ActionListener;
006import java.awt.event.KeyAdapter;
007import java.awt.event.KeyEvent;
008import java.awt.event.MouseAdapter;
009import java.awt.event.MouseEvent;
010import java.util.ArrayList;
011import java.util.List;
012
013import javax.swing.AbstractListModel;
014import javax.swing.JList;
015import javax.swing.JPanel;
016import javax.swing.JScrollPane;
017import javax.swing.ListSelectionModel;
018import javax.swing.event.DocumentEvent;
019import javax.swing.event.DocumentListener;
020import javax.swing.event.ListSelectionListener;
021
022public abstract class SearchTextResultListPanel<T> extends JPanel {
023
024    protected final JosmTextField edSearchText;
025    protected final JList<T> lsResult;
026    protected final ResultListModel<T> lsResultModel = new ResultListModel<>();
027
028    protected final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
029
030    private transient ActionListener dblClickListener;
031    private transient ActionListener clickListener;
032
033    protected abstract void filterItems();
034
035    /**
036     * Constructs a new {@code SearchTextResultListPanel}.
037     */
038    public SearchTextResultListPanel() {
039        super(new BorderLayout());
040
041        edSearchText = new JosmTextField();
042        edSearchText.getDocument().addDocumentListener(new DocumentListener() {
043            @Override
044            public void removeUpdate(DocumentEvent e) {
045                filterItems();
046            }
047
048            @Override
049            public void insertUpdate(DocumentEvent e) {
050                filterItems();
051            }
052
053            @Override
054            public void changedUpdate(DocumentEvent e) {
055                filterItems();
056            }
057        });
058        edSearchText.addKeyListener(new KeyAdapter() {
059            @Override
060            public void keyPressed(KeyEvent e) {
061                switch (e.getKeyCode()) {
062                    case KeyEvent.VK_DOWN:
063                        selectItem(lsResult.getSelectedIndex() + 1);
064                        break;
065                    case KeyEvent.VK_UP:
066                        selectItem(lsResult.getSelectedIndex() - 1);
067                        break;
068                    case KeyEvent.VK_PAGE_DOWN:
069                        selectItem(lsResult.getSelectedIndex() + 10);
070                        break;
071                    case KeyEvent.VK_PAGE_UP:
072                        selectItem(lsResult.getSelectedIndex() - 10);
073                        break;
074                    case KeyEvent.VK_HOME:
075                        selectItem(0);
076                        break;
077                    case KeyEvent.VK_END:
078                        selectItem(lsResultModel.getSize());
079                        break;
080                    default: // Do nothing
081                }
082            }
083        });
084        add(edSearchText, BorderLayout.NORTH);
085
086        lsResult = new JList<>(lsResultModel);
087        lsResult.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
088        lsResult.addMouseListener(new MouseAdapter() {
089            @Override
090            public void mouseClicked(MouseEvent e) {
091                if (e.getClickCount() > 1) {
092                    if (dblClickListener != null)
093                        dblClickListener.actionPerformed(null);
094                } else {
095                    if (clickListener != null)
096                        clickListener.actionPerformed(null);
097                }
098            }
099        });
100        add(new JScrollPane(lsResult), BorderLayout.CENTER);
101    }
102
103    protected static class ResultListModel<T> extends AbstractListModel<T> {
104
105        private transient List<T> items = new ArrayList<>();
106
107        public synchronized void setItems(List<T> items) {
108            this.items = items;
109            fireContentsChanged(this, 0, Integer.MAX_VALUE);
110        }
111
112        @Override
113        public synchronized T getElementAt(int index) {
114            return items.get(index);
115        }
116
117        @Override
118        public synchronized int getSize() {
119            return items.size();
120        }
121
122        public synchronized boolean isEmpty() {
123            return items.isEmpty();
124        }
125    }
126
127    public synchronized void init() {
128        listSelectionListeners.clear();
129        edSearchText.setText("");
130        filterItems();
131    }
132
133    private synchronized void selectItem(int newIndex) {
134        if (newIndex < 0) {
135            newIndex = 0;
136        }
137        if (newIndex > lsResultModel.getSize() - 1) {
138            newIndex = lsResultModel.getSize() - 1;
139        }
140        lsResult.setSelectedIndex(newIndex);
141        lsResult.ensureIndexIsVisible(newIndex);
142    }
143
144    public synchronized void clearSelection() {
145        lsResult.clearSelection();
146    }
147
148    public synchronized int getItemCount() {
149        return lsResultModel.getSize();
150    }
151
152    public void setDblClickListener(ActionListener dblClickListener) {
153        this.dblClickListener = dblClickListener;
154    }
155
156    public void setClickListener(ActionListener clickListener) {
157        this.clickListener = clickListener;
158    }
159
160    /**
161     * Adds a selection listener to the presets list.
162     *
163     * @param selectListener The list selection listener
164     * @since 7412
165     */
166    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
167        lsResult.getSelectionModel().addListSelectionListener(selectListener);
168        listSelectionListeners.add(selectListener);
169    }
170
171    /**
172     * Removes a selection listener from the presets list.
173     *
174     * @param selectListener The list selection listener
175     * @since 7412
176     */
177    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
178        listSelectionListeners.remove(selectListener);
179        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
180    }
181}