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 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 { 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 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 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 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 > 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 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 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 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 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 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 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 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 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 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 544 OUString SAL_CALL MacSpellChecker::getImplementationName() 545 { 546 MutexGuard aGuard( GetLinguMutex() ); 547 548 return getImplementationName_Static(); 549 } 550 551 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 565 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames() 566 { 567 MutexGuard aGuard( GetLinguMutex() ); 568 569 return getSupportedServiceNames_Static(); 570 } 571 572 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 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