1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <com/sun/star/uno/Reference.h>
21 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
23 #include <com/sun/star/linguistic2/SpellFailure.hpp>
24 #include <cppuhelper/factory.hxx>
25 #include <cppuhelper/supportsservice.hxx>
26 #include <com/sun/star/registry/XRegistryKey.hpp>
27 #include <tools/debug.hxx>
28 #include <osl/mutex.hxx>
30 #include <macspellimp.hxx>
32 #include <linguistic/spelldta.hxx>
33 #include <unotools/pathoptions.hxx>
34 #include <unotools/useroptions.hxx>
35 #include <osl/file.hxx>
36 #include <rtl/ustrbuf.hxx>
40 using namespace com::sun::star;
41 using namespace com::sun::star::beans;
42 using namespace com::sun::star::lang;
43 using namespace com::sun::star::uno;
44 using namespace com::sun::star::linguistic2;
45 using namespace linguistic;
47 using ::rtl::OUString;
49 using ::rtl::OUStringBuffer;
50 using ::rtl::OUStringToOString;
52 MacSpellChecker::MacSpellChecker() :
53 aEvtListeners( GetLinguMutex() )
62 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
63 macSpell = [NSSpellChecker sharedSpellChecker];
64 macTag = [NSSpellChecker uniqueSpellDocumentTag];
69 MacSpellChecker::~MacSpellChecker()
72 if (aDEncs) delete[] aDEncs;
74 if (aDLocs) delete[] aDLocs;
76 if (aDNames) delete[] aDNames;
79 pPropHelper->RemoveAsPropListener();
83 PropertyHelper_Spell & MacSpellChecker::GetPropHelper_Impl()
87 Reference< XLinguProperties > xPropSet( GetLinguProperties(), UNO_QUERY );
89 pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
90 xPropHelper = pPropHelper;
91 pPropHelper->AddAsPropListener(); //! after a reference is established
97 Sequence< Locale > SAL_CALL MacSpellChecker::getLocales()
98 throw(RuntimeException)
100 MutexGuard aGuard( GetLinguMutex() );
102 // this routine should return the locales supported by the installed
103 // dictionaries. So here we need to parse both the user edited
104 // dictionary list and the shared dictionary list
105 // to see what dictionaries the admin/user has installed
107 int numshr; // number of shared dictionary entries
108 rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
110 std::vector<NSString *> postspdict;
114 // invoke a dictionary manager to get the user dictionary list
115 // TODO How on Mac OS X?
117 // invoke a second dictionary manager to get the shared dictionary list
118 NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
120 //Test for existence of the dictionaries
121 for (unsigned int i = 0; i < [aLocales count]; i++)
123 NSString* pLangStr = (NSString*)[aLocales objectAtIndex:i];
124 if( [macSpell setLanguage:pLangStr ] )
126 postspdict.push_back( pLangStr );
130 numshr = postspdict.size();
132 // we really should merge these and remove duplicates but since
133 // users can name their dictionaries anything they want it would
134 // be impossible to know if a real duplication exists unless we
135 // add some unique key to each myspell dictionary
139 aDLocs = new Locale [numdict];
140 aDEncs = new rtl_TextEncoding [numdict];
141 aDNames = new OUString [numdict];
142 aSuppLocales.realloc(numdict);
143 Locale * pLocale = aSuppLocales.getArray();
149 //first add the user dictionaries
152 // now add the shared dictionaries
153 for (i = 0; i < numshr; i++) {
154 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
155 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
156 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
157 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
158 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
159 Locale nLoc( lang, country, OUString() );
161 //eliminate duplicates (is this needed for MacOS?)
162 for (j = 0; j < numlocs; j++) {
163 if (nLoc == pLocale[j]) newloc = 0;
166 pLocale[numlocs] = nLoc;
174 aSuppLocales.realloc(numlocs);
177 /* no dictionary.lst found so register no dictionaries */
182 aSuppLocales.realloc(0);
191 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
192 throw(RuntimeException)
194 MutexGuard aGuard( GetLinguMutex() );
197 if (!aSuppLocales.getLength())
200 sal_Int32 nLen = aSuppLocales.getLength();
201 for (sal_Int32 i = 0; i < nLen; ++i)
203 const Locale *pLocale = aSuppLocales.getConstArray();
204 if (rLocale == pLocale[i])
214 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
216 rtl_TextEncoding aEnc;
218 // initialize a myspell object for each dictionary once
219 // (note: mutex is held higher up in isValid)
224 // first handle smart quotes both single and double
225 OUStringBuffer rBuf(rWord);
226 sal_Int32 n = rBuf.getLength();
228 for (sal_Int32 ix=0; ix < n; ix++) {
230 if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
231 if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
233 OUString nWord(rBuf.makeStringAndClear());
238 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
239 NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
240 NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
241 if(rLocale.Country.getLength()>0)
243 NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
244 NSString* aTag = @"_";
245 NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
247 aLang = [aLang stringByAppendingString:aTaggedCountry];
251 NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
264 nRes = SpellFailure::SPELLING_ERROR;
275 MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
276 const PropertyValues& rProperties )
277 throw(IllegalArgumentException, RuntimeException)
279 MutexGuard aGuard( GetLinguMutex() );
281 if (rLocale == Locale() || !rWord.getLength())
284 if (!hasLocale( rLocale ))
287 // Get property values to be used.
288 // These are be the default values set in the SN_LINGU_PROPERTIES
289 // PropertySet which are overridden by the supplied ones from the
291 // You'll probably like to use a simpler solution than the provided
292 // one using the PropertyHelper_Spell.
294 PropertyHelper_Spell &rHelper = GetPropHelper();
295 rHelper.SetTmpPropVals( rProperties );
297 sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
300 sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
301 // postprocess result for errors that should be ignored
302 if ( (!rHelper.IsSpellUpperCase() && IsUpper( rWord, nLang ))
303 || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
304 || (!rHelper.IsSpellCapitalization()
305 && nFailure == SpellFailure::CAPTION_ERROR)
310 return (nFailure == -1);
314 Reference< XSpellAlternatives >
315 MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
317 // Retrieves the return values for the 'spell' function call in case
318 // of a misspelled word.
319 // Especially it may give a list of suggested (correct) words:
321 Reference< XSpellAlternatives > xRes;
322 // note: mutex is held by higher up by spell which covers both
324 sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
326 Sequence< OUString > aStr( 0 );
328 // first handle smart quotes (single and double)
329 OUStringBuffer rBuf(rWord);
330 sal_Int32 n = rBuf.getLength();
332 for (sal_Int32 ix=0; ix < n; ix++) {
334 if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
335 if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
337 OUString nWord(rBuf.makeStringAndClear());
341 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
342 NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
343 NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
344 if(rLocale.Country.getLength()>0)
346 NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
347 NSString* aTag = @"_";
348 NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
350 aLang = [aLang stringByAppendingString:aTaggedCountry];
352 [macSpell setLanguage:aLang];
353 NSArray *guesses = [macSpell guessesForWordRange:NSMakeRange(0, [aNSStr length]) inString:aNSStr language:aLang inSpellDocumentWithTag:0];
354 count = [guesses count];
357 aStr.realloc( count );
358 OUString *pStr = aStr.getArray();
359 for (int ii=0; ii < count; ii++)
361 // if needed add: if (suglst[ii] == NULL) continue;
362 NSString* guess = [guesses objectAtIndex:ii];
363 OUString cvtwrd(reinterpret_cast<const sal_Unicode*>([guess cStringUsingEncoding:NSUnicodeStringEncoding]), (sal_Int32)[guess length]);
370 // now return an empty alternative for no suggestions or the list of alternatives if some found
371 SpellAlternatives *pAlt = new SpellAlternatives;
372 pAlt->SetWordLanguage( rWord, nLang );
373 pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
374 pAlt->SetAlternatives( aStr );
383 Reference< XSpellAlternatives > SAL_CALL
384 MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
385 const PropertyValues& rProperties )
386 throw(IllegalArgumentException, RuntimeException)
388 MutexGuard aGuard( GetLinguMutex() );
390 if (rLocale == Locale() || !rWord.getLength())
393 if (!hasLocale( rLocale ))
396 Reference< XSpellAlternatives > xAlt;
397 if (!isValid( rWord, rLocale, rProperties ))
399 xAlt = GetProposals( rWord, rLocale );
405 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
406 const Reference< XMultiServiceFactory > & /*rSMgr*/ )
410 Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
416 MacSpellChecker::addLinguServiceEventListener(
417 const Reference< XLinguServiceEventListener >& rxLstnr )
418 throw(RuntimeException)
420 MutexGuard aGuard( GetLinguMutex() );
423 if (!bDisposing && rxLstnr.is())
425 bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
432 MacSpellChecker::removeLinguServiceEventListener(
433 const Reference< XLinguServiceEventListener >& rxLstnr )
434 throw(RuntimeException)
436 MutexGuard aGuard( GetLinguMutex() );
439 if (!bDisposing && rxLstnr.is())
441 DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
442 bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
449 MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
450 throw(RuntimeException)
452 MutexGuard aGuard( GetLinguMutex() );
453 return OUString( "Mac OS X Spell Checker" );
458 MacSpellChecker::initialize( const Sequence< Any >& rArguments )
459 throw(Exception, RuntimeException)
461 MutexGuard aGuard( GetLinguMutex() );
465 sal_Int32 nLen = rArguments.getLength();
468 Reference< XLinguProperties > xPropSet;
469 rArguments.getConstArray()[0] >>= xPropSet;
470 //rArguments.getConstArray()[1] >>= xDicList;
472 //! Pointer allows for access of the non-UNO functions.
473 //! And the reference to the UNO-functions while increasing
474 //! the ref-count and will implicitly free the memory
475 //! when the object is not longer used.
476 pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
477 xPropHelper = pPropHelper;
478 pPropHelper->AddAsPropListener(); //! after a reference is established
481 OSL_FAIL( "wrong number of arguments in sequence" );
488 MacSpellChecker::dispose()
489 throw(RuntimeException)
491 MutexGuard aGuard( GetLinguMutex() );
496 EventObject aEvtObj( (XSpellChecker *) this );
497 aEvtListeners.disposeAndClear( aEvtObj );
503 MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
504 throw(RuntimeException)
506 MutexGuard aGuard( GetLinguMutex() );
508 if (!bDisposing && rxListener.is())
509 aEvtListeners.addInterface( rxListener );
514 MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
515 throw(RuntimeException)
517 MutexGuard aGuard( GetLinguMutex() );
519 if (!bDisposing && rxListener.is())
520 aEvtListeners.removeInterface( rxListener );
523 // Service specific part
524 OUString SAL_CALL MacSpellChecker::getImplementationName()
525 throw(RuntimeException)
527 MutexGuard aGuard( GetLinguMutex() );
529 return getImplementationName_Static();
532 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
533 throw(RuntimeException)
535 return cppu::supportsService(this, ServiceName);
538 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
539 throw(RuntimeException)
541 MutexGuard aGuard( GetLinguMutex() );
543 return getSupportedServiceNames_Static();
546 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
549 MutexGuard aGuard( GetLinguMutex() );
551 Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
552 aSNS.getArray()[0] = SN_SPELLCHECKER;
556 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
557 XMultiServiceFactory * pServiceManager, void * )
560 if ( MacSpellChecker::getImplementationName_Static().equalsAscii( pImplName ) )
562 Reference< XSingleServiceFactory > xFactory =
563 cppu::createOneInstanceFactory(
565 MacSpellChecker::getImplementationName_Static(),
566 MacSpellChecker_CreateInstance,
567 MacSpellChecker::getSupportedServiceNames_Static());
568 // acquire, because we return an interface pointer instead of a reference
570 pRet = xFactory.get();
578 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */