Back to index

lightning-sunbird  0.9+nobinonly
nsEditorSpellCheck.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code..
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 2002
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *      Kin Blas <kin@netscape.com>
00024  *      Akkana Peck <akkana@netscape.com>
00025  *      Charley Manske <cmanske@netscape.com>
00026  *      Neil Deakin <neil@mozdevgroup.com>
00027  *      Brett Wilson <brettw@gmail.com>
00028  *
00029  * Alternatively, the contents of this file may be used under the terms of
00030  * either the GNU General Public License Version 2 or later (the "GPL"), or
00031  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00032  * in which case the provisions of the GPL or the LGPL are applicable instead
00033  * of those above. If you wish to allow use of your version of this file only
00034  * under the terms of either the GPL or the LGPL, and not to allow others to
00035  * use your version of this file under the terms of the MPL, indicate your
00036  * decision by deleting the provisions above and replace them with the notice
00037  * and other provisions required by the GPL or the LGPL. If you do not delete
00038  * the provisions above, a recipient may use your version of this file under
00039  * the terms of any one of the MPL, the GPL or the LGPL.
00040  *
00041  * ***** END LICENSE BLOCK ***** */
00042 
00043 #include "nsEditorSpellCheck.h"
00044 
00045 #include "nsITextServicesDocument.h"
00046 #include "nsISpellChecker.h"
00047 #include "nsISelection.h"
00048 #include "nsIDOMRange.h"
00049 #include "nsIEditor.h"
00050 
00051 #include "nsIComponentManager.h"
00052 #include "nsXPIDLString.h"
00053 #include "nsIPrefBranch.h"
00054 #include "nsIPrefService.h"
00055 #include "nsISupportsPrimitives.h"
00056 #include "nsServiceManagerUtils.h"
00057 #include "nsIChromeRegistry.h"
00058 #include "nsString.h"
00059 #include "nsReadableUtils.h"
00060 #include "nsITextServicesFilter.h"
00061 
00062 NS_IMPL_ISUPPORTS2(nsEditorSpellCheck,
00063                    nsIEditorSpellCheck,
00064                    nsIEditorSpellCheck_MOZILLA_1_8_BRANCH)
00065 
00066 nsEditorSpellCheck::nsEditorSpellCheck()
00067   : mSuggestedWordIndex(0)
00068   , mDictionaryIndex(0)
00069 {
00070 }
00071 
00072 nsEditorSpellCheck::~nsEditorSpellCheck()
00073 {
00074   // Make sure we blow the spellchecker away, just in
00075   // case it hasn't been destroyed already.
00076   mSpellChecker = nsnull;
00077 }
00078 
00079 // The problem is that if the spell checker does not exist, we can not tell
00080 // which dictionaries are installed. This function works around the problem,
00081 // allowing callers to ask if we can spell check without actually doing so (and
00082 // enabling or disabling UI as necessary). This just creates a spellcheck
00083 // object if needed and asks it for the dictionary list.
00084 NS_IMETHODIMP
00085 nsEditorSpellCheck::CanSpellCheck(PRBool* _retval)
00086 {
00087   nsresult rv;
00088   nsCOMPtr<nsISpellChecker> spellChecker;
00089   if (! mSpellChecker) {
00090     spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
00091     NS_ENSURE_SUCCESS(rv, rv);
00092   } else {
00093     spellChecker = mSpellChecker;
00094   }
00095   nsStringArray dictList;
00096   rv = spellChecker->GetDictionaryList(&dictList);
00097   NS_ENSURE_SUCCESS(rv, rv);
00098 
00099   *_retval = (dictList.Count() > 0);
00100   return NS_OK;
00101 }
00102 
00103 NS_IMETHODIMP    
00104 nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, PRBool aEnableSelectionChecking)
00105 {
00106   nsresult rv;
00107 
00108   // We can spell check with any editor type
00109   nsCOMPtr<nsITextServicesDocument>tsDoc =
00110      do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
00111   NS_ENSURE_SUCCESS(rv, rv);
00112 
00113   if (!tsDoc)
00114     return NS_ERROR_NULL_POINTER;
00115 
00116   tsDoc->SetFilter(mTxtSrvFilter);
00117 
00118   // Pass the editor to the text services document
00119   rv = tsDoc->InitWithEditor(aEditor);
00120   NS_ENSURE_SUCCESS(rv, rv);
00121 
00122   if (aEnableSelectionChecking) {
00123     // Find out if the section is collapsed or not.
00124     // If it isn't, we want to spellcheck just the selection.
00125 
00126     nsCOMPtr<nsISelection> selection;
00127 
00128     rv = aEditor->GetSelection(getter_AddRefs(selection));
00129     NS_ENSURE_SUCCESS(rv, rv);
00130     NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
00131 
00132     PRInt32 count = 0;
00133 
00134     rv = selection->GetRangeCount(&count);
00135     NS_ENSURE_SUCCESS(rv, rv);
00136 
00137     if (count > 0) {
00138       nsCOMPtr<nsIDOMRange> range;
00139 
00140       rv = selection->GetRangeAt(0, getter_AddRefs(range));
00141       NS_ENSURE_SUCCESS(rv, rv);
00142 
00143       PRBool collapsed = PR_FALSE;
00144       rv = range->GetCollapsed(&collapsed);
00145       NS_ENSURE_SUCCESS(rv, rv);
00146 
00147       if (!collapsed) {
00148         // We don't want to touch the range in the selection,
00149         // so create a new copy of it.
00150 
00151         nsCOMPtr<nsIDOMRange> rangeBounds;
00152         rv =  range->CloneRange(getter_AddRefs(rangeBounds));
00153         NS_ENSURE_SUCCESS(rv, rv);
00154         NS_ENSURE_TRUE(rangeBounds, NS_ERROR_FAILURE);
00155 
00156         // Make sure the new range spans complete words.
00157 
00158         rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
00159         NS_ENSURE_SUCCESS(rv, rv);
00160 
00161         // Now tell the text services that you only want
00162         // to iterate over the text in this range.
00163 
00164         rv = tsDoc->SetExtent(rangeBounds);
00165         NS_ENSURE_SUCCESS(rv, rv);
00166       }
00167     }
00168   }
00169 
00170   mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
00171   NS_ENSURE_SUCCESS(rv, rv);
00172 
00173   if (!mSpellChecker)
00174     return NS_ERROR_NULL_POINTER;
00175 
00176   rv = mSpellChecker->SetDocument(tsDoc, PR_TRUE);
00177   NS_ENSURE_SUCCESS(rv, rv);
00178 
00179   // Tell the spellchecker what dictionary to use:
00180 
00181   nsXPIDLString dictName;
00182 
00183   nsCOMPtr<nsIPrefBranch> prefBranch =
00184     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
00185 
00186   if (NS_SUCCEEDED(rv) && prefBranch) {
00187     nsCOMPtr<nsISupportsString> prefString;
00188     rv = prefBranch->GetComplexValue("spellchecker.dictionary",
00189                                      NS_GET_IID(nsISupportsString),
00190                                      getter_AddRefs(prefString));
00191     if (NS_SUCCEEDED(rv) && prefString)
00192       prefString->GetData(dictName);
00193   }
00194 
00195   if (dictName.IsEmpty())
00196   {
00197     // Prefs didn't give us a dictionary name, so just get the current
00198     // locale and use that as the default dictionary name!
00199 
00200     nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
00201       do_GetService(NS_CHROMEREGISTRY_CONTRACTID, &rv);
00202 
00203     if (NS_SUCCEEDED(rv) && packageRegistry) {
00204       nsCAutoString utf8DictName;
00205       rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("editor"),
00206                                               utf8DictName);
00207       AppendUTF8toUTF16(utf8DictName, dictName);
00208     }
00209   }
00210 
00211   PRBool setDictionary = PR_FALSE;
00212   if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
00213     rv = SetCurrentDictionary(dictName.get());
00214     if (NS_SUCCEEDED(rv))
00215       setDictionary = PR_TRUE;
00216   }
00217 
00218   // If there was no dictionary specified by spellchecker.dictionary and setting it to the 
00219   // locale dictionary didn't work, try to use the first dictionary we find. This helps when 
00220   // the first dictionary is installed
00221   if (! setDictionary) {
00222     nsStringArray dictList;
00223     rv = mSpellChecker->GetDictionaryList(&dictList);
00224     NS_ENSURE_SUCCESS(rv, rv);
00225     if (dictList.Count() > 0) {
00226       rv = SetCurrentDictionary(dictList[0]->get());
00227       if (NS_SUCCEEDED(rv))
00228         SaveDefaultDictionary();
00229     }
00230   }
00231 
00232   // If an error was thrown while checking the dictionary pref, just
00233   // fail silently so that the spellchecker dialog is allowed to come
00234   // up. The user can manually reset the language to their choice on
00235   // the dialog if it is wrong.
00236 
00237   DeleteSuggestedWordList();
00238 
00239   return NS_OK;
00240 }
00241 
00242 NS_IMETHODIMP    
00243 nsEditorSpellCheck::GetNextMisspelledWord(PRUnichar **aNextMisspelledWord)
00244 {
00245   if (!mSpellChecker)
00246     return NS_ERROR_NOT_INITIALIZED;
00247 
00248   nsAutoString nextMisspelledWord;
00249   
00250   DeleteSuggestedWordList();
00251   nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
00252                                                   &mSuggestedWordList);
00253 
00254   *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
00255   return rv;
00256 }
00257 
00258 NS_IMETHODIMP    
00259 nsEditorSpellCheck::GetSuggestedWord(PRUnichar **aSuggestedWord)
00260 {
00261   nsAutoString word;
00262   if ( mSuggestedWordIndex < mSuggestedWordList.Count())
00263   {
00264     mSuggestedWordList.StringAt(mSuggestedWordIndex, word);
00265     mSuggestedWordIndex++;
00266   } else {
00267     // A blank string signals that there are no more strings
00268     word.Truncate();
00269   }
00270 
00271   *aSuggestedWord = ToNewUnicode(word);
00272   return NS_OK;
00273 }
00274 
00275 NS_IMETHODIMP    
00276 nsEditorSpellCheck::CheckCurrentWord(const PRUnichar *aSuggestedWord,
00277                                      PRBool *aIsMisspelled)
00278 {
00279   if (!mSpellChecker)
00280     return NS_ERROR_NOT_INITIALIZED;
00281 
00282   DeleteSuggestedWordList();
00283   return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
00284                                   aIsMisspelled, &mSuggestedWordList);
00285 }
00286 
00287 NS_IMETHODIMP    
00288 nsEditorSpellCheck::CheckCurrentWordNoSuggest(const PRUnichar *aSuggestedWord,
00289                                               PRBool *aIsMisspelled)
00290 {
00291   if (!mSpellChecker)
00292     return NS_ERROR_NOT_INITIALIZED;
00293 
00294   return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
00295                                   aIsMisspelled, nsnull);
00296 }
00297 
00298 NS_IMETHODIMP    
00299 nsEditorSpellCheck::ReplaceWord(const PRUnichar *aMisspelledWord,
00300                                 const PRUnichar *aReplaceWord,
00301                                 PRBool           allOccurrences)
00302 {
00303   if (!mSpellChecker)
00304     return NS_ERROR_NOT_INITIALIZED;
00305 
00306   return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
00307                                 nsDependentString(aReplaceWord), allOccurrences);
00308 }
00309 
00310 NS_IMETHODIMP    
00311 nsEditorSpellCheck::IgnoreWordAllOccurrences(const PRUnichar *aWord)
00312 {
00313   if (!mSpellChecker)
00314     return NS_ERROR_NOT_INITIALIZED;
00315 
00316   return mSpellChecker->IgnoreAll(nsDependentString(aWord));
00317 }
00318 
00319 NS_IMETHODIMP    
00320 nsEditorSpellCheck::GetPersonalDictionary()
00321 {
00322   if (!mSpellChecker)
00323     return NS_ERROR_NOT_INITIALIZED;
00324 
00325    // We can spell check with any editor type
00326   mDictionaryList.Clear();
00327   mDictionaryIndex = 0;
00328   return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
00329 }
00330 
00331 NS_IMETHODIMP    
00332 nsEditorSpellCheck::GetPersonalDictionaryWord(PRUnichar **aDictionaryWord)
00333 {
00334   nsAutoString word;
00335   if ( mDictionaryIndex < mDictionaryList.Count())
00336   {
00337     mDictionaryList.StringAt(mDictionaryIndex, word);
00338     mDictionaryIndex++;
00339   } else {
00340     // A blank string signals that there are no more strings
00341     word.Truncate();
00342   }
00343 
00344   *aDictionaryWord = ToNewUnicode(word);
00345   return NS_OK;
00346 }
00347 
00348 NS_IMETHODIMP    
00349 nsEditorSpellCheck::AddWordToDictionary(const PRUnichar *aWord)
00350 {
00351   if (!mSpellChecker)
00352     return NS_ERROR_NOT_INITIALIZED;
00353 
00354   return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
00355 }
00356 
00357 NS_IMETHODIMP    
00358 nsEditorSpellCheck::RemoveWordFromDictionary(const PRUnichar *aWord)
00359 {
00360   if (!mSpellChecker)
00361     return NS_ERROR_NOT_INITIALIZED;
00362 
00363   return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
00364 }
00365 
00366 NS_IMETHODIMP    
00367 nsEditorSpellCheck::GetDictionaryList(PRUnichar ***aDictionaryList, PRUint32 *aCount)
00368 {
00369   if (!mSpellChecker)
00370     return NS_ERROR_NOT_INITIALIZED;
00371 
00372   if (!aDictionaryList || !aCount)
00373     return NS_ERROR_NULL_POINTER;
00374 
00375   *aDictionaryList = 0;
00376   *aCount          = 0;
00377 
00378   nsStringArray dictList;
00379 
00380   nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
00381 
00382   if (NS_FAILED(rv))
00383     return rv;
00384 
00385   PRUnichar **tmpPtr = 0;
00386 
00387   if (dictList.Count() < 1)
00388   {
00389     // If there are no dictionaries, return an array containing
00390     // one element and a count of one.
00391 
00392     tmpPtr = (PRUnichar **)nsMemory::Alloc(sizeof(PRUnichar *));
00393 
00394     if (!tmpPtr)
00395       return NS_ERROR_OUT_OF_MEMORY;
00396 
00397     *tmpPtr          = 0;
00398     *aDictionaryList = tmpPtr;
00399     *aCount          = 0;
00400 
00401     return NS_OK;
00402   }
00403 
00404   tmpPtr = (PRUnichar **)nsMemory::Alloc(sizeof(PRUnichar *) * dictList.Count());
00405 
00406   if (!tmpPtr)
00407     return NS_ERROR_OUT_OF_MEMORY;
00408 
00409   *aDictionaryList = tmpPtr;
00410   *aCount          = dictList.Count();
00411 
00412   nsAutoString dictStr;
00413 
00414   PRUint32 i;
00415 
00416   for (i = 0; i < *aCount; i++)
00417   {
00418     dictList.StringAt(i, dictStr);
00419     tmpPtr[i] = ToNewUnicode(dictStr);
00420   }
00421 
00422   return rv;
00423 }
00424 
00425 NS_IMETHODIMP    
00426 nsEditorSpellCheck::GetCurrentDictionary(PRUnichar **aDictionary)
00427 {
00428   if (!mSpellChecker)
00429     return NS_ERROR_NOT_INITIALIZED;
00430 
00431   if (!aDictionary)
00432     return NS_ERROR_NULL_POINTER;
00433 
00434   *aDictionary = 0;
00435 
00436   nsAutoString dictStr;
00437   nsresult rv = mSpellChecker->GetCurrentDictionary(dictStr);
00438   NS_ENSURE_SUCCESS(rv, rv);
00439 
00440   *aDictionary = ToNewUnicode(dictStr);
00441 
00442   return rv;
00443 }
00444 
00445 NS_IMETHODIMP    
00446 nsEditorSpellCheck::SetCurrentDictionary(const PRUnichar *aDictionary)
00447 {
00448   if (!mSpellChecker)
00449     return NS_ERROR_NOT_INITIALIZED;
00450 
00451   if (!aDictionary)
00452     return NS_ERROR_NULL_POINTER;
00453 
00454   return mSpellChecker->SetCurrentDictionary(nsDependentString(aDictionary));
00455 }
00456 
00457 NS_IMETHODIMP    
00458 nsEditorSpellCheck::UninitSpellChecker()
00459 {
00460   if (!mSpellChecker)
00461     return NS_ERROR_NOT_INITIALIZED;
00462 
00463   // we preserve the last selected language, but ignore errors so we continue
00464   // to uninitialize
00465   nsresult rv = SaveDefaultDictionary();
00466   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "failed to set default dictionary");
00467 
00468   // Cleanup - kill the spell checker
00469   DeleteSuggestedWordList();
00470   mDictionaryList.Clear();
00471   mDictionaryIndex = 0;
00472   mSpellChecker = 0;
00473   return NS_OK;
00474 }
00475 
00476 // Save the last used dictionary to the user's preferences.
00477 NS_IMETHODIMP
00478 nsEditorSpellCheck::SaveDefaultDictionary()
00479 {
00480   nsresult rv;
00481   nsCOMPtr<nsIPrefBranch> prefBranch =
00482     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
00483 
00484   if (NS_SUCCEEDED(rv) && prefBranch)
00485   {
00486     PRUnichar *dictName = nsnull;
00487     rv = GetCurrentDictionary(&dictName);
00488 
00489     if (NS_SUCCEEDED(rv) && dictName && *dictName) {
00490       nsCOMPtr<nsISupportsString> prefString =
00491         do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
00492 
00493       if (NS_SUCCEEDED(rv) && prefString) {
00494         prefString->SetData(nsDependentString(dictName));
00495         rv = prefBranch->SetComplexValue("spellchecker.dictionary",
00496                                          NS_GET_IID(nsISupportsString),
00497                                          prefString);
00498       }
00499     }
00500     if (dictName)
00501       nsMemory::Free(dictName);
00502   }
00503   return rv;
00504 }
00505 
00506 
00507 /* void setFilter (in nsITextServicesFilter filter); */
00508 NS_IMETHODIMP 
00509 nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
00510 {
00511   mTxtSrvFilter = filter;
00512   return NS_OK;
00513 }
00514 
00515 nsresult    
00516 nsEditorSpellCheck::DeleteSuggestedWordList()
00517 {
00518   mSuggestedWordList.Clear();
00519   mSuggestedWordIndex = 0;
00520   return NS_OK;
00521 }