001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.widgets;
003
004import org.openstreetmap.josm.gui.util.DocumentAdapter;
005
006import java.awt.BorderLayout;
007import java.awt.event.ActionListener;
008import java.awt.event.KeyAdapter;
009import java.awt.event.KeyEvent;
010import java.awt.event.MouseAdapter;
011import java.awt.event.MouseEvent;
012import java.util.ArrayList;
013import java.util.List;
014
015import javax.swing.AbstractListModel;
016import javax.swing.JList;
017import javax.swing.JPanel;
018import javax.swing.JScrollPane;
019import javax.swing.ListSelectionModel;
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    protected SearchTextResultListPanel() {
043        super(new BorderLayout());
044
045        edSearchText = new FilterField();
046        edSearchText.getDocument().addDocumentListener(DocumentAdapter.create(ignore -> filterItems()));
047        edSearchText.addKeyListener(new KeyAdapter() {
048            @Override
049            public void keyPressed(KeyEvent e) {
050                switch (e.getKeyCode()) {
051                    case KeyEvent.VK_DOWN:
052                        selectItem(lsResult.getSelectedIndex() + 1);
053                        break;
054                    case KeyEvent.VK_UP:
055                        selectItem(lsResult.getSelectedIndex() - 1);
056                        break;
057                    case KeyEvent.VK_PAGE_DOWN:
058                        selectItem(lsResult.getSelectedIndex() + 10);
059                        break;
060                    case KeyEvent.VK_PAGE_UP:
061                        selectItem(lsResult.getSelectedIndex() - 10);
062                        break;
063                    case KeyEvent.VK_HOME:
064                        selectItem(0);
065                        break;
066                    case KeyEvent.VK_END:
067                        selectItem(lsResultModel.getSize());
068                        break;
069                    default: // Do nothing
070                }
071            }
072        });
073        add(edSearchText, BorderLayout.NORTH);
074
075        lsResult = new JList<>(lsResultModel);
076        lsResult.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
077        lsResult.addMouseListener(new MouseAdapter() {
078            @Override
079            public void mouseClicked(MouseEvent e) {
080                if (e.getClickCount() > 1) {
081                    if (dblClickListener != null)
082                        dblClickListener.actionPerformed(null);
083                } else {
084                    if (clickListener != null)
085                        clickListener.actionPerformed(null);
086                }
087            }
088        });
089        add(new JScrollPane(lsResult), BorderLayout.CENTER);
090    }
091
092    protected static class ResultListModel<T> extends AbstractListModel<T> {
093
094        private transient List<T> items = new ArrayList<>();
095
096        public synchronized void setItems(List<T> items) {
097            this.items = items;
098            fireContentsChanged(this, 0, Integer.MAX_VALUE);
099        }
100
101        @Override
102        public synchronized T getElementAt(int index) {
103            return items.get(index);
104        }
105
106        @Override
107        public synchronized int getSize() {
108            return items.size();
109        }
110
111        public synchronized boolean isEmpty() {
112            return items.isEmpty();
113        }
114    }
115
116    /**
117     * Initializes and clears the panel.
118     */
119    public synchronized void init() {
120        listSelectionListeners.clear();
121        edSearchText.setText("");
122        filterItems();
123    }
124
125    private synchronized void selectItem(int newIndex) {
126        if (newIndex < 0) {
127            newIndex = 0;
128        }
129        if (newIndex > lsResultModel.getSize() - 1) {
130            newIndex = lsResultModel.getSize() - 1;
131        }
132        lsResult.setSelectedIndex(newIndex);
133        lsResult.ensureIndexIsVisible(newIndex);
134    }
135
136    /**
137     * Clear the selected result
138     */
139    public synchronized void clearSelection() {
140        lsResult.clearSelection();
141    }
142
143    /**
144     * Get the number of items available
145     * @return The number of search result items available
146     */
147    public synchronized int getItemCount() {
148        return lsResultModel.getSize();
149    }
150
151    /**
152     * Returns the search text entered by user.
153     * @return the search text entered by user
154     * @since 14975
155     */
156    public String getSearchText() {
157        return edSearchText.getText();
158    }
159
160    /**
161     * Sets a listener to be invoked on double click
162     * @param dblClickListener The double click listener
163     */
164    public void setDblClickListener(ActionListener dblClickListener) {
165        this.dblClickListener = dblClickListener;
166    }
167
168    /**
169     * Sets a listener to be invoked on single click
170     * @param clickListener The click listener
171     */
172    public void setClickListener(ActionListener clickListener) {
173        this.clickListener = clickListener;
174    }
175
176    /**
177     * Adds a selection listener to the presets list.
178     *
179     * @param selectListener The list selection listener
180     * @since 7412
181     */
182    public synchronized void addSelectionListener(ListSelectionListener selectListener) {
183        lsResult.getSelectionModel().addListSelectionListener(selectListener);
184        listSelectionListeners.add(selectListener);
185    }
186
187    /**
188     * Removes a selection listener from the presets list.
189     *
190     * @param selectListener The list selection listener
191     * @since 7412
192     */
193    public synchronized void removeSelectionListener(ListSelectionListener selectListener) {
194        listSelectionListeners.remove(selectListener);
195        lsResult.getSelectionModel().removeListSelectionListener(selectListener);
196    }
197}