Bug 597811: Make mozJSComponentLoader use JSVERSION_LATEST. (r=sayrer)
[mozilla-central.git] / toolkit / content / InlineSpellChecker.jsm
blobe4b0939e58891273e1dea9213a806c5df7854939
1 /* ***** BEGIN LICENSE BLOCK *****
2  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Mozilla Public License Version
5  * 1.1 (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  * http://www.mozilla.org/MPL/
8  *
9  * Software distributed under the License is distributed on an "AS IS" basis,
10  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11  * for the specific language governing rights and limitations under the
12  * License.
13  *
14  * The Original Code is Inline spellcheck code
15  *
16  * The Initial Developer of the Original Code is
17  * Google Inc.
18  * Portions created by the Initial Developer are Copyright (C) 2005
19  * the Initial Developer. All Rights Reserved.
20  *
21  * Contributor(s):
22  *   Brett Wilson <brettw@gmail.com>
23  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
38 var EXPORTED_SYMBOLS = [ "InlineSpellChecker" ];
39 var gLanguageBundle;
40 var gRegionBundle;
42 function InlineSpellChecker(aEditor) {
43   this.init(aEditor);
46 InlineSpellChecker.prototype = {
47   // Call this function to initialize for a given editor
48   init: function(aEditor)
49   {
50     this.uninit();
51     this.mEditor = aEditor;
52     try {
53       this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
54       // note: this might have been NULL if there is no chance we can spellcheck
55     } catch(e) {
56       this.mInlineSpellChecker = null;
57     }
58   },
60   // call this to clear state
61   uninit: function()
62   {
63     this.mInlineSpellChecker = null;
64     this.mOverMisspelling = false;
65     this.mMisspelling = "";
66     this.mMenu = null;
67     this.mSpellSuggestions = [];
68     this.mSuggestionItems = [];
69     this.mDictionaryMenu = null;
70     this.mDictionaryNames = [];
71     this.mDictionaryItems = [];
72   },
74   // for each UI event, you must call this function, it will compute the
75   // word the cursor is over
76   initFromEvent: function(rangeParent, rangeOffset)
77   {
78     this.mOverMisspelling = false;
80     if (!rangeParent || !this.mInlineSpellChecker)
81       return;
83     var selcon = this.mEditor.selectionController;
84     var spellsel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
85     if (spellsel.rangeCount == 0)
86       return; // easy case - no misspellings
88     var range = this.mInlineSpellChecker.getMisspelledWord(rangeParent,
89                                                           rangeOffset);
90     if (! range)
91       return; // not over a misspelled word
93     this.mMisspelling = range.toString();
94     this.mOverMisspelling = true;
95     this.mWordNode = rangeParent;
96     this.mWordOffset = rangeOffset;
97   },
99   // returns false if there should be no spellchecking UI enabled at all, true
100   // means that you can at least give the user the ability to turn it on.
101   get canSpellCheck()
102   {
103     // inline spell checker objects will be created only if there are actual
104     // dictionaries available
105     return (this.mInlineSpellChecker != null);
106   },
108   // Whether spellchecking is enabled in the current box
109   get enabled()
110   {
111     return (this.mInlineSpellChecker &&
112             this.mInlineSpellChecker.enableRealTimeSpell);
113   },
114   set enabled(isEnabled)
115   {
116     if (this.mInlineSpellChecker)
117       this.mEditor.setSpellcheckUserOverride(isEnabled);
118   },
120   // returns true if the given event is over a misspelled word
121   get overMisspelling()
122   {
123     return this.mOverMisspelling;
124   },
126   // this prepends up to "maxNumber" suggestions at the given menu position
127   // for the word under the cursor. Returns the number of suggestions inserted.
128   addSuggestionsToMenu: function(menu, insertBefore, maxNumber)
129   {
130     if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
131       return 0; // nothing to do
133     var spellchecker = this.mInlineSpellChecker.spellChecker;
134     if (! spellchecker.CheckCurrentWord(this.mMisspelling))
135       return 0;  // word seems not misspelled after all (?)
137     this.mMenu = menu;
138     this.mSpellSuggestions = [];
139     this.mSuggestionItems = [];
140     for (var i = 0; i < maxNumber; i ++) {
141       var suggestion = spellchecker.GetSuggestedWord();
142       if (! suggestion.length)
143         break;
144       this.mSpellSuggestions.push(suggestion);
146       var item = menu.ownerDocument.createElement("menuitem");
147       this.mSuggestionItems.push(item);
148       item.setAttribute("label", suggestion);
149       item.setAttribute("value", suggestion);
150       // this function thing is necessary to generate a callback with the
151       // correct binding of "val" (the index in this loop).
152       var callback = function(me, val) { return function(evt) { me.replaceMisspelling(val); } };
153       item.addEventListener("command", callback(this, i), true);
154       item.setAttribute("class", "spell-suggestion");
155       menu.insertBefore(item, insertBefore);
156     }
157     return this.mSpellSuggestions.length;
158   },
160   // undoes the work of addSuggestionsToMenu for the same menu
161   // (call from popup hiding)
162   clearSuggestionsFromMenu: function()
163   {
164     for (var i = 0; i < this.mSuggestionItems.length; i ++) {
165       this.mMenu.removeChild(this.mSuggestionItems[i]);
166     }
167     this.mSuggestionItems = [];
168   },
170   // returns the number of dictionary languages. If insertBefore is NULL, this
171   // does an append to the given menu
172   addDictionaryListToMenu: function(menu, insertBefore)
173   {
174     this.mDictionaryMenu = menu;
175     this.mDictionaryNames = [];
176     this.mDictionaryItems = [];
178     if (! gLanguageBundle) {
179       // create the bundles for language and region
180       var bundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
181                                     .getService(Components.interfaces.nsIStringBundleService);
182       gLanguageBundle = bundleService.createBundle(
183           "chrome://global/locale/languageNames.properties");
184       gRegionBundle = bundleService.createBundle(
185           "chrome://global/locale/regionNames.properties");
186     }
188     if (! this.mInlineSpellChecker || ! this.enabled)
189       return 0;
190     var spellchecker = this.mInlineSpellChecker.spellChecker;
191     var o1 = {}, o2 = {};
192     spellchecker.GetDictionaryList(o1, o2);
193     var list = o1.value;
194     var listcount = o2.value;
195     var curlang = spellchecker.GetCurrentDictionary();
196     var isoStrArray;
198     for (var i = 0; i < list.length; i ++) {
199       // get the display name for this dictionary
200       isoStrArray = list[i].split(/[-_]/);
201       var displayName = "";
202       if (gLanguageBundle && isoStrArray[0]) {
203         try {
204           displayName = gLanguageBundle.GetStringFromName(isoStrArray[0].toLowerCase());
205         } catch(e) {} // ignore language bundle errors
206         if (gRegionBundle && isoStrArray[1]) {
207           try {
208             displayName += " / " + gRegionBundle.GetStringFromName(isoStrArray[1].toLowerCase());
209           } catch(e) {} // ignore region bundle errors
210           if (isoStrArray[2])
211             displayName += " (" + isoStrArray[2] + ")";
212         }
213       }
215       // if we didn't get a name, just use the raw dictionary name
216       if (displayName.length == 0)
217         displayName = list[i];
219       this.mDictionaryNames.push(list[i]);
220       var item = menu.ownerDocument.createElement("menuitem");
221       item.setAttribute("id", "spell-check-dictionary-" + list[i]);
222       item.setAttribute("label", displayName);
223       item.setAttribute("type", "radio");
224       this.mDictionaryItems.push(item);
225       if (curlang == list[i]) {
226         item.setAttribute("checked", "true");
227       } else {
228         var callback = function(me, val) { return function(evt) { me.selectDictionary(val); } };
229         item.addEventListener("command", callback(this, i), true);
230       }
231       if (insertBefore)
232         menu.insertBefore(item, insertBefore);
233       else
234         menu.appendChild(item);
235     }
236     return list.length;
237   },
239   // undoes the work of addDictionaryListToMenu for the menu
240   // (call on popup hiding)
241   clearDictionaryListFromMenu: function()
242   {
243     for (var i = 0; i < this.mDictionaryItems.length; i ++) {
244       this.mDictionaryMenu.removeChild(this.mDictionaryItems[i]);
245     }
246     this.mDictionaryItems = [];
247   },
249   // callback for selecting a dictionary
250   selectDictionary: function(index)
251   {
252     if (! this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
253       return;
254     var spellchecker = this.mInlineSpellChecker.spellChecker;
255     spellchecker.SetCurrentDictionary(this.mDictionaryNames[index]);
256     spellchecker.saveDefaultDictionary();
257     this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
258   },
260   // callback for selecting a suggesteed replacement
261   replaceMisspelling: function(index)
262   {
263     if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
264       return;
265     if (index < 0 || index >= this.mSpellSuggestions.length)
266       return;
267     this.mInlineSpellChecker.replaceWord(this.mWordNode, this.mWordOffset,
268                                          this.mSpellSuggestions[index]);
269   },
271   // callback for enabling or disabling spellchecking
272   toggleEnabled: function()
273   {
274     this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
275   },
277   // callback for adding the current misspelling to the user-defined dictionary
278   addToDictionary: function()
279   {
280     this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
281   },
282   ignoreWord: function()
283   {
284     this.mInlineSpellChecker.ignoreWord(this.mMisspelling);
285   }