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<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 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 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 int 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 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 simplier 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 > 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 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 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 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 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 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 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 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 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 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 556 OUString SAL_CALL MacSpellChecker::getImplementationName() 557 throw(RuntimeException) 558 { 559 MutexGuard aGuard( GetLinguMutex() ); 560 561 return getImplementationName_Static(); 562 } 563 564 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 579 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames() 580 throw(RuntimeException) 581 { 582 MutexGuard aGuard( GetLinguMutex() ); 583 584 return getSupportedServiceNames_Static(); 585 } 586 587 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 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