1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code..
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2002
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Kin Blas <kin@netscape.com>
24 : * Akkana Peck <akkana@netscape.com>
25 : * Charley Manske <cmanske@netscape.com>
26 : * Neil Deakin <neil@mozdevgroup.com>
27 : * Brett Wilson <brettw@gmail.com>
28 : *
29 : * Alternatively, the contents of this file may be used under the terms of
30 : * either the GNU General Public License Version 2 or later (the "GPL"), or
31 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 : * in which case the provisions of the GPL or the LGPL are applicable instead
33 : * of those above. If you wish to allow use of your version of this file only
34 : * under the terms of either the GPL or the LGPL, and not to allow others to
35 : * use your version of this file under the terms of the MPL, indicate your
36 : * decision by deleting the provisions above and replace them with the notice
37 : * and other provisions required by the GPL or the LGPL. If you do not delete
38 : * the provisions above, a recipient may use your version of this file under
39 : * the terms of any one of the MPL, the GPL or the LGPL.
40 : *
41 : * ***** END LICENSE BLOCK ***** */
42 :
43 : #include "nsEditorSpellCheck.h"
44 :
45 : #include "nsStyleUtil.h"
46 : #include "nsIContent.h"
47 : #include "nsIDOMElement.h"
48 : #include "nsITextServicesDocument.h"
49 : #include "nsISpellChecker.h"
50 : #include "nsISelection.h"
51 : #include "nsIDOMRange.h"
52 : #include "nsIEditor.h"
53 : #include "nsIHTMLEditor.h"
54 :
55 : #include "nsIComponentManager.h"
56 : #include "nsIContentPrefService.h"
57 : #include "nsServiceManagerUtils.h"
58 : #include "nsIChromeRegistry.h"
59 : #include "nsString.h"
60 : #include "nsReadableUtils.h"
61 : #include "nsITextServicesFilter.h"
62 : #include "nsUnicharUtils.h"
63 : #include "mozilla/Services.h"
64 : #include "mozilla/Preferences.h"
65 :
66 : using namespace mozilla;
67 :
68 : class UpdateDictionnaryHolder {
69 : private:
70 : nsEditorSpellCheck* mSpellCheck;
71 : public:
72 0 : UpdateDictionnaryHolder(nsEditorSpellCheck* esc): mSpellCheck(esc) {
73 0 : if (mSpellCheck) {
74 0 : mSpellCheck->BeginUpdateDictionary();
75 : }
76 0 : }
77 0 : ~UpdateDictionnaryHolder() {
78 0 : if (mSpellCheck) {
79 0 : mSpellCheck->EndUpdateDictionary();
80 : }
81 0 : }
82 : };
83 :
84 : #define CPS_PREF_NAME NS_LITERAL_STRING("spellcheck.lang")
85 :
86 0 : class LastDictionary MOZ_FINAL {
87 : public:
88 : /**
89 : * Store current dictionary for editor document url. Use content pref
90 : * service.
91 : */
92 : NS_IMETHOD StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary);
93 :
94 : /**
95 : * Get last stored current dictionary for editor document url.
96 : */
97 : NS_IMETHOD FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary);
98 :
99 : /**
100 : * Forget last current dictionary stored for editor document url.
101 : */
102 : NS_IMETHOD ClearCurrentDictionary(nsIEditor* aEditor);
103 :
104 : /**
105 : * get uri of editor's document.
106 : *
107 : */
108 : static nsresult GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI);
109 : };
110 :
111 : // static
112 : nsresult
113 0 : LastDictionary::GetDocumentURI(nsIEditor* aEditor, nsIURI * *aURI)
114 : {
115 0 : NS_ENSURE_ARG_POINTER(aEditor);
116 0 : NS_ENSURE_ARG_POINTER(aURI);
117 :
118 0 : nsCOMPtr<nsIDOMDocument> domDoc;
119 0 : aEditor->GetDocument(getter_AddRefs(domDoc));
120 0 : NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
121 :
122 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
123 0 : NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
124 :
125 0 : nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
126 0 : NS_ENSURE_TRUE(docUri, NS_ERROR_FAILURE);
127 :
128 0 : *aURI = docUri;
129 0 : NS_ADDREF(*aURI);
130 0 : return NS_OK;
131 : }
132 :
133 : NS_IMETHODIMP
134 0 : LastDictionary::FetchLastDictionary(nsIEditor* aEditor, nsAString& aDictionary)
135 : {
136 0 : NS_ENSURE_ARG_POINTER(aEditor);
137 :
138 : nsresult rv;
139 :
140 0 : nsCOMPtr<nsIURI> docUri;
141 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
142 0 : NS_ENSURE_SUCCESS(rv, rv);
143 :
144 : nsCOMPtr<nsIContentPrefService> contentPrefService =
145 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
146 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_AVAILABLE);
147 :
148 0 : nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
149 0 : NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
150 0 : uri->SetAsISupports(docUri);
151 :
152 : bool hasPref;
153 0 : if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
154 0 : nsCOMPtr<nsIVariant> pref;
155 0 : contentPrefService->GetPref(uri, CPS_PREF_NAME, nsnull, getter_AddRefs(pref));
156 0 : pref->GetAsAString(aDictionary);
157 : } else {
158 0 : aDictionary.Truncate();
159 : }
160 :
161 0 : return NS_OK;
162 : }
163 :
164 : NS_IMETHODIMP
165 0 : LastDictionary::StoreCurrentDictionary(nsIEditor* aEditor, const nsAString& aDictionary)
166 : {
167 0 : NS_ENSURE_ARG_POINTER(aEditor);
168 :
169 : nsresult rv;
170 :
171 0 : nsCOMPtr<nsIURI> docUri;
172 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
173 0 : NS_ENSURE_SUCCESS(rv, rv);
174 :
175 0 : nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
176 0 : NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
177 0 : uri->SetAsISupports(docUri);
178 :
179 0 : nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
180 0 : NS_ENSURE_TRUE(prefValue, NS_ERROR_OUT_OF_MEMORY);
181 0 : prefValue->SetAsAString(aDictionary);
182 :
183 : nsCOMPtr<nsIContentPrefService> contentPrefService =
184 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
185 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
186 :
187 0 : return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
188 : }
189 :
190 : NS_IMETHODIMP
191 0 : LastDictionary::ClearCurrentDictionary(nsIEditor* aEditor)
192 : {
193 0 : NS_ENSURE_ARG_POINTER(aEditor);
194 :
195 : nsresult rv;
196 :
197 0 : nsCOMPtr<nsIURI> docUri;
198 0 : rv = GetDocumentURI(aEditor, getter_AddRefs(docUri));
199 0 : NS_ENSURE_SUCCESS(rv, rv);
200 :
201 0 : nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
202 0 : NS_ENSURE_TRUE(uri, NS_ERROR_OUT_OF_MEMORY);
203 0 : uri->SetAsISupports(docUri);
204 :
205 : nsCOMPtr<nsIContentPrefService> contentPrefService =
206 0 : do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
207 0 : NS_ENSURE_TRUE(contentPrefService, NS_ERROR_NOT_INITIALIZED);
208 :
209 0 : return contentPrefService->RemovePref(uri, CPS_PREF_NAME);
210 : }
211 :
212 : LastDictionary* nsEditorSpellCheck::gDictionaryStore = nsnull;
213 :
214 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditorSpellCheck)
215 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditorSpellCheck)
216 :
217 0 : NS_INTERFACE_MAP_BEGIN(nsEditorSpellCheck)
218 0 : NS_INTERFACE_MAP_ENTRY(nsIEditorSpellCheck)
219 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditorSpellCheck)
220 0 : NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsEditorSpellCheck)
221 0 : NS_INTERFACE_MAP_END
222 :
223 1464 : NS_IMPL_CYCLE_COLLECTION_3(nsEditorSpellCheck,
224 : mEditor,
225 : mSpellChecker,
226 : mTxtSrvFilter)
227 :
228 0 : nsEditorSpellCheck::nsEditorSpellCheck()
229 : : mSuggestedWordIndex(0)
230 : , mDictionaryIndex(0)
231 : , mEditor(nsnull)
232 0 : , mUpdateDictionaryRunning(false)
233 : {
234 0 : }
235 :
236 0 : nsEditorSpellCheck::~nsEditorSpellCheck()
237 : {
238 : // Make sure we blow the spellchecker away, just in
239 : // case it hasn't been destroyed already.
240 0 : mSpellChecker = nsnull;
241 0 : }
242 :
243 : // The problem is that if the spell checker does not exist, we can not tell
244 : // which dictionaries are installed. This function works around the problem,
245 : // allowing callers to ask if we can spell check without actually doing so (and
246 : // enabling or disabling UI as necessary). This just creates a spellcheck
247 : // object if needed and asks it for the dictionary list.
248 : NS_IMETHODIMP
249 0 : nsEditorSpellCheck::CanSpellCheck(bool* _retval)
250 : {
251 : nsresult rv;
252 0 : nsCOMPtr<nsISpellChecker> spellChecker;
253 0 : if (! mSpellChecker) {
254 0 : spellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
255 0 : NS_ENSURE_SUCCESS(rv, rv);
256 : } else {
257 0 : spellChecker = mSpellChecker;
258 : }
259 0 : nsTArray<nsString> dictList;
260 0 : rv = spellChecker->GetDictionaryList(&dictList);
261 0 : NS_ENSURE_SUCCESS(rv, rv);
262 :
263 0 : *_retval = (dictList.Length() > 0);
264 0 : return NS_OK;
265 : }
266 :
267 : NS_IMETHODIMP
268 0 : nsEditorSpellCheck::InitSpellChecker(nsIEditor* aEditor, bool aEnableSelectionChecking)
269 : {
270 0 : NS_ENSURE_TRUE(aEditor, NS_ERROR_NULL_POINTER);
271 0 : mEditor = aEditor;
272 :
273 : nsresult rv;
274 :
275 0 : if (!gDictionaryStore) {
276 0 : gDictionaryStore = new LastDictionary();
277 : }
278 :
279 :
280 : // We can spell check with any editor type
281 : nsCOMPtr<nsITextServicesDocument>tsDoc =
282 0 : do_CreateInstance("@mozilla.org/textservices/textservicesdocument;1", &rv);
283 0 : NS_ENSURE_SUCCESS(rv, rv);
284 :
285 0 : NS_ENSURE_TRUE(tsDoc, NS_ERROR_NULL_POINTER);
286 :
287 0 : tsDoc->SetFilter(mTxtSrvFilter);
288 :
289 : // Pass the editor to the text services document
290 0 : rv = tsDoc->InitWithEditor(aEditor);
291 0 : NS_ENSURE_SUCCESS(rv, rv);
292 :
293 0 : if (aEnableSelectionChecking) {
294 : // Find out if the section is collapsed or not.
295 : // If it isn't, we want to spellcheck just the selection.
296 :
297 0 : nsCOMPtr<nsISelection> selection;
298 :
299 0 : rv = aEditor->GetSelection(getter_AddRefs(selection));
300 0 : NS_ENSURE_SUCCESS(rv, rv);
301 0 : NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
302 :
303 0 : PRInt32 count = 0;
304 :
305 0 : rv = selection->GetRangeCount(&count);
306 0 : NS_ENSURE_SUCCESS(rv, rv);
307 :
308 0 : if (count > 0) {
309 0 : nsCOMPtr<nsIDOMRange> range;
310 :
311 0 : rv = selection->GetRangeAt(0, getter_AddRefs(range));
312 0 : NS_ENSURE_SUCCESS(rv, rv);
313 :
314 0 : bool collapsed = false;
315 0 : rv = range->GetCollapsed(&collapsed);
316 0 : NS_ENSURE_SUCCESS(rv, rv);
317 :
318 0 : if (!collapsed) {
319 : // We don't want to touch the range in the selection,
320 : // so create a new copy of it.
321 :
322 0 : nsCOMPtr<nsIDOMRange> rangeBounds;
323 0 : rv = range->CloneRange(getter_AddRefs(rangeBounds));
324 0 : NS_ENSURE_SUCCESS(rv, rv);
325 0 : NS_ENSURE_TRUE(rangeBounds, NS_ERROR_FAILURE);
326 :
327 : // Make sure the new range spans complete words.
328 :
329 0 : rv = tsDoc->ExpandRangeToWordBoundaries(rangeBounds);
330 0 : NS_ENSURE_SUCCESS(rv, rv);
331 :
332 : // Now tell the text services that you only want
333 : // to iterate over the text in this range.
334 :
335 0 : rv = tsDoc->SetExtent(rangeBounds);
336 0 : NS_ENSURE_SUCCESS(rv, rv);
337 : }
338 : }
339 : }
340 :
341 0 : mSpellChecker = do_CreateInstance(NS_SPELLCHECKER_CONTRACTID, &rv);
342 0 : NS_ENSURE_SUCCESS(rv, rv);
343 :
344 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NULL_POINTER);
345 :
346 0 : rv = mSpellChecker->SetDocument(tsDoc, true);
347 0 : NS_ENSURE_SUCCESS(rv, rv);
348 :
349 : // do not fail if UpdateCurrentDictionary fails because this method may
350 : // succeed later.
351 0 : UpdateCurrentDictionary();
352 0 : return NS_OK;
353 : }
354 :
355 : NS_IMETHODIMP
356 0 : nsEditorSpellCheck::GetNextMisspelledWord(PRUnichar **aNextMisspelledWord)
357 : {
358 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
359 :
360 0 : nsAutoString nextMisspelledWord;
361 :
362 0 : DeleteSuggestedWordList();
363 : // Beware! This may flush notifications via synchronous
364 : // ScrollSelectionIntoView.
365 0 : nsresult rv = mSpellChecker->NextMisspelledWord(nextMisspelledWord,
366 0 : &mSuggestedWordList);
367 :
368 0 : *aNextMisspelledWord = ToNewUnicode(nextMisspelledWord);
369 0 : return rv;
370 : }
371 :
372 : NS_IMETHODIMP
373 0 : nsEditorSpellCheck::GetSuggestedWord(PRUnichar **aSuggestedWord)
374 : {
375 0 : nsAutoString word;
376 0 : if ( mSuggestedWordIndex < PRInt32(mSuggestedWordList.Length()))
377 : {
378 0 : *aSuggestedWord = ToNewUnicode(mSuggestedWordList[mSuggestedWordIndex]);
379 0 : mSuggestedWordIndex++;
380 : } else {
381 : // A blank string signals that there are no more strings
382 0 : *aSuggestedWord = ToNewUnicode(EmptyString());
383 : }
384 0 : return NS_OK;
385 : }
386 :
387 : NS_IMETHODIMP
388 0 : nsEditorSpellCheck::CheckCurrentWord(const PRUnichar *aSuggestedWord,
389 : bool *aIsMisspelled)
390 : {
391 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
392 :
393 0 : DeleteSuggestedWordList();
394 0 : return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
395 0 : aIsMisspelled, &mSuggestedWordList);
396 : }
397 :
398 : NS_IMETHODIMP
399 0 : nsEditorSpellCheck::CheckCurrentWordNoSuggest(const PRUnichar *aSuggestedWord,
400 : bool *aIsMisspelled)
401 : {
402 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
403 :
404 0 : return mSpellChecker->CheckWord(nsDependentString(aSuggestedWord),
405 0 : aIsMisspelled, nsnull);
406 : }
407 :
408 : NS_IMETHODIMP
409 0 : nsEditorSpellCheck::ReplaceWord(const PRUnichar *aMisspelledWord,
410 : const PRUnichar *aReplaceWord,
411 : bool allOccurrences)
412 : {
413 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
414 :
415 0 : return mSpellChecker->Replace(nsDependentString(aMisspelledWord),
416 0 : nsDependentString(aReplaceWord), allOccurrences);
417 : }
418 :
419 : NS_IMETHODIMP
420 0 : nsEditorSpellCheck::IgnoreWordAllOccurrences(const PRUnichar *aWord)
421 : {
422 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
423 :
424 0 : return mSpellChecker->IgnoreAll(nsDependentString(aWord));
425 : }
426 :
427 : NS_IMETHODIMP
428 0 : nsEditorSpellCheck::GetPersonalDictionary()
429 : {
430 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
431 :
432 : // We can spell check with any editor type
433 0 : mDictionaryList.Clear();
434 0 : mDictionaryIndex = 0;
435 0 : return mSpellChecker->GetPersonalDictionary(&mDictionaryList);
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : nsEditorSpellCheck::GetPersonalDictionaryWord(PRUnichar **aDictionaryWord)
440 : {
441 0 : if ( mDictionaryIndex < PRInt32( mDictionaryList.Length()))
442 : {
443 0 : *aDictionaryWord = ToNewUnicode(mDictionaryList[mDictionaryIndex]);
444 0 : mDictionaryIndex++;
445 : } else {
446 : // A blank string signals that there are no more strings
447 0 : *aDictionaryWord = ToNewUnicode(EmptyString());
448 : }
449 :
450 0 : return NS_OK;
451 : }
452 :
453 : NS_IMETHODIMP
454 0 : nsEditorSpellCheck::AddWordToDictionary(const PRUnichar *aWord)
455 : {
456 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
457 :
458 0 : return mSpellChecker->AddWordToPersonalDictionary(nsDependentString(aWord));
459 : }
460 :
461 : NS_IMETHODIMP
462 0 : nsEditorSpellCheck::RemoveWordFromDictionary(const PRUnichar *aWord)
463 : {
464 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
465 :
466 0 : return mSpellChecker->RemoveWordFromPersonalDictionary(nsDependentString(aWord));
467 : }
468 :
469 : NS_IMETHODIMP
470 0 : nsEditorSpellCheck::GetDictionaryList(PRUnichar ***aDictionaryList, PRUint32 *aCount)
471 : {
472 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
473 :
474 0 : NS_ENSURE_TRUE(aDictionaryList && aCount, NS_ERROR_NULL_POINTER);
475 :
476 0 : *aDictionaryList = 0;
477 0 : *aCount = 0;
478 :
479 0 : nsTArray<nsString> dictList;
480 :
481 0 : nsresult rv = mSpellChecker->GetDictionaryList(&dictList);
482 :
483 0 : NS_ENSURE_SUCCESS(rv, rv);
484 :
485 0 : PRUnichar **tmpPtr = 0;
486 :
487 0 : if (dictList.Length() < 1)
488 : {
489 : // If there are no dictionaries, return an array containing
490 : // one element and a count of one.
491 :
492 0 : tmpPtr = (PRUnichar **)nsMemory::Alloc(sizeof(PRUnichar *));
493 :
494 0 : NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
495 :
496 0 : *tmpPtr = 0;
497 0 : *aDictionaryList = tmpPtr;
498 0 : *aCount = 0;
499 :
500 0 : return NS_OK;
501 : }
502 :
503 0 : tmpPtr = (PRUnichar **)nsMemory::Alloc(sizeof(PRUnichar *) * dictList.Length());
504 :
505 0 : NS_ENSURE_TRUE(tmpPtr, NS_ERROR_OUT_OF_MEMORY);
506 :
507 0 : *aDictionaryList = tmpPtr;
508 0 : *aCount = dictList.Length();
509 :
510 : PRUint32 i;
511 :
512 0 : for (i = 0; i < *aCount; i++)
513 : {
514 0 : tmpPtr[i] = ToNewUnicode(dictList[i]);
515 : }
516 :
517 0 : return rv;
518 : }
519 :
520 : NS_IMETHODIMP
521 0 : nsEditorSpellCheck::GetCurrentDictionary(nsAString& aDictionary)
522 : {
523 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
524 :
525 0 : return mSpellChecker->GetCurrentDictionary(aDictionary);
526 : }
527 :
528 : NS_IMETHODIMP
529 0 : nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
530 : {
531 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
532 :
533 0 : if (!mUpdateDictionaryRunning) {
534 :
535 0 : nsDefaultStringComparator comparator;
536 0 : nsAutoString langCode;
537 0 : PRInt32 dashIdx = aDictionary.FindChar('-');
538 0 : if (dashIdx != -1) {
539 0 : langCode.Assign(Substring(aDictionary, 0, dashIdx));
540 : } else {
541 0 : langCode.Assign(aDictionary);
542 : }
543 :
544 0 : if (mPreferredLang.IsEmpty() || !nsStyleUtil::DashMatchCompare(mPreferredLang, langCode, comparator)) {
545 : // When user sets dictionary manually, we store this value associated
546 : // with editor url.
547 0 : gDictionaryStore->StoreCurrentDictionary(mEditor, aDictionary);
548 : } else {
549 : // If user sets a dictionary matching (even partially), lang defined by
550 : // document, we consider content pref has been canceled, and we clear it.
551 0 : gDictionaryStore->ClearCurrentDictionary(mEditor);
552 : }
553 :
554 : // Also store it in as a preference. It will be used as a default value
555 : // when everything else fails.
556 0 : Preferences::SetString("spellchecker.dictionary", aDictionary);
557 : }
558 0 : return mSpellChecker->SetCurrentDictionary(aDictionary);
559 : }
560 :
561 : NS_IMETHODIMP
562 0 : nsEditorSpellCheck::CheckCurrentDictionary()
563 : {
564 0 : mSpellChecker->CheckCurrentDictionary();
565 :
566 : // Check if our current dictionary is still available.
567 0 : nsAutoString currentDictionary;
568 0 : nsresult rv = GetCurrentDictionary(currentDictionary);
569 0 : if (NS_SUCCEEDED(rv) && !currentDictionary.IsEmpty()) {
570 0 : return NS_OK;
571 : }
572 :
573 : // If our preferred current dictionary has gone, pick another one.
574 0 : nsTArray<nsString> dictList;
575 0 : rv = mSpellChecker->GetDictionaryList(&dictList);
576 0 : NS_ENSURE_SUCCESS(rv, rv);
577 :
578 0 : if (dictList.Length() > 0) {
579 0 : rv = SetCurrentDictionary(dictList[0]);
580 0 : NS_ENSURE_SUCCESS(rv, rv);
581 : }
582 :
583 0 : return NS_OK;
584 : }
585 :
586 : NS_IMETHODIMP
587 0 : nsEditorSpellCheck::UninitSpellChecker()
588 : {
589 0 : NS_ENSURE_TRUE(mSpellChecker, NS_ERROR_NOT_INITIALIZED);
590 :
591 : // Cleanup - kill the spell checker
592 0 : DeleteSuggestedWordList();
593 0 : mDictionaryList.Clear();
594 0 : mDictionaryIndex = 0;
595 0 : mSpellChecker = 0;
596 0 : return NS_OK;
597 : }
598 :
599 :
600 : /* void setFilter (in nsITextServicesFilter filter); */
601 : NS_IMETHODIMP
602 0 : nsEditorSpellCheck::SetFilter(nsITextServicesFilter *filter)
603 : {
604 0 : mTxtSrvFilter = filter;
605 0 : return NS_OK;
606 : }
607 :
608 : nsresult
609 0 : nsEditorSpellCheck::DeleteSuggestedWordList()
610 : {
611 0 : mSuggestedWordList.Clear();
612 0 : mSuggestedWordIndex = 0;
613 0 : return NS_OK;
614 : }
615 :
616 : NS_IMETHODIMP
617 0 : nsEditorSpellCheck::UpdateCurrentDictionary()
618 : {
619 : nsresult rv;
620 :
621 0 : UpdateDictionnaryHolder holder(this);
622 :
623 : // Get language with html5 algorithm
624 0 : nsCOMPtr<nsIContent> rootContent;
625 0 : nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(mEditor);
626 0 : if (htmlEditor) {
627 0 : rootContent = htmlEditor->GetActiveEditingHost();
628 : } else {
629 0 : nsCOMPtr<nsIDOMElement> rootElement;
630 0 : rv = mEditor->GetRootElement(getter_AddRefs(rootElement));
631 0 : NS_ENSURE_SUCCESS(rv, rv);
632 0 : rootContent = do_QueryInterface(rootElement);
633 : }
634 0 : NS_ENSURE_TRUE(rootContent, NS_ERROR_FAILURE);
635 :
636 0 : mPreferredLang.Truncate();
637 0 : rootContent->GetLang(mPreferredLang);
638 :
639 : // Tell the spellchecker what dictionary to use:
640 :
641 : // First try to get dictionary from content prefs. If we have one, do not got
642 : // further. Use this exact dictionary.
643 0 : nsAutoString dictName;
644 0 : rv = gDictionaryStore->FetchLastDictionary(mEditor, dictName);
645 0 : if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
646 0 : if (NS_FAILED(SetCurrentDictionary(dictName))) {
647 : // may be dictionary was uninstalled ?
648 0 : gDictionaryStore->ClearCurrentDictionary(mEditor);
649 : }
650 0 : return NS_OK;
651 : }
652 :
653 0 : if (mPreferredLang.IsEmpty()) {
654 0 : nsCOMPtr<nsIDocument> doc = rootContent->GetCurrentDoc();
655 0 : NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
656 0 : doc->GetContentLanguage(mPreferredLang);
657 : }
658 :
659 : // Then, try to use language computed from element
660 0 : if (!mPreferredLang.IsEmpty()) {
661 0 : dictName.Assign(mPreferredLang);
662 : }
663 :
664 : // otherwise, get language from preferences
665 0 : nsAutoString preferedDict(Preferences::GetLocalizedString("spellchecker.dictionary"));
666 0 : if (dictName.IsEmpty()) {
667 0 : dictName.Assign(preferedDict);
668 : }
669 :
670 0 : if (dictName.IsEmpty())
671 : {
672 : // Prefs didn't give us a dictionary name, so just get the current
673 : // locale and use that as the default dictionary name!
674 :
675 : nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
676 0 : mozilla::services::GetXULChromeRegistryService();
677 :
678 0 : if (packageRegistry) {
679 0 : nsCAutoString utf8DictName;
680 0 : rv = packageRegistry->GetSelectedLocale(NS_LITERAL_CSTRING("global"),
681 0 : utf8DictName);
682 0 : AppendUTF8toUTF16(utf8DictName, dictName);
683 : }
684 : }
685 :
686 0 : if (NS_SUCCEEDED(rv) && !dictName.IsEmpty()) {
687 0 : rv = SetCurrentDictionary(dictName);
688 0 : if (NS_FAILED(rv)) {
689 : // required dictionary was not available. Try to get a dictionary
690 : // matching at least language part of dictName:
691 :
692 0 : nsAutoString langCode;
693 0 : PRInt32 dashIdx = dictName.FindChar('-');
694 0 : if (dashIdx != -1) {
695 0 : langCode.Assign(Substring(dictName, 0, dashIdx));
696 : } else {
697 0 : langCode.Assign(dictName);
698 : }
699 :
700 0 : nsDefaultStringComparator comparator;
701 :
702 : // try dictionary.spellchecker preference if it starts with langCode (and
703 : // if we haven't tried it already)
704 0 : if (!preferedDict.IsEmpty() && !dictName.Equals(preferedDict) &&
705 0 : nsStyleUtil::DashMatchCompare(preferedDict, langCode, comparator)) {
706 0 : rv = SetCurrentDictionary(preferedDict);
707 : }
708 :
709 : // Otherwise, try langCode (if we haven't tried it already)
710 0 : if (NS_FAILED(rv)) {
711 0 : if (!dictName.Equals(langCode) && !preferedDict.Equals(langCode)) {
712 0 : rv = SetCurrentDictionary(langCode);
713 : }
714 : }
715 :
716 : // Otherwise, try any available dictionary aa-XX
717 0 : if (NS_FAILED(rv)) {
718 : // loop over avaible dictionaries; if we find one with required
719 : // language, use it
720 0 : nsTArray<nsString> dictList;
721 0 : rv = mSpellChecker->GetDictionaryList(&dictList);
722 0 : NS_ENSURE_SUCCESS(rv, rv);
723 0 : PRInt32 i, count = dictList.Length();
724 0 : for (i = 0; i < count; i++) {
725 0 : nsAutoString dictStr(dictList.ElementAt(i));
726 :
727 0 : if (dictStr.Equals(dictName) ||
728 0 : dictStr.Equals(preferedDict) ||
729 0 : dictStr.Equals(langCode)) {
730 : // We have already tried it
731 0 : continue;
732 : }
733 :
734 0 : if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) &&
735 0 : NS_SUCCEEDED(SetCurrentDictionary(dictStr))) {
736 : break;
737 : }
738 : }
739 : }
740 : }
741 : }
742 :
743 : // If we have not set dictionary, and the editable element doesn't have a
744 : // lang attribute, we try to get a dictionary. First try, en-US. If it does
745 : // not work, pick the first one.
746 0 : if (mPreferredLang.IsEmpty()) {
747 0 : nsAutoString currentDictionary;
748 0 : rv = GetCurrentDictionary(currentDictionary);
749 0 : if (NS_FAILED(rv) || currentDictionary.IsEmpty()) {
750 0 : rv = SetCurrentDictionary(NS_LITERAL_STRING("en-US"));
751 0 : if (NS_FAILED(rv)) {
752 0 : nsTArray<nsString> dictList;
753 0 : rv = mSpellChecker->GetDictionaryList(&dictList);
754 0 : if (NS_SUCCEEDED(rv) && dictList.Length() > 0) {
755 0 : SetCurrentDictionary(dictList[0]);
756 : }
757 : }
758 : }
759 : }
760 :
761 : // If an error was thrown while setting the dictionary, just
762 : // fail silently so that the spellchecker dialog is allowed to come
763 : // up. The user can manually reset the language to their choice on
764 : // the dialog if it is wrong.
765 :
766 0 : DeleteSuggestedWordList();
767 :
768 0 : return NS_OK;
769 : }
770 :
771 : void
772 1403 : nsEditorSpellCheck::ShutDown() {
773 1403 : delete gDictionaryStore;
774 5795 : }
|