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}