xref: /trunk/main/lingucomponent/source/spellcheck/macosxspell/macspellimp.cxx (revision 45c5a00162811bd03b87fcb4fe9996782f2b59ec)
1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_mac.hxx"
26 #include <com/sun/star/uno/Reference.h>
27 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
28 
29 #include <com/sun/star/linguistic2/SpellFailure.hpp>
30 #include <cppuhelper/factory.hxx>   // helper for factories
31 #include <com/sun/star/registry/XRegistryKey.hpp>
32 #include <tools/debug.hxx>
33 #include <unotools/processfactory.hxx>
34 #include <osl/mutex.hxx>
35 
36 #include <macspellimp.hxx>
37 
38 #include <linguistic/spelldta.hxx>
39 #include <unotools/pathoptions.hxx>
40 #include <unotools/useroptions.hxx>
41 #include <osl/file.hxx>
42 #include <rtl/ustrbuf.hxx>
43 
44 
45 using namespace utl;
46 using namespace osl;
47 using namespace rtl;
48 using namespace com::sun::star;
49 using namespace com::sun::star::beans;
50 using namespace com::sun::star::lang;
51 using namespace com::sun::star::uno;
52 using namespace com::sun::star::linguistic2;
53 using namespace linguistic;
54 
55 MacSpellChecker::MacSpellChecker() :
56     aEvtListeners   ( GetLinguMutex() )
57 {
58     aDEncs = NULL;
59     aDLocs = NULL;
60     aDNames = NULL;
61     bDisposing = sal_False;
62     pPropHelper = NULL;
63     numdict = 0;
64     NSApplicationLoad();
65     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
66     macSpell = [NSSpellChecker sharedSpellChecker];
67     macTag = [NSSpellChecker uniqueSpellDocumentTag];
68     [pool release];
69 }
70 
71 
72 MacSpellChecker::~MacSpellChecker()
73 {
74   numdict = 0;
75   if (aDEncs) delete[] aDEncs;
76   aDEncs = NULL;
77   if (aDLocs) delete[] aDLocs;
78   aDLocs = NULL;
79   if (aDNames) delete[] aDNames;
80   aDNames = NULL;
81   if (pPropHelper)
82      pPropHelper->RemoveAsPropListener();
83 }
84 
85 
86 PropertyHelper_Spell & MacSpellChecker::GetPropHelper_Impl()
87 {
88     if (!pPropHelper)
89     {
90         Reference< XPropertySet >   xPropSet( GetLinguProperties(), UNO_QUERY );
91 
92         pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
93         xPropHelper = pPropHelper;
94         pPropHelper->AddAsPropListener();   //! after a reference is established
95     }
96     return *pPropHelper;
97 }
98 
99 
100 Sequence< Locale > SAL_CALL MacSpellChecker::getLocales()
101 {
102     MutexGuard  aGuard( GetLinguMutex() );
103 
104         // this routine should return the locales supported by the installed
105         // dictionaries.  So here we need to parse both the user edited
106         // dictionary list and the shared dictionary list
107         // to see what dictionaries the admin/user has installed
108 
109         int numusr;          // number of user dictionary entries
110         int numshr;          // number of shared dictionary entries
111         // dictentry * spdict;  // shared dict entry pointer
112         // dictentry * updict;  // user dict entry pointer
113         SvtPathOptions aPathOpt;
114         rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
115 
116         std::vector<NSString*> postspdict;
117         std::vector<NSString*> postupdict;
118 
119     if (!numdict) {
120 
121         // invoke a dictionary manager to get the user dictionary list
122         // TODO How on Mac OS X?
123 
124         // invoke a second  dictionary manager to get the shared dictionary list
125         NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
126 
127         //Test for existence of the dictionaries
128         for (unsigned int i = 0; i < [aLocales count]; i++)
129         {
130             NSString* pLangStr = (NSString*)[aLocales objectAtIndex:i];
131             if( [macSpell setLanguage:pLangStr ] )
132             {
133                 postspdict.push_back( pLangStr );
134             }
135         }
136 
137         numusr = postupdict.size();
138         numshr = postspdict.size();
139 
140         // we really should merge these and remove duplicates but since
141         // users can name their dictionaries anything they want it would
142         // be impossible to know if a real duplication exists unless we
143         // add some unique key to each myspell dictionary
144         numdict = numshr + numusr;
145 
146         if (numdict) {
147             aDLocs = new Locale [numdict];
148             aDEncs  = new rtl_TextEncoding [numdict];
149             aDNames = new OUString [numdict];
150             aSuppLocales.realloc(numdict);
151             Locale * pLocale = aSuppLocales.getArray();
152             int numlocs = 0;
153             int newloc;
154             int i,j;
155             int k = 0;
156 
157             //first add the user dictionaries
158             //TODO for MAC?
159 
160             // now add the shared dictionaries
161             for (i = 0; i < numshr; i++) {
162                 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
163                 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
164                 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
165                 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
166                 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
167                 Locale nLoc( lang, country, OUString() );
168                 newloc = 1;
169                 //eliminate duplicates (is this needed for MacOS?)
170                 for (j = 0; j < numlocs; j++) {
171                     if (nLoc == pLocale[j]) newloc = 0;
172                 }
173                 if (newloc) {
174                     pLocale[numlocs] = nLoc;
175                     numlocs++;
176                 }
177                 aDLocs[k] = nLoc;
178                 //pointer to Hunspell dictionary - not needed for MAC
179                 //aDicts[k] = NULL;
180                 aDEncs[k] = 0;
181                 // Dictionary file names not valid for Mac Spell
182                 //aDNames[k] = aPathOpt.GetLinguisticPath() + A2OU("/ooo/") + A2OU(postspdict[i]->filename);
183                 k++;
184             }
185 
186             aSuppLocales.realloc(numlocs);
187 
188         } else {
189             /* no dictionary.lst found so register no dictionaries */
190             numdict = 0;
191             //aDicts = NULL;
192                 aDEncs  = NULL;
193                 aDLocs = NULL;
194                 aDNames = NULL;
195                 aSuppLocales.realloc(0);
196             }
197 
198             /* de-allocation of memory is handled inside the DictMgr */
199             // updict = NULL;
200             // spdict = NULL;
201 
202         }
203 
204     return aSuppLocales;
205 }
206 
207 
208 
209 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
210 {
211     MutexGuard  aGuard( GetLinguMutex() );
212 
213     sal_Bool bRes = sal_False;
214     if (!aSuppLocales.getLength())
215         getLocales();
216 
217     sal_Int32 nLen = aSuppLocales.getLength();
218     for (sal_Int32 i = 0;  i < nLen;  ++i)
219     {
220         const Locale *pLocale = aSuppLocales.getConstArray();
221         if (rLocale == pLocale[i])
222         {
223             bRes = sal_True;
224             break;
225         }
226     }
227     return bRes;
228 }
229 
230 
231 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
232 {
233     rtl_TextEncoding aEnc;
234 
235     // initialize a myspell object for each dictionary once
236         // (note: mutex is held higher up in isValid)
237 
238 
239     sal_Int16 nRes = -1;
240 
241         // first handle smart quotes both single and double
242     OUStringBuffer rBuf(rWord);
243         sal_Int32 n = rBuf.getLength();
244         sal_Unicode c;
245     for (sal_Int32 ix=0; ix < n; ix++) {
246         c = rBuf.charAt(ix);
247             if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
248             if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
249         }
250         OUString nWord(rBuf.makeStringAndClear());
251 
252     if (n)
253     {
254         aEnc = 0;
255         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
256         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
257         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
258         if(rLocale.Country.getLength()>0)
259         {
260             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
261             NSString* aTag = @"_";
262             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
263             [aLang autorelease];
264             aLang = [aLang  stringByAppendingString:aTaggedCountry];
265         }
266 
267         NSInteger aCount;
268         NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
269         int rVal = 0;
270         if(range.length>0)
271         {
272             rVal = -1;
273         }
274         else
275         {
276             rVal = 1;
277         }
278         [pool release];
279         if (rVal != 1)
280         {
281             nRes = SpellFailure::SPELLING_ERROR;
282         } else {
283             return -1;
284         }
285     }
286     return nRes;
287 }
288 
289 
290 
291 sal_Bool SAL_CALL
292     MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
293             const PropertyValues& rProperties )
294 {
295     MutexGuard  aGuard( GetLinguMutex() );
296 
297     if (rLocale == Locale()  ||  !rWord.getLength())
298         return sal_True;
299 
300     if (!hasLocale( rLocale ))
301 #ifdef LINGU_EXCEPTIONS
302         throw( IllegalArgumentException() );
303 #else
304         return sal_True;
305 #endif
306 
307     // Get property values to be used.
308     // These are be the default values set in the SN_LINGU_PROPERTIES
309     // PropertySet which are overridden by the supplied ones from the
310     // last argument.
311     // You'll probably like to use a simpler solution than the provided
312     // one using the PropertyHelper_Spell.
313 
314     PropertyHelper_Spell &rHelper = GetPropHelper();
315     rHelper.SetTmpPropVals( rProperties );
316 
317     sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
318     if (nFailure != -1)
319     {
320         sal_Int16 nLang = LocaleToLanguage( rLocale );
321         // postprocess result for errors that should be ignored
322         if (   (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang ))
323             || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
324             || (!rHelper.IsSpellCapitalization()
325                 &&  nFailure == SpellFailure::CAPTION_ERROR)
326         )
327             nFailure = -1;
328     }
329 
330     return (nFailure == -1);
331 }
332 
333 
334 Reference< XSpellAlternatives >
335     MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
336 {
337     // Retrieves the return values for the 'spell' function call in case
338     // of a misspelled word.
339     // Especially it may give a list of suggested (correct) words:
340 
341     Reference< XSpellAlternatives > xRes;
342         // note: mutex is held by higher up by spell which covers both
343 
344     sal_Int16 nLang = LocaleToLanguage( rLocale );
345     int count;
346     Sequence< OUString > aStr( 0 );
347 
348         // first handle smart quotes (single and double)
349     OUStringBuffer rBuf(rWord);
350         sal_Int32 n = rBuf.getLength();
351         sal_Unicode c;
352     for (sal_Int32 ix=0; ix < n; ix++) {
353          c = rBuf.charAt(ix);
354              if ((c == 0x201C) || (c == 0x201D)) rBuf.setCharAt(ix,(sal_Unicode)0x0022);
355              if ((c == 0x2018) || (c == 0x2019)) rBuf.setCharAt(ix,(sal_Unicode)0x0027);
356         }
357         OUString nWord(rBuf.makeStringAndClear());
358 
359     if (n)
360     {
361         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
362         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
363         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
364         if(rLocale.Country.getLength()>0)
365         {
366             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
367             NSString* aTag = @"_";
368             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
369             [aLang autorelease];
370             aLang = [aLang  stringByAppendingString:aTaggedCountry];
371         }
372         [macSpell setLanguage:aLang];
373         NSArray *guesses = [macSpell guessesForWord:aNSStr];
374         count = [guesses count];
375         if (count)
376         {
377            aStr.realloc( count );
378            OUString *pStr = aStr.getArray();
379                for (int ii=0; ii < count; ii++)
380                {
381                   // if needed add: if (suglst[ii] == NULL) continue;
382                   NSString* guess = [guesses objectAtIndex:ii];
383                   OUString cvtwrd((const sal_Unicode*)[guess cStringUsingEncoding:NSUnicodeStringEncoding], (sal_Int32)[guess length]);
384                   pStr[ii] = cvtwrd;
385                }
386         }
387        [pool release];
388     }
389 
390             // now return an empty alternative for no suggestions or the list of alternatives if some found
391         SpellAlternatives *pAlt = new SpellAlternatives;
392             String aTmp(rWord);
393         pAlt->SetWordLanguage( aTmp, nLang );
394         pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
395         pAlt->SetAlternatives( aStr );
396         xRes = pAlt;
397         return xRes;
398 
399 }
400 
401 
402 
403 
404 Reference< XSpellAlternatives > SAL_CALL
405     MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
406             const PropertyValues& rProperties )
407 {
408     MutexGuard  aGuard( GetLinguMutex() );
409 
410     if (rLocale == Locale()  ||  !rWord.getLength())
411         return NULL;
412 
413     if (!hasLocale( rLocale ))
414 #ifdef LINGU_EXCEPTIONS
415         throw( IllegalArgumentException() );
416 #else
417         return NULL;
418 #endif
419 
420     Reference< XSpellAlternatives > xAlt;
421     if (!isValid( rWord, rLocale, rProperties ))
422     {
423         xAlt =  GetProposals( rWord, rLocale );
424     }
425     return xAlt;
426 }
427 
428 
429 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
430             const Reference< XMultiServiceFactory > & /*rSMgr*/ )
431 {
432 
433     Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
434     return xService;
435 }
436 
437 
438 sal_Bool SAL_CALL
439     MacSpellChecker::addLinguServiceEventListener(
440             const Reference< XLinguServiceEventListener >& rxLstnr )
441 {
442     MutexGuard  aGuard( GetLinguMutex() );
443 
444     sal_Bool bRes = sal_False;
445     if (!bDisposing && rxLstnr.is())
446     {
447         bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
448     }
449     return bRes;
450 }
451 
452 
453 sal_Bool SAL_CALL
454     MacSpellChecker::removeLinguServiceEventListener(
455             const Reference< XLinguServiceEventListener >& rxLstnr )
456 {
457     MutexGuard  aGuard( GetLinguMutex() );
458 
459     sal_Bool bRes = sal_False;
460     if (!bDisposing && rxLstnr.is())
461     {
462         DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
463         bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
464     }
465     return bRes;
466 }
467 
468 
469 OUString SAL_CALL
470     MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
471 {
472     MutexGuard  aGuard( GetLinguMutex() );
473     return A2OU( "Mac OS X Spell Checker" );
474 }
475 
476 
477 void SAL_CALL
478     MacSpellChecker::initialize( const Sequence< Any >& rArguments )
479 {
480     MutexGuard  aGuard( GetLinguMutex() );
481 
482     if (!pPropHelper)
483     {
484         sal_Int32 nLen = rArguments.getLength();
485         if (2 == nLen)
486         {
487             Reference< XPropertySet >   xPropSet;
488             rArguments.getConstArray()[0] >>= xPropSet;
489             //rArguments.getConstArray()[1] >>= xDicList;
490 
491             //! Pointer allows for access of the non-UNO functions.
492             //! And the reference to the UNO-functions while increasing
493             //! the ref-count and will implicitly free the memory
494             //! when the object is not longer used.
495             pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
496             xPropHelper = pPropHelper;
497             pPropHelper->AddAsPropListener();   //! after a reference is established
498         }
499         else
500             DBG_ERROR( "wrong number of arguments in sequence" );
501 
502     }
503 }
504 
505 
506 void SAL_CALL
507     MacSpellChecker::dispose()
508 {
509     MutexGuard  aGuard( GetLinguMutex() );
510 
511     if (!bDisposing)
512     {
513         bDisposing = sal_True;
514         EventObject aEvtObj( (XSpellChecker *) this );
515         aEvtListeners.disposeAndClear( aEvtObj );
516     }
517 }
518 
519 
520 void SAL_CALL
521     MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
522 {
523     MutexGuard  aGuard( GetLinguMutex() );
524 
525     if (!bDisposing && rxListener.is())
526         aEvtListeners.addInterface( rxListener );
527 }
528 
529 
530 void SAL_CALL
531     MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
532 {
533     MutexGuard  aGuard( GetLinguMutex() );
534 
535     if (!bDisposing && rxListener.is())
536         aEvtListeners.removeInterface( rxListener );
537 }
538 
539 
540 ///////////////////////////////////////////////////////////////////////////
541 // Service specific part
542 //
543 
544 OUString SAL_CALL MacSpellChecker::getImplementationName()
545 {
546     MutexGuard  aGuard( GetLinguMutex() );
547 
548     return getImplementationName_Static();
549 }
550 
551 
552 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
553 {
554     MutexGuard  aGuard( GetLinguMutex() );
555 
556     Sequence< OUString > aSNL = getSupportedServiceNames();
557     const OUString * pArray = aSNL.getConstArray();
558     for( sal_Int32 i = 0; i < aSNL.getLength(); i++ )
559         if( pArray[i] == ServiceName )
560             return sal_True;
561     return sal_False;
562 }
563 
564 
565 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
566 {
567     MutexGuard  aGuard( GetLinguMutex() );
568 
569     return getSupportedServiceNames_Static();
570 }
571 
572 
573 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
574         throw()
575 {
576     MutexGuard  aGuard( GetLinguMutex() );
577 
578     Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
579     aSNS.getArray()[0] = A2OU( SN_SPELLCHECKER );
580     return aSNS;
581 }
582 
583 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
584             XMultiServiceFactory * pServiceManager, void *  )
585 {
586     void * pRet = 0;
587     if ( !MacSpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
588     {
589         Reference< XSingleServiceFactory > xFactory =
590             cppu::createOneInstanceFactory(
591                 pServiceManager,
592                 MacSpellChecker::getImplementationName_Static(),
593                 MacSpellChecker_CreateInstance,
594                 MacSpellChecker::getSupportedServiceNames_Static());
595         // acquire, because we return an interface pointer instead of a reference
596         xFactory->acquire();
597         pRet = xFactory.get();
598     }
599     return pRet;
600 }
601 
602 
603 ///////////////////////////////////////////////////////////////////////////
604