/** * 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); } } }