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