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

org.jdesktop.swingx.autocomplete.AutoCompleteDecorator Maven / Gradle / Ivy

The newest version!
/*
 * $Id: AutoCompleteDecorator.java 3750 2010-08-08 04:01:13Z kschaefe $
 *
 * 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 org.jdesktop.swingx.autocomplete;

import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusListener;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeListener;
import java.util.List;

import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.UIResource;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.StyledDocument;
import javax.swing.text.TextAction;

import org.jdesktop.swingx.autocomplete.workarounds.MacOSXPopupLocationFix;

/**
 * This class contains only static utility methods that can be used to set up
 * automatic completion for some Swing components.
 * 

Usage examples:

*


 * JComboBox comboBox = [...];
 * AutoCompleteDecorator.decorate(comboBox);
 * 
 * List items = [...];
 * JTextField textField = [...];
 * AutoCompleteDecorator.decorate(textField, items);
 * 
 * JList list = [...];
 * JTextField textField = [...];
 * AutoCompleteDecorator.decorate(list, textField);
 * 

* * @author Thomas Bierhance * @author Karl Schaefer */ public class AutoCompleteDecorator { //these keys were pulled from BasicComboBoxUI from Sun JDK 1.6.0_20 private static final List COMBO_BOX_ACTIONS = unmodifiableList(asList("selectNext", "selectNext2", "selectPrevious", "selectPrevious2", "pageDownPassThrough", "pageUpPassThrough", "homePassThrough", "endPassThrough")); /** * A TextAction that provides an error feedback for the text component that invoked * the action. The error feedback is most likely a "beep". */ private static final Object errorFeedbackAction = new TextAction("provide-error-feedback") { public void actionPerformed(ActionEvent e) { UIManager.getLookAndFeel().provideErrorFeedback(getTextComponent(e)); } }; private AutoCompleteDecorator() { //prevents instantiation } private static void installMap(InputMap componentMap, boolean strict) { InputMap map = new AutoComplete.InputMap(); if (strict) { map.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 map.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DELETE, 0), errorFeedbackAction); map.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, java.awt.event.InputEvent.CTRL_DOWN_MASK), errorFeedbackAction); } else { // VK_BACKSPACE will move the selection to the left if the selected item is in the list // it will delete the previous character otherwise map.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_BACK_SPACE, 0), "nonstrict-backspace"); // leave VK_DELETE and CTRL+VK_X as is } map.setParent(componentMap.getParent()); componentMap.setParent(map); } static AutoCompleteDocument createAutoCompleteDocument( AbstractAutoCompleteAdaptor adaptor, boolean strictMatching, ObjectToStringConverter stringConverter, Document delegate) { if (delegate instanceof StyledDocument) { return new AutoCompleteStyledDocument(adaptor, strictMatching, stringConverter, (StyledDocument) delegate); } return new AutoCompleteDocument(adaptor, strictMatching, stringConverter, delegate); } /** * 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 * @see #decorate(JComboBox, ObjectToStringConverter) */ public static void decorate(JComboBox comboBox) { decorate(comboBox, null); } /** * 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. *

* Note: the {@code AutoCompleteDecorator} will alter the state of * the {@code JComboBox} to be editable. This can cause side effects with * layouts and sizing. {@code JComboBox} caches the size, which differs * depending on the component's editability. Therefore, if the component's * size is accessed prior to being decorated and then the cached size is * forced to be recalculated, the size of the component will change. *

* Because the size of the component can be altered (recalculated), the * decorator does not attempt to set any sizes on the supplied * {@code JComboBox}. Users that need to ensure sizes of supplied combos * should take measures to set the size of the combo. * * @param comboBox * a combo box * @param stringConverter * the converter used to transform items to strings */ public static void decorate(JComboBox comboBox, ObjectToStringConverter stringConverter) { undecorate(comboBox); boolean strictMatching = !comboBox.isEditable(); // has to be editable comboBox.setEditable(true); // fix the popup location MacOSXPopupLocationFix.install(comboBox); // configure the text component=editor component JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent(); final AbstractAutoCompleteAdaptor adaptor = new ComboBoxAdaptor(comboBox); final AutoCompleteDocument document = createAutoCompleteDocument(adaptor, strictMatching, stringConverter, editorComponent.getDocument()); decorate(editorComponent, document, adaptor); editorComponent.addKeyListener(new AutoComplete.KeyAdapter(comboBox)); //set before adding the listener for the editor comboBox.setEditor(new AutoCompleteComboBoxEditor(comboBox.getEditor(), document.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. AutoComplete.PropertyChangeListener pcl = new AutoComplete.PropertyChangeListener(comboBox); comboBox.addPropertyChangeListener("editor", pcl); comboBox.addPropertyChangeListener("enabled", pcl); if (!strictMatching) { ActionMap map = comboBox.getActionMap(); for (String key : COMBO_BOX_ACTIONS) { Action a = map.get(key); map.put(key, new AutoComplete.SelectionAction(a)); } } } static void undecorate(JComboBox comboBox) { JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent(); if (editorComponent.getDocument() instanceof AutoCompleteDocument) { AutoCompleteDocument doc = (AutoCompleteDocument) editorComponent.getDocument(); if (doc.strictMatching) { ActionMap map = comboBox.getActionMap(); for (String key : COMBO_BOX_ACTIONS) { map.put(key, null); } } //remove old property change listener for (PropertyChangeListener l : comboBox.getPropertyChangeListeners("editor")) { if (l instanceof AutoComplete.PropertyChangeListener) { comboBox.removePropertyChangeListener("editor", l); } } for (PropertyChangeListener l : comboBox.getPropertyChangeListeners("enabled")) { if (l instanceof AutoComplete.PropertyChangeListener) { comboBox.removePropertyChangeListener("enabled", l); } } AutoCompleteComboBoxEditor editor = (AutoCompleteComboBoxEditor) comboBox.getEditor(); comboBox.setEditor(editor.wrapped); //remove old key listener for (KeyListener l : editorComponent.getKeyListeners()) { if (l instanceof AutoComplete.KeyAdapter) { editorComponent.removeKeyListener(l); break; } } undecorate(editorComponent); for (ActionListener l : comboBox.getActionListeners()) { if (l instanceof ComboBoxAdaptor) { comboBox.removeActionListener(l); break; } } //TODO remove aqua fix //TODO reset editibility } } /** * 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 JList 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, null); } /** * 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 JList 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) { undecorate(list); AbstractAutoCompleteAdaptor adaptor = new ListAdaptor(list, textComponent, stringConverter); AutoCompleteDocument document = createAutoCompleteDocument(adaptor, true, stringConverter, textComponent.getDocument()); decorate(textComponent, document, adaptor); } static void undecorate(JList list) { for (ListSelectionListener l : list.getListSelectionListeners()) { if (l instanceof ListAdaptor) { list.removeListSelectionListener(l); break; } } } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given List. * @param textComponent the text component that will be used for automatic * completion. * @param items contains the items that are used for autocompletion * @param strictMatching true, if only given items should be allowed to be entered */ public static void decorate(JTextComponent textComponent, List items, boolean strictMatching) { decorate(textComponent, items, strictMatching, null); } /** * Enables automatic completion for the given JTextComponent based on the * items contained in the given List. * @param items contains the items that are used for autocompletion * @param textComponent the text component that will be used for automatic * completion. * @param strictMatching true, 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 items, boolean strictMatching, ObjectToStringConverter stringConverter) { AbstractAutoCompleteAdaptor adaptor = new TextComponentAdaptor(textComponent, items); AutoCompleteDocument document = createAutoCompleteDocument(adaptor, strictMatching, stringConverter, textComponent.getDocument()); decorate(textComponent, document, adaptor); } /** * 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, AbstractAutoCompleteAdaptor adaptor) { undecorate(textComponent); // 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 AutoComplete.FocusAdapter(adaptor)); // Tweak some key bindings InputMap editorInputMap = textComponent.getInputMap(); while (editorInputMap != null) { InputMap parent = editorInputMap.getParent(); if (parent instanceof UIResource) { installMap(editorInputMap, document.isStrictMatching()); break; } editorInputMap = parent; } ActionMap editorActionMap = textComponent.getActionMap(); editorActionMap.put("nonstrict-backspace", new NonStrictBackspaceAction( editorActionMap.get(DefaultEditorKit.deletePrevCharAction), editorActionMap.get(DefaultEditorKit.selectionBackwardAction), adaptor)); } static void undecorate(JTextComponent textComponent) { Document doc = textComponent.getDocument(); if (doc instanceof AutoCompleteDocument) { //remove autocomplete key/action mappings InputMap map = textComponent.getInputMap(); while (map.getParent() != null) { InputMap parent = map.getParent(); if (parent instanceof AutoComplete.InputMap) { map.setParent(parent.getParent()); } map = parent; } textComponent.getActionMap().put("nonstrict-backspace", null); //remove old focus listener for (FocusListener l : textComponent.getFocusListeners()) { if (l instanceof AutoComplete.FocusAdapter) { textComponent.removeFocusListener(l); break; } } //reset to original document textComponent.setDocument(((AutoCompleteDocument) doc).delegate); } } static class NonStrictBackspaceAction extends TextAction { Action backspace; Action selectionBackward; AbstractAutoCompleteAdaptor adaptor; public NonStrictBackspaceAction(Action backspace, Action selectionBackward, AbstractAutoCompleteAdaptor adaptor) { super("nonstrict-backspace"); this.backspace = backspace; this.selectionBackward = selectionBackward; this.adaptor = adaptor; } public void actionPerformed(ActionEvent e) { if (adaptor.listContainsSelectedItem()) { selectionBackward.actionPerformed(e); } else { backspace.actionPerformed(e); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy