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 }