~ubuntu-branches/ubuntu/trusty/libswingx-java/trusty

« back to all changes in this revision

Viewing changes to src/java/org/jdesktop/swingx/autocomplete/AutoCompleteDecorator.java

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Werner
  • Date: 2008-03-08 16:18:24 UTC
  • Revision ID: james.westby@ubuntu.com-20080308161824-wsahvl9pwzjcea3g
Tags: upstream-0.9.2
ImportĀ upstreamĀ versionĀ 0.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id: AutoCompleteDecorator.java,v 1.13 2007/11/17 03:15:10 kschaefe Exp $
 
3
 *
 
4
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 
5
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 
6
 *
 
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.
 
11
 * 
 
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.
 
16
 * 
 
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
 
20
 */
 
21
package org.jdesktop.swingx.autocomplete;
 
22
 
 
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;
 
32
 
 
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;
 
44
 
 
45
import org.jdesktop.swingx.autocomplete.workarounds.MacOSXPopupLocationFix;
 
46
 
 
47
/**
 
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>
 
51
 * <p><pre><code>
 
52
 * JComboBox comboBox = [...];
 
53
 * AutoCompleteDecorator.<b>decorate</b>(comboBox);
 
54
 * 
 
55
 * List items = [...];
 
56
 * JTextField textField = [...];
 
57
 * AutoCompleteDecorator.<b>decorate</b>(textField, items);
 
58
 * 
 
59
 * JList list = [...];
 
60
 * JTextField textField = [...];
 
61
 * AutoCompleteDecorator.<b>decorate</b>(list, textField);
 
62
 * </code></pre></p>
 
63
 *
 
64
 * @author Thomas Bierhance
 
65
 */
 
66
public class AutoCompleteDecorator {
 
67
    private static void removeFocusListener(Component c) {
 
68
        FocusListener[] listeners = c.getFocusListeners();
 
69
        
 
70
        for (FocusListener l : listeners) {
 
71
            if (l instanceof AutoCompleteFocusAdapter) {
 
72
                c.removeFocusListener(l);
 
73
            }
 
74
        }
 
75
    }
 
76
    
 
77
    private static void removeKeyListener(Component c) {
 
78
        KeyListener[] listeners = c.getKeyListeners();
 
79
        
 
80
        for (KeyListener l : listeners) {
 
81
            if (l instanceof AutoCompleteKeyAdapter) {
 
82
                c.removeKeyListener(l);
 
83
            }
 
84
        }
 
85
    }
 
86
    
 
87
    private static void removePropertyChangeListener(Component c) {
 
88
        PropertyChangeListener[] listeners = c.getPropertyChangeListeners("editor");
 
89
        
 
90
        for (PropertyChangeListener l : listeners) {
 
91
            if (l instanceof AutoCompletePropertyChangeListener) {
 
92
                c.removePropertyChangeListener("editor", l);
 
93
            }
 
94
        }
 
95
    }
 
96
    
 
97
    /**
 
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
 
101
     * completion.
 
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
 
104
     */
 
105
    public static void decorate(JTextComponent textComponent, List<?> items, boolean strictMatching) {
 
106
        decorate(textComponent, items, strictMatching, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
 
107
    }
 
108
    
 
109
    /**
 
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
 
114
     * completion.
 
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
 
117
     */
 
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);
 
122
    }
 
123
    
 
124
    /**
 
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
 
130
     * completion
 
131
     */
 
132
    public static void decorate(JList list, JTextComponent textComponent) {
 
133
        decorate(list, textComponent, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
 
134
    }
 
135
    
 
136
    /**
 
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
 
142
     * completion
 
143
     * @param stringConverter the converter used to transform items to strings
 
144
     */
 
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);
 
149
    }
 
150
    
 
151
    /**
 
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)
 
157
     */
 
158
    public static void decorate(final JComboBox comboBox) {
 
159
        decorate(comboBox, ObjectToStringConverter.DEFAULT_IMPLEMENTATION);
 
160
    }
 
161
    
 
162
    /**
 
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.
 
166
     * <p>
 
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.
 
173
     * <p>
 
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.
 
178
     * 
 
179
     * @param comboBox
 
180
     *                a combo box
 
181
     * @param stringConverter
 
182
     *                the converter used to transform items to strings
 
183
     */
 
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);
 
190
 
 
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);
 
196
        
 
197
        //remove old key listener
 
198
        removeKeyListener(editorComponent);
 
199
        
 
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);
 
213
                }
 
214
            }
 
215
        };
 
216
        editorComponent.addKeyListener(keyListener);
 
217
        
 
218
        if (stringConverter!=ObjectToStringConverter.DEFAULT_IMPLEMENTATION) {
 
219
            comboBox.setEditor(new AutoCompleteComboBoxEditor(comboBox.getEditor(), stringConverter));
 
220
        }
 
221
        
 
222
        //remove old property change listener
 
223
        removePropertyChangeListener(comboBox);
 
224
        
 
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());
 
232
                }
 
233
                
 
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.
 
242
                    } else {
 
243
                        decorate((JTextComponent) editor.getEditorComponent(), document, adaptor);
 
244
                        editor.getEditorComponent().addKeyListener(keyListener);
 
245
                    }
 
246
                }
 
247
            }
 
248
        });
 
249
    }
 
250
    
 
251
    /**
 
252
     * Decorates a given text component for automatic completion using the
 
253
     * given AutoCompleteDocument and AbstractAutoCompleteAdaptor.
 
254
     * 
 
255
     * 
 
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
 
259
     */
 
260
    public static void decorate(JTextComponent textComponent, AutoCompleteDocument document, final AbstractAutoCompleteAdaptor adaptor) {
 
261
        // install the document on the text component
 
262
        textComponent.setDocument(document);
 
263
        
 
264
        //remove old focus listener
 
265
        removeFocusListener(textComponent);
 
266
        
 
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();
 
273
            }
 
274
        });
 
275
        
 
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);
 
284
        } else {
 
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),
 
293
                    adaptor));
 
294
        }
 
295
    }
 
296
    
 
297
    static class NonStrictBackspaceAction extends TextAction {
 
298
        Action backspace;
 
299
        Action selectionBackward;
 
300
        AbstractAutoCompleteAdaptor adaptor;
 
301
        
 
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;
 
307
        }
 
308
        
 
309
        public void actionPerformed(ActionEvent e) {
 
310
            if (adaptor.listContainsSelectedItem()) {
 
311
                selectionBackward.actionPerformed(e);
 
312
            } else {
 
313
                backspace.actionPerformed(e);
 
314
            }
 
315
        }
 
316
    }
 
317
    
 
318
    /**
 
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".
 
321
     */
 
322
    static Object errorFeedbackAction = new TextAction("provide-error-feedback") {
 
323
        public void actionPerformed(ActionEvent e) {
 
324
            UIManager.getLookAndFeel().provideErrorFeedback(getTextComponent(e));
 
325
        }
 
326
    };
 
327
}