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 {
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
hasLocale(const Locale & rLocale)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
GetSpellFailure(const OUString & rWord,const Locale & rLocale)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
isValid(const OUString & rWord,const Locale & rLocale,const PropertyValues & rProperties)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 >
GetProposals(const OUString & rWord,const Locale & rLocale)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
spell(const OUString & rWord,const Locale & rLocale,const PropertyValues & rProperties)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
MacSpellChecker_CreateInstance(const Reference<XMultiServiceFactory> &)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
addLinguServiceEventListener(const Reference<XLinguServiceEventListener> & rxLstnr)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
removeLinguServiceEventListener(const Reference<XLinguServiceEventListener> & rxLstnr)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
getServiceDisplayName(const Locale &)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
initialize(const Sequence<Any> & rArguments)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
dispose()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
addEventListener(const Reference<XEventListener> & rxListener)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
removeEventListener(const Reference<XEventListener> & rxListener)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
getImplementationName()544 OUString SAL_CALL MacSpellChecker::getImplementationName()
545 {
546 MutexGuard aGuard( GetLinguMutex() );
547
548 return getImplementationName_Static();
549 }
550
551
supportsService(const OUString & ServiceName)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
getSupportedServiceNames()565 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
566 {
567 MutexGuard aGuard( GetLinguMutex() );
568
569 return getSupportedServiceNames_Static();
570 }
571
572
getSupportedServiceNames_Static()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
MacSpellChecker_getFactory(const sal_Char * pImplName,XMultiServiceFactory * pServiceManager,void *)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