/**
* Code taken freely from
* http://www.java-engineer.com/java/auto-complete.html
*/
//------------------------------------------------------------------------------
// Copyright (c) 1999-2001 Matt Welsh. All Rights Reserved.
//------------------------------------------------------------------------------
package com.limegroup.gnutella.gui;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.FocusEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Iterator;
import java.util.Vector;
import javax.swing.JButton;
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.Popup;
import javax.swing.PopupFactory;
import javax.swing.ScrollPaneConstants;
import javax.swing.UIManager;
import javax.swing.text.Document;
import com.limegroup.gnutella.util.CommonUtils;
/**
*
* @author Matt Welsh (matt@matt-welsh.com)
*
* @modified Sam Berlin
* .. to not implement AutoComplete, not take
* the dictionary in the constructor, allow the Dictionary
* and listeners to be lazily created, and update the dictionary at will.
* .. update to use Popup/PopupFactory.
*
* @modified David Soh (yunharla00@hotmail.com)
* Added enhancements/fixes:
* 1. Fixed messing up of "Chinese (Simplified) Microsoft Pinyin IME 3.0" input
* for bilingual users of Windows XP (Eng).
* 2. Popup to show and allow selection of
* a. all autocomplete matches for current text, or
* b. all available entries
* Notes:
* Using JComboBox for popup messes up IME input even more!
* JPopupMenu item selection (GUI) is not properly updated with some L&F.
*
*/
public class ClearableAutoCompleteTextField extends AutoCompleteTextField {
public ClearableAutoCompleteTextField() { super(); init(); }
public ClearableAutoCompleteTextField(Document a, String b, int c) {super(a, b, c); init();}
public ClearableAutoCompleteTextField(int a) { super(a); init();}
public ClearableAutoCompleteTextField(String a) { super(a); init();}
public ClearableAutoCompleteTextField(String a, int b) { super(a, b); init();}
/**
* Sets up stuff.
*/
private void init() {
enableEvents(AWTEvent.KEY_EVENT_MASK);
enableEvents(AWTEvent.HIERARCHY_EVENT_MASK);
enableEvents(AWTEvent.FOCUS_EVENT_MASK);
}
/**
* Fires an action event.
*
* If the popup is visible, this resets the current
* text to be the selection on the popup (if something was selected)
* prior to firing the event.
*/
protected void fireActionPerformed() {
if(popup != null) {
String selection = (String)entryList.getSelectedValue();
hidePopup();
if(selection != null) {
setText(selection);
return;
}
}
super.fireActionPerformed();
}
/**
* Forwards necessary events to the AutoCompleteList.
*/
public void processKeyEvent(KeyEvent evt) {
if(evt.getKeyCode() == KeyEvent.VK_UP || evt.getKeyCode() == KeyEvent.VK_DOWN)
evt.consume();
super.processKeyEvent(evt);
if(dict != null) {
switch(evt.getID()) {
case KeyEvent.KEY_PRESSED:
switch(evt.getKeyCode()) {
case KeyEvent.VK_UP:
if(popup != null)
entryList.decrementSelection();
else
showPopup(dict.getIterator());
break;
case KeyEvent.VK_DOWN:
if(popup != null)
entryList.incrementSelection();
else
showPopup(dict.getIterator());
break;
}
break;
case KeyEvent.KEY_TYPED:
switch(evt.getKeyChar()) {
case KeyEvent.VK_ESCAPE:
if (popup != null) {
hidePopup();
selectAll();
}
break;
case KeyEvent.VK_ENTER:
break;
default:
autoCompleteInput();
}
}
}
}
/**
* Ensures the popup gets hidden if this text-box is hidden and that
* the popup is shown if a previous show is pending (from trying to
* autocomplete while it wasn't visible).
*/
protected void processHierarchyEvent(HierarchyEvent evt) {
super.processHierarchyEvent(evt);
if((evt.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) {
boolean showing = isShowing();
if(!showing && popup != null)
hidePopup();
else if(showing && popup == null && showPending)
autoCompleteInput();
}
}
/**
* Ensures that if we lose focus, the popup goes away.
*/
protected void processFocusEvent(FocusEvent evt) {
super.processFocusEvent(evt);
if(evt.getID() == FocusEvent.FOCUS_LOST) {
if(popup != null)
hidePopup();
}
}
//----------------------------------------------------------------------------
// Protected methods
//----------------------------------------------------------------------------
// overwritten to disable
protected void setUp() {
}
/**
* Gets the component that is the popup listing other choices.
*/
protected JComponent getPopupComponent() {
if(entryPanel != null)
return entryPanel;
entryPanel = new JPanel(new GridBagLayout());
entryPanel.setBorder(UIManager.getBorder("List.border"));
entryPanel.setBackground(UIManager.getColor("List.background"));
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.gridwidth = GridBagConstraints.REMAINDER;
entryList = new AutoCompleteList();
JScrollPane entryScrollPane = new JScrollPane(entryList);
entryScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
entryScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
entryPanel.add(entryScrollPane, c);
entryPanel.add(new ClearHistory(), c);
return entryPanel;
}
/**
* Fills the popup with text & shows it.
*/
protected void showPopup(Iterator iter) {
getPopupComponent(); // construct the component.
boolean different = false;
Vector v = new Vector();
ListModel model = entryList.getModel();
for(int i = 0; iter.hasNext(); i++) {
Object next = iter.next();
v.add(next);
if(!different && i < model.getSize())
different |= !next.equals(model.getElementAt(i));
}
different |= model.getSize() != v.size();
// if things were different, reset the data.
if(different) {
entryList.setListData(v);
entryList.clearSelection();
}
entryList.setCurrentText(getText());
showPopup();
}
/**
* Shows the popup.
*/
public void showPopup() {
// only show the popup if we're currently visible.
// due to delay in focus-forwarding & key-pressing events,
// we may not be visible by the time this is called.
if(popup == null && entryList.getModel().getSize() > 0) {
if(isShowing()) {
Point origin = getLocationOnScreen();
PopupFactory pf = PopupFactory.getSharedInstance();
Component parent = this;
// OSX doesn't handle MOUSE_CLICKED events correctly
// using medium-weight popups, so we need to force
// PopupFactory to return a heavy-weight popup.
// This is done by adding a panel into a Popup, which implicitly
// adds it into a Popup.HeavyWeightWindow, which PopupFactory happens
// to check as a condition for returning a heavy-weight popup.
// In an ideal world, the OSX bug would be fixed.
// In a less ideal world, Popup & PopupFactory would be written so that
// outside developers can correctly subclass methods.
if(CommonUtils.isMacOSX()) {
parent = new JPanel();
new MyPopup(this, parent, 0, 0);
}
popup = pf.getPopup(parent, getPopupComponent(), origin.x, origin.y + getHeight() + 1);
showPending = false;
popup.show();
} else {
showPending = true;
}
}
}
/**
* Hides the popup window.
*/
public void hidePopup() {
showPending = false;
if(popup != null) {
popup.hide();
popup = null;
}
}
/**
* Displays the popup window with a list of auto-completable choices,
* if any exist.
*/
public void autoCompleteInput() {
String input = getText();
if (input != null && input.length() > 0) {
Iterator it = dict.getIterator(input);
if(it.hasNext())
showPopup(it);
else
hidePopup();
} else {
hidePopup();
}
}
//----------------------------------------------------------------------------
// Fields
//----------------------------------------------------------------------------
/** The list auto-completable items are shown in */
protected AutoCompleteList entryList;
/** The panel the popup is shown in. */
protected JPanel entryPanel;
/** The popup the scroll pane is in */
protected Popup popup;
/** Whether or not we tried to show a popup while this wasn't showing */
protected boolean showPending;
/**
* Component that clears the history of the dictionary when clicked.
*/
private class ClearHistory extends JButton {
ClearHistory() {
super(GUIMediator.getStringResource("GENERAL_CLEAR_HISTORY"));
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
setFocusable(false);
}
protected void processMouseEvent(MouseEvent me) {
super.processMouseEvent(me);
if(me.getID() == MouseEvent.MOUSE_CLICKED) {
ClearableAutoCompleteTextField.this.dict.clear();
hidePopup();
}
}
}
/**
* A list that's used to show auto-complete items.
*/
private class AutoCompleteList extends JList {
private String currentText;
AutoCompleteList() {
super();
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setFocusable(false);
}
/**
* Sets the text field's selection with the clicked item.
*/
protected void processMouseEvent(MouseEvent me) {
super.processMouseEvent(me);
if(me.getID() == MouseEvent.MOUSE_CLICKED) {
int idx = locationToIndex(me.getPoint());
if(idx != -1 && isSelectedIndex(idx)) {
String selection = (String)getSelectedValue();
ClearableAutoCompleteTextField.this.setText(selection);
ClearableAutoCompleteTextField.this.hidePopup();
}
}
}
/**
* Sets the text to place in the text field when items are unselected.
*/
void setCurrentText(String text) {
currentText = text;
}
/**
* Increments the selection by one.
*/
void incrementSelection() {
if(getSelectedIndex() == getModel().getSize() - 1) {
ClearableAutoCompleteTextField.this.setText(currentText);
clearSelection();
} else {
int selectedIndex = getSelectedIndex() + 1;
setSelectedIndex(selectedIndex);
ensureIndexIsVisible(selectedIndex);
ClearableAutoCompleteTextField.this.setText((String)getSelectedValue());
}
}
/**
* Decrements the selection by one.
*/
void decrementSelection() {
if(getSelectedIndex() == 0) {
ClearableAutoCompleteTextField.this.setText(currentText);
clearSelection();
} else {
int selectedIndex = getSelectedIndex();
if(selectedIndex == -1)
selectedIndex = getModel().getSize() - 1;
else
selectedIndex--;
setSelectedIndex(selectedIndex);
ensureIndexIsVisible(selectedIndex);
ClearableAutoCompleteTextField.this.setText((String)getSelectedValue());
}
}
/**
* Sets the size according to the number of entries.
*/
public Dimension getPreferredScrollableViewportSize() {
int width = ClearableAutoCompleteTextField.this.getSize().width - 2;
int rows = Math.min(getModel().getSize(), 8);
int height = rows * getCellBounds(0, 0).height;
return new Dimension(width, height);
}
}
/**
* Subclass that provides access to the constructor.
*/
private static class MyPopup extends Popup {
public MyPopup(Component owner, Component contents, int x, int y) {
super(owner, contents, x, y);
}
}
}