/*
* JOrtho
*
* Copyright (C) 2005-2008 by i-net software
*
* This program 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 2 of the
* License, or (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
* Created on 05.11.2005
*/
package com.inet.jortho;
import java.awt.EventQueue;
import java.util.Locale;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Highlighter.Highlight;
/**
* This class check a <code>JTextComponent</code> automatically (in the background) for orthography. Spell error are
* highlighted with a red zigzag line.
*
* @author Volker Berlin
*/
class AutoSpellChecker implements DocumentListener, LanguageChangeListener {
private static final RedZigZagPainter painter = new RedZigZagPainter();
/**
* Remove the AutoSpellChecker from the given JTextComponent.
*
* @param text
* the JTextComponent
*/
static void disable(final JTextComponent text) {
final AbstractDocument doc = (AbstractDocument) text.getDocument();
for (final DocumentListener listener : doc.getDocumentListeners()) {
if (listener instanceof AutoSpellChecker) {
final AutoSpellChecker autoSpell = (AutoSpellChecker) listener;
doc.removeDocumentListener(autoSpell);
AutoSpellChecker.removeHighlights(text);
}
}
}
/**
* Refresh the highlighting. This can be useful if the dictionary was modify.
*
* @param text
* the JTextComponent
*/
static void refresh(final JTextComponent text) {
final AbstractDocument doc = (AbstractDocument) text.getDocument();
for (final DocumentListener listener : doc.getDocumentListeners()) {
if (listener instanceof AutoSpellChecker) {
final AutoSpellChecker autoSpell = (AutoSpellChecker) listener;
autoSpell.checkAll();
}
}
}
private static void removeHighlights(final JTextComponent text) {
final Highlighter highlighter = text.getHighlighter();
for (final Highlight highlight : highlighter.getHighlights()) {
if (highlight.getPainter() == painter) {
highlighter.removeHighlight(highlight);
}
}
}
private Dictionary dictionary;
private final JTextComponent jText;
private Locale locale;
private final SpellCheckerOptions options;
public AutoSpellChecker(final JTextComponent text, final SpellCheckerOptions options) {
jText = text;
this.options = options == null ? SpellChecker.getOptions() : options;
jText.getDocument().addDocumentListener(this);
SpellChecker.addLanguageChangeLister(this);
dictionary = SpellChecker.getCurrentDictionary();
locale = SpellChecker.getCurrentLocale();
checkAll();
}
/*====================================================================
*
* Methods of interface DocumentListener
*
*===================================================================*/
/**
* {@inheritDoc}
*/
public void changedUpdate(final DocumentEvent ev) {
//Nothing
}
/**
// * Check the completely text. Because this can consume many times with large Documents that this will do in a thread
* in the background step by step.
*/
private void checkAll() {
if (jText == null) {
//the needed objects does not exists
return;
}
if (dictionary == null) {
AutoSpellChecker.removeHighlights(jText);
return;
}
final Thread thread = new Thread(new Runnable() {
public void run() {
final Document document = jText.getDocument();
for (int i = 0; i < document.getLength();) {
try {
final Element element = ((AbstractDocument) document).getParagraphElement(i);
i = element.getEndOffset();
checkElement(element);
}
catch (final java.lang.Exception ex) {
return;
}
}
}
}, "JOrtho checkall");
thread.setPriority(Thread.NORM_PRIORITY - 1);
thread.setDaemon(true);
thread.start();
}
/**
* Check the spelling of the text of an element.
*
* @param element
* the to checking Element
*/
private void checkElement(final javax.swing.text.Element element) {
try {
if(! EventQueue.isDispatchThread()){
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
checkElement(element);
return;
}
});
}
catch (Exception e) {
e.printStackTrace();
}
}
final int i = element.getStartOffset();
final int l = ((AbstractDocument) jText.getDocument()).getLength();
final int j = Math.min(element.getEndOffset(), l);
if (i >= j) {
return;
}
// prevent a NPE if the dictionary is currently not loaded.
final Dictionary dic = dictionary;
final Locale loc = locale;
if (dic == null || loc == null) {
return;
}
final Tokenizer tok = new Tokenizer(jText, dic, loc, i, j, options);
String word;
final Highlighter highlighter = jText.getHighlighter();
while ((word = tok.nextInvalidWord()) != null) {
final int wordOffset = tok.getWordOffset();
highlighter.addHighlight(wordOffset, wordOffset + word.length(), painter);
}
}
catch (final BadLocationException e) {
e.printStackTrace();
}
}
private void removeHighlighters(final javax.swing.text.Element element) {
{
final int i = element.getStartOffset();
final int j = element.getEndOffset();
final Highlighter highlighter = jText.getHighlighter();
final Highlight[] highlights = highlighter.getHighlights();
for (int k = highlights.length; --k >= 0;) {
final Highlight highlight = highlights[k];
final int hlStartOffset = highlight.getStartOffset();
final int hlEndOffset = highlight.getEndOffset();
if ((i <= hlStartOffset && hlStartOffset <= j) || (i <= hlEndOffset && hlEndOffset <= j)) {
if (highlight.getPainter() == painter) {
highlighter.removeHighlight(highlight);
}
}
}
}
}
/**
* Check the Elements on the given position.
*/
private void checkElements(int offset, final int length) {
final int end = offset + length;
final Document document = jText.getDocument();
Element element;
do {
try {
// We need to use a ParagraphElement because a CharacterElement produce problems with formating in a word
element = ((AbstractDocument) document).getParagraphElement(offset);
}
catch (final java.lang.Exception ex) {
return;
}
removeHighlighters(element);
checkElement(element);
offset = element.getEndOffset();
} while (offset <= end && offset < document.getLength());
}
/**
* {@inheritDoc}
*/
public void insertUpdate(final DocumentEvent ev) {
checkElements(ev.getOffset(), ev.getLength());
}
/**
* {@inheritDoc}
*/
public void languageChanged(final LanguageChangeEvent ev) {
dictionary = SpellChecker.getCurrentDictionary();
locale = SpellChecker.getCurrentLocale();
checkAll();
}
/**
* {@inheritDoc}
*/
public void removeUpdate(final DocumentEvent ev) {
checkElements(ev.getOffset(), 0);
}
}