package org.limewire.ui.swing.components; import java.awt.AWTEvent; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Collections; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; import net.miginfocom.swing.MigLayout; import org.limewire.collection.AutoCompleteDictionary; import org.limewire.collection.NECallable; import org.limewire.collection.StringTrieSet; import org.limewire.concurrent.ExecutorsHelper; import org.limewire.concurrent.ListeningExecutorService; import org.limewire.concurrent.ListeningFuture; import org.limewire.concurrent.SimpleFuture; import org.limewire.ui.swing.util.SwingUtils; /** An autocompleter that shows its suggestions in a list and can have new suggestions added. */ public class BasicAutoCompleter implements AutoCompleter { private final ListeningExecutorService queue = ExecutorsHelper.newProcessingQueue("AutoCompleteQueue"); private final JPanel entryPanel; private final AutoCompleteList entryList; private ListeningFuture<Boolean> lastFuture; private AutoCompleterCallback callback; private String currentText; private AutoCompleteDictionary dictionary; public BasicAutoCompleter() { this(new StringTrieSet(true)); } public BasicAutoCompleter(AutoCompleteDictionary dictionary) { this.dictionary = dictionary; entryPanel = new JPanel(new MigLayout("insets 0, gap 0, fill")); entryPanel.setBorder(UIManager.getBorder("List.border")); entryPanel.setBackground(UIManager.getColor("List.background")); entryList = new AutoCompleteList(); JScrollPane entryScrollPane = new JScrollPane(entryList); entryScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); entryScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); entryPanel.add(entryScrollPane, "grow"); } public void setDictionary(AutoCompleteDictionary dictionary) { this.dictionary = dictionary; } public void addAutoCompleteSuggestion(String text) { dictionary.addEntry(text); } @Override public void setAutoCompleterCallback(AutoCompleterCallback callback) { this.callback = callback; } @Override public boolean isAutoCompleteAvailable() { return entryList.getModel().getSize() != 0; } @Override public void decrementSelection() { entryList.decrementSelection(); } @Override public JComponent getRenderComponent() { return entryPanel; } @Override public String getSelectedAutoCompleteString() { return (String)entryList.getSelectedValue(); } @Override public void incrementSelection() { entryList.incrementSelection(); } @Override public ListeningFuture<Boolean> setInput(String input) { if(input == null) { input = ""; } currentText = input; NECallable<Boolean> runner = new NECallable<Boolean>() { @Override public Boolean call() { try { final Collection<String> suggestions = dictionary.getPrefixedBy(currentText); SwingUtils.invokeNowOrWaitWithInterrupted(new Runnable() { @Override public void run() { setSuggestions(suggestions); } }); return true; } catch (InterruptedException ignored) { return false; } } }; if(dictionary.isImmediate()) { runner.call(); return new SimpleFuture<Boolean>(true); } else { if(lastFuture != null) { lastFuture.cancel(true); } setSuggestions(Collections.<String>emptyList()); lastFuture = queue.submit(runner); return lastFuture; } } private void setSuggestions(Collection<String> suggestions) { ListModel model = entryList.getModel(); boolean different = suggestions.size() != model.getSize(); if(!different) { // Look closer... int i = 0; for(String item : suggestions) { if(i < model.getSize() && item.equals(model.getElementAt(i))) { different = true; break; } i++; } } // if things were different, reset the data. if(different) { entryList.setModel(new CollectionBackedListModel(suggestions)); entryList.setVisibleRowCount(Math.min(8, suggestions.size())); entryList.clearSelection(); } } /** A list that's used to show auto-complete items. */ private class AutoCompleteList extends JList { AutoCompleteList() { enableEvents(AWTEvent.MOUSE_EVENT_MASK); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); setFocusable(false); } /** * Sets the text field's selection with the clicked item. */ @Override protected void processMouseEvent(MouseEvent me) { super.processMouseEvent(me); if(me.getID() == MouseEvent.MOUSE_CLICKED) { int idx = locationToIndex(me.getPoint()); if(idx != -1 && isSelectedIndex(idx)) { callback.itemSuggested((String)getSelectedValue(), false, true); } } } /** * Increments the selection by one. */ void incrementSelection() { if(getSelectedIndex() == getModel().getSize() - 1) { callback.itemSuggested(currentText, true, false); clearSelection(); } else { int selectedIndex = getSelectedIndex() + 1; setSelectedIndex(selectedIndex); ensureIndexIsVisible(selectedIndex); callback.itemSuggested((String)getSelectedValue(), true, false); } } /** * Decrements the selection by one. */ void decrementSelection() { if(getSelectedIndex() == 0) { callback.itemSuggested(currentText, true, false); clearSelection(); } else { int selectedIndex = getSelectedIndex(); if(selectedIndex == -1) selectedIndex = getModel().getSize() - 1; else selectedIndex--; setSelectedIndex(selectedIndex); ensureIndexIsVisible(selectedIndex); callback.itemSuggested((String)getSelectedValue(), true, false); } } } }