/*
* 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 10.11.2005
*/
package com.inet.jortho;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.JTextComponent;
/**
* The Dialog for continues checking the orthography.
* @author Volker Berlin
*/
class SpellCheckerDialog extends JDialog implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
final private JButton addToDic = new JButton(Utils.getResource("addToDictionary"));
final private JButton change = new JButton(Utils.getResource("change"));
final private JButton changeAll = new JButton(Utils.getResource("changeAll"));
/** Map of change all words */
final private HashMap<String, String> changeWords = new HashMap<String, String>();
final private JButton close = new JButton(Utils.getResource("close"));
private Dictionary dictionary;
final private JButton editDic = new JButton(Utils.getResource("editDictionary"));
final private JButton ignore = new JButton(Utils.getResource("ignore"));
final private JButton ignoreAll = new JButton(Utils.getResource("ignoreAll"));
/** List of ignore all words */
final private ArrayList<String> ignoreWords = new ArrayList<String>();
private boolean isDictionaryModify;
private JTextComponent jText;
final private JLabel notFound = new JLabel();
private final SpellCheckerOptions options;
final private JList suggestionsList = new JList();
private Tokenizer tok;
final private JTextField word = new JTextField();
SpellCheckerDialog(final Dialog owner) throws HeadlessException {
this(owner, false, null);
}
SpellCheckerDialog(final Dialog owner, final boolean modal, final SpellCheckerOptions options) {
super(owner, modal);
this.options = options == null ? SpellChecker.getOptions() : options;
init();
}
SpellCheckerDialog(final Frame owner) {
this(owner, false, null);
}
SpellCheckerDialog(final Frame owner, final boolean modal, final SpellCheckerOptions options) {
super(owner, modal);
this.options = options == null ? SpellChecker.getOptions() : options;
init();
}
public void actionPerformed(final ActionEvent ev) {
final Object source = ev.getSource();
if (source == ignore) {
searchNext();
}
else if (source == close) {
dispose();
}
else {
final String newWord = word.getText();
final String oldWord = notFound.getText();
if (source == ignoreAll) {
ignoreWords.add(oldWord);
searchNext();
}
else if (source == addToDic) {
final UserDictionaryProvider provider = SpellChecker.getUserDictionaryProvider();
if (provider != null) {
provider.addWord(oldWord);
}
dictionary.add(oldWord);
dictionary.trimToSize();
isDictionaryModify = true;
searchNext();
}
else if (source == editDic) {
new DictionaryEditDialog(this).setVisible(true);
}
else if (source == change) {
replaceWord(oldWord, newWord);
searchNext();
}
else if (source == changeAll) {
changeWords.put(oldWord, newWord);
replaceWord(oldWord, newWord);
searchNext();
}
}
}
@Override
public void dispose() {
super.dispose();
if (isDictionaryModify) {
AutoSpellChecker.refresh(jText);
}
}
final private void init() {
try {
final Image image = ImageIO.read(getClass().getResourceAsStream("icon.png"));
// setIconImage appeared in Java 6.0 so use reflection to be compatible
// with earlier JVMs. Equivalent to calling setIcomImage(image);
final Class<Dialog> cls = Dialog.class;
final java.lang.reflect.Method m = cls.getMethod("setIconImage", new Class[] { Image.class });
m.invoke(this, new Object[] { image });
}
catch (final Throwable e1) {
// can occur in Java 5 or if the icon was removed, then use the default
}
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final Container cont = getContentPane();
cont.setLayout(new GridBagLayout());
final Insets insetL = new Insets(8, 8, 0, 8);
final Insets insetR = new Insets(8, 0, 0, 8);
cont.add(new JLabel(Utils.getResource("notInDictionary") + ":"), new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, insetL, 0, 0));
notFound.setForeground(Color.RED);
notFound.setText("xxxxxxxxxx");
cont.add(notFound, new GridBagConstraints(2, 1, 1, 1, 0.0, 0.0, GridBagConstraints.SOUTHWEST,
GridBagConstraints.NONE, insetL, 0, 0));
cont.add(word, new GridBagConstraints(1, 2, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetL, 0, 0));
cont.add(new JLabel(Utils.getResource("suggestions") + ":"), new GridBagConstraints(1, 3, 2, 1, 0.0, 0.0,
GridBagConstraints.SOUTHWEST, GridBagConstraints.NONE, insetL, 0, 0));
final JScrollPane scrollPane = new JScrollPane(suggestionsList);
cont.add(scrollPane, new GridBagConstraints(1, 4, 2, 5, 1.0, 1.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0));
cont.add(ignore, new GridBagConstraints(3, 1, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(ignoreAll, new GridBagConstraints(3, 2, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(addToDic, new GridBagConstraints(3, 3, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(editDic, new GridBagConstraints(3, 4, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(change, new GridBagConstraints(3, 5, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(changeAll, new GridBagConstraints(3, 6, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(close, new GridBagConstraints(3, 7, 1, 1, 0.0, 0.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
cont.add(new JLabel(), new GridBagConstraints(3, 8, 1, 1, 0.0, 1.0, GridBagConstraints.NORTHWEST,
GridBagConstraints.HORIZONTAL, insetR, 0, 0));
ignore.addActionListener(this);
ignoreAll.addActionListener(this);
addToDic.addActionListener(this);
editDic.addActionListener(this);
change.addActionListener(this);
changeAll.addActionListener(this);
close.addActionListener(this);
//ESCAPE Taste
close.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false),
"ESCAPE");
close.getActionMap().put("ESCAPE", new AbstractAction() {
/**
*
*/
private static final long serialVersionUID = 1L;
public void actionPerformed(final ActionEvent e) {
dispose();
}
});
word.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(final DocumentEvent ev) {
// disable "Add To Dictionary" if word was changed, not this word would added else the original misspelled word
addToDic.setEnabled(false);
}
public void insertUpdate(final DocumentEvent ev) {
// disable "Add To Dictionary" if word was changed, not this word would added else the original misspelled word
addToDic.setEnabled(false);
}
public void removeUpdate(final DocumentEvent ev) {
// disable "Add To Dictionary" if word was changed, not this word would added else the original misspelled word
addToDic.setEnabled(false);
}
});
suggestionsList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(final ListSelectionEvent ev) {
// Update the word field if a suggestion is click
if (!ev.getValueIsAdjusting() && suggestionsList.getSelectedIndex() >= 0) {
word.setText((String) suggestionsList.getSelectedValue());
addToDic.setEnabled(true);
}
}
});
final boolean isUserDictionary = SpellChecker.getUserDictionaryProvider() != null;
addToDic.setEnabled(isUserDictionary);
editDic.setEnabled(isUserDictionary);
pack();
}
private void replaceWord(final String oldWord, final String newWord) {
jText.setSelectionStart(tok.getWordOffset());
jText.setSelectionEnd(tok.getWordOffset() + oldWord.length());
jText.replaceSelection(newWord);
tok.updatePhrase();
}
/**
* Search the next misspelling word. If found it then refresh the dialog with the new information.
* ignoreWords and changeWords will handle automatically.
* @return true, if found a spell error.
*/
private boolean searchNext() {
String wordStr;
while (true) {
wordStr = tok.nextInvalidWord();
if (wordStr == null) {
dispose();
String title = SpellChecker.getApplicationName();
if (title == null) {
title = this.getTitle();
}
JOptionPane.showMessageDialog(getParent(), Utils.getResource("msgFinish"), title,
JOptionPane.INFORMATION_MESSAGE);
return false;
}
if (ignoreWords.contains(wordStr)) {
continue;
}
final String changeTo = changeWords.get(wordStr);
if (changeTo != null) {
replaceWord(wordStr, changeTo);
continue;
}
break;
}
word.setText(wordStr);
notFound.setText(wordStr);
final List<Suggestion> list = dictionary.searchSuggestions(wordStr);
final boolean needCapitalization = tok.isFirstWordInSentence() && Utils.isFirstCapitalized(wordStr);
final Vector<String> suggestionsVector = new Vector<String>();
for (int i = 0; i < list.size() && i < options.getSuggestionsLimitDialog(); i++) {
final Suggestion sugestion = list.get(i);
String newWord = sugestion.getWord();
if (needCapitalization) {
newWord = Utils.getCapitalized(newWord);
}
if (i == 0) {
word.setText(newWord);
}
suggestionsVector.add(newWord);
}
suggestionsList.setListData(suggestionsVector);
addToDic.setEnabled(true);
return true;
}
public void show(final JTextComponent jTextComponent, final Dictionary dic, final Locale loc) {
jText = jTextComponent;
dictionary = dic;
change.requestFocus();
setTitle(Utils.getResource("spelling") + ": " + loc.getDisplayLanguage());
tok = new Tokenizer(jTextComponent, dic, loc, options);
if (searchNext()) {
// if the JTextComponent is large and has a scrollpane then Java use the bounds
// and not the visible rect. This is bad
Container parent = jTextComponent;
while (parent != null && !(parent instanceof JScrollPane)) {
if (parent instanceof JComponent) {
final JComponent jcomp = (JComponent) parent;
if (jcomp.getVisibleRect().height == jcomp.getBounds().height) {
break;
}
}
if (parent.getParent() != null) {
parent = parent.getParent();
}
else {
break;
}
}
setLocationRelativeTo(parent);
setVisible(true);
}
}
}