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 SvtPathOptions aPathOpt;
109 rtl_TextEncoding aEnc = RTL_TEXTENCODING_UTF8;
111 std::vector<NSString *> postspdict;
115 // invoke a dictionary manager to get the user dictionary list
116 // TODO How on Mac OS X?
118 // invoke a second dictionary manager to get the shared dictionary list
119 NSArray *aLocales = [NSLocale availableLocaleIdentifiers];
121 //Test for existence of the dictionaries
122 for (unsigned int i = 0; i < [aLocales count]; i++)
124 NSString* pLangStr = (NSString*)[aLocales objectAtIndex:i];
125 if( [macSpell setLanguage:pLangStr ] )
127 postspdict.push_back( pLangStr );
131 numshr = postspdict.size();
133 // we really should merge these and remove duplicates but since
134 // users can name their dictionaries anything they want it would
135 // be impossible to know if a real duplication exists unless we
136 // add some unique key to each myspell dictionary
140 aDLocs = new Locale [numdict];
141 aDEncs = new rtl_TextEncoding [numdict];
142 aDNames = new OUString [numdict];
143 aSuppLocales.realloc(numdict);
144 Locale * pLocale = aSuppLocales.getArray();
150 //first add the user dictionaries
153 // now add the shared dictionaries
154 for (i = 0; i < numshr; i++) {
155 NSDictionary *aLocDict = [ NSLocale componentsFromLocaleIdentifier:postspdict[i] ];
156 NSString* aLang = [ aLocDict objectForKey:NSLocaleLanguageCode ];
157 NSString* aCountry = [ aLocDict objectForKey:NSLocaleCountryCode ];
158 OUString lang([aLang cStringUsingEncoding: NSUTF8StringEncoding], [aLang length], aEnc);
159 OUString country([ aCountry cStringUsingEncoding: NSUTF8StringEncoding], [aCountry length], aEnc);
160 Locale nLoc( lang, country, OUString() );
162 //eliminate duplicates (is this needed for MacOS?)
163 for (j = 0; j < numlocs; j++) {
164 if (nLoc == pLocale[j]) newloc = 0;
167 pLocale[numlocs] = nLoc;
175 aSuppLocales.realloc(numlocs);
178 /* no dictionary.lst found so register no dictionaries */
183 aSuppLocales.realloc(0);
192 sal_Bool SAL_CALL MacSpellChecker::hasLocale(const Locale& rLocale)
193 throw(RuntimeException)
195 MutexGuard aGuard( GetLinguMutex() );
197 sal_Bool bRes = sal_False;
198 if (!aSuppLocales.getLength())
201 sal_Int32 nLen = aSuppLocales.getLength();
202 for (sal_Int32 i = 0; i < nLen; ++i)
204 const Locale *pLocale = aSuppLocales.getConstArray();
205 if (rLocale == pLocale[i])
215 sal_Int16 MacSpellChecker::GetSpellFailure( const OUString &rWord, const Locale &rLocale )
217 rtl_TextEncoding aEnc;
219 // initialize a myspell object for each dictionary once
220 // (note: mutex is held higher up in isValid)
225 // first handle smart quotes both single and double
226 OUStringBuffer rBuf(rWord);
227 sal_Int32 n = rBuf.getLength();
229 for (sal_Int32 ix=0; ix < n; ix++) {
231 if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
232 if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
234 OUString nWord(rBuf.makeStringAndClear());
239 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
240 NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
241 NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength()];
242 if(rLocale.Country.getLength()>0)
244 NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength()];
245 NSString* aTag = @"_";
246 NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
248 aLang = [aLang stringByAppendingString:aTaggedCountry];
252 NSRange range = [macSpell checkSpellingOfString:aNSStr startingAt:0 language:aLang wrap:sal_False inSpellDocumentWithTag:macTag wordCount:&aCount];
265 nRes = SpellFailure::SPELLING_ERROR;
276 MacSpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
277 const PropertyValues& rProperties )
278 throw(IllegalArgumentException, RuntimeException)
280 MutexGuard aGuard( GetLinguMutex() );
282 if (rLocale == Locale() || !rWord.getLength())
285 if (!hasLocale( rLocale ))
288 // Get property values to be used.
289 // These are be the default values set in the SN_LINGU_PROPERTIES
290 // PropertySet which are overridden by the supplied ones from the
292 // You'll probably like to use a simplier solution than the provided
293 // one using the PropertyHelper_Spell.
295 PropertyHelper_Spell &rHelper = GetPropHelper();
296 rHelper.SetTmpPropVals( rProperties );
298 sal_Int16 nFailure = GetSpellFailure( rWord, rLocale );
301 sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
302 // postprocess result for errors that should be ignored
303 if ( (!rHelper.IsSpellUpperCase() && IsUpper( rWord, nLang ))
304 || (!rHelper.IsSpellWithDigits() && HasDigits( rWord ))
305 || (!rHelper.IsSpellCapitalization()
306 && nFailure == SpellFailure::CAPTION_ERROR)
311 return (nFailure == -1);
315 Reference< XSpellAlternatives >
316 MacSpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
318 // Retrieves the return values for the 'spell' function call in case
319 // of a misspelled word.
320 // Especially it may give a list of suggested (correct) words:
322 Reference< XSpellAlternatives > xRes;
323 // note: mutex is held by higher up by spell which covers both
325 sal_Int16 nLang = LinguLocaleToLanguage( rLocale );
327 Sequence< OUString > aStr( 0 );
329 // first handle smart quotes (single and double)
330 OUStringBuffer rBuf(rWord);
331 sal_Int32 n = rBuf.getLength();
333 for (sal_Int32 ix=0; ix < n; ix++) {
335 if ((c == 0x201C) || (c == 0x201D)) rBuf[ix] = (sal_Unicode)0x0022;
336 if ((c == 0x2018) || (c == 0x2019)) rBuf[ix] = (sal_Unicode)0x0027;
338 OUString nWord(rBuf.makeStringAndClear());
342 NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
343 NSString* aNSStr = [[NSString alloc] initWithCharacters: nWord.getStr() length: nWord.getLength()];
344 NSString* aLang = [[NSString alloc] initWithCharacters: rLocale.Language.getStr() length: rLocale.Language.getLength() ];
345 if(rLocale.Country.getLength()>0)
347 NSString* aCountry = [[NSString alloc] initWithCharacters: rLocale.Country.getStr() length: rLocale.Country.getLength() ];
348 NSString* aTag = @"_";
349 NSString* aTaggedCountry = [aTag stringByAppendingString:aCountry];
351 aLang = [aLang stringByAppendingString:aTaggedCountry];
353 [macSpell setLanguage:aLang];
354 NSArray *guesses = [macSpell guessesForWord:aNSStr];
355 count = [guesses count];
358 aStr.realloc( count );
359 OUString *pStr = aStr.getArray();
360 for (int ii=0; ii < count; ii++)
362 // if needed add: if (suglst[ii] == NULL) continue;
363 NSString* guess = [guesses objectAtIndex:ii];
364 OUString cvtwrd((const sal_Unicode*)[guess cStringUsingEncoding:NSUnicodeStringEncoding], (sal_Int32)[guess length]);
371 // now return an empty alternative for no suggestions or the list of alternatives if some found
372 SpellAlternatives *pAlt = new SpellAlternatives;
373 pAlt->SetWordLanguage( rWord, nLang );
374 pAlt->SetFailureType( SpellFailure::SPELLING_ERROR );
375 pAlt->SetAlternatives( aStr );
384 Reference< XSpellAlternatives > SAL_CALL
385 MacSpellChecker::spell( const OUString& rWord, const Locale& rLocale,
386 const PropertyValues& rProperties )
387 throw(IllegalArgumentException, RuntimeException)
389 MutexGuard aGuard( GetLinguMutex() );
391 if (rLocale == Locale() || !rWord.getLength())
394 if (!hasLocale( rLocale ))
397 Reference< XSpellAlternatives > xAlt;
398 if (!isValid( rWord, rLocale, rProperties ))
400 xAlt = GetProposals( rWord, rLocale );
406 Reference< XInterface > SAL_CALL MacSpellChecker_CreateInstance(
407 const Reference< XMultiServiceFactory > & /*rSMgr*/ )
411 Reference< XInterface > xService = (cppu::OWeakObject*) new MacSpellChecker;
417 MacSpellChecker::addLinguServiceEventListener(
418 const Reference< XLinguServiceEventListener >& rxLstnr )
419 throw(RuntimeException)
421 MutexGuard aGuard( GetLinguMutex() );
423 sal_Bool bRes = sal_False;
424 if (!bDisposing && rxLstnr.is())
426 bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
433 MacSpellChecker::removeLinguServiceEventListener(
434 const Reference< XLinguServiceEventListener >& rxLstnr )
435 throw(RuntimeException)
437 MutexGuard aGuard( GetLinguMutex() );
439 sal_Bool bRes = sal_False;
440 if (!bDisposing && rxLstnr.is())
442 DBG_ASSERT( xPropHelper.is(), "xPropHelper non existent" );
443 bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
450 MacSpellChecker::getServiceDisplayName( const Locale& /*rLocale*/ )
451 throw(RuntimeException)
453 MutexGuard aGuard( GetLinguMutex() );
454 return OUString( "Mac OS X Spell Checker" );
459 MacSpellChecker::initialize( const Sequence< Any >& rArguments )
460 throw(Exception, RuntimeException)
462 MutexGuard aGuard( GetLinguMutex() );
466 sal_Int32 nLen = rArguments.getLength();
469 Reference< XLinguProperties > xPropSet;
470 rArguments.getConstArray()[0] >>= xPropSet;
471 //rArguments.getConstArray()[1] >>= xDicList;
473 //! Pointer allows for access of the non-UNO functions.
474 //! And the reference to the UNO-functions while increasing
475 //! the ref-count and will implicitly free the memory
476 //! when the object is not longer used.
477 pPropHelper = new PropertyHelper_Spell( (XSpellChecker *) this, xPropSet );
478 xPropHelper = pPropHelper;
479 pPropHelper->AddAsPropListener(); //! after a reference is established
482 OSL_FAIL( "wrong number of arguments in sequence" );
489 MacSpellChecker::dispose()
490 throw(RuntimeException)
492 MutexGuard aGuard( GetLinguMutex() );
497 EventObject aEvtObj( (XSpellChecker *) this );
498 aEvtListeners.disposeAndClear( aEvtObj );
504 MacSpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
505 throw(RuntimeException)
507 MutexGuard aGuard( GetLinguMutex() );
509 if (!bDisposing && rxListener.is())
510 aEvtListeners.addInterface( rxListener );
515 MacSpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
516 throw(RuntimeException)
518 MutexGuard aGuard( GetLinguMutex() );
520 if (!bDisposing && rxListener.is())
521 aEvtListeners.removeInterface( rxListener );
524 // Service specific part
525 OUString SAL_CALL MacSpellChecker::getImplementationName()
526 throw(RuntimeException)
528 MutexGuard aGuard( GetLinguMutex() );
530 return getImplementationName_Static();
533 sal_Bool SAL_CALL MacSpellChecker::supportsService( const OUString& ServiceName )
534 throw(RuntimeException)
536 return cppu::supportsService(this, ServiceName);
539 Sequence< OUString > SAL_CALL MacSpellChecker::getSupportedServiceNames()
540 throw(RuntimeException)
542 MutexGuard aGuard( GetLinguMutex() );
544 return getSupportedServiceNames_Static();
547 Sequence< OUString > MacSpellChecker::getSupportedServiceNames_Static()
550 MutexGuard aGuard( GetLinguMutex() );
552 Sequence< OUString > aSNS( 1 ); // auch mehr als 1 Service moeglich
553 aSNS.getArray()[0] = SN_SPELLCHECKER;
557 void * SAL_CALL MacSpellChecker_getFactory( const sal_Char * pImplName,
558 XMultiServiceFactory * pServiceManager, void * )
561 if ( !MacSpellChecker::getImplementationName_Static().compareToAscii( pImplName ) )
563 Reference< XSingleServiceFactory > xFactory =
564 cppu::createOneInstanceFactory(
566 MacSpellChecker::getImplementationName_Static(),
567 MacSpellChecker_CreateInstance,
568 MacSpellChecker::getSupportedServiceNames_Static());
569 // acquire, because we return an interface pointer instead of a reference
571 pRet = xFactory.get();
577 ///////////////////////////////////////////////////////////////////////////
579 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */