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
022/**
023 * A panel containing a search text field and a list of results for that search text.
024 * @param <T> The class of the things that are searched
025 */
026public abstract class SearchTextResultListPanel<T> extends JPanel {
027
028    protected final JosmTextField edSearchText;
029    protected final JList<T> lsResult;
030    protected final ResultListModel<T> lsResultModel = new ResultListModel<>();
031
032    protected final transient List<ListSelectionListener> listSelectionListeners = new ArrayList<>();
033
034    private transient ActionListener dblClickListener;
035    private transient ActionListener clickListener;
036
037    protected abstract void filterItems();
038
039    /**
040     * Constructs a new {@code SearchTextResultListPanel}.
041     */
042    public SearchTextResultListPanel() {
043        super(new BorderLayout());
044
045        edSearchText = new JosmTextField();
046        edSearchText.getDocument().addDocumentListener(new DocumentListener() {
047            @Override
048            public void removeUpdate(DocumentEvent e) {
049                filterItems();
050            }
051
052            @Override
053            public void insertUpdate(DocumentEvent e) {
054                filterItems();
055            }
056
057            @Override
058            public void changedUpdate(DocumentEvent e) {
059                filterItems();
060            }
061        });
062        edSearchText.addKeyListener(new KeyAdapter() {
063            @Override
064            public void keyPressed(KeyEvent e) {
065                switch (e.getKeyCode()) {
066                    case KeyEvent.VK_DOWN:
067                        selectItem(lsResult.getSelectedIndex() + 1);
068                        break;
069                    case KeyEvent.VK_UP:
070                        selectItem(lsResult.getSelectedIndex() - 1);
071                        break;
072                    case KeyEvent.VK_PAGE_DOWN:
073                        selectItem(lsResult.getSelectedIndex() + 10);
074                        break;
075                    case KeyEvent.VK_PAGE_UP:
076                        selectItem(lsResult.getSelectedIndex() - 10);
077                        break;
078                    case KeyEvent.VK_HOME:
079                        selectItem(0);
080                        break;
081                    case KeyEvent.VK_END:
082                        selectItem(lsResultModel.getSize());
083                        break;
084                    default: // Do nothing
085                }
086            }
087        });
088        add(edSearchText, BorderLayout.NORTH);
089
090        lsResult = new JList<>(lsResultModel);
091        lsResult.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
092        lsResult.addMouseListener(new MouseAdapter() {
093            @Override
094            public void mouseClicked(MouseEvent e) {
095                if (e.getClickCount() > 1) {
096                    if (dblClickListener != null)
097                        dblClickListener.actionPerformed(null);
098                } else {
099                    if (clickListener != null)
100                        clickListener.actionPerformed(null);
101                }
102            }
103        });
104        add(new JScrollPane(lsResult), BorderLayout.CENTER);
105    }
106
107    protected static class ResultListModel<T> extends AbstractListModel<T> {
108
109        private transient List<T> items = new ArrayList<>();
110
111        public synchronized void setItems(List<T> items) {
112            this.items = items;
113            fireContentsChanged(this, 0, Integer.MAX_VALUE);
114        }
115
116        @Override
117        public synchronized T getElementAt(int index) {
118            return items.get(index);
119        }
120
121        @Override
122        public synchronized int getSize() {
123            return items.size();
124        }
125
126        public synchronized boolean isEmpty() {
127            return items.isEmpty();
128        }
129    }
130
131    /**
132     * Initializes and clears the panel.
133     */
134    public synchronized void init() {
135        listSelectionListeners.clear();
136        edSearchText.setText("");
137        filterItems();
138    }
139
140    private synchronized void selectItem(int newIndex) {
141        if (newIndex < 0) {
142            newIndex = 0;
143        }
144        if (newIndex > lsResultModel.getSize() - 1) {
145            newIndex = lsResultModel.getSize() - 1;
146        }
147        lsResult.setSelectedIndex(newIndex);
148        lsResult.ensureIndexIsVisible(newIndex);
149    }
150
151    /**
152     * Clear the selected result
153     */
154    public synchronized void clearSelection() {
155        lsResult.clearSelection();
156    }
157
158    /**
159     * Get the number of items available
160     * @return The number of search result items available
161     */
162    public synchronized int getItemCount() {
163        return lsResultModel.getSize();
164    }
165
166    /**
167     * Sets a listener to be invoked on double click
168     * @param dblClickListener The double click listener
169     */
170    public void setDblClickListener(ActionListener dblClickListener) {
171        this.dblClickListener = dblClickListener;
172    }
173
174    /**
175     * Sets a listener to be invoked on ssingle click
176     * @param clickListener The click listener
177     */
178    public void setClickListener(ActionListener clickListener) {
179        this.clickListener = clickListener;
180    }
181
182    /**
183     * Adds a selection listener to the presets list.
184     *
185     * @param selectListener The list selection listener
186     * @since 7412
187     */
188    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
189        lsResult.getSelectionModel().addListSelectionListener(selectListener);
190        listSelectionListeners.add(selectListener);
191    }
192
193    /**
194     * Removes a selection listener from the presets list.
195     *
196     * @param selectListener The list selection listener
197     * @since 7412
198     */
199    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
200        listSelectionListeners.remove(selectListener);
201        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
202    }
203}