/* Jazzy - a Java library for Spell Checking Copyright (C) 2001 Mindaugas Idzelis Full text of license can be found in LICENSE.txt This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package com.swabunga.spell.swing.autospell; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ResourceBundle; import javax.swing.JEditorPane; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.AttributeSet; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyledDocument; import com.swabunga.spell.engine.Configuration; import com.swabunga.spell.event.DocumentWordTokenizer; import com.swabunga.spell.event.SpellChecker; /** * This class handles the actual autospelling by implementing some listeners on the spellchecked JEditorPane and Document. * * @author Robert Gustavsson (robert@lindesign.se) * */ public class AutoSpellCheckHandler extends MouseAdapter implements DocumentListener, AutoSpellConstants { private SpellChecker sCheck = null; private Configuration config = Configuration.getConfiguration(); private ResourceBundle messages = null; public AutoSpellCheckHandler(SpellChecker sc) { sCheck = sc; } public AutoSpellCheckHandler(SpellChecker sc, ResourceBundle rs) { this(sc); messages = rs; } public void addJEditorPane(JEditorPane pane) { StyledDocument doc = (StyledDocument) pane.getDocument(); markupSpelling(doc, 0, doc.getLength() - 1); doc.addDocumentListener(this); pane.addMouseListener(this); } public void removeJEditorPane(JEditorPane pane) { Document doc = pane.getDocument(); pane.removeMouseListener(this); doc.removeDocumentListener(this); } private void markupSpelling(StyledDocument doc, int start, int end) { int wordStart = -1, wordEnd = -1; String word; DocumentWordTokenizer docTok; docTok = new DocumentWordTokenizer(doc); if (start > 0) { docTok.posStartFullWordFrom(start); } while (docTok.hasMoreWords() && docTok.getCurrentWordPosition() <= end) { word = docTok.nextWord(); wordStart = docTok.getCurrentWordPosition(); // Mark non word parts (spaces) as correct if (wordEnd != -1) { // System.out.println("Space:"+wordEnd+","+wordStart); markAsCorrect(doc, wordEnd, wordStart); } wordEnd = docTok.getCurrentWordEnd(); if (wordEnd > doc.getLength()) { wordEnd = doc.getLength() - 1; } if (wordStart >= wordEnd) { continue; } // System.out.println("Word:"+wordStart+","+wordEnd); if (sCheck.isCorrect(word) || sCheck.isIgnored(word)) { markAsCorrect(doc, wordStart, wordEnd); } else { markAsMisspelled(doc, wordStart, wordEnd); } } // Mark the rest (if any) as correct. if (wordEnd < end && wordEnd != -1) { // System.out.println("End:"+wordEnd+","+end); markAsCorrect(doc, wordEnd, end); } } private void markAsMisspelled(StyledDocument doc, int start, int end) { SimpleAttributeSet attr; attr = new SimpleAttributeSet(); attr.addAttribute(wordMisspelled, wordMisspelledTrue); doc.setCharacterAttributes(start, end - start, attr, false); } private void markAsCorrect(StyledDocument doc, int start, int end) { SimpleAttributeSet attr; attr = new SimpleAttributeSet(doc.getCharacterElement((start + end) / 2).getAttributes()); attr.removeAttribute(wordMisspelled); if (end >= start) { doc.setCharacterAttributes(start, end - start, attr, true); } } private void handleDocumentChange(DocumentEvent evt) { Element curElem, parElem; StyledDocument doc; int start, end; if (evt.getDocument() instanceof StyledDocument) { doc = (StyledDocument) evt.getDocument(); curElem = doc.getCharacterElement(evt.getOffset()); parElem = curElem.getParentElement(); if (parElem != null) { start = parElem.getStartOffset(); end = parElem.getEndOffset(); } else { start = curElem.getStartOffset(); end = curElem.getEndOffset(); } // System.out.println("curElem: "+curElem.getStartOffset()+", "+curElem.getEndOffset()); // System.out.println("parElem: "+parElem.getStartOffset()+", "+parElem.getEndOffset()); // System.out.println("change: "+start+", "+end); markupSpelling(doc, start, end); } } private void showSuggestionPopup(JEditorPane pane, Point p) { StyledDocument doc; JMenuItem item; AttributeSet attr; int pos = pane.viewToModel(p); DocumentWordTokenizer docTok; String word; java.util.List<com.swabunga.spell.engine.Word> suggestions; JPopupMenu popup; ReplaceListener repList; if (pos >= 0) { doc = (StyledDocument) pane.getDocument(); attr = doc.getCharacterElement(pos).getAttributes(); if (attr.containsAttribute(wordMisspelled, wordMisspelledTrue)) { docTok = new DocumentWordTokenizer(doc); docTok.posStartFullWordFrom(pos); word = docTok.nextWord(); suggestions = sCheck.getSuggestions(word, config.getInteger(Configuration.SPELL_THRESHOLD)); popup = new JPopupMenu(); repList = new ReplaceListener(docTok); for (int i = 0; i < suggestions.size(); i++) { com.swabunga.spell.engine.Word w = suggestions.get(i); item = new JMenuItem(w.getWord()); item.setActionCommand(w.getWord()); item.addActionListener(repList); popup.add(item); } popup.addSeparator(); item = new JMenuItem(); if (messages != null) { item.setText(messages.getString("IGNOREALL")); } else { item.setText("Ignore All"); } item.setActionCommand(word); item.addActionListener(new IgnoreAllListener(doc)); popup.add(item); item = new JMenuItem(); if (messages != null) { item.setText(messages.getString("ADD")); } else { item.setText("Add word to wordlist"); } item.setActionCommand(word); item.addActionListener(new AddToDictListener(doc)); popup.add(item); popup.show(pane, p.x, p.y); } } } // DocumentListener implementation // ------------------------------------------------------------------ @Override public void changedUpdate(DocumentEvent evt) { } @Override public void insertUpdate(DocumentEvent evt) { Runnable r = new SpellCheckChange(evt); SwingUtilities.invokeLater(r); } @Override public void removeUpdate(DocumentEvent evt) { Runnable r = new SpellCheckChange(evt); SwingUtilities.invokeLater(r); } // MouseListener implementation // ------------------------------------------------------------------ @Override public void mouseReleased(MouseEvent evt) { JEditorPane pane; if (!(evt.getComponent() instanceof JEditorPane)) { return; } if (evt.isPopupTrigger()) { pane = (JEditorPane) evt.getComponent(); if (pane.isEditable()) { showSuggestionPopup(pane, new Point(evt.getX(), evt.getY())); } } } // INNER CLASSES // ------------------------------------------------------------------ private class SpellCheckChange implements Runnable { private DocumentEvent evt; public SpellCheckChange(DocumentEvent evt) { this.evt = evt; } @Override public void run() { handleDocumentChange(evt); } } private class ReplaceListener implements ActionListener { DocumentWordTokenizer tok; public ReplaceListener(DocumentWordTokenizer tok) { this.tok = tok; } @Override public void actionPerformed(ActionEvent evt) { tok.replaceWord(evt.getActionCommand()); } } private class AddToDictListener implements ActionListener { private StyledDocument doc; public AddToDictListener(StyledDocument doc) { this.doc = doc; } @Override public void actionPerformed(ActionEvent evt) { sCheck.addToDictionary(evt.getActionCommand()); Runnable r = new MarkUpSpellingAll(doc); SwingUtilities.invokeLater(r); } } private class IgnoreAllListener implements ActionListener { private StyledDocument doc; public IgnoreAllListener(StyledDocument doc) { this.doc = doc; } @Override public void actionPerformed(ActionEvent evt) { sCheck.ignoreAll(evt.getActionCommand()); Runnable r = new MarkUpSpellingAll(doc); SwingUtilities.invokeLater(r); } } private class MarkUpSpellingAll implements Runnable { private StyledDocument doc; public MarkUpSpellingAll(StyledDocument doc) { this.doc = doc; } @Override public void run() { markupSpelling(doc, 0, doc.getLength()); } } }