1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
14 * The Original Code is Inline spellcheck code
16 * The Initial Developer of the Original Code is
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
22 * Brett Wilson <brettw@gmail.com>
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.
36 * ***** END LICENSE BLOCK ***** */
38 var EXPORTED_SYMBOLS = [ "InlineSpellChecker" ];
42 function InlineSpellChecker(aEditor) {
46 InlineSpellChecker.prototype = {
47 // Call this function to initialize for a given editor
48 init: function(aEditor)
51 this.mEditor = aEditor;
53 this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
54 // note: this might have been NULL if there is no chance we can spellcheck
56 this.mInlineSpellChecker = null;
60 // call this to clear state
63 this.mInlineSpellChecker = null;
64 this.mOverMisspelling = false;
65 this.mMisspelling = "";
67 this.mSpellSuggestions = [];
68 this.mSuggestionItems = [];
69 this.mDictionaryMenu = null;
70 this.mDictionaryNames = [];
71 this.mDictionaryItems = [];
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)
78 this.mOverMisspelling = false;
80 if (!rangeParent || !this.mInlineSpellChecker)
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,
91 return; // not over a misspelled word
93 this.mMisspelling = range.toString();
94 this.mOverMisspelling = true;
95 this.mWordNode = rangeParent;
96 this.mWordOffset = rangeOffset;
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.
103 // inline spell checker objects will be created only if there are actual
104 // dictionaries available
105 return (this.mInlineSpellChecker != null);
108 // Whether spellchecking is enabled in the current box
111 return (this.mInlineSpellChecker &&
112 this.mInlineSpellChecker.enableRealTimeSpell);
114 set enabled(isEnabled)
116 if (this.mInlineSpellChecker)
117 this.mEditor.setSpellcheckUserOverride(isEnabled);
120 // returns true if the given event is over a misspelled word
121 get overMisspelling()
123 return this.mOverMisspelling;
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)
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 (?)
138 this.mSpellSuggestions = [];
139 this.mSuggestionItems = [];
140 for (var i = 0; i < maxNumber; i ++) {
141 var suggestion = spellchecker.GetSuggestedWord();
142 if (! suggestion.length)
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);
157 return this.mSpellSuggestions.length;
160 // undoes the work of addSuggestionsToMenu for the same menu
161 // (call from popup hiding)
162 clearSuggestionsFromMenu: function()
164 for (var i = 0; i < this.mSuggestionItems.length; i ++) {
165 this.mMenu.removeChild(this.mSuggestionItems[i]);
167 this.mSuggestionItems = [];
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)
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");
188 if (! this.mInlineSpellChecker || ! this.enabled)
190 var spellchecker = this.mInlineSpellChecker.spellChecker;
191 var o1 = {}, o2 = {};
192 spellchecker.GetDictionaryList(o1, o2);
194 var listcount = o2.value;
195 var curlang = spellchecker.GetCurrentDictionary();
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]) {
204 displayName = gLanguageBundle.GetStringFromName(isoStrArray[0].toLowerCase());
205 } catch(e) {} // ignore language bundle errors
206 if (gRegionBundle && isoStrArray[1]) {
208 displayName += " / " + gRegionBundle.GetStringFromName(isoStrArray[1].toLowerCase());
209 } catch(e) {} // ignore region bundle errors
211 displayName += " (" + isoStrArray[2] + ")";
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");
228 var callback = function(me, val) { return function(evt) { me.selectDictionary(val); } };
229 item.addEventListener("command", callback(this, i), true);
232 menu.insertBefore(item, insertBefore);
234 menu.appendChild(item);
239 // undoes the work of addDictionaryListToMenu for the menu
240 // (call on popup hiding)
241 clearDictionaryListFromMenu: function()
243 for (var i = 0; i < this.mDictionaryItems.length; i ++) {
244 this.mDictionaryMenu.removeChild(this.mDictionaryItems[i]);
246 this.mDictionaryItems = [];
249 // callback for selecting a dictionary
250 selectDictionary: function(index)
252 if (! this.mInlineSpellChecker || index < 0 || index >= this.mDictionaryNames.length)
254 var spellchecker = this.mInlineSpellChecker.spellChecker;
255 spellchecker.SetCurrentDictionary(this.mDictionaryNames[index]);
256 spellchecker.saveDefaultDictionary();
257 this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
260 // callback for selecting a suggesteed replacement
261 replaceMisspelling: function(index)
263 if (! this.mInlineSpellChecker || ! this.mOverMisspelling)
265 if (index < 0 || index >= this.mSpellSuggestions.length)
267 this.mInlineSpellChecker.replaceWord(this.mWordNode, this.mWordOffset,
268 this.mSpellSuggestions[index]);
271 // callback for enabling or disabling spellchecking
272 toggleEnabled: function()
274 this.mEditor.setSpellcheckUserOverride(!this.mInlineSpellChecker.enableRealTimeSpell);
277 // callback for adding the current misspelling to the user-defined dictionary
278 addToDictionary: function()
280 this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
282 ignoreWord: function()
284 this.mInlineSpellChecker.ignoreWord(this.mMisspelling);