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 * Returns the search text entered by user. 168 * @return the search text entered by user 169 * @since 14975 170 */ 171 public String getSearchText() { 172 return edSearchText.getText(); 173 } 174 175 /** 176 * Sets a listener to be invoked on double click 177 * @param dblClickListener The double click listener 178 */ 179 public void setDblClickListener(ActionListener dblClickListener) { 180 this.dblClickListener = dblClickListener; 181 } 182 183 /** 184 * Sets a listener to be invoked on ssingle click 185 * @param clickListener The click listener 186 */ 187 public void setClickListener(ActionListener clickListener) { 188 this.clickListener = clickListener; 189 } 190 191 /** 192 * Adds a selection listener to the presets list. 193 * 194 * @param selectListener The list selection listener 195 * @since 7412 196 */ 197 public synchronized void addSelectionListener(ListSelectionListener selectListener) { 198 lsResult.getSelectionModel().addListSelectionListener(selectListener); 199 listSelectionListeners.add(selectListener); 200 } 201 202 /** 203 * Removes a selection listener from the presets list. 204 * 205 * @param selectListener The list selection listener 206 * @since 7412 207 */ 208 public synchronized void removeSelectionListener(ListSelectionListener selectListener) { 209 listSelectionListeners.remove(selectListener); 210 lsResult.getSelectionModel().removeListSelectionListener(selectListener); 211 } 212}