All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jidesoft.swing.AutoCompletion Maven / Gradle / Ivy

There is a newer version: 3.6.18
Show newest version
/*
 * @(#)AutoCompletion.java 6/22/2005
 *
 * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.utils.PortingUtils;
import com.jidesoft.utils.SystemInfo;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.*;
import javax.swing.tree.TreePath;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;

/**
 * AutoCompletion is a helper class to make JTextComponent or JComboBox auto-complete based on a list of
 * known items.
 * 

* There are three constructors. The simplest one is {@link #AutoCompletion(javax.swing.JComboBox)}. It takes any * combobox and make it auto completion. If you are looking for an auto-complete combobox solution, this is all you * need. However AutoCompletion can do more than that. There are two more constructors. One is {@link * #AutoCompletion(javax.swing.text.JTextComponent, Searchable)}. It will use {@link Searchable} which is another * component available in JIDE to make the JTextCompoent auto-complete. We used Searchable here because it provides a * common interface to access the element in JTree, JList or JTable. In the other word, the known list item we used to * auto-complete can be got from JTree or JList or even JTable or any other component as long as it has Searchable * interface implemented. The last constructor takes any java.util.List and use it as auto completion list. *

* The only option available on AutoCompletion is {@link #setStrict(boolean)}. If it's true, it will not * allow user to type in anything that is not in the known item list. If false, user can type in whatever he/she wants. * If the text can match with a item in the known item list, it will still auto-complete. *

* * @author Thomas Bierhance * @author JIDE Software, Inc. */ public class AutoCompletion { private Searchable _searchable; private JTextComponent _textComponent; private AutoCompletionDocument _document; // flag to indicate if setSelectedItem has been called // subsequent calls to remove/insertString should be ignored private boolean _selecting = false; private boolean _hidePopupOnFocusLoss; // we use this flag to workaround the bug that setText() will trigger auto-completion private boolean _keyTyped = false; private boolean _hitBackspace = false; private boolean _hitBackspaceOnSelection; private KeyListener _editorKeyListener; // private FocusListener _editorFocusListener; private boolean _strict = true; private boolean _strictCompletion = true; private PropertyChangeListener _propertyChangeListener; private JComboBox _comboBox; private Document _oldDocument; /** * The client property for AutoCompletion instance. When AutoCompletion is installed on a text component, this * client property has the AutoCompletion. */ public static final String CLIENT_PROPERTY_AUTO_COMPLETION = "AutoCompletion"; public AutoCompletion(final JComboBox comboBox) { this(comboBox, new ComboBoxSearchable(comboBox)); } public AutoCompletion(final JComboBox comboBox, Searchable searchable) { _searchable = searchable; _propertyChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if ("editor".equals(e.getPropertyName())) { if (e.getNewValue() != null) { _textComponent = (JTextComponent) ((ComboBoxEditor) e.getNewValue()).getEditorComponent(); configureEditor(getTextComponent()); } } } }; _comboBox = comboBox; _searchable.setWildcardEnabled(false); if (_searchable instanceof ComboBoxSearchable) { ((ComboBoxSearchable) _searchable).setShowPopupDuringSearching(false); } _textComponent = (JTextComponent) comboBox.getEditor().getEditorComponent(); installListeners(); } public AutoCompletion(final JTextComponent textComponent, final Searchable searchable) { _searchable = searchable; _searchable.setWildcardEnabled(false); _textComponent = textComponent; registerSelectionListener(getSearchable()); installListeners(); } public AutoCompletion(final JTextComponent textComponent, final List list) { this(textComponent, new Searchable(new JLabel()) { int _selectIndex = -1; @Override protected int getSelectedIndex() { return _selectIndex; } @Override protected void setSelectedIndex(int index, boolean incremental) { _selectIndex = index; } @Override protected int getElementCount() { return list.size(); } @Override protected Object getElementAt(int index) { return list.get(index); } @Override protected String convertElementToString(Object element) { return "" + element; } }); } public AutoCompletion(final JTextComponent textComponent, final Object[] array) { this(textComponent, new Searchable(new JLabel()) { int _selectIndex = -1; @Override protected int getSelectedIndex() { return _selectIndex; } @Override protected void setSelectedIndex(int index, boolean incremental) { _selectIndex = index; } @Override protected int getElementCount() { return array.length; } @Override protected Object getElementAt(int index) { return array[index]; } @Override protected String convertElementToString(Object element) { return "" + element; } }); } private void registerSelectionListener(Searchable searchable) { if (searchable.getComponent() instanceof JList) { final JList list = (JList) getSearchable().getComponent(); list.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { int index = list.getSelectedIndex(); if (index != -1) { getTextComponent().setText(getSearchable().convertElementToString(list.getModel().getElementAt(index))); highlightCompletedText(0); } } }); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, list, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, list, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, list, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, list, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); } else if (searchable.getComponent() instanceof JTree) { final JTree tree = (JTree) getSearchable().getComponent(); tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath treePath = tree.getSelectionPath(); if (treePath != null) { getTextComponent().setText("" + treePath.getLastPathComponent()); highlightCompletedText(0); } else { getTextComponent().setText(""); } } }); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, tree, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, tree, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, tree, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)); DelegateAction.replaceAction(getTextComponent(), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, tree, JComponent.WHEN_FOCUSED, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)); } } private boolean isKeyTyped() { return _keyTyped; } private void setKeyTyped(boolean keyTyped) { _keyTyped = keyTyped; } private void setInitValue() { int index = getSearchable().getSelectedIndex(); if (index != -1) { Object selected = getSearchable().getElementAt(index); if (selected != null) _document.setText(getSearchable().convertElementToString(selected)); highlightCompletedText(0); } else { _document.setText(""); } } /** * Uninstalls the listeners so that the component is not auto-completion anymore. */ public void uninstallListeners() { if (_propertyChangeListener != null && _comboBox != null) { _comboBox.removePropertyChangeListener(_propertyChangeListener); } if (getTextComponent() != null) { getTextComponent().removeKeyListener(_editorKeyListener); // getTextComponent().removeFocusListener(_editorFocusListener); String text = getTextComponent().getText(); if (_oldDocument != null) { getTextComponent().setDocument(_oldDocument); _oldDocument = null; } getTextComponent().setText(text); } getTextComponent().putClientProperty(CLIENT_PROPERTY_AUTO_COMPLETION, null); } /** * Installs the listeners needed for auto-completion feature. Please note, this method is already called when you * create AutoCompletion. Unless you called {@link #uninstallListeners()}, there is no need to call this method * yourself. */ public void installListeners() { if (_comboBox != null && _propertyChangeListener != null) { _comboBox.addPropertyChangeListener(_propertyChangeListener); } _editorKeyListener = new KeyAdapter() { private boolean _deletePressed; private String _saveText; @Override public void keyPressed(KeyEvent e) { _hitBackspace = false; if (KeyEvent.VK_ESCAPE != e.getKeyCode()) { setKeyTyped(true); } switch (e.getKeyCode()) { // determine if the pressed key is backspace (needed by the remove method) case KeyEvent.VK_BACK_SPACE: if (isStrict()) { _hitBackspace = true; _hitBackspaceOnSelection = getTextComponent().getSelectionStart() != getTextComponent().getSelectionEnd(); } break; // ignore delete key case KeyEvent.VK_DELETE: if (isStrict()) { _deletePressed = true; _saveText = getTextComponent().getText(); } break; } } @Override public void keyReleased(KeyEvent e) { super.keyReleased(e); try { if (_deletePressed) { _deletePressed = false; String text = getTextComponent().getText(); int index = getSearchable().findFirst(text); if (index != -1) { if (text.length() == 0) { setSelectedItem(""); } else { Object item = getSearchable().getElementAt(index); setSelectedItem(item); getTextComponent().setText(getSearchable().convertElementToString(item)); // this is what auto complete is // select the completed part highlightCompletedText(text.length()); } } else { // didn't find a matching one if (isStrict()) { getTextComponent().setText(_saveText); e.consume(); PortingUtils.notifyUser(_textComponent); } } } } finally { if (KeyEvent.VK_ESCAPE != e.getKeyCode()) { setKeyTyped(false); } } } }; // Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when tabbing out _hidePopupOnFocusLoss = SystemInfo.isJdk15Above(); // // Highlight whole text when gaining focus // _editorFocusListener = new FocusAdapter() { // @Override // public void focusGained(FocusEvent e) { // highlightCompletedText(0); // } // // @Override // public void focusLost(FocusEvent e) { // // Workaround for Bug 5100422 - Hide Popup on focus loss //// if (_hidePopupOnFocusLoss) comboBox.setPopupVisible(false); // } // }; _document = createDocument(); configureEditor(getTextComponent()); getTextComponent().putClientProperty(CLIENT_PROPERTY_AUTO_COMPLETION, this); } /** * Creates AutoCompletionDocument. * * @return the AutoCompletionDocument. */ protected AutoCompletionDocument createDocument() { return new AutoCompletionDocument(); } private void configureEditor(JTextComponent textComponent) { if (getTextComponent() != null) { getTextComponent().removeKeyListener(_editorKeyListener); // getTextComponent().removeFocusListener(_editorFocusListener); } if (textComponent != null) { _textComponent = textComponent; getTextComponent().addKeyListener(_editorKeyListener); // getTextComponent().addFocusListener(_editorFocusListener); String text = getTextComponent().getText(); _oldDocument = getTextComponent().getDocument(); if (_oldDocument instanceof AbstractDocument && _document != null) { _document.setDocumentFilter(((AbstractDocument) _oldDocument).getDocumentFilter()); } getTextComponent().setDocument(_document); getTextComponent().setText(text); } } /** * The document class used by AutoCompletion. */ protected class AutoCompletionDocument extends PlainDocument { @Override public void remove(int offs, int len) throws BadLocationException { // return immediately when _selecting an item if (_selecting) return; if (_hitBackspace) { // user hit backspace => move the selection backwards // old item keeps being selected if (offs > 0) { if (_hitBackspaceOnSelection) offs--; } else { // User hit backspace with the cursor positioned on the start => beep PortingUtils.notifyUser(_textComponent); } highlightCompletedText(offs); } else { super.remove(offs, len); } } @Override public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { // return immediately when _selecting an item if (_selecting) return; // insert the string into the document super.insertString(offs, str, a); if (isKeyTyped() || isStrict()) { // lookup and select a matching item final String text = getText(0, getLength()); int exactIndex = getSearchable().findFirstExactly(text); if (exactIndex != -1) { return; } int index = getSearchable().findFromCursor(text); Object item; if (index != -1) { item = getSearchable().getElementAt(index); setSelectedItem(item); setText(getSearchable().convertElementToString(item)); // this is what auto complete is // select the completed part highlightCompletedText(offs + str.length()); } else { // didn't find a matching one if (isStrict()) { if (offs == 0 && text.equals(str)) { getSearchable().textChanged(""); index = getSearchable().findFromCursor(text); if (index != -1) { item = getSearchable().getElementAt(index); setSelectedItem(item); setText(getSearchable().convertElementToString(item)); // this is what auto complete is // select the completed part highlightCompletedText(offs + str.length()); } else { // didn't find a matching one if (isStrict()) { index = getSearchable().getSelectedIndex(); if (index == -1) { if (getSearchable().getElementCount() > 0) { index = 0; getSearchable().setSelectedIndex(0, false); } } if (index != -1) { item = getSearchable().getElementAt(index); offs = offs - str.length(); // imitate no insert (later on offs will be incremented by str.length(): selection won't move forward) PortingUtils.notifyUser(_textComponent); setText(getSearchable().convertElementToString(item)); // select the completed part highlightCompletedText(offs + str.length()); } } } return; } index = getSearchable().getSelectedIndex(); if (index == -1) { if (getSearchable().getElementCount() > 0) { index = 0; getSearchable().setSelectedIndex(0, false); } } if (index != -1) { item = getSearchable().getElementAt(index); offs = offs - str.length(); // imitate no insert (later on offs will be incremented by str.length(): selection won't move forward) PortingUtils.notifyUser(_textComponent); setText(getSearchable().convertElementToString(item)); // select the completed part highlightCompletedText(offs + str.length()); } } } } } protected void setText(String text) { try { // remove all text and insert the completed string if (isStrictCompletion()) { super.remove(0, getLength()); super.insertString(0, text, null); } else { String existingText = super.getText(0, getLength()); int matchIndex = existingText.length() <= text.length() ? existingText.length() : text.length(); // try to find a match for (int i = 0; i < existingText.length(); i++) { if (!existingText.substring(0, matchIndex).equalsIgnoreCase(text.substring(0, matchIndex))) { matchIndex--; } } // remove the no-match part and complete with the one in Searchable super.remove(matchIndex, getLength() - matchIndex); super.insertString(matchIndex, text.substring(matchIndex), null); } } catch (BadLocationException e) { throw new RuntimeException(e.toString()); } } } private void highlightCompletedText(int start) { int length = getTextComponent().getDocument().getLength(); getTextComponent().setCaretPosition(length); if (start < 0) { start = 0; } if (start > length) { start = length; } getTextComponent().moveCaretPosition(start); } private void setSelectedItem(Object item) { _selecting = true; for (int i = 0, n = getSearchable().getElementCount(); i < n; i++) { Object currentItem = getSearchable().getElementAt(i); // current item starts with the pattern? if (JideSwingUtilities.equals(item, currentItem)) { getSearchable().setSelectedIndex(i, false); } } _selecting = false; } /** * Gets the strict property. * * @return the value of strict property. */ public boolean isStrict() { return _strict; } /** * Sets the strict property. If true, it will not allow user to type in anything that is not in the known item list. * If false, user can type in whatever he/she wants. If the text can match with a item in the known item list, it * will still auto-complete. * * @param strict */ public void setStrict(boolean strict) { _strict = strict; } /** * Gets the strict completion property. * * @return the value of strict completion property. * @see #setStrictCompletion(boolean) */ public boolean isStrictCompletion() { return _strictCompletion; } /** * Sets the strict completion property. If true, in case insensitive searching, it will always use the exact item in * the Searchable to replace whatever user types. For example, when Searchable has an item "Arial" and user types in * "AR", if this flag is true, it will auto-completed as "Arial". If false, it will be auto-completed as "ARial". Of * course, this flag will only make a difference if Searchable is case insensitive. * * @param strictCompletion */ public void setStrictCompletion(boolean strictCompletion) { _strictCompletion = strictCompletion; } /** * Gets the underlying text component which auto-completes. * * @return the underlying text component. */ protected JTextComponent getTextComponent() { return _textComponent; } /** * Gets the underlying Searchable. If you use the constructor {@link #AutoCompletion(javax.swing.text.JTextComponent, Searchable)}, * the return value will be the Searchable you passed in. If you use the other twoconstructorss, internally we will * still create a Searchable. If so, this Searchable will be returned. * * @return the Searchable. */ public Searchable getSearchable() { return _searchable; } /** * When auto-completion is enabled on a text component, we will set a client property on it. This method will look * for this client property and return you the instance of the AutoCompletion that is installed on the component. * * @param component the component. * @return the AutoCompletion. If null, it means there is no AutoCompletion installed. */ public static AutoCompletion getAutoCompletion(JComponent component) { if (component == null) return null; Object clientProperty = component.getClientProperty(CLIENT_PROPERTY_AUTO_COMPLETION); if (clientProperty instanceof AutoCompletion) { return (AutoCompletion) clientProperty; } return null; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy