001    /*
002     * Copyright 2002-2005 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.list;
017    
018    import java.awt.event.FocusEvent;
019    import java.awt.event.FocusListener;
020    import java.awt.event.KeyAdapter;
021    import java.awt.event.KeyEvent;
022    
023    import javax.swing.JComboBox;
024    import javax.swing.JTextField;
025    
026    /**
027     * Provides auto-completion for an editable combobox. Based on public domain postings.
028     * Original author unknown.  Also copied some code from {@link ComboBoxAutoCompletion}
029     * to deal with focus loss.
030     * 
031     * @author Larry Streepy
032     * 
033     */
034    public class EditableComboBoxAutoCompletion extends KeyAdapter {
035    
036        private final FocusHandler focusHandler = new FocusHandler();
037    
038        private final JComboBox comboBox;
039    
040        private final JTextField editor;
041    
042        /**
043         * Adds autocompletion support to the given <code>combobox</code>.
044         * 
045         * @param comboBox the combobox to augment
046         */
047        public EditableComboBoxAutoCompletion(JComboBox comboBox) {
048            this.comboBox = comboBox;
049            editor = (JTextField)comboBox.getEditor().getEditorComponent();
050            editor.addKeyListener(this);
051            editor.addFocusListener(focusHandler);
052        }
053    
054        /**
055         * Handle a key release event. See if what they've type so far matches anything in the
056         * selectable items list. If so, then show the popup and select the item. If not, then
057         * hide the popup.
058         * 
059         * @param e key event
060         */
061        public void keyReleased(KeyEvent e) {
062            char ch = e.getKeyChar();
063            if (ch == KeyEvent.CHAR_UNDEFINED || Character.isISOControl(ch))
064                return;
065            int pos = editor.getCaretPosition();
066            String str = editor.getText();
067            if (str.length() == 0)
068                return;
069    
070            boolean matchFound = false;
071            for (int k = 0; k < comboBox.getItemCount(); k++) {
072                String item = comboBox.getItemAt(k).toString();
073                if (startsWithIgnoreCase(item, str)) {
074                    comboBox.setSelectedIndex(k);
075                    editor.setText(item);
076                    editor.setCaretPosition(item.length());
077                    editor.moveCaretPosition(pos);
078    
079                    // show popup when the user types
080                    if (comboBox.isDisplayable())
081                        comboBox.setPopupVisible(true);
082    
083                    matchFound = true;
084                    break;
085                }
086            }
087            if (!matchFound) {
088                // hide popup when there is no match
089                comboBox.setPopupVisible(false);
090            }
091        }
092    
093        /**
094         * See if one string begins with another, ignoring case.
095         * 
096         * @param str1 The string to test
097         * @param str2 The prefix to test for
098         * @return true if str1 starts with str2, ingnoring case
099         */
100        private boolean startsWithIgnoreCase(String str1, String str2) {
101            return str1 != null && str2 != null && str1.toUpperCase().startsWith(str2.toUpperCase());
102        }
103    
104        /**
105         * Highlight the text from the given start location to the end of the text.
106         * 
107         * @param start Starting location to highlight
108         */
109        private void highlightText(int start) {
110            editor.setCaretPosition(editor.getText().length());
111            editor.moveCaretPosition(start);
112        }
113    
114        /**
115         * This class handles focus events to provide a work-around for a java 1.5 bug.
116         */
117        private final class FocusHandler implements FocusListener {
118    
119            // Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when
120            // tabbing out
121            private boolean hidePopupOnFocusLoss = System.getProperty("java.version").startsWith("1.5");
122    
123            public void focusGained(FocusEvent e) {
124                // Highlight whole text when gaining focus
125                highlightText(0);
126            }
127    
128            public void focusLost(FocusEvent e) {
129                // Workaround for Bug 5100422 - Hide Popup on focus loss
130                if (hidePopupOnFocusLoss)
131                    comboBox.setPopupVisible(false);
132            }
133        }
134    }