/* 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; import java.awt.Cursor; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.io.File; import java.util.List; import javax.swing.JEditorPane; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.plaf.TextUI; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.Position; import javax.swing.text.StyleConstants; import javax.swing.text.StyledEditorKit; import com.swabunga.spell.engine.SpellDictionary; import com.swabunga.spell.engine.SpellDictionaryHashMap; import com.swabunga.spell.engine.Word; /** * * use: JTextPane pane = new JTextPane(); pane.setEditorKit(new SpellEditorKit()); * * * * * * @author Stig Tanggaard April 14, 2002 */ public class SpellEditorKit extends StyledEditorKit { // private static Color replyColor = null; // private static Cursor linkCursor; // private Cursor newCursor; protected static final Cursor linkCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); protected Cursor defaultCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); static SpellDictionary dictionary; public SpellEditorKit(File file) { super(); Object o; try { o = new SpellDictionaryHashMap(file); } catch (Exception f) { o = null; } dictionary = (SpellDictionary) o; } @Override public void install(JEditorPane c) { LinkController adapt = new LinkController(); c.addMouseMotionListener(adapt); c.addMouseListener(adapt); super.install(c); // System.out.println("install stylededitorkit on editorpane"); defaultCursor = c.getCursor(); } @Override public Document createDefaultDocument() { return new SpellCheckedDocument(dictionary); } public class LinkController extends MouseAdapter implements MouseMotionListener { private Element curElem = null; /** * If true, the current element (curElem) represents an image. */ private boolean curElemImage = false; private String href = null; /** * This is used by viewToModel to avoid allocing a new array each time. */ private Position.Bias[] bias = new Position.Bias[1]; /** * Current offset. */ private int curOffset; private int linkoffset = 0; /** * Called for a mouse click event. If the component is read-only (ie a browser) then the clicked event is used to drive an attempt * to follow the reference specified by a link. * * @param e * the mouse event * @see MouseListener#mouseClicked */ @Override public void mouseClicked(MouseEvent e) { if (e.isPopupTrigger()) { return; } JEditorPane editor = (JEditorPane) e.getSource(); if (editor.isEditable()) { Point pt = new Point(e.getX(), e.getY()); int pos = editor.viewToModel(pt); if (pos >= 0 && href != null) { // JPopupMenu menu = new JPopupMenu(); SpellCheckedDocument hdoc = (SpellCheckedDocument) editor.getDocument(); Element elem = hdoc.getCharacterElement(pos); try { final String word = hdoc.getText(elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset()); List list = dictionary.getSuggestions(word, 5); JPopupMenu popup = new JPopupMenu(); int index = 0; ReplaceListener listener = new ReplaceListener(elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset(), hdoc); while (index < list.size() && index < 5) { Word w = (Word) list.get(index); JMenuItem item = new JMenuItem(w.getWord()); item.setActionCommand(w.getWord()); item.addActionListener(listener); popup.add(item); index++; } JMenuItem item = new JMenuItem("Add word to wordlist"); item.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { dictionary.addWord(word); } }); popup.add(item); popup.show(editor, e.getX(), e.getY()); // System.out.println("" + elem.getStartOffset() + " count " + elem.getEndOffset()); } catch (BadLocationException f) { System.out.println("" + elem.getStartOffset() + " count " + elem.getElementCount()); } } } href = null; } // ignore the drags @Override public void mouseDragged(MouseEvent e) { } // track the moving of the mouse. @Override public void mouseMoved(MouseEvent e) { int pos = -1; // int offset = 0; JEditorPane editor = (JEditorPane) e.getSource(); // MailEditorKit kit = (MailEditorKit)editor.getEditorKit(); boolean adjustCursor = true; Cursor newCursor = defaultCursor; if (editor.isEditable()) { Point pt = new Point(e.getX(), e.getY()); pos = editor.getUI().viewToModel(editor, pt, bias); if (bias[0] == Position.Bias.Backward && pos > 0) { pos--; } if (pos >= 0 && editor.getDocument() instanceof SpellCheckedDocument) { SpellCheckedDocument hdoc = (SpellCheckedDocument) editor.getDocument(); Element elem = hdoc.getCharacterElement(pos); if (!doesElementContainLocation(editor, elem, pos, e.getX(), e.getY())) { elem = null; } if (curElem != elem || curElemImage) { // Element lastElem = curElem; curElem = elem; // String href = null; curElemImage = false; if (elem != null) { AttributeSet a = elem.getAttributes(); // System.out.println( // a.getAttribute(StyleConstants.NameAttribute)); if (a.getAttribute(StyleConstants.NameAttribute) == SpellCheckedDocument.ERROR_STYLE) { newCursor = linkCursor; linkoffset = elem.getStartOffset(); // System.out.println("test + " + pos); try { href = editor.getDocument().getText(elem.getStartOffset(), elem.getEndOffset() - elem.getStartOffset()); } catch (BadLocationException f) { } // offset = elem.getStartOffset(); } else { href = null; } } } else { adjustCursor = false; } curOffset = pos; } } // adjustCursor && if (adjustCursor && editor.getCursor() != newCursor) { editor.setCursor(newCursor); } } /** * Returns true if the View representing <code>e</code> contains the location <code>x</code>, <code>y</code>. <code>offset</code> * gives the offset into the Document to check for. */ private boolean doesElementContainLocation(JEditorPane editor, Element e, int offset, int x, int y) { if (e != null && offset > 0 && e.getStartOffset() == offset) { try { TextUI ui = editor.getUI(); Shape s1 = ui.modelToView(editor, offset, Position.Bias.Forward); Rectangle r1 = s1 instanceof Rectangle ? (Rectangle) s1 : s1.getBounds(); Shape s2 = ui.modelToView(editor, e.getEndOffset(), Position.Bias.Backward); Rectangle r2 = s2 instanceof Rectangle ? (Rectangle) s2 : s2.getBounds(); r1.add(r2); return r1.contains(x, y); } catch (BadLocationException ble) { } } return true; } } class ReplaceListener implements ActionListener { int offset; int length; Document doc; public ReplaceListener(int offset, int length, Document doc) { this.offset = offset; this.length = length; this.doc = doc; } @Override public void actionPerformed(ActionEvent e) { try { doc.remove(offset, length); doc.insertString(offset, e.getActionCommand(), null); } catch (BadLocationException f) { } } } }