bump product version to 4.2.0.1
[LibreOffice.git] / lingucomponent / source / spellcheck / macosxspell / macspellimp.mm
blob9bed1265656c6624623eef7760da3e4ffe1715d9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
20 #include <com/sun/star/uno/Reference.h>
21 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
23 #include <com/sun/star/linguistic2/SpellFailure.hpp>
24 #include <cppuhelper/factory.hxx>
25 #include <cppuhelper/supportsservice.hxx>
26 #include <com/sun/star/registry/XRegistryKey.hpp>
27 #include <tools/debug.hxx>
28 #include <osl/mutex.hxx>
30 #include <macspellimp.hxx>
32 #include <linguistic/spelldta.hxx>
33 #include <unotools/pathoptions.hxx>
34 #include <unotools/useroptions.hxx>
35 #include <osl/file.hxx>
36 #include <rtl/ustrbuf.hxx>
38 using namespace utl;
39 using namespace osl;
40 using namespace com::sun::star;
41 using namespace com::sun::star::beans;
42 using namespace com::sun::star::lang;
43 using namespace com::sun::star::uno;
44 using namespace com::sun::star::linguistic2;
45 using namespace linguistic;
47 using ::rtl::OUString;
48 using ::rtl::OString;
49 using ::rtl::OUStringBuffer;
50 using ::rtl::OUStringToOString;
52 MacSpellChecker::MacSpellChecker() :
53     aEvtListeners( GetLinguMutex() )
55     aDEncs = NULL;
56     aDLocs = NULL;
57     aDNames = NULL;
58     bDisposing = false;
59     pPropHelper = NULL;
60     numdict = 0;
61     NSApplicationLoad();
62     NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
63     macSpell = [NSSpellChecker sharedSpellChecker];
64     macTag = [NSSpellChecker uniqueSpellDocumentTag];
65     [pool release];
69 MacSpellChecker::~MacSpellChecker()
71   numdict = 0;
72   if (aDEncs) delete[] aDEncs;
73   aDEncs = NULL;
74   if (aDLocs) delete[] aDLocs;
75   aDLocs = NULL;
76   if (aDNames) delete[] aDNames;
77   aDNames = NULL;
78   if (pPropHelper)
79      pPropHelper->RemoveAsPropListener();
83 PropertyHelper_Spell & MacSpellChecker::GetPropHelper_Impl()
85     if (!pPropHelper)
86     {
87         Reference< XLinguProperties >   xPropSet( GetLinguProperties(), UNO_QUERY );
89         pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
90         xPropHelper = pPropHelper;
91         pPropHelper->AddAsPropListener();   //! after a reference is established
92     }
93     return *pPropHelper;
97 Sequence< Locale > SAL_CALL MacSpellChecker::getLocales()
98         throw(RuntimeException)
100     MutexGuard  aGuard( GetLinguMutex() );
102         // this routine should return the locales supported by the installed
103         // dictionaries.  So here we need to parse both the user edited
104         // dictionary list and the shared dictionary list
105         // to see what dictionaries the admin/user has installed
107         int numshr;          // number of shared dictionary entries
108         SvtPathOptions aPathOpt;
109         rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
111         std::vector<NSString *> postspdict;
113     if (!numdict) {
115         // invoke a dictionary manager to get the user dictionary list
116         // TODO How on Mac OS X?
118         // invoke a second  dictionary manager to get the shared dictionary list
119         NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
121         //Test for existence of the dictionaries
122         for (unsigned int i = 0; i < [aLocales count]; i++)
123         {
124             NSString* pLangStr = (NSString*)[aLocales objectAtIndex:i];
125             if( [macSpell setLanguage:pLangStr ] )
126             {
127                 postspdict.push_back( pLangStr );
128             }
129         }
131         numshr = postspdict.size();
133         // we really should merge these and remove duplicates but since
134         // users can name their dictionaries anything they want it would
135         // be impossible to know if a real duplication exists unless we
136         // add some unique key to each myspell dictionary
137         numdict = numshr;
139         if (numdict) {
140             aDLocs = new Locale [numdict];
141             aDEncs  = new rtl_TextEncoding [numdict];
142             aDNames = new OUString [numdict];
143             aSuppLocales.realloc(numdict);
144             Locale * pLocale = aSuppLocales.getArray();
145             int numlocs = 0;
146             int newloc;
147             int i,j;
148             int k = 0;
150             //first add the user dictionaries
151             //TODO for MAC?
153             // now add the shared dictionaries
154             for (i = 0; i < numshr; i++) {
155                 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
156                 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
157                 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
158                 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
159                 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
160                 Locale nLoc( lang, country, OUString() );
161                 newloc = 1;
162                 //eliminate duplicates (is this needed for MacOS?)
163                 for (j = 0; j < numlocs; j++) {
164                     if (nLoc == pLocale[j]) newloc = 0;
165                 }
166                 if (newloc) {
167                     pLocale[numlocs] = nLoc;
168                     numlocs++;
169                 }
170                 aDLocs[k] = nLoc;
171                 aDEncs[k] = 0;
172                 k++;
173             }
175             aSuppLocales.realloc(numlocs);
177         } else {
178             /* no dictionary.lst found so register no dictionaries */
179             numdict = 0;
180                 aDEncs  = NULL;
181                 aDLocs = NULL;
182                 aDNames = NULL;
183                 aSuppLocales.realloc(0);
184             }
185         }
187     return aSuppLocales;
192 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
193         throw(RuntimeException)
195     MutexGuard  aGuard( GetLinguMutex() );
197     sal_Bool bRes = sal_False;
198     if (!aSuppLocales.getLength())
199         getLocales();
201     sal_Int32 nLen = aSuppLocales.getLength();
202     for (sal_Int32 i = 0;  i < nLen;  ++i)
203     {
204         const Locale *pLocale = aSuppLocales.getConstArray();
205         if (rLocale == pLocale[i])
206         {
207             bRes = sal_True;
208             break;
209         }
210     }
211     return bRes;
215 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
217     rtl_TextEncoding aEnc;
219     // initialize a myspell object for each dictionary once
220         // (note: mutex is held higher up in isValid)
223     sal_Int16 nRes = -1;
225         // first handle smart quotes both single and double
226     OUStringBuffer rBuf(rWord);
227         sal_Int32 n = rBuf.getLength();
228         sal_Unicode c;
229     for (sal_Int32 ix=0; ix < n; ix++) {
230         c = rBuf[ix];
231         if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
232         if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
233         }
234         OUString nWord(rBuf.makeStringAndClear());
236     if (n)
237     {
238         aEnc = 0;
239         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
240         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
241         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
242         if(rLocale.Country.getLength()>0)
243         {
244             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
245             NSString* aTag = @"_";
246             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
247             [aLang autorelease];
248             aLang = [aLang  stringByAppendingString:aTaggedCountry];
249         }
251         NSInteger aCount;
252         NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
253         int rVal = 0;
254         if(range.length>0)
255         {
256             rVal = -1;
257         }
258         else
259         {
260             rVal = 1;
261         }
262         [pool release];
263         if (rVal != 1)
264         {
265             nRes = SpellFailure::SPELLING_ERROR;
266         } else {
267             return -1;
268         }
269     }
270     return nRes;
275 sal_Bool SAL_CALL
276     MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
277             const PropertyValues& rProperties )
278         throw(IllegalArgumentException, RuntimeException)
280     MutexGuard  aGuard( GetLinguMutex() );
282      if (rLocale == Locale()  ||  !rWord.getLength())
283         return sal_True;
285     if (!hasLocale( rLocale ))
286         return sal_True;
288     // Get property values to be used.
289     // These are be the default values set in the SN_LINGU_PROPERTIES
290     // PropertySet which are overridden by the supplied ones from the
291     // last argument.
292     // You'll probably like to use a simplier solution than the provided
293     // one using the PropertyHelper_Spell.
295     PropertyHelper_Spell &rHelper = GetPropHelper();
296     rHelper.SetTmpPropVals( rProperties );
298     sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
299     if (nFailure != -1)
300     {
301         sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
302         // postprocess result for errors that should be ignored
303         if (   (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang ))
304             || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
305             || (!rHelper.IsSpellCapitalization()
306                 &&  nFailure == SpellFailure::CAPTION_ERROR)
307         )
308             nFailure = -1;
309     }
311     return (nFailure == -1);
315 Reference< XSpellAlternatives >
316     MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
318     // Retrieves the return values for the 'spell' function call in case
319     // of a misspelled word.
320     // Especially it may give a list of suggested (correct) words:
322     Reference< XSpellAlternatives > xRes;
323         // note: mutex is held by higher up by spell which covers both
325     sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
326     int count;
327     Sequence< OUString > aStr( 0 );
329         // first handle smart quotes (single and double)
330     OUStringBuffer rBuf(rWord);
331         sal_Int32 n = rBuf.getLength();
332         sal_Unicode c;
333     for (sal_Int32 ix=0; ix < n; ix++) {
334          c = rBuf[ix];
335          if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
336          if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
337         }
338         OUString nWord(rBuf.makeStringAndClear());
340     if (n)
341     {
342         NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
343         NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
344         NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
345         if(rLocale.Country.getLength()>0)
346         {
347             NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
348             NSString* aTag = @"_";
349             NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
350             [aLang autorelease];
351             aLang = [aLang  stringByAppendingString:aTaggedCountry];
352         }
353         [macSpell setLanguage:aLang];
354         NSArray *guesses = [macSpell guessesForWord:aNSStr];
355         count = [guesses count];
356         if (count)
357         {
358            aStr.realloc( count );
359            OUString *pStr = aStr.getArray();
360                for (int ii=0; ii < count; ii++)
361                {
362                   // if needed add: if (suglst[ii] == NULL) continue;
363                   NSString* guess = [guesses objectAtIndex:ii];
364                   OUString cvtwrd((const sal_Unicode*)[guess cStringUsingEncoding:NSUnicodeStringEncoding], (sal_Int32)[guess length]);
365                   pStr[ii] = cvtwrd;
366                }
367         }
368        [pool release];
369     }
371             // now return an empty alternative for no suggestions or the list of alternatives if some found
372         SpellAlternatives *pAlt = new SpellAlternatives;
373         pAlt->SetWordLanguage( rWord, nLang );
374         pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
375         pAlt->SetAlternatives( aStr );
376         xRes = pAlt;
377         return xRes;
384 Reference< XSpellAlternatives > SAL_CALL
385     MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
386             const PropertyValues& rProperties )
387         throw(IllegalArgumentException, RuntimeException)
389     MutexGuard  aGuard( GetLinguMutex() );
391      if (rLocale == Locale()  ||  !rWord.getLength())
392         return NULL;
394     if (!hasLocale( rLocale ))
395         return NULL;
397     Reference< XSpellAlternatives > xAlt;
398     if (!isValid( rWord, rLocale, rProperties ))
399     {
400         xAlt =  GetProposals( rWord, rLocale );
401     }
402     return xAlt;
406 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
407             const Reference< XMultiServiceFactory > & /*rSMgr*/ )
408         throw(Exception)
411     Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
412     return xService;
416 sal_Bool SAL_CALL
417     MacSpellChecker::addLinguServiceEventListener(
418             const Reference< XLinguServiceEventListener >& rxLstnr )
419         throw(RuntimeException)
421     MutexGuard  aGuard( GetLinguMutex() );
423     sal_Bool bRes = sal_False;
424     if (!bDisposing && rxLstnr.is())
425     {
426         bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
427     }
428     return bRes;
432 sal_Bool SAL_CALL
433     MacSpellChecker::removeLinguServiceEventListener(
434             const Reference< XLinguServiceEventListener >& rxLstnr )
435         throw(RuntimeException)
437     MutexGuard  aGuard( GetLinguMutex() );
439     sal_Bool bRes = sal_False;
440     if (!bDisposing && rxLstnr.is())
441     {
442         DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
443         bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
444     }
445     return bRes;
449 OUString SAL_CALL
450     MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
451         throw(RuntimeException)
453     MutexGuard  aGuard( GetLinguMutex() );
454     return OUString( "Mac OS X Spell Checker" );
458 void SAL_CALL
459     MacSpellChecker::initialize( const Sequence< Any >& rArguments )
460         throw(Exception, RuntimeException)
462     MutexGuard  aGuard( GetLinguMutex() );
464     if (!pPropHelper)
465     {
466         sal_Int32 nLen = rArguments.getLength();
467         if (2 == nLen)
468         {
469             Reference< XLinguProperties >   xPropSet;
470             rArguments.getConstArray()[0] >>= xPropSet;
471             //rArguments.getConstArray()[1] >>= xDicList;
473             //! Pointer allows for access of the non-UNO functions.
474             //! And the reference to the UNO-functions while increasing
475             //! the ref-count and will implicitly free the memory
476             //! when the object is not longer used.
477             pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
478             xPropHelper = pPropHelper;
479             pPropHelper->AddAsPropListener();   //! after a reference is established
480         }
481         else
482             OSL_FAIL( "wrong number of arguments in sequence" );
484     }
488 void SAL_CALL
489     MacSpellChecker::dispose()
490         throw(RuntimeException)
492     MutexGuard  aGuard( GetLinguMutex() );
494     if (!bDisposing)
495     {
496         bDisposing = true;
497         EventObject aEvtObj( (XSpellChecker *) this );
498         aEvtListeners.disposeAndClear( aEvtObj );
499     }
503 void SAL_CALL
504     MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
505         throw(RuntimeException)
507     MutexGuard  aGuard( GetLinguMutex() );
509     if (!bDisposing && rxListener.is())
510         aEvtListeners.addInterface( rxListener );
514 void SAL_CALL
515     MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
516         throw(RuntimeException)
518     MutexGuard  aGuard( GetLinguMutex() );
520     if (!bDisposing && rxListener.is())
521         aEvtListeners.removeInterface( rxListener );
524 // Service specific part
525 OUString SAL_CALL MacSpellChecker::getImplementationName()
526         throw(RuntimeException)
528     MutexGuard  aGuard( GetLinguMutex() );
530     return getImplementationName_Static();
533 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
534         throw(RuntimeException)
536     return cppu::supportsService(this, ServiceName);
539 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
540         throw(RuntimeException)
542     MutexGuard  aGuard( GetLinguMutex() );
544     return getSupportedServiceNames_Static();
547 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
548         throw()
550     MutexGuard  aGuard( GetLinguMutex() );
552     Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
553     aSNS.getArray()[0] = SN_SPELLCHECKER;
554     return aSNS;
557 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
558             XMultiServiceFactory * pServiceManager, void *  )
560     void * pRet = 0;
561     if ( !MacSpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
562     {
563         Reference< XSingleServiceFactory > xFactory =
564             cppu::createOneInstanceFactory(
565                 pServiceManager,
566                 MacSpellChecker::getImplementationName_Static(),
567                 MacSpellChecker_CreateInstance,
568                 MacSpellChecker::getSupportedServiceNames_Static());
569         // acquire, because we return an interface pointer instead of a reference
570         xFactory->acquire();
571         pRet = xFactory.get();
572     }
573     return pRet;
577 ///////////////////////////////////////////////////////////////////////////
579 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */