1 /* AutoCompleteComboDocument.java
2  * from http://www.orbital-computer.de/JComboBox/
3  *
4  * created: 2007
5  *
6  * This file is part of Artemis
7  *
8  * Copyright (C) 2007  Genome Research Limited
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  *
24  **/
25 
26 package uk.ac.sanger.artemis.components.genebuilder;
27 
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.awt.event.FocusAdapter;
31 import java.awt.event.FocusEvent;
32 import java.awt.event.KeyAdapter;
33 import java.awt.event.KeyEvent;
34 
35 import javax.swing.JComboBox;
36 import javax.swing.text.AttributeSet;
37 import javax.swing.text.BadLocationException;
38 import javax.swing.text.JTextComponent;
39 import javax.swing.text.PlainDocument;
40 
41 import org.gmod.schema.cv.CvTerm;
42 
43   public class AutoCompleteComboDocument extends PlainDocument
44   {
45     private static final long serialVersionUID = 1L;
46     private JComboBox comboBox;
47 
48     private JTextComponent editor;
49     // flag to indicate if setSelectedItem has been called
50     // subsequent calls to remove/insertString should be ignored
51     boolean selecting=false;
52     boolean hidePopupOnFocusLoss;
53     boolean hitBackspace=false;
54     boolean hitBackspaceOnSelection;
55 
56 
AutoCompleteComboDocument(final JComboBox comboBox)57     public AutoCompleteComboDocument(final JComboBox comboBox)
58     {
59       this.comboBox = comboBox;
60 
61       editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
62       editor.setDocument(this);
63 
64       if(comboBox.getModel().getSize() <= comboBox.getMaximumRowCount())
65         comboBox.setMaximumRowCount(comboBox.getModel().getSize()-1);
66 
67       comboBox.addActionListener(new ActionListener()
68       {
69           public void actionPerformed(ActionEvent e)
70           {
71               if (!selecting) highlightCompletedText(0);
72           }
73       });
74 
75       editor.addKeyListener(new KeyAdapter()
76       {
77         public void keyPressed(KeyEvent e)
78         {
79           if(comboBox.isDisplayable())
80             comboBox.setPopupVisible(true);
81           hitBackspace=false;
82           switch (e.getKeyCode())
83           {
84             // determine if the pressed key is backspace (needed by the remove method)
85             case KeyEvent.VK_BACK_SPACE :
86               hitBackspace=true;
87               hitBackspaceOnSelection=editor.getSelectionStart()!=editor.getSelectionEnd();
88               break;
89           }
90         }
91       });
92 
93       // Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when tabbing out
94       hidePopupOnFocusLoss=System.getProperty("java.version").startsWith("1.5");
95       // Highlight whole text when gaining focus
96       editor.addFocusListener(new FocusAdapter()
97       {
98           public void focusGained(FocusEvent e)
99           {
100               highlightCompletedText(0);
101           }
102           public void focusLost(FocusEvent e)
103           {
104             // Workaround for Bug 5100422 - Hide Popup on focus loss
105             if (hidePopupOnFocusLoss) comboBox.setPopupVisible(false);
106           }
107       });
108       // Handle initially selected object
109       Object selected = comboBox.getSelectedItem();
110       if(selected!=null)
111         setText(getStringValue(selected));
112       highlightCompletedText(0);
113     }
114 
remove(int offs, int len)115     public void remove(int offs, int len) throws BadLocationException
116     {
117       // return immediately when selecting an item
118       if (selecting)
119         return;
120       if (hitBackspace)
121       {
122         // user hit backspace => move the selection backwards
123         // old item keeps being selected
124         if(offs>0)
125           if(hitBackspaceOnSelection)
126             offs--;
127         else
128         {
129           // User hit backspace with the cursor positioned on the start => beep
130           comboBox.getToolkit().beep(); // when available use: UIManager.getLookAndFeel().provideErrorFeedback(combo
131         }
132         highlightCompletedText(offs);
133       }
134       else
135         super.remove(offs, len);
136     }
137 
insertString(int offs, String str, AttributeSet a)138     public void insertString(int offs, String str, AttributeSet a)
139         throws BadLocationException
140     {
141       // return immediately when selecting an item
142       if(selecting)
143         return;
144       super.insertString(offs, str, a);
145 
146       // lookup and select a matching item
147       Object item = lookupItem(getText(0, getLength()));
148 
149       boolean listContainsSelectedItem = true;
150       if(item == null)
151       {
152         item = comboBox.getModel().getSelectedItem();
153         listContainsSelectedItem = false;
154       }
155       setSelectedItem(item);
156       setText(getStringValue(item));
157       // select the completed part
158       if(listContainsSelectedItem)
159         highlightCompletedText(offs + str.length());
160     }
161 
setText(String text)162     private void setText(String text)
163     {
164       try
165       {
166         super.remove(0, getLength());
167         super.insertString(0, text, null);
168       }
169       catch(BadLocationException e)
170       {
171         throw new RuntimeException(e.toString());
172       }
173     }
174 
highlightCompletedText(int start)175     private void highlightCompletedText(int start)
176     {
177       editor.setCaretPosition(getLength());
178       editor.moveCaretPosition(start);
179     }
180 
setSelectedItem(Object item)181     private void setSelectedItem(Object item)
182     {
183       selecting = true;
184       comboBox.getModel().setSelectedItem(item);
185       selecting = false;
186     }
187 
lookupItem(String pattern)188     private Object lookupItem(String pattern)
189     {
190       Object selectedItem = comboBox.getModel().getSelectedItem();
191       String selectedItemStr = getStringValue(selectedItem);
192 
193       // only search if the currently selected does not match
194       if(selectedItemStr != null
195           && startsWithIgnoreCase(selectedItemStr, pattern))
196         return selectedItem;
197       else
198       {
199         // iterate over all items
200         for(int i = 0, n = comboBox.getModel().getSize(); i < n; i++)
201         {
202           Object currentItem = comboBox.getModel().getElementAt(i);
203           String currentItemStr = getStringValue(currentItem);
204 
205           // current item starts with the pattern?
206           if(startsWithIgnoreCase(currentItemStr, pattern))
207             return currentItem;
208         }
209       }
210 
211       return null;
212     }
213 
getStringValue(Object item)214     private String getStringValue(Object item)
215     {
216       String itemStr = null;
217       if(item != null)
218       {
219         if(item instanceof String)
220           itemStr = item.toString();
221         else
222           itemStr = ((CvTerm)item).getName();
223       }
224       return itemStr;
225     }
226 
startsWithIgnoreCase(String str1, String str2)227     private boolean startsWithIgnoreCase(String str1, String str2)
228     {
229       return str1.toUpperCase().startsWith(str2.toUpperCase());
230     }
231   }