Package com.inet.jortho

Source Code of com.inet.jortho.SpellChecker$LanguageAction

/*
*  JOrtho
*
*  Copyright (C) 2005-2009 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.12.2007
*/
package com.inet.jortho;

import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Locale;
import java.util.Properties;
import java.util.WeakHashMap;

import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JToggleButton;
import javax.swing.text.JTextComponent;

/**
* This class is the major class of the spell checker JOrtho (Java Orthography Checker).
* In the most cases this is the only class that you need to add spell checking to your application.
* First you need to do a one-time registration of your dictionaries. In standalone applications this can
* look like:
* <code><pre>
* SpellChecker.registerDictionaries( new URL("file", null, ""), "en,de", "de" );
* </pre></code>
* and in an applet this will look like:
* <code><pre>
* SpellChecker.registerDictionaries( getCodeBase(), "en,de", "en" );
* </pre></code>
* After this you can register your text component that should have the spell checker features
* (Highlighter, context menu, spell checking dialog).
* This looks like:<code><pre>
* JTextPane text = new JTextPane();
* SpellChecker.register( text );
* </pre></code>
* @author Volker Berlin
*/
public class SpellChecker {
   
    private final static ArrayList<LanguageAction> languages = new ArrayList<LanguageAction>();
    private static Dictionary currentDictionary;
    private static Locale currentLocale = Locale.ENGLISH;
    private static UserDictionaryProvider userDictionaryProvider;
    private static CustomDictionaryProvider customDictionaryProvider;
    private final static java.util.Map<LanguageChangeListener, Object> listeners = Collections.synchronizedMap( new WeakHashMap<LanguageChangeListener, Object>() );
    private static String applicationName;
    private static final SpellCheckerOptions globalOptions = new SpellCheckerOptions();
   
    /**
     * Duplicate of Action.SELECTED_KEY since 1.6
     */
    static final String SELECTED_KEY = "SwingSelectedKey";
   
    /**
     * There is no instance needed of SpellChecker. All methods are static.
     */
    private SpellChecker(){/*nothing*/}
   
    /**
     * Sets the UserDictionaryProvider. This is needed if the user should be able to add their own words.
     * This method must be called before {@link #registerDictionaries(URL, String, String)}.
     *
     * @param userDictionaryProvider the new UserDictionaryProvider or null
     * @see #setCustomDictionaryProvider(CustomDictionaryProvider)
     * @see #getUserDictionaryProvider()
     * @see #registerDictionaries(URL, String, String)
     */
    public static void setUserDictionaryProvider( UserDictionaryProvider userDictionaryProvider ) {
        SpellChecker.userDictionaryProvider = userDictionaryProvider;
    }

    /**
     * Gets the currently set UserDictionaryProvider. If none has been set then null is returned.
     *
     * @see #setUserDictionaryProvider(UserDictionaryProvider)
     */
    public static UserDictionaryProvider getUserDictionaryProvider() {
        return SpellChecker.userDictionaryProvider;
    }
   
    /**
     * Set a CustomDictionaryProvider. This can be used to add an expert dictionary
     * like a medical dictionary or a chemical dictionary.
     *
     * @param customDictionaryProvider the new CustomDictionaryProvider or null
     * @see #setUserDictionaryProvider(UserDictionaryProvider)
     * @see #registerDictionaries(URL, String, String)
     */
    public static void setCustomDictionaryProvider( CustomDictionaryProvider customDictionaryProvider ) {
        SpellChecker.customDictionaryProvider = customDictionaryProvider;
    }
   
    /**
     * Gets the currently set CustomDictionaryProvider. If none has been set then null is returned.
     *
     * @see #setCustomDictionaryProvider(CustomDictionaryProvider)
     */
    public static CustomDictionaryProvider getCustomDictionaryProvider() {
        return SpellChecker.customDictionaryProvider;
    }

    /**
     * Read the default dictionary from the classpath.
     */
    public static void registerDictionaries(File file, String activeLocale) {
        if (file != null && file.exists() && file.canRead()) {
            try {
                registerDictionaries( file.toURI().toURL(), activeLocale );
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Dictionary file does not exist or can't be read.");
        }


    }
    /**
     * Registers the available dictionaries. The dictionaries' URLs must have the form "dictionary_xx.xxxxx" and must be
     * relative to the baseURL. The available languages and extension of the dictionaries is load from a configuration file.
     * The configuration file must also relative to the baseURL and must be named dictionaries.cnf, dictionaries.properties or
     * dictionaries.txt. If the dictionary of the active Locale does not exist, the first dictionary is loaded. There is
     * only one dictionary loaded in memory at a given time. The configuration file has a Java Properties format. Currently
     * there are the follow options:
     * <ul>
     * <li>languages</li>
     * <li>extension</li>
     * </ul>
     *
     * @param baseURL
     *            the base URL where the dictionaries and configuration file can be found. If null then URL("file", null, "")
     *            is used.
     * @param activeLocale
     *            the locale that should be loaded and made active. If null or empty then the default locale is used.
     */
    public static void registerDictionaries( URL baseURL, String activeLocale ) {
        if( baseURL == null ){
            try {
                baseURL = new URL("file", null, "");
            } catch( MalformedURLException e ) {
                // should never occur because the URL is valid
                e.printStackTrace();
            }
        }
        InputStream input;
        try {
            input = new URL( baseURL, "dictionaries.cnf" ).openStream();
        } catch( Exception e1 ) {
            try {
                input = new URL( baseURL, "dictionaries.properties" ).openStream();
            } catch( Exception e2 ) {
                try {
                    input = new URL( baseURL, "dictionaries.txt" ).openStream();
                } catch( Exception e3 ) {
                    System.err.println( "JOrtho configuration file not found!" );
                    e1.printStackTrace();
                    e2.printStackTrace();
                    e3.printStackTrace();
                    return;
                }
            }
        }
        Properties props = new Properties();
        try {
            props.load( input );
        } catch( IOException e ) {
            e.printStackTrace();
            return;
        }
        String availableLocales = props.getProperty( "languages" );
        String extension = props.getProperty( "extension", ".ortho" );
        registerDictionaries( baseURL, availableLocales, activeLocale, extension );
    }

    private static void loadDictionaryPropertiesAndRegister(InputStream input) {

    }

    /**
     * Registers the available dictionaries. The dictionaries' URLs must have the form "dictionary_xx.ortho" and must be
     * relative to the baseURL. If the dictionary of the active Locale does not exist, the first dictionary is loaded.
     * There is only one dictionary loaded in memory at a given time.
     *
     * @param baseURL
     *            the base URL where the dictionaries can be found. If null then URL("file", null, "") is used.
     * @param availableLocales
     *            a comma separated list of locales
     * @param activeLocale
     *            the locale that should be loaded and made active. If null or empty then the default locale is used.
     * @see #setUserDictionaryProvider(UserDictionaryProvider)
     */
    public static void registerDictionaries( URL baseURL, String availableLocales, String activeLocale ) {
        registerDictionaries( baseURL, availableLocales, activeLocale, ".ortho" );
    }

    /**
     * Registers the available dictionaries. The dictionaries' URLs must have the form "dictionary_xx.xxxxx" and must be
     * relative to the baseURL. The extension can be set via parameter.
     * If the dictionary of the active Locale does not exist, the first dictionary is loaded.
     * There is only one dictionary loaded in memory at a given time.
     *
     * @param baseURL
     *            the base URL where the dictionaries can be found. If null then URL("file", null, "") is used.
     * @param availableLocales
     *            a comma separated list of locales
     * @param activeLocale
     *            the locale that should be loaded and made active. If null or empty then the default locale is used.
     * @param extension
     *            the file extension of the dictionaries. Some web server like the IIS6 does not support the default ".ortho".
     * @see #setUserDictionaryProvider(UserDictionaryProvider)
     */
    public static void registerDictionaries( URL baseURL, String availableLocales, String activeLocale, String extension ) {
        if( baseURL == null ){
            try {
                baseURL = new URL("file", null, "");
            } catch( MalformedURLException e ) {
                // should never occur because the URL is valid
                e.printStackTrace();
            }
        }
        if( activeLocale == null ) {
            activeLocale = "";
        }
        activeLocale = activeLocale.trim();
        if( activeLocale.length() == 0 ) {
            activeLocale = Locale.getDefault().getLanguage();
        }
       
        boolean activeSelected = false;
        for( String locale : availableLocales.split( "," ) ) {
            locale = locale.trim().toLowerCase();
            if(locale.length() > 0){
                LanguageAction action = new LanguageAction( baseURL, new Locale( locale ), extension );
                languages.remove( action );
                languages.add( action );
                if( locale.equals( activeLocale ) ) {
                    action.actionPerformed( null );
                    activeSelected = true;
                }
            }
        }
        // if nothing selected then select the first entry
        if( !activeSelected && languages.size() > 0 ) {
            LanguageAction action = languages.get( 0 );
            action.actionPerformed( null );
        }
       
        //sort the display names in order of the current language
        Collections.sort( languages );
    }

    public static void reloadCurrentDictionary() {
        for (LanguageAction action : languages) {
            if (action.getLocale() == currentLocale) {
                    action.actionPerformed(null);
            }
        }

    }
   
    /**
     * Activate the spell checker for the given <code>JTextComponent</code>. The call is equal to register( text,
     * true, true ).
     *
     * @param text
     *            the JTextComponent
     * @throws NullPointerException
     *             if text is null
     */
    public static void register( final JTextComponent text) throws NullPointerException{
        register( text, true, true, true );
    }

    /**
     * Activates the spell checker for the given <code>JTextComponent</code>. You do not need to unregister if the
     * JTextComponent is not needed anymore.
     *
     * @param text
     *            the JTextComponent
     * @param hasPopup
     *            if true, the JTextComponent is to have a popup menu with the menu item "Orthography" and "Languages".
     * @param hasShortKey
     *            if true, pressing the F7 key will display the spell check dialog.
     * @param hasAutoSpell
     *            if true, the JTextComponent has a auto spell checking.
     * @throws NullPointerException
     *             if text is null
     */
    public static void register( final JTextComponent text, boolean hasPopup, boolean hasShortKey, boolean hasAutoSpell ) throws NullPointerException {
        if( hasPopup ) {
            enablePopup( text, true );
        }
        if( hasShortKey ) {
           // enableShortKey( text, true );
        }
        if( hasAutoSpell ) {
            enableAutoSpell( text, true );
        }
    }
   
    /**
     * Removes all spell checker features from the JTextComponent. This does not need to be called
     * if the text component is no longer needed.
     * @param text the JTextComponent
     */
    public static void unregister( JTextComponent text ){
      //  enableShortKey( text, false );
        enablePopup( text, false );
        enableAutoSpell( text, false );
    }
   
    /**
     * Enable or disable the F7 key. Pressing the F7 key will display the spell check dialog. This also
     * register an Action with the name "spell-checking".
     * @param text the JTextComponent that should change
     * @param enable true, enable the feature.
     */
/*   public static void enableShortKey( final JTextComponent text, boolean enable ){
        enableShortKey( text, enable, null );
    }*/
   
    /**
     * Enable or disable the F7 key. Pressing the F7 key will display the spell check dialog. This also
     * register an Action with the name "spell-checking".
     * @param text the JTextComponent that should change
     * @param enable true, enable the feature.
     * @param options override the default options for this menu.
     */
/*   public static void enableShortKey( final JTextComponent text, boolean enable, final SpellCheckerOptions options ){
        if( enable ){
            text.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_F7, 0 ), "spell-checking" );
            text.getActionMap().put( "spell-checking", new AbstractAction(){
                public void actionPerformed( ActionEvent e ) {
                    showSpellCheckerDialog( text, options );
                }
            });
        }else{
            text.getActionMap().remove( "spell-checking" );
        }
    } */
   
    /**
     * Show the Spell Checker dialog for the given JTextComponent. It will be do nothing if
     * the JTextComponent is not editable or there are no dictionary loaded.
     * The action for this method can you receive via:
     * <code><pre>
     * Action action = text.getActionMap().get("spell-checking");
     * </pre></code>
     * The action is only available if you have enable the short key (F7).
     * @param text JTextComponent to check
     * @param options override the default options for this menu.
     */
  /*  public static void showSpellCheckerDialog( final JTextComponent text, SpellCheckerOptions options ) {
        if( !text.isEditable() ) {
            // only editable text component have spell checking
            return;
        }
        Dictionary dictionary = currentDictionary;
        if( dictionary != null ) {
            Window parent = SwingUtilities.getWindowAncestor( text );
            SpellCheckerDialog dialog;
            if( parent instanceof Frame ) {
                dialog = new SpellCheckerDialog( (Frame)parent, true, options );
            } else {
                dialog = new SpellCheckerDialog( (Dialog)parent, true, options );
            }
            dialog.show( text, dictionary, currentLocale );
        }
    }*/
   
    /**
     * Enable or disable the popup menu with the menu item "Orthography" and "Languages".
     * @param text the JTextComponent that should change
     * @param enable true, enable the feature.
     */
    public static void enablePopup( JTextComponent text, boolean enable ){
        if( enable ){
            //final JPopupMenu menu = new JPopupMenu();
            //menu.add( createCheckerMenu() );
            //menu.add( createLanguagesMenu() );
            //text.addMouseListener( new PopupListener(menu) );
            text.addMouseListener(new PopupListener(text, currentDictionary, currentLocale));
        } else {
            for(MouseListener listener : text.getMouseListeners()){
                if(listener instanceof PopupListener){
                    text.removeMouseListener( listener );
                }
            }
        }
    }
   
    /**
     * Enable or disable the auto spell checking feature (red zigzag line) for a text component.
     * If you change the document then you need to reenable it.
     *
     * @param text
     *            the JTextComponent that should change
     * @param enable
     *            true, enable the feature.
     */
    public static void enableAutoSpell( JTextComponent text, boolean enable ){
        enableAutoSpell( text, enable, null );
    }

    /**
     * Enable or disable the auto spell checking feature (red zigzag line) for a text component. If you change the
     * document then you need to reenable it.
     *
     * @param text
     *            the JTextComponent that should change
     * @param enable
     *            true, enable the feature.
     * @param options
     *            override the default options for this menu.
     */
    public static void enableAutoSpell( JTextComponent text, boolean enable, SpellCheckerOptions options ){
        if( enable ){
            new AutoSpellChecker( text, options );
        } else {
            AutoSpellChecker.disable( text );
        }
    }
   
    /**
     * Adds a LanguageChangeListener. You do not need to remove it if the LanguageChangeListener is not needed
     * anymore. You need a hard reference to the listener because the SpellChecker hold only a WeakReference.
     *
     * @param listener
     *            listener to add
     * @see LanguageChangeListener
     */
    public static void addLanguageChangeLister(LanguageChangeListener listener){
        listeners.put( listener, null );
    }
   
    /**
     * Removes the LanguageChangeListener.
     * @param listener listener to remove
     */
    public static void removeLanguageChangeLister(LanguageChangeListener listener){
        listeners.remove( listener );
    }
   
    /**
     * Helper method to fire an Language change event.
     */
    private static void fireLanguageChanged( Locale oldLocale ) {
        LanguageChangeEvent ev = new LanguageChangeEvent( currentLocale, oldLocale );
        for( LanguageChangeListener listener : listeners.keySet() ) {
            listener.languageChanged( ev );
        }
    }

    /**
     * Creates a menu item "Languages" (or the equivalent depending on the user language) with a sub-menu
     * that lists all available dictionary languages.
     * You can use this to add this menu item to your own popup or to your menu bar.
     * <code><pre>
     * JPopupMenu popup = new JPopupMenu();
     * popup.add( SpellChecker.createLanguagesMenu() );
     * </pre></code>
     * @return the new menu.
     */
    public static JMenu createLanguagesMenu(){
        return createLanguagesMenu( null );
    }
   
    /**
     * Creates a menu item "Languages" (or the equivalent depending on the user language) with a sub-menu
     * that lists all available dictionary languages.
     * You can use this to add this menu item to your own popup or to your menu bar.
     * <code><pre>
     * JPopupMenu popup = new JPopupMenu();
     * popup.add( SpellChecker.createLanguagesMenu() );
     * </pre></code>
     * @param options override the default options for this menu.
     * @return the new menu.
     */
    public static JMenu createLanguagesMenu(SpellCheckerOptions options){
        JMenu menu = new JMenu(Utils.getResource("languages"));
        ButtonGroup group = new ButtonGroup();
        menu.setEnabled( languages.size() > 0 );
       
        for(LanguageAction action : languages){
            JRadioButtonMenuItem item = new JRadioButtonMenuItem( action );
            //Hack that all items of the action have the same state.
            //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4133141
            item.setModel( new ActionToggleButtonModel(action) );
            menu.add( item );
            group.add( item );
        }
       
        if(options == null ){
            options = SpellChecker.getOptions();
        }
       
        if(languages.size() > 0 && options.isLanguageDisableVisible()){
          menu.addSeparator();
            JRadioButtonMenuItem item = new JRadioButtonMenuItem( DisableLanguageAction.instance );
            item.setModel( new ActionToggleButtonModel(DisableLanguageAction.instance) );
            menu.add( item );
            group.add( item );
        }
       
        return menu;
    }
   
    private static class ActionToggleButtonModel extends JToggleButton.ToggleButtonModel{
        private final AbtsractLanguageAction action;
       
        ActionToggleButtonModel(AbtsractLanguageAction action){
            this.action = action;
        }
       
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isSelected() {
            return Boolean.TRUE.equals(action.getValue(SELECTED_KEY));
        }
       
        /**
         * {@inheritDoc}
         */
        @Override
        public void setSelected( boolean b ) {
            // copy from super.setSelected
            ButtonGroup group = getGroup();
            if (group != null) {
                // use the group model instead
                group.setSelected(this, b);
                b = group.isSelected(this);
            }

            if (isSelected() == b) {
                return;
            }

            action.setSelected( b );

            // Send ChangeEvent
            fireStateChanged();

            // Send ItemEvent
            fireItemStateChanged(
                    new ItemEvent(this,
                                  ItemEvent.ITEM_STATE_CHANGED,
                                  this,
                                  this.isSelected() ?  ItemEvent.SELECTED : ItemEvent.DESELECTED));

        }
    }
   
    /**
     * Base class for languages change actions. This class has a static state to solv
     * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4133141
     */
    private static abstract class AbtsractLanguageAction extends AbstractAction{
       
        // the current active (selected) LanguageAction
        private static AbtsractLanguageAction currentAction;
       
        public AbtsractLanguageAction( String name ) {
            super(name);
        }

        /**
         * Selects or deselects the menu item.
         *
         * @param b
         *            true selects the menu item, false deselects the menu item.
         */
        public void setSelected( boolean b ) {
            if( b ) {
                // because there are some problems with multiple ButtonGroups that we duplicate some of the logic here
                if( currentAction != null && currentAction != this ) {
                    currentAction.setSelected( false );
                }
                currentAction = this;
            }
            putValue( SELECTED_KEY, Boolean.valueOf( b ) );
        }

    }

    /**
     * Action for disable all dictionary language.
     */
    private static class DisableLanguageAction extends AbtsractLanguageAction{
      static DisableLanguageAction instance = new DisableLanguageAction();

        private DisableLanguageAction() {
            super(Utils.getResource("disable"));
        }
       
        public void actionPerformed( ActionEvent ev ) {
            if( !isEnabled() ) {
                //because multiple MenuItems share the same action that
                //also the event occur multiple time
                return;
            }
            setEnabled( false );
            setSelected( true );
            try {
                currentDictionary = null;
                Locale oldLocale = currentLocale;
                currentLocale = null;
                fireLanguageChanged( oldLocale );
            } finally {
                setEnabled( true );
            }
        }
    }
   
    /**
     * Action for change the current dictionary language.
     */
    private static class LanguageAction extends AbtsractLanguageAction implements Comparable<LanguageAction>{
       
        private final URL baseURL;
        private final Locale locale;
        private String extension;
       
        LanguageAction(URL baseURL, Locale locale, String extension){
            super( locale.getDisplayLanguage() );
            this.baseURL = baseURL;
            this.locale = locale;
            this.extension = extension;
        }

        public Locale getLocale() {
            return locale;
        }

        public void actionPerformed( ActionEvent ev ) {
            if( !isEnabled() ){
                //because multiple MenuItems share the same action that
                //also the event occur multiple time
                return;
            }
            setEnabled( false );
            setSelected( true );
           
            Thread thread = new Thread( new Runnable() {
                public void run() {
                    try {
                        DictionaryFactory factory = new DictionaryFactory();
                        try {
                            factory.loadWordList( new URL( baseURL, "dictionary/dictionary_" + locale + extension ) );
                        } catch( Exception ex ) {
                            JOptionPane.showMessageDialog( null, ex.toString(), "Error", JOptionPane.ERROR_MESSAGE );
                        }
                         try {
                            CustomDictionaryProvider provider = userDictionaryProvider;
                            if( provider != null ) {
                                Iterator<String> userWords = provider.getWords( locale );
                                if( userWords != null ) {
                                    factory.loadWords( userWords );
                                }
                            }
                            provider = customDictionaryProvider;
                            if( provider != null ) {
                                Iterator<String> userWords = provider.getWords( locale );
                                if( userWords != null ) {
                                    factory.loadWords( userWords );
                                }
                            }
                        } catch( Exception ex ) {
                            JOptionPane.showMessageDialog( null, ex.toString(), "Error", JOptionPane.ERROR_MESSAGE );
                        }
                        Locale oldLocale = locale;
                        currentDictionary = factory.create();
                        factory = null; // make memory faster free
                        currentLocale = locale;
                        fireLanguageChanged( oldLocale );
                    } finally {
                        setEnabled( true );
                    }
                }
            });
            thread.setPriority( Thread.NORM_PRIORITY );
            thread.setDaemon( true );
            thread.start();
        }
       
        @Override
        public boolean equals(Object obj){
            if(obj instanceof LanguageAction){
                return locale.equals( ((LanguageAction)obj).locale );
            }
            return false;
        }
       
        @Override
        public int hashCode(){
            return locale.hashCode();
        }

        /**
         * Sort the displaynames in the order of the current language
         */
        public int compareTo( LanguageAction obj ) {
            return toString().compareTo( obj.toString() );
        }
    }

    /**
     * Get the current <code>Dictionary</code>. The current dictionary will be set if the user one select or on calling <code>registerDictionaries</code>.
     * @return the current <code>Dictionary</code> or null if not set.
     * @see #registerDictionaries(URL, String, String)
     */
    static Dictionary getCurrentDictionary() {
        return currentDictionary;
    }

    /**
     * Gets the current <code>Locale</code>. The current Locale will be set if the user selects
     * one, or when calling <ode>registerDictionaries</code>.
     * @return the current <code>Locale</code> or null if none is set.
     * @see #registerDictionaries(URL, String, String)
     */
    public static Locale getCurrentLocale() {
        return currentLocale;
    }
   
    /**
     * Set the title of your application. This valuse is used as title for info boxes (JOptionPane).
     * If not set then the translated "Spelling" is used.
     */
    public static void setApplicationName( String name ){
        applicationName = name;
    }

    /**
     * Get the title of your application.
     */
    public static String getApplicationName(){
        return applicationName;
    }
    /**
     * Get the default SpellCheckerOptions. This object is a singleton. That there is no get method.
     * @return the default SpellCheckerOptions
     */
    public static SpellCheckerOptions getOptions(){
        return globalOptions;
    }
}
TOP

Related Classes of com.inet.jortho.SpellChecker$LanguageAction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.