package net.sf.jabref.gui; import javax.swing.text.JTextComponent; import javax.swing.text.BadLocationException; import net.sf.jabref.autocompleter.AbstractAutoCompleter; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.FocusListener; import java.awt.event.FocusEvent; /** * Created by Morten O. Alver, 16 Feb. 2007 */ public class AutoCompleteListener extends KeyAdapter implements FocusListener { AbstractAutoCompleter completer; protected String toSetIn = null, lastBeginning = null; protected int lastCaretPosition = -1; protected Object[] lastCompletions = null; protected int lastShownCompletion = 0; // This field is set if the focus listener should call another focus listener // after finishing. This is needed because the autocomplete listener must // run before the focus listener responsible for storing the current edit. protected FocusListener nextFocusListener = null; // These variables keep track of the situation from time to time. public AutoCompleteListener(AbstractAutoCompleter completer) { this.completer = completer; } /** * This method is used if the focus listener should call another focus listener * after finishing. This is needed because the autocomplete listener must * run before the focus listener responsible for storing the current edit. * * @param listener The listener to call. */ public void setNextFocusListener(FocusListener listener) { this.nextFocusListener = listener; } public void keyPressed(KeyEvent e) { if ((toSetIn != null) && (e.getKeyCode() == KeyEvent.VK_ENTER)) { JTextComponent comp = (JTextComponent) e.getSource(); int end = comp.getSelectionEnd(); comp.select(end, end); e.consume(); return; } // Cycle through alternative completions when user presses PGUP/PGDN: else if ((e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) && (lastCompletions != null)) { cycle((JTextComponent) e.getSource(), 1); e.consume(); } else if ((e.getKeyCode() == KeyEvent.VK_PAGE_UP) && (lastCompletions != null)) { cycle((JTextComponent) e.getSource(), -1); e.consume(); } } private void cycle(JTextComponent comp, int increment) { lastShownCompletion += increment; if (lastShownCompletion >= lastCompletions.length) lastShownCompletion = 0; else if (lastShownCompletion < 0) lastShownCompletion = lastCompletions.length-1; String sno = (String)(lastCompletions[lastShownCompletion]); toSetIn = sno.substring(lastBeginning.length()-1); StringBuffer alltext = new StringBuffer(comp.getText()); int deletedChars = comp.getSelectionEnd() - comp.getSelectionStart(); alltext.delete(comp.getSelectionStart(), comp.getSelectionEnd()); int cp = comp.getCaretPosition() - deletedChars; alltext.insert(cp, toSetIn.substring(1)); //Util.pr(alltext.toString()); comp.setText(alltext.toString()); comp.setCaretPosition(cp+toSetIn.length()-1); comp.select(cp, cp + sno.length() - lastBeginning.length()); lastCaretPosition = comp.getCaretPosition(); //System.out.println("ToSetIn: '"+toSetIn+"'"); } public void keyTyped(KeyEvent e) { char ch = e.getKeyChar(); if (Character.isLetter(ch)) { JTextComponent comp = (JTextComponent) e.getSource(); if ((toSetIn != null) && (toSetIn.length() > 1) && (ch == toSetIn.charAt(1))) { // User continues on the word that was suggested. toSetIn = toSetIn.substring(1); if (toSetIn.length() > 0) { int cp = comp.getCaretPosition(); //comp.setCaretPosition(cp+1-toSetIn.); //System.out.println(cp-toSetIn.length()+" - "+cp); comp.select(cp + 1 - toSetIn.length(), cp); lastBeginning = lastBeginning + ch; e.consume(); lastCaretPosition = comp.getCaretPosition(); //System.out.println("Added char: '"+toSetIn+"'"); //System.out.println("LastBeginning: '"+lastBeginning+"'"); lastCompletions = findCompletions(lastBeginning, comp); lastShownCompletion = 0; for (int i = 0; i < lastCompletions.length; i++) { Object lastCompletion = lastCompletions[i]; //System.out.println("Completion["+i+"] = "+lastCompletion); if (((String)lastCompletion).endsWith(toSetIn)) { lastShownCompletion = i; break; } } //System.out.println("Index now: "+lastShownCompletion); if (toSetIn.length() < 2) toSetIn = null; return; } } if ((toSetIn != null) && ((toSetIn.length() <= 1) || (ch != toSetIn.charAt(1)))) { // User discontinues the word that was suggested. lastBeginning = lastBeginning + ch; Object[] completed = findCompletions(lastBeginning, comp); if ((completed != null) && (completed.length > 0)) { lastShownCompletion = 0; lastCompletions = completed; String sno = (String) (completed[0]); int lastLen = toSetIn.length() - 1; toSetIn = sno.substring(lastBeginning.length() - 1); String text = comp.getText(); //Util.pr(""+lastLen); comp.setText(text.substring(0, lastCaretPosition - lastLen) + toSetIn + text.substring(lastCaretPosition)); comp.select(lastCaretPosition + 1 - lastLen, lastCaretPosition + toSetIn.length() - lastLen); lastCaretPosition = comp.getCaretPosition(); e.consume(); return; } else { toSetIn = null; return; } } StringBuffer currentword = getCurrentWord(comp); if (currentword == null) return; currentword.append(ch); Object[] completed = findCompletions(currentword.toString(), comp); String prefix = completer.getPrefix(); String cWord = (prefix != null) && (prefix.length() > 0) ? currentword.toString().substring(prefix.length()) : currentword.toString(); int no = 0; // We use the first word in the array of completions. if ((completed != null) && (completed.length > 0)) { lastShownCompletion = 0; lastCompletions = completed; String sno = (String) (completed[no]); toSetIn = sno.substring(cWord.length() - 1); StringBuffer alltext = new StringBuffer(comp.getText()); int cp = comp.getCaretPosition(); alltext.insert(cp, toSetIn); comp.setText(alltext.toString()); comp.setCaretPosition(cp); comp.select(cp + 1, cp + 1 + sno.length() - cWord.length()); e.consume(); lastCaretPosition = comp.getCaretPosition(); lastBeginning = cWord; return; } } toSetIn = null; lastCompletions = null; } protected Object[] findCompletions(String beginning, JTextComponent comp) { return completer.complete(beginning); } protected StringBuffer getCurrentWord(JTextComponent comp) { StringBuffer res = new StringBuffer(); String upToCaret; try { upToCaret = comp.getText(0, comp.getCaretPosition()); // We now have the text from the start of the field up to the caret position. // In most fields, we are only interested in the currently edited word, so we // seek from the caret backward to the closest space: if (!completer.isSingleUnitField()) { if ((comp.getCaretPosition() < comp.getText().length()) && !Character.isWhitespace(comp.getText().charAt(comp.getCaretPosition()))) return null; boolean found = false; int piv = upToCaret.length() - 1; while (!found && (piv >= 0)) { if (Character.isWhitespace(upToCaret.charAt(piv))) found = true; else piv--; } //if (piv < 0) //piv = 0; res.append(upToCaret.substring(piv + 1)); } // For fields such as "journal" it is more reasonable to try to complete on the entire // text field content, so we skip the searching and keep the entire part up to the caret: else res.append(upToCaret); //Util.pr("AutoCompListener: "+res.toString()); } catch (BadLocationException ex) { } return res; } final static int ANY_NAME = 0, FIRST_NAME = 1, LAST_NAME = 2; protected int findNamePositionStatus(JTextComponent comp) { String upToCaret; try { upToCaret = comp.getText(0, comp.getCaretPosition()); // Clip off evertyhing up to and including the last " and " before: upToCaret = upToCaret.substring(upToCaret.lastIndexOf(" and ")+1); int commaIndex = upToCaret.indexOf(','); if (commaIndex < 0) return ANY_NAME; else return FIRST_NAME; } catch (BadLocationException ex) { return ANY_NAME; } } public void focusGained(FocusEvent event) { if (nextFocusListener != null) nextFocusListener.focusGained(event); } public void focusLost(FocusEvent event) { if (lastCompletions != null) { JTextComponent comp = (JTextComponent)event.getSource(); clearCurrentSuggestion(comp); } if (nextFocusListener != null) nextFocusListener.focusLost(event); } public void clearCurrentSuggestion(JTextComponent comp) { if (lastCompletions != null) { int selStart = comp.getSelectionStart(); String text = comp.getText(); comp.setText(text.substring(0, selStart) + text.substring(comp.getSelectionEnd())); comp.setCaretPosition(selStart); lastCompletions = null; lastShownCompletion = 0; lastCaretPosition = -1; toSetIn = null; } } }