/* Jajuk Specific version of this swingx class to fix * this: https://swingx.dev.java.net/issues/show_bug.cgi?id=464 * * This file has been adapted to Jajuk by the Jajuk Team. * Jajuk Copyright (C) 2007 The Jajuk Team * * The original copyrights and license follow: * * Copyright 2004 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All rights * reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package ext; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; 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; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.ComboBoxEditor; import javax.swing.InputMap; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.KeyStroke; import javax.swing.UIManager; import javax.swing.text.DefaultEditorKit; import javax.swing.text.JTextComponent; import javax.swing.text.TextAction; import org.jdesktop.swingx.autocomplete.AbstractAutoCompleteAdaptor; import org.jdesktop.swingx.autocomplete.AutoCompleteComboBoxEditor; import org.jdesktop.swingx.autocomplete.ComboBoxAdaptor; import org.jdesktop.swingx.autocomplete.ListAdaptor; import org.jdesktop.swingx.autocomplete.ObjectToStringConverter; import org.jdesktop.swingx.autocomplete.TextComponentAdaptor; /** * This class contains only static utility methods that can be used to set up * automatic completion for some Swing components. * <p> * Usage examples: * </p> * <p> * * <pre><code> * JComboBox comboBox = [...]; * AutoCompleteDecorator.<b>decorate</b>(comboBox); * * List items = [...]; * JTextField textField = [...]; * AutoCompleteDecorator.<b>decorate</b>(textField, items); * * JList list = [...]; * JTextField textField = [...]; * AutoCompleteDecorator.<b>decorate</b>(list, textField); * </code></pre> * * </p> * * @author Thomas Bierhance */ public final class AutoCompleteDecorator { /** * private constructor to avoid instantiating utility class. */ private AutoCompleteDecorator() { } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given <tt>List</tt>. * * @param textComponent the text component that will be used for automatic completion. * @param items contains the items that are used for autocompletion * @param strictMatching <tt>true</tt>, if only given items should be allowed to be * entered */ public static void decorate(JTextComponent textComponent, List<Object> items, boolean strictMatching) { decorate(textComponent, items, strictMatching, ObjectToStringConverter.DEFAULT_IMPLEMENTATION); } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given <tt>List</tt>. * * @param textComponent the text component that will be used for automatic completion. * @param items contains the items that are used for autocompletion * @param strictMatching <tt>true</tt>, if only given items should be allowed to be * entered * @param stringConverter the converter used to transform items to strings */ public static void decorate(JTextComponent textComponent, List<Object> items, boolean strictMatching, ObjectToStringConverter stringConverter) { AbstractAutoCompleteAdaptor adaptor = new TextComponentAdaptor(textComponent, items); AutoCompleteDocument document = new AutoCompleteDocument(adaptor, strictMatching, stringConverter); decorate(textComponent, document, adaptor); } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given JList. The two components will be * synchronized. The automatic completion will always be strict. * * @param list a <tt>JList</tt> containing the items for automatic completion * @param textComponent the text component that will be enabled for automatic completion */ public static void decorate(JList list, JTextComponent textComponent) { decorate(list, textComponent, ObjectToStringConverter.DEFAULT_IMPLEMENTATION); } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given JList. The two components will be * synchronized. The automatic completion will always be strict. * * @param list a <tt>JList</tt> containing the items for automatic completion * @param textComponent the text component that will be used for automatic completion * @param stringConverter the converter used to transform items to strings */ public static void decorate(JList list, JTextComponent textComponent, ObjectToStringConverter stringConverter) { AbstractAutoCompleteAdaptor adaptor = new ListAdaptor(list, textComponent, stringConverter); AutoCompleteDocument document = new AutoCompleteDocument(adaptor, true, stringConverter); decorate(textComponent, document, adaptor); } /** * Enables automatic completion for the given JComboBox. The automatic * completion will be strict (only items from the combo box can be selected) * if the combo box is not editable. * * @param comboBox a combo box */ public static void decorate(final JComboBox comboBox) { decorate(comboBox, ObjectToStringConverter.DEFAULT_IMPLEMENTATION); } /** * Enables automatic completion for the given JComboBox. The automatic * completion will be strict (only items from the combo box can be selected) * if the combo box is not editable. * * @param comboBox a combo box * @param stringConverter the converter used to transform items to strings */ public static void decorate(final JComboBox comboBox, final ObjectToStringConverter stringConverter) { boolean strictMatching = !comboBox.isEditable(); // has to be editable comboBox.setEditable(true); // configure the text component=editor component JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent(); final AbstractAutoCompleteAdaptor adaptor = new ComboBoxAdaptor(comboBox); final AutoCompleteDocument document = new AutoCompleteDocument(adaptor, strictMatching, stringConverter); decorate(editorComponent, document, adaptor); // show the popup list when the user presses a key final KeyListener keyListener = new KeyAdapter() { @Override public void keyPressed(KeyEvent keyEvent) { // don't popup on action keys (cursor movements, etc...) if (keyEvent.isActionKey()) { return; } // don't popup if the combobox isn't visible anyway if (comboBox.isDisplayable() && !comboBox.isPopupVisible()) { int keyCode = keyEvent.getKeyCode(); // don't popup when the user hits shift,ctrl or alt if (keyCode == KeyEvent.VK_SHIFT || keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_ALT) { return; } // don't popup when the user hits escape (see issue #311) if (keyCode == KeyEvent.VK_ESCAPE) { return; } comboBox.setPopupVisible(true); } } }; editorComponent.addKeyListener(keyListener); if (stringConverter != ObjectToStringConverter.DEFAULT_IMPLEMENTATION) { comboBox.setEditor(new AutoCompleteComboBoxEditor(comboBox.getEditor(), stringConverter)); } // Changing the l&f can change the combobox' editor which in turn // would not be autocompletion-enabled. The new editor needs to be // set-up. comboBox.addPropertyChangeListener("editor", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent e) { ComboBoxEditor editor = (ComboBoxEditor) e.getNewValue(); if (editor != null && editor.getEditorComponent() != null) { if (!(editor instanceof AutoCompleteComboBoxEditor) && stringConverter != ObjectToStringConverter.DEFAULT_IMPLEMENTATION) { comboBox.setEditor(new AutoCompleteComboBoxEditor(editor, stringConverter)); // Don't do the decorate step here because calling // setEditor will trigger // the propertychange listener a second time, which will // do the decorate // and addKeyListener step. } else { decorate((JTextComponent) editor.getEditorComponent(), document, adaptor); editor.getEditorComponent().addKeyListener(keyListener); } } } }); } /** * Decorates a given text component for automatic completion using the given * AutoCompleteDocument and AbstractAutoCompleteAdaptor. * * @param textComponent a text component that should be decorated * @param document the AutoCompleteDocument to be installed on the text component * @param adaptor the AbstractAutoCompleteAdaptor to be used */ public static void decorate(JTextComponent textComponent, AutoCompleteDocument document, final AbstractAutoCompleteAdaptor adaptor) { // install the document on the text component textComponent.setDocument(document); // mark entire text when the text component gains focus // otherwise the last mark would have been retained which is quiet // confusing textComponent.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { adaptor.markEntireText(); } }); // Tweak some key bindings InputMap editorInputMap = textComponent.getInputMap(); if (document.isStrictMatching()) { // move the selection to the left on VK_BACK_SPACE editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_BACK_SPACE, 0), DefaultEditorKit.selectionBackwardAction); // ignore VK_DELETE and CTRL+VK_X and beep instead when strict // matching editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DELETE, 0), errorFeedbackAction); editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, java.awt.event.InputEvent.CTRL_DOWN_MASK), errorFeedbackAction); } else { ActionMap editorActionMap = textComponent.getActionMap(); // leave VK_DELETE and CTRL+VK_X as is // VK_BACKSPACE will move the selection to the left if the selected // item is in the list // it will delete the previous character otherwise editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_BACK_SPACE, 0), "nonstrict-backspace"); editorActionMap.put("nonstrict-backspace", new NonStrictBackspaceAction(editorActionMap.get(DefaultEditorKit.deletePrevCharAction), editorActionMap.get(DefaultEditorKit.selectionBackwardAction), adaptor)); } } /** * . */ static class NonStrictBackspaceAction extends TextAction { /** Generated serialVersionUID. */ private static final long serialVersionUID = -5508607690462561673L; private Action backspace; private Action selectionBackward; private AbstractAutoCompleteAdaptor adaptor; /** * Instantiates a new non strict backspace action. * * @param backspace * @param selectionBackward * @param adaptor */ public NonStrictBackspaceAction(Action backspace, Action selectionBackward, AbstractAutoCompleteAdaptor adaptor) { super("nonstrict-backspace"); this.backspace = backspace; this.selectionBackward = selectionBackward; this.adaptor = adaptor; } /* (non-Javadoc) * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent) */ @Override public void actionPerformed(ActionEvent e) { if (adaptor.listContainsSelectedItem()) { selectionBackward.actionPerformed(e); } else { backspace.actionPerformed(e); } } } /** A TextAction that provides an error feedback for the text component that invoked the action. The error feedback is most likely a "beep". */ static Object errorFeedbackAction = new TextAction("provide-error-feedback") { private static final long serialVersionUID = -3868819565696640330L; @Override public void actionPerformed(ActionEvent e) { UIManager.getLookAndFeel().provideErrorFeedback(getTextComponent(e)); } }; }