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