package org.limewire.ui.swing.search;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import net.miginfocom.swing.MigLayout;
import org.jdesktop.application.Resource;
import org.limewire.collection.AutoCompleteDictionary;
import org.limewire.collection.StringTrieSet;
import org.limewire.ui.swing.components.AutoCompleter;
import org.limewire.ui.swing.components.CollectionBackedListModel;
import org.limewire.ui.swing.components.ExtendedCompoundBorder;
import org.limewire.ui.swing.components.SideLineBorder;
import org.limewire.ui.swing.util.FontUtils;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.I18n;
/** An autocompleter that shows its suggestions in a list and can have new suggestions added. */
public class HistoryAndFriendAutoCompleter implements AutoCompleter {
@Resource private Color selectionBackground;
@Resource private Font font;
private final JPanel entryPanel;
private AutoCompleterCallback callback;
private String currentText;
private boolean showSuggestions = true;
private AutoCompleteDictionary historyDictionary;
private AutoCompleteDictionary suggestionDictionary;
private final AutoCompleteList entryList;
public HistoryAndFriendAutoCompleter() {
this(new StringTrieSet(true));
}
public HistoryAndFriendAutoCompleter(AutoCompleteDictionary dictionary) {
GuiUtils.assignResources(this);
this.suggestionDictionary = dictionary;
entryPanel = new JPanel(new MigLayout("insets 0, gap 0, fill"));
entryPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
entryPanel.setBackground(UIManager.getColor("List.background"));
entryList = new AutoCompleteList();
JScrollPane entryScrollPane = new JScrollPane(entryList);
entryScrollPane.setBorder(BorderFactory.createEmptyBorder());
entryScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
entryScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
entryPanel.add(entryScrollPane, "grow");
}
public void setSuggestionsShown(boolean value) {
this.showSuggestions = value;
}
public void setSuggestionDictionary(AutoCompleteDictionary dictionary) {
this.suggestionDictionary = dictionary;
}
public void setHistoryDictionary(AutoCompleteDictionary dictionary) {
this.historyDictionary = dictionary;
}
@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() {
Object selection = entryList.getSelectedValue();
if(selection != null) {
return selection.toString();
} else {
return null;
}
}
@Override
public void incrementSelection() {
entryList.incrementSelection();
}
@Override
public void setInput(String input) {
if(input == null) {
input = "";
}
currentText = input;
Collection<String> histories = historyDictionary.getPrefixedBy(currentText);
ArrayList<Entry> items = new ArrayList<Entry>(histories.size());
for(String string : histories) {
items.add(new Entry(string, Entry.Reason.HISTORY));
}
if(showSuggestions) {
Collection<String> suggestions = suggestionDictionary.getPrefixedBy(currentText);
items.ensureCapacity(items.size() + suggestions.size());
boolean needFirstSuggestion = true;
for(String string : suggestions) {
if(needFirstSuggestion) {
items.add(new Entry(string, Entry.Reason.FIRST_SUGGESTION));
needFirstSuggestion = false;
} else {
items.add(new Entry(string, Entry.Reason.SUGGESTION));
}
}
}
entryList.setModel(new CollectionBackedListModel(items));
entryList.setVisibleRowCount(Math.min(8, items.size()));
}
/** 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);
setCellRenderer(new Renderer());
setFont(font);
setSelectionBackground(selectionBackground);
setSelectionForeground(Color.BLACK);
}
// override to return true always, to enforce '...' added
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
/**
* 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(getSelectedValue().toString(), 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(getSelectedValue().toString(), 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(getSelectedValue().toString(), true, false);
}
}
}
private static class Renderer extends DefaultListCellRenderer {
private final ExtendedCompoundBorder compoundBorder;
private final JPanel firstSuggestionPanel;
private final DefaultListCellRenderer firstSuggestionLabel;
private final JLabel firstSuggestionTitle;
private final Border firstSuggestionBorder = new SideLineBorder(Color.GRAY, SideLineBorder.Side.TOP);
public Renderer() {
compoundBorder = new ExtendedCompoundBorder(BorderFactory.createEmptyBorder(), BorderFactory.createEmptyBorder(0, 2, 0, 2));
firstSuggestionPanel = new JPanel();
firstSuggestionPanel.setLayout(new MigLayout("fill, gap 0, insets 0"));
firstSuggestionPanel.setBorder(compoundBorder);
firstSuggestionLabel = new DefaultListCellRenderer();
firstSuggestionTitle = new DefaultListCellRenderer();
firstSuggestionTitle.setText(I18n.tr("Friend Suggestions"));
FontUtils.changeSize(firstSuggestionTitle, -1);
firstSuggestionTitle.setForeground(Color.GRAY);
firstSuggestionPanel.add(firstSuggestionLabel, "alignx left, grow, wmin 0, gapright 10");
firstSuggestionPanel.add(firstSuggestionTitle, "alignx right");
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
String render;
if(value == null) {
render = "";
} else {
render = value.toString();
if(value instanceof Entry) {
if(((Entry)value).reason == Entry.Reason.FIRST_SUGGESTION) {
firstSuggestionLabel.getListCellRendererComponent(list, render, index, isSelected, cellHasFocus);
firstSuggestionLabel.setBorder(BorderFactory.createEmptyBorder());
if(isSelected) {
firstSuggestionTitle.setForeground(list.getSelectionForeground());
} else {
firstSuggestionTitle.setForeground(Color.GRAY);
}
if(index != 0) {
compoundBorder.setOuterBorder(firstSuggestionBorder);
} else {
compoundBorder.setOuterBorder(BorderFactory.createEmptyBorder());
}
firstSuggestionTitle.setBackground(firstSuggestionLabel.getBackground());
firstSuggestionPanel.setBackground(firstSuggestionLabel.getBackground());
return firstSuggestionPanel;
}
}
}
super.getListCellRendererComponent(list, render, index, isSelected, cellHasFocus);
compoundBorder.setOuterBorder(getBorder());
setBorder(compoundBorder);
return this;
}
}
private static class Entry {
private static enum Reason { HISTORY, FIRST_SUGGESTION, SUGGESTION };
private final String item;
private final Reason reason;
public Entry(String item, Reason reason) {
this.item = item;
this.reason = reason;
}
@Override
public String toString() {
return item;
}
@Override
public boolean equals(Object obj) {
if(obj == this) {
return true;
} else {
return ((Entry)obj).item.equals(item) && ((Entry)obj).reason == reason;
}
}
}
}