001/*
002 * $Id: AutoComplete.java 4051 2011-07-19 20:17:05Z kschaefe $
003 *
004 * Copyright 2010 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.autocomplete;
022
023import static org.jdesktop.swingx.autocomplete.AutoCompleteDecorator.createAutoCompleteDocument;
024import static org.jdesktop.swingx.autocomplete.AutoCompleteDecorator.decorate;
025import static org.jdesktop.swingx.autocomplete.AutoCompleteDecorator.undecorate;
026
027import java.awt.event.ActionEvent;
028import java.awt.event.FocusEvent;
029import java.awt.event.KeyEvent;
030import java.awt.event.KeyListener;
031import java.beans.PropertyChangeEvent;
032
033import javax.swing.Action;
034import javax.swing.JComboBox;
035import javax.swing.text.JTextComponent;
036
037/**
038 * 
039 * @author kschaefer
040 */
041final class AutoComplete {
042    static class InputMap extends javax.swing.InputMap {
043        private static final long serialVersionUID = 1L;
044    }
045
046    static class FocusAdapter extends java.awt.event.FocusAdapter {
047        private AbstractAutoCompleteAdaptor adaptor;
048
049        public FocusAdapter(AbstractAutoCompleteAdaptor adaptor) {
050            this.adaptor = adaptor;
051        }
052
053        @Override
054        public void focusGained(FocusEvent e) {
055            adaptor.markEntireText();
056        }
057    }
058    
059    static class KeyAdapter extends java.awt.event.KeyAdapter {
060        private JComboBox comboBox;
061        
062        public KeyAdapter(JComboBox comboBox) {
063            this.comboBox = comboBox;
064        }
065        
066        @Override
067        public void keyPressed(KeyEvent keyEvent) {
068            // don't popup on action keys (cursor movements, etc...)
069            if (keyEvent.isActionKey()) {
070                return;
071            }
072            
073            // don't popup if the combobox isn't visible anyway
074            if (comboBox.isDisplayable() && !comboBox.isPopupVisible()) {
075                int keyCode = keyEvent.getKeyCode();
076                // don't popup when the user hits shift,ctrl or alt
077                if (keyCode==KeyEvent.VK_SHIFT || keyCode==KeyEvent.VK_CONTROL || keyCode==KeyEvent.VK_ALT) return;
078                // don't popup when the user hits escape (see issue #311)
079                if (keyCode==KeyEvent.VK_ENTER || keyCode==KeyEvent.VK_ESCAPE) return;
080                comboBox.setPopupVisible(true);
081            }
082        }
083    }
084
085    static class PropertyChangeListener implements java.beans.PropertyChangeListener {
086        private JComboBox comboBox;
087        
088        public PropertyChangeListener(JComboBox comboBox) {
089            this.comboBox = comboBox;
090        }
091        
092        /**
093         * {@inheritDoc}
094         */
095        @Override
096        @SuppressWarnings("nls")
097        public void propertyChange(PropertyChangeEvent evt) {
098            if ("editor".equals(evt.getPropertyName())) {
099                handleEditor(evt);
100            } else if ("enabled".equals(evt.getPropertyName())) {
101                handleEnabled(evt);
102            }
103        }
104        
105        private void handleEnabled(PropertyChangeEvent evt) {
106            if (Boolean.TRUE.equals(evt.getNewValue())) {
107                comboBox.setEditable(true);
108            } else {
109                JTextComponent textComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
110                boolean strictMatching = ((AutoCompleteDocument) textComponent.getDocument()).strictMatching;
111                
112                comboBox.setEditable(!strictMatching);
113            }
114        }
115
116        private void handleEditor(PropertyChangeEvent evt) {
117            if (evt.getNewValue() instanceof AutoCompleteComboBoxEditor) {
118                return;
119            }
120            
121            AutoCompleteComboBoxEditor acEditor = (AutoCompleteComboBoxEditor) evt.getOldValue();
122            boolean strictMatching = false;
123            
124            if (acEditor.getEditorComponent() != null) {
125                JTextComponent textComponent = (JTextComponent) acEditor.getEditorComponent();
126                strictMatching = ((AutoCompleteDocument) textComponent.getDocument()).strictMatching;
127                
128                undecorate(textComponent);
129                
130                for (KeyListener l : textComponent.getKeyListeners()) {
131                    if (l instanceof KeyAdapter) {
132                        textComponent.removeKeyListener(l);
133                        break;
134                    }
135                }
136            }
137
138            JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
139            AbstractAutoCompleteAdaptor adaptor = new ComboBoxAdaptor(comboBox);
140            AutoCompleteDocument document = createAutoCompleteDocument(adaptor, strictMatching,
141                    acEditor.stringConverter, editorComponent.getDocument());
142            decorate(editorComponent, document, adaptor);
143            
144            editorComponent.addKeyListener(new AutoComplete.KeyAdapter(comboBox));
145            
146            //set before adding the listener for the editor
147            comboBox.setEditor(new AutoCompleteComboBoxEditor(comboBox.getEditor(), document.stringConverter));
148        }
149    }
150
151    static class SelectionAction implements Action {
152        private Action delegate;
153        
154        public SelectionAction(Action delegate) {
155            this.delegate = delegate;
156        }
157        
158        /**
159         * {@inheritDoc}
160         */
161        @Override
162        public void actionPerformed(ActionEvent e) {
163            JComboBox comboBox = (JComboBox) e.getSource();
164            JTextComponent textComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
165            AutoCompleteDocument doc = (AutoCompleteDocument) textComponent.getDocument();
166            
167            // doing this prevents the updating of the selected item to "" during the remove prior
168            // to the insert in JTextComponent.setText
169            doc.strictMatching = true;
170            try {
171                delegate.actionPerformed(e);
172            } finally {
173                doc.strictMatching = false;
174            }
175        }
176
177        /**
178         * {@inheritDoc}
179         */
180        @Override
181        public void addPropertyChangeListener(java.beans.PropertyChangeListener listener) {
182            delegate.addPropertyChangeListener(listener);
183        }
184        
185        /**
186         * {@inheritDoc}
187         */
188        @Override
189        public void removePropertyChangeListener(java.beans.PropertyChangeListener listener) {
190            delegate.removePropertyChangeListener(listener);
191        }
192        
193        /**
194         * {@inheritDoc}
195         */
196        @Override
197        public Object getValue(String key) {
198            return delegate.getValue(key);
199        }
200
201        /**
202         * {@inheritDoc}
203         */
204        @Override
205        public void putValue(String key, Object value) {
206            delegate.putValue(key, value);
207        }
208        
209        /**
210         * {@inheritDoc}
211         */
212        @Override
213        public boolean isEnabled() {
214            return delegate.isEnabled();
215        }
216
217        /**
218         * {@inheritDoc}
219         */
220        @Override
221        public void setEnabled(boolean b) {
222            delegate.setEnabled(b);
223        }
224    }
225    
226    private AutoComplete() {
227        // prevent instantiation
228    }
229}