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