2
* $Id: AutoCompleteDecorator.java,v 1.13 2007/11/17 03:15:10 kschaefe Exp $
4
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5
* Santa Clara, California 95054, U.S.A. All rights reserved.
7
* This library is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2.1 of the License, or (at your option) any later version.
12
* This library is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this library; if not, write to the Free Software
19
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
package org.jdesktop.swingx.autocomplete;
23
import java.awt.Component;
24
import java.awt.event.ActionEvent;
25
import java.awt.event.FocusEvent;
26
import java.awt.event.FocusListener;
27
import java.awt.event.KeyEvent;
28
import java.awt.event.KeyListener;
29
import java.beans.PropertyChangeEvent;
30
import java.beans.PropertyChangeListener;
31
import java.util.List;
33
import javax.swing.Action;
34
import javax.swing.ActionMap;
35
import javax.swing.ComboBoxEditor;
36
import javax.swing.InputMap;
37
import javax.swing.JComboBox;
38
import javax.swing.JList;
39
import javax.swing.KeyStroke;
40
import javax.swing.UIManager;
41
import javax.swing.text.DefaultEditorKit;
42
import javax.swing.text.JTextComponent;
43
import javax.swing.text.TextAction;
45
import org.jdesktop.swingx.autocomplete.workarounds.MacOSXPopupLocationFix;
48
* This class contains only static utility methods that can be used to set up
49
* automatic completion for some Swing components.
50
* <p>Usage examples:</p>
52
* JComboBox comboBox = [...];
53
* AutoCompleteDecorator.<b>decorate</b>(comboBox);
56
* JTextField textField = [...];
57
* AutoCompleteDecorator.<b>decorate</b>(textField, items);
60
* JTextField textField = [...];
61
* AutoCompleteDecorator.<b>decorate</b>(list, textField);
64
* @author Thomas Bierhance
66
public class AutoCompleteDecorator {
67
private static void removeFocusListener(Component c) {
68
FocusListener[] listeners = c.getFocusListeners();
70
for (FocusListener l : listeners) {
71
if (l instanceof AutoCompleteFocusAdapter) {
72
c.removeFocusListener(l);
77
private static void removeKeyListener(Component c) {
78
KeyListener[] listeners = c.getKeyListeners();
80
for (KeyListener l : listeners) {
81
if (l instanceof AutoCompleteKeyAdapter) {
82
c.removeKeyListener(l);
87
private static void removePropertyChangeListener(Component c) {
88
PropertyChangeListener[] listeners = c.getPropertyChangeListeners("editor");
90
for (PropertyChangeListener l : listeners) {
91
if (l instanceof AutoCompletePropertyChangeListener) {
92
c.removePropertyChangeListener("editor", l);
98
* Enables automatic completion for the given JTextComponent based on the
99
* items contained in the given <tt>List</tt>.
100
* @param textComponent the text component that will be used for automatic
102
* @param items contains the items that are used for autocompletion
103
* @param strictMatching <tt>true</tt>, if only given items should be allowed to be entered
105
public static void decorate(JTextComponent textComponent, List<?> items, boolean strictMatching) {
106
decorate(textComponent, items, strictMatching, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
110
* Enables automatic completion for the given JTextComponent based on the
111
* items contained in the given <tt>List</tt>.
112
* @param items contains the items that are used for autocompletion
113
* @param textComponent the text component that will be used for automatic
115
* @param strictMatching <tt>true</tt>, if only given items should be allowed to be entered
116
* @param stringConverter the converter used to transform items to strings
118
public static void decorate(JTextComponent textComponent, List<?> items, boolean strictMatching, ObjectToStringConverter stringConverter) {
119
AbstractAutoCompleteAdaptor adaptor = new TextComponentAdaptor(textComponent, items);
120
AutoCompleteDocument document = new AutoCompleteDocument(adaptor, strictMatching, stringConverter);
121
decorate(textComponent, document, adaptor);
125
* Enables automatic completion for the given JTextComponent based on the
126
* items contained in the given JList. The two components will be
127
* synchronized. The automatic completion will always be strict.
128
* @param list a <tt>JList</tt> containing the items for automatic completion
129
* @param textComponent the text component that will be enabled for automatic
132
public static void decorate(JList list, JTextComponent textComponent) {
133
decorate(list, textComponent, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
137
* Enables automatic completion for the given JTextComponent based on the
138
* items contained in the given JList. The two components will be
139
* synchronized. The automatic completion will always be strict.
140
* @param list a <tt>JList</tt> containing the items for automatic completion
141
* @param textComponent the text component that will be used for automatic
143
* @param stringConverter the converter used to transform items to strings
145
public static void decorate(JList list, JTextComponent textComponent, ObjectToStringConverter stringConverter) {
146
AbstractAutoCompleteAdaptor adaptor = new ListAdaptor(list, textComponent, stringConverter);
147
AutoCompleteDocument document = new AutoCompleteDocument(adaptor, true, stringConverter);
148
decorate(textComponent, document, adaptor);
152
* Enables automatic completion for the given JComboBox. The automatic
153
* completion will be strict (only items from the combo box can be selected)
154
* if the combo box is not editable.
155
* @param comboBox a combo box
156
* @see #decorate(JComboBox, ObjectToStringConverter)
158
public static void decorate(final JComboBox comboBox) {
159
decorate(comboBox, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
163
* Enables automatic completion for the given JComboBox. The automatic
164
* completion will be strict (only items from the combo box can be selected)
165
* if the combo box is not editable.
167
* <b>Note:</b> the {@code AutoCompleteDecorator} will alter the state of
168
* the {@code JComboBox} to be editable. This can cause side effects with
169
* layouts and sizing. {@code JComboBox} caches the size, which differs
170
* depending on the component's editability. Therefore, if the component's
171
* size is accesed prior to being decorated and then the cached size is
172
* forced to be recalculated, the size of the component will change.
174
* Because the size of the component can be altered (recalculated), the
175
* decorator does not attempt to set any sizes on the supplied
176
* {@code JComboBox}. Users that need to ensure sizes of supplied combos
177
* should take measures to set the size of the combo.
181
* @param stringConverter
182
* the converter used to transform items to strings
184
public static void decorate(final JComboBox comboBox, final ObjectToStringConverter stringConverter) {
185
boolean strictMatching = !comboBox.isEditable();
186
// has to be editable
187
comboBox.setEditable(true);
188
// fix the popup location
189
MacOSXPopupLocationFix.install(comboBox);
191
// configure the text component=editor component
192
JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
193
final AbstractAutoCompleteAdaptor adaptor = new ComboBoxAdaptor(comboBox);
194
final AutoCompleteDocument document = new AutoCompleteDocument(adaptor, strictMatching, stringConverter);
195
decorate(editorComponent, document, adaptor);
197
//remove old key listener
198
removeKeyListener(editorComponent);
200
// show the popup list when the user presses a key
201
final KeyListener keyListener = new AutoCompleteKeyAdapter() {
202
public void keyPressed(KeyEvent keyEvent) {
203
// don't popup on action keys (cursor movements, etc...)
204
if (keyEvent.isActionKey()) return;
205
// don't popup if the combobox isn't visible anyway
206
if (comboBox.isDisplayable() && !comboBox.isPopupVisible()) {
207
int keyCode = keyEvent.getKeyCode();
208
// don't popup when the user hits shift,ctrl or alt
209
if (keyCode==KeyEvent.VK_SHIFT || keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_ALT) return;
210
// don't popup when the user hits escape (see issue #311)
211
if (keyCode==KeyEvent.VK_ESCAPE) return;
212
comboBox.setPopupVisible(true);
216
editorComponent.addKeyListener(keyListener);
218
if (stringConverter!=ObjectToStringConverter.DEFAULT_IMPLEMENTATION) {
219
comboBox.setEditor(new AutoCompleteComboBoxEditor(comboBox.getEditor(), stringConverter));
222
//remove old property change listener
223
removePropertyChangeListener(comboBox);
225
// Changing the l&f can change the combobox' editor which in turn
226
// would not be autocompletion-enabled. The new editor needs to be set-up.
227
comboBox.addPropertyChangeListener("editor", new AutoCompletePropertyChangeListener() {
228
public void propertyChange(PropertyChangeEvent e) {
229
ComboBoxEditor editor = (ComboBoxEditor) e.getOldValue();
230
if (editor != null && editor.getEditorComponent() != null) {
231
removeKeyListener(editor.getEditorComponent());
234
editor = (ComboBoxEditor) e.getNewValue();
235
if (editor!=null && editor.getEditorComponent()!=null) {
236
if (!(editor instanceof AutoCompleteComboBoxEditor)
237
&& stringConverter!=ObjectToStringConverter.DEFAULT_IMPLEMENTATION) {
238
comboBox.setEditor(new AutoCompleteComboBoxEditor(editor, stringConverter));
239
// Don't do the decorate step here because calling setEditor will trigger
240
// the propertychange listener a second time, which will do the decorate
241
// and addKeyListener step.
243
decorate((JTextComponent) editor.getEditorComponent(), document, adaptor);
244
editor.getEditorComponent().addKeyListener(keyListener);
252
* Decorates a given text component for automatic completion using the
253
* given AutoCompleteDocument and AbstractAutoCompleteAdaptor.
256
* @param textComponent a text component that should be decorated
257
* @param document the AutoCompleteDocument to be installed on the text component
258
* @param adaptor the AbstractAutoCompleteAdaptor to be used
260
public static void decorate(JTextComponent textComponent, AutoCompleteDocument document, final AbstractAutoCompleteAdaptor adaptor) {
261
// install the document on the text component
262
textComponent.setDocument(document);
264
//remove old focus listener
265
removeFocusListener(textComponent);
267
// mark entire text when the text component gains focus
268
// otherwise the last mark would have been retained which is quiet confusing
269
textComponent.addFocusListener(new AutoCompleteFocusAdapter() {
270
public void focusGained(FocusEvent e) {
271
JTextComponent textComponent = (JTextComponent) e.getSource();
272
adaptor.markEntireText();
276
// Tweak some key bindings
277
InputMap editorInputMap = textComponent.getInputMap();
278
if (document.isStrictMatching()) {
279
// move the selection to the left on VK_BACK_SPACE
280
editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_BACK_SPACE, 0), DefaultEditorKit.selectionBackwardAction);
281
// ignore VK_DELETE and CTRL+VK_X and beep instead when strict matching
282
editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_DELETE, 0), errorFeedbackAction);
283
editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_X, java.awt.event.InputEvent.CTRL_DOWN_MASK), errorFeedbackAction);
285
ActionMap editorActionMap = textComponent.getActionMap();
286
// leave VK_DELETE and CTRL+VK_X as is
287
// VK_BACKSPACE will move the selection to the left if the selected item is in the list
288
// it will delete the previous character otherwise
289
editorInputMap.put(KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_BACK_SPACE, 0), "nonstrict-backspace");
290
editorActionMap.put("nonstrict-backspace", new NonStrictBackspaceAction(
291
editorActionMap.get(DefaultEditorKit.deletePrevCharAction),
292
editorActionMap.get(DefaultEditorKit.selectionBackwardAction),
297
static class NonStrictBackspaceAction extends TextAction {
299
Action selectionBackward;
300
AbstractAutoCompleteAdaptor adaptor;
302
public NonStrictBackspaceAction(Action backspace, Action selectionBackward, AbstractAutoCompleteAdaptor adaptor) {
303
super("nonstrict-backspace");
304
this.backspace = backspace;
305
this.selectionBackward = selectionBackward;
306
this.adaptor = adaptor;
309
public void actionPerformed(ActionEvent e) {
310
if (adaptor.listContainsSelectedItem()) {
311
selectionBackward.actionPerformed(e);
313
backspace.actionPerformed(e);
319
* A TextAction that provides an error feedback for the text component that invoked
320
* the action. The error feedback is most likely a "beep".
322
static Object errorFeedbackAction = new TextAction("provide-error-feedback") {
323
public void actionPerformed(ActionEvent e) {
324
UIManager.getLookAndFeel().provideErrorFeedback(getTextComponent(e));