package com.devdaily.justwrite.view.actions; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JOptionPane; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import com.devdaily.justwrite.controller.MainFrameController; import com.devdaily.justwrite.view.JustWriteMainFrame; import com.devdaily.justwrite.view.LookAheadTextPane; /** * Copyright 2010, Alvin Alexander, http://devdaily.com. * This software is distributed under the terms of the * GNU General Public License. * * This file is part of an application named JustWrite. * * JustWrite is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JustWrite is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JustWrite. If not, see <http://www.gnu.org/licenses/>. * * This class implement the "auto complete" feature. * */ public class CompleteWordAction extends AbstractAction { MainFrameController mainFrameController; JustWriteMainFrame mainFrame; LookAheadTextPane textPane; boolean alreadyHandlingWordCompletion; // variables i need to keep around in case the user hits [esc] repeatedly String currentPartialWord; // the partial word fragment the user has typed List<String> matchingWordCandidates; // word candidates, based on the current partial word fragment the user has typed String wordCandidate0; // the 0th element from the wordCandidate array int partialWordLength; int lastWordCandidateLength; // length of the last word candidate int originalCaretPosition; int lastCaretPosition = -99; int lastArrayPosition = 0; Document document; public CompleteWordAction(MainFrameController mainFrameController, JustWriteMainFrame mainFrame, String name, Integer mnemonic) { super(name, null); this.mainFrameController = mainFrameController; this.mainFrame = mainFrame; this.textPane = mainFrame.getEditorPane(); putValue(MNEMONIC_KEY, mnemonic); } public void actionPerformed(ActionEvent e) { // System.err.println("WordArray: " + matchingWordCandidates); // my test for now to show that the user is still in the same place, // hitting the [esc] key, looking for more matches int currentCaretPosition = textPane.getCaretPosition(); if (currentCaretPosition == lastCaretPosition) { // i think the user has hit the [esc] key more than once for the current word fragment; // assume our word array is already populated, and get the next element from // that array, if there is a next element to be had int numArrayElems = matchingWordCandidates.size(); // ex: 4 int nextArrayElem = lastArrayPosition + 1; if (nextArrayElem < numArrayElems) { // we still have another element in the array, let's show it String nextWordCandidate = matchingWordCandidates.get(nextArrayElem); // undo the effects of the last wordCandidate we tried; // delete the last characters that were added (from originalCaretPos to last CaretPos) try { document.remove(originalCaretPosition, lastCaretPosition-originalCaretPosition); // caret should be at the originalCaretPosition; try the next word nextWordCandidate = removeUndesiredCharacters(nextWordCandidate); partialWordLength = currentPartialWord.length(); // "th" == 2 int wordCandidateLength = nextWordCandidate.length(); // "thought" == 7 lastWordCandidateLength = wordCandidateLength; String endingWordFragment = nextWordCandidate.substring(partialWordLength); document.insertString(originalCaretPosition, endingWordFragment, null); lastCaretPosition = textPane.getCaretPosition(); // reset all of our pointers lastArrayPosition = nextArrayElem; return; } catch (BadLocationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } else { // still in the out 'if' loop, meaning the caret locations are the same, // and the user is hitting the [esc] key; but here, we want to go back to // showing the original word fragment by itself // we still have another element in the array, let's show it String fakeWordCandidate = ""; try { // undo the effects of the last wordCandidate we tried; // delete the last characters that were added (from originalCaretPos to last CaretPos) document.remove(originalCaretPosition, lastCaretPosition-originalCaretPosition); // caret should be at the originalCaretPosition; try the next word fakeWordCandidate = removeUndesiredCharacters(fakeWordCandidate); lastCaretPosition = textPane.getCaretPosition(); // reset all of our pointers; this is a trick/kludge to get back to the 0th array element lastArrayPosition = -1; return; } catch (BadLocationException ble) { // TODO } } } // come down here when the currentCaretPosition != lastCaretPosition. // this means that we are starting from ground zero with a totally new word // reset our carryover references lastCaretPosition = -1; originalCaretPosition = -99; matchingWordCandidates = null; currentPartialWord = null; wordCandidate0 = null; partialWordLength = 0; lastWordCandidateLength = 0; lastArrayPosition = 0; // re-get the document reference (technically not needed until later) document = textPane.getDocument(); // get the current word currentPartialWord = textPane.getCurrentWordBeforeCursor(); // get potential matching words from the document String documentText = textPane.getDocumentText(); matchingWordCandidates = getMatchingWords(currentPartialWord, documentText); if (matchingWordCandidates==null || matchingWordCandidates.size()==0) return; // get the 0th word candidate wordCandidate0 = matchingWordCandidates.get(0); // remove any poopy characters from this word fragment (", ', comma, -, ;, parens, etc.) wordCandidate0 = removeUndesiredCharacters(wordCandidate0); partialWordLength = currentPartialWord.length(); // "th" == 2 int wordCandidateLength = wordCandidate0.length(); // "thought" == 7 lastWordCandidateLength = wordCandidateLength; String endingWordFragment = wordCandidate0.substring(partialWordLength); // add the difference to the end of the word fragment in the editor; // position the cursor at the end of the completed word originalCaretPosition = textPane.getCaretPosition(); try { document.insertString(originalCaretPosition, endingWordFragment, null); lastCaretPosition = textPane.getCaretPosition(); } catch (BadLocationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // if the user press [Esc] again, show the next wordCandidate } private String removeUndesiredCharacters(final String s) { String string = null; string = s.replaceAll("-", ""); string = string.replaceAll(";", ""); string = string.replaceAll(":", ""); string = string.replaceAll("\\."," "); string = string.replaceAll("\\?"," "); string = string.replaceAll("\\("," "); string = string.replaceAll("\\)", " "); return string; } /** * Gets a List of all the Strings in the documentText that begin with * the partialWord. */ private List<String> getMatchingWords(String partialWord, String documentText) { if (partialWord==null) return null; if (partialWord.trim().equals("")) return null; List<String> wordCandidates = new ArrayList<String>(); Set<String> wordSet = getDocumentWords(documentText); for (String s : wordSet) { if (s.startsWith(partialWord) == true) { wordCandidates.add(s); } } return wordCandidates; } /** * Get a Set of all the words in the current document. */ private Set<String> getDocumentWords(final String documentText) { Set<String> wordSet = new TreeSet<String>(); // TODO there's probably a much more efficient way to do this String docText = documentText; docText = docText.replaceAll("\\s", " "); docText = docText.replaceAll("\\."," "); docText = docText.replaceAll("\\?"," "); docText = docText.replaceAll("\\("," "); docText = docText.replaceAll("\\)", " "); StringTokenizer st = new StringTokenizer(docText," ",false); int numTokens = st.countTokens(); int i=0; // never add the last token for (i=0; i<numTokens-1; i++) { String s = st.nextToken(); wordSet.add(s); } return wordSet; } }