1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <com/sun/star/uno/Reference.h>
21 
22 #include <com/sun/star/linguistic2/SpellFailure.hpp>
23 #include <com/sun/star/linguistic2/XLinguProperties.hpp>
24 #include <comphelper/lok.hxx>
25 #include <comphelper/processfactory.hxx>
26 #include <cppuhelper/supportsservice.hxx>
27 #include <cppuhelper/weak.hxx>
28 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
29 #include <tools/debug.hxx>
30 #include <osl/mutex.hxx>
31 #include <osl/thread.h>
32 #include <com/sun/star/ucb/XSimpleFileAccess.hpp>
33 
34 #include <lingutil.hxx>
35 #include <hunspell.hxx>
36 #include "sspellimp.hxx"
37 
38 #include <linguistic/misc.hxx>
39 #include <linguistic/spelldta.hxx>
40 #include <i18nlangtag/languagetag.hxx>
41 #include <svtools/strings.hrc>
42 #include <unotools/lingucfg.hxx>
43 #include <unotools/resmgr.hxx>
44 #include <osl/file.hxx>
45 #include <rtl/ustrbuf.hxx>
46 #include <rtl/textenc.h>
47 #include <sal/log.hxx>
48 
49 #include <numeric>
50 #include <utility>
51 #include <vector>
52 #include <set>
53 #include <string.h>
54 
55 using namespace osl;
56 using namespace com::sun::star;
57 using namespace com::sun::star::beans;
58 using namespace com::sun::star::lang;
59 using namespace com::sun::star::uno;
60 using namespace com::sun::star::linguistic2;
61 using namespace linguistic;
62 
63 // XML-header of SPELLML queries
64 #if !defined SPELL_XML
65 constexpr OUStringLiteral SPELL_XML = u"<?xml?>";
66 #endif
67 
68 // only available in hunspell >= 1.5
69 #if !defined MAXWORDLEN
70 #define MAXWORDLEN 176
71 #endif
72 
SpellChecker()73 SpellChecker::SpellChecker() :
74     m_aEvtListeners(GetLinguMutex()),
75     m_bDisposing(false)
76 {
77 }
78 
DictItem(OUString i_DName,Locale i_DLoc,rtl_TextEncoding i_DEnc)79 SpellChecker::DictItem::DictItem(OUString i_DName, Locale i_DLoc, rtl_TextEncoding i_DEnc)
80     : m_aDName(std::move(i_DName))
81     , m_aDLoc(std::move(i_DLoc))
82     , m_aDEnc(i_DEnc)
83 {
84 }
85 
~SpellChecker()86 SpellChecker::~SpellChecker()
87 {
88     if (m_pPropHelper)
89     {
90         m_pPropHelper->RemoveAsPropListener();
91     }
92 }
93 
GetPropHelper_Impl()94 PropertyHelper_Spelling & SpellChecker::GetPropHelper_Impl()
95 {
96     if (!m_pPropHelper)
97     {
98         Reference< XLinguProperties >   xPropSet = GetLinguProperties();
99 
100         m_pPropHelper.reset( new PropertyHelper_Spelling( static_cast<XSpellChecker *>(this), xPropSet ) );
101         m_pPropHelper->AddAsPropListener();   //! after a reference is established
102     }
103     return *m_pPropHelper;
104 }
105 
getLocales()106 Sequence< Locale > SAL_CALL SpellChecker::getLocales()
107 {
108     MutexGuard  aGuard( GetLinguMutex() );
109 
110     // this routine should return the locales supported by the installed
111     // dictionaries.
112     if (m_DictItems.empty())
113     {
114         SvtLinguConfig aLinguCfg;
115 
116         // get list of extension dictionaries-to-use
117         // (or better speaking: the list of dictionaries using the
118         // new configuration entries).
119         std::vector< SvtLinguConfigDictionaryEntry > aDics;
120         uno::Sequence< OUString > aFormatList;
121         aLinguCfg.GetSupportedDictionaryFormatsFor( u"SpellCheckers"_ustr,
122                 u"org.openoffice.lingu.MySpellSpellChecker"_ustr, aFormatList );
123         for (auto const& format : aFormatList)
124         {
125             std::vector< SvtLinguConfigDictionaryEntry > aTmpDic(
126                     aLinguCfg.GetActiveDictionariesByFormat(format) );
127             aDics.insert( aDics.end(), aTmpDic.begin(), aTmpDic.end() );
128         }
129 
130         //!! for compatibility with old dictionaries (the ones not using extensions
131         //!! or new configuration entries, but still using the dictionary.lst file)
132         //!! Get the list of old style spell checking dictionaries to use...
133         std::vector< SvtLinguConfigDictionaryEntry > aOldStyleDics(
134                 GetOldStyleDics( "DICT" ) );
135 
136         // to prefer dictionaries with configuration entries we will only
137         // use those old style dictionaries that add a language that
138         // is not yet supported by the list of new style dictionaries
139         MergeNewStyleDicsAndOldStyleDics( aDics, aOldStyleDics );
140 
141         if (!aDics.empty())
142         {
143             uno::Reference< lang::XMultiServiceFactory > xServiceFactory(comphelper::getProcessServiceFactory());
144             uno::Reference< ucb::XSimpleFileAccess > xAccess(xServiceFactory->createInstance(u"com.sun.star.ucb.SimpleFileAccess"_ustr), uno::UNO_QUERY);
145             // get supported locales from the dictionaries-to-use...
146             std::set<OUString> aLocaleNamesSet;
147             for (auto const& dict : aDics)
148             {
149                 const uno::Sequence< OUString > aLocaleNames( dict.aLocaleNames );
150                 uno::Sequence< OUString > aLocations( dict.aLocations );
151                 SAL_WARN_IF(
152                     aLocaleNames.hasElements() && !aLocations.hasElements(),
153                     "lingucomponent", "no locations");
154                 if (aLocations.hasElements())
155                 {
156                     if (xAccess.is() && xAccess->exists(aLocations[0]))
157                     {
158                         for (auto const& locale : aLocaleNames)
159                         {
160                             if (!comphelper::LibreOfficeKit::isAllowlistedLanguage(locale))
161                                 continue;
162 
163                             aLocaleNamesSet.insert(locale);
164                         }
165                     }
166                     else
167                     {
168                         SAL_WARN(
169                             "lingucomponent",
170                             "missing <" << aLocations[0] << ">");
171                     }
172                 }
173             }
174             // ... and add them to the resulting sequence
175             m_aSuppLocales.realloc( aLocaleNamesSet.size() );
176             std::transform(
177                 aLocaleNamesSet.begin(), aLocaleNamesSet.end(), m_aSuppLocales.getArray(),
178                 [](auto const& localeName) { return LanguageTag::convertToLocale(localeName); });
179 
180             //! For each dictionary and each locale we need a separate entry.
181             //! If this results in more than one dictionary per locale than (for now)
182             //! it is undefined which dictionary gets used.
183             //! In the future the implementation should support using several dictionaries
184             //! for one locale.
185             sal_uInt32 nDictSize = std::accumulate(aDics.begin(), aDics.end(), sal_uInt32(0),
186                 [](const sal_uInt32 nSum, const SvtLinguConfigDictionaryEntry& dict) {
187                     return nSum + dict.aLocaleNames.getLength(); });
188 
189             // add dictionary information
190             m_DictItems.reserve(nDictSize);
191             for (auto const& dict : aDics)
192             {
193                 if (dict.aLocaleNames.hasElements() &&
194                     dict.aLocations.hasElements())
195                 {
196                     const uno::Sequence< OUString > aLocaleNames( dict.aLocaleNames );
197 
198                     // currently only one language per dictionary is supported in the actual implementation...
199                     // Thus here we work-around this by adding the same dictionary several times.
200                     // Once for each of its supported locales.
201                     for (auto const& localeName : aLocaleNames)
202                     {
203                         // also both files have to be in the same directory and the
204                         // file names must only differ in the extension (.aff/.dic).
205                         // Thus we use the first location only and strip the extension part.
206                         OUString aLocation = dict.aLocations[0];
207                         sal_Int32 nPos = aLocation.lastIndexOf( '.' );
208                         aLocation = aLocation.copy( 0, nPos );
209 
210                         m_DictItems.emplace_back(aLocation, LanguageTag::convertToLocale(localeName), RTL_TEXTENCODING_DONTKNOW);
211                     }
212                 }
213             }
214             DBG_ASSERT( nDictSize == m_DictItems.size(), "index mismatch?" );
215         }
216         else
217         {
218             // no dictionary found so register no dictionaries
219             m_aSuppLocales.realloc(0);
220         }
221     }
222 
223     return m_aSuppLocales;
224 }
225 
hasLocale(const Locale & rLocale)226 sal_Bool SAL_CALL SpellChecker::hasLocale(const Locale& rLocale)
227 {
228     MutexGuard  aGuard( GetLinguMutex() );
229 
230     bool bRes = false;
231     if (!m_aSuppLocales.hasElements())
232         getLocales();
233 
234     for (auto const& suppLocale : m_aSuppLocales)
235     {
236         if (rLocale == suppLocale)
237         {
238             bRes = true;
239             break;
240         }
241     }
242     return bRes;
243 }
244 
GetSpellFailure(const OUString & rWord,const Locale & rLocale,int & rInfo)245 sal_Int16 SpellChecker::GetSpellFailure(const OUString &rWord, const Locale &rLocale, int& rInfo)
246 {
247     if (rWord.getLength() > MAXWORDLEN)
248         return -1;
249 
250     Hunspell * pMS = nullptr;
251     rtl_TextEncoding eEnc = RTL_TEXTENCODING_DONTKNOW;
252 
253     // initialize a myspell object for each dictionary once
254     // (note: mutex is held higher up in isValid)
255 
256     sal_Int16 nRes = -1;
257 
258     // first handle smart quotes both single and double
259     OUStringBuffer rBuf(rWord);
260     sal_Int32 n = rBuf.getLength();
261     sal_Unicode c;
262     sal_Int32 extrachar = 0;
263 
264     for (sal_Int32 ix=0; ix < n; ix++)
265     {
266         c = rBuf[ix];
267         if ((c == 0x201C) || (c == 0x201D))
268             rBuf[ix] = u'"';
269         else if ((c == 0x2018) || (c == 0x2019))
270             rBuf[ix] = u'\'';
271 
272         // recognize words with Unicode ligatures and ZWNJ/ZWJ characters (only
273         // with 8-bit encoded dictionaries. For UTF-8 encoded dictionaries
274         // set ICONV and IGNORE aff file options, if needed.)
275         else if ((c == 0x200C) || (c == 0x200D) ||
276             ((c >= 0xFB00) && (c <= 0xFB04)))
277                 extrachar = 1;
278     }
279     OUString nWord(rBuf.makeStringAndClear());
280 
281     if (n)
282     {
283         for (auto& currDict : m_DictItems)
284         {
285             pMS = nullptr;
286             eEnc = RTL_TEXTENCODING_DONTKNOW;
287 
288             if (rLocale == currDict.m_aDLoc)
289             {
290                 if (!currDict.m_pDict)
291                 {
292                     OUString dicpath = currDict.m_aDName + ".dic";
293                     OUString affpath = currDict.m_aDName + ".aff";
294                     OUString dict;
295                     OUString aff;
296                     osl::FileBase::getSystemPathFromFileURL(dicpath,dict);
297                     osl::FileBase::getSystemPathFromFileURL(affpath,aff);
298 #if defined(_WIN32)
299                     // workaround for Windows specific problem that the
300                     // path length in calls to 'fopen' is limited to somewhat
301                     // about 120+ characters which will usually be exceed when
302                     // using dictionaries as extensions. (Hunspell waits UTF-8 encoded
303                     // path with \\?\ long path prefix.)
304                     OString aTmpaff = Win_AddLongPathPrefix(OUStringToOString(aff, RTL_TEXTENCODING_UTF8));
305                     OString aTmpdict = Win_AddLongPathPrefix(OUStringToOString(dict, RTL_TEXTENCODING_UTF8));
306 #else
307                     OString aTmpaff(OU2ENC(aff,osl_getThreadTextEncoding()));
308                     OString aTmpdict(OU2ENC(dict,osl_getThreadTextEncoding()));
309 #endif
310 
311                     currDict.m_pDict = std::make_unique<Hunspell>(aTmpaff.getStr(),aTmpdict.getStr());
312 #if defined(H_DEPRECATED)
313                     currDict.m_aDEnc = getTextEncodingFromCharset(currDict.m_pDict->get_dict_encoding().c_str());
314 #else
315                     currDict.m_aDEnc = getTextEncodingFromCharset(currDict.m_pDict->get_dic_encoding());
316 #endif
317                 }
318                 pMS  = currDict.m_pDict.get();
319                 eEnc = currDict.m_aDEnc;
320             }
321 
322             if (pMS)
323             {
324                 // we don't want to work with a default text encoding since following incorrect
325                 // results may occur only for specific text and thus may be hard to notice.
326                 // Thus better always make a clean exit here if the text encoding is in question.
327                 // Hopefully something not working at all will raise proper attention quickly. ;-)
328                 DBG_ASSERT( eEnc != RTL_TEXTENCODING_DONTKNOW, "failed to get text encoding! (maybe incorrect encoding string in file)" );
329                 if (eEnc == RTL_TEXTENCODING_DONTKNOW)
330                     return -1;
331 
332                 OString aWrd(OU2ENC(nWord,eEnc));
333 #if defined(H_DEPRECATED)
334                 bool bVal = pMS->spell(std::string(aWrd), &rInfo);
335 #else
336                 bool bVal = pMS->spell(aWrd.getStr(), &rInfo) != 0;
337 #endif
338                 if (!bVal) {
339                     if (extrachar && (eEnc != RTL_TEXTENCODING_UTF8)) {
340                         OUStringBuffer aBuf(nWord);
341                         n = aBuf.getLength();
342                         for (sal_Int32 ix=n-1; ix >= 0; ix--)
343                         {
344                           switch (aBuf[ix]) {
345                             case 0xFB00: aBuf.remove(ix, 1); aBuf.insert(ix, "ff"); break;
346                             case 0xFB01: aBuf.remove(ix, 1); aBuf.insert(ix, "fi"); break;
347                             case 0xFB02: aBuf.remove(ix, 1); aBuf.insert(ix, "fl"); break;
348                             case 0xFB03: aBuf.remove(ix, 1); aBuf.insert(ix, "ffi"); break;
349                             case 0xFB04: aBuf.remove(ix, 1); aBuf.insert(ix, "ffl"); break;
350                             case 0x200C:
351                             case 0x200D: aBuf.remove(ix, 1); break;
352                           }
353                         }
354                         OUString aWord(aBuf.makeStringAndClear());
355                         OString bWrd(OU2ENC(aWord, eEnc));
356 #if defined(H_DEPRECATED)
357                         bVal = pMS->spell(std::string(bWrd), &rInfo);
358 #else
359                         bVal = pMS->spell(bWrd.getStr(), &rInfo) != 0;
360 #endif
361                         if (bVal) return -1;
362                     }
363                     nRes = SpellFailure::SPELLING_ERROR;
364                 } else {
365                     return -1;
366                 }
367                 pMS = nullptr;
368             }
369         }
370     }
371 
372     return nRes;
373 }
374 
isValid(const OUString & rWord,const Locale & rLocale,const css::uno::Sequence<css::beans::PropertyValue> & rProperties)375 sal_Bool SAL_CALL SpellChecker::isValid( const OUString& rWord, const Locale& rLocale,
376             const css::uno::Sequence< css::beans::PropertyValue >& rProperties )
377 {
378     MutexGuard  aGuard( GetLinguMutex() );
379 
380     if (rLocale == Locale()  ||  rWord.isEmpty())
381         return true;
382 
383     if (!hasLocale( rLocale ))
384         return true;
385 
386     // return sal_False to process SPELLML requests (they are longer than the header)
387     if (rWord.match(SPELL_XML, 0) && (rWord.getLength() > 10)) return false;
388 
389     // Get property values to be used.
390     // These are be the default values set in the SN_LINGU_PROPERTIES
391     // PropertySet which are overridden by the supplied ones from the
392     // last argument.
393     // You'll probably like to use a simpler solution than the provided
394     // one using the PropertyHelper_Spell.
395     PropertyHelper_Spelling& rHelper = GetPropHelper();
396     rHelper.SetTmpPropVals( rProperties );
397 
398     int nInfo = 0;
399     sal_Int16 nFailure = GetSpellFailure( rWord, rLocale, nInfo );
400     if (nFailure != -1 && !rWord.match(SPELL_XML, 0))
401     {
402         LanguageType nLang = LinguLocaleToLanguage( rLocale );
403         // postprocess result for errors that should be ignored
404         const bool bIgnoreError =
405                 (!rHelper.IsSpellUpperCase()  && IsUpper( rWord, nLang )) ||
406                 (!rHelper.IsSpellWithDigits() && HasDigits( rWord ));
407         if (bIgnoreError)
408             nFailure = -1;
409     }
410 //#define SPELL_COMPOUND 1 << 0
411 
412     // valid word, but it's a rule-based compound word
413     if ( nFailure == -1 && (nInfo & SPELL_COMPOUND) )
414     {
415         bool bHasHyphen = rWord.indexOf('-') > -1;
416         if ( (bHasHyphen && !rHelper.IsSpellHyphenatedCompound()) ||
417              (!bHasHyphen && !rHelper.IsSpellClosedCompound()) )
418         {
419             return false;
420         }
421     }
422 
423     return (nFailure == -1);
424 }
425 
426 Reference< XSpellAlternatives >
GetProposals(const OUString & rWord,const Locale & rLocale)427     SpellChecker::GetProposals( const OUString &rWord, const Locale &rLocale )
428 {
429     // Retrieves the return values for the 'spell' function call in case
430     // of a misspelled word.
431     // Especially it may give a list of suggested (correct) words:
432     Reference< XSpellAlternatives > xRes;
433     // note: mutex is held by higher up by spell which covers both
434 
435     Hunspell* pMS = nullptr;
436     rtl_TextEncoding eEnc = RTL_TEXTENCODING_DONTKNOW;
437 
438     // first handle smart quotes (single and double)
439     OUStringBuffer rBuf(rWord);
440     sal_Int32 n = rBuf.getLength();
441     sal_Unicode c;
442     for (sal_Int32 ix=0; ix < n; ix++)
443     {
444         c = rBuf[ix];
445         if ((c == 0x201C) || (c == 0x201D))
446             rBuf[ix] = u'"';
447         if ((c == 0x2018) || (c == 0x2019))
448             rBuf[ix] = u'\'';
449     }
450     OUString nWord(rBuf.makeStringAndClear());
451 
452     if (n)
453     {
454         LanguageType nLang = LinguLocaleToLanguage( rLocale );
455         int numsug = 0;
456 
457         Sequence< OUString > aStr( 0 );
458         for (const auto& currDict : m_DictItems)
459         {
460             pMS = nullptr;
461             eEnc = RTL_TEXTENCODING_DONTKNOW;
462 
463             if (rLocale == currDict.m_aDLoc)
464             {
465                 pMS  = currDict.m_pDict.get();
466                 eEnc = currDict.m_aDEnc;
467             }
468 
469             if (pMS)
470             {
471                 OString aWrd(OU2ENC(nWord,eEnc));
472 #if defined(H_DEPRECATED)
473                 std::vector<std::string> suglst = pMS->suggest(std::string(aWrd));
474                 if (!suglst.empty())
475                 {
476                     aStr.realloc(numsug + suglst.size());
477                     OUString *pStr = aStr.getArray();
478                     for (size_t ii = 0; ii < suglst.size(); ++ii)
479                     {
480                         pStr[numsug + ii] = OUString(suglst[ii].c_str(), suglst[ii].size(), eEnc);
481                     }
482                     numsug += suglst.size();
483                 }
484 #else
485                 char ** suglst = nullptr;
486                 int count = pMS->suggest(&suglst, aWrd.getStr());
487                 if (count)
488                 {
489                     aStr.realloc( numsug + count );
490                     OUString *pStr = aStr.getArray();
491                     for (int ii=0; ii < count; ++ii)
492                     {
493                         OUString cvtwrd(suglst[ii],strlen(suglst[ii]),eEnc);
494                         pStr[numsug + ii] = cvtwrd;
495                     }
496                     numsug += count;
497                 }
498                 pMS->free_list(&suglst, count);
499 #endif
500             }
501         }
502 
503         // now return an empty alternative for no suggestions or the list of alternatives if some found
504         xRes = SpellAlternatives::CreateSpellAlternatives( rWord, nLang, SpellFailure::SPELLING_ERROR, aStr );
505         return xRes;
506     }
507     return xRes;
508 }
509 
spell(const OUString & rWord,const Locale & rLocale,const css::uno::Sequence<css::beans::PropertyValue> & rProperties)510 Reference< XSpellAlternatives > SAL_CALL SpellChecker::spell(
511         const OUString& rWord, const Locale& rLocale,
512         const css::uno::Sequence< css::beans::PropertyValue >& rProperties )
513 {
514     MutexGuard  aGuard( GetLinguMutex() );
515 
516     if (rLocale == Locale()  ||  rWord.isEmpty())
517         return nullptr;
518 
519     if (!hasLocale( rLocale ))
520         return nullptr;
521 
522     Reference< XSpellAlternatives > xAlt;
523     if (!isValid( rWord, rLocale, rProperties ))
524     {
525         xAlt =  GetProposals( rWord, rLocale );
526     }
527     return xAlt;
528 }
529 
addLinguServiceEventListener(const Reference<XLinguServiceEventListener> & rxLstnr)530 sal_Bool SAL_CALL SpellChecker::addLinguServiceEventListener(
531         const Reference< XLinguServiceEventListener >& rxLstnr )
532 {
533     MutexGuard  aGuard( GetLinguMutex() );
534 
535     bool bRes = false;
536     if (!m_bDisposing && rxLstnr.is())
537     {
538         bRes = GetPropHelper().addLinguServiceEventListener( rxLstnr );
539     }
540     return bRes;
541 }
542 
removeLinguServiceEventListener(const Reference<XLinguServiceEventListener> & rxLstnr)543 sal_Bool SAL_CALL SpellChecker::removeLinguServiceEventListener(
544         const Reference< XLinguServiceEventListener >& rxLstnr )
545 {
546     MutexGuard  aGuard( GetLinguMutex() );
547 
548     bool bRes = false;
549     if (!m_bDisposing && rxLstnr.is())
550     {
551         bRes = GetPropHelper().removeLinguServiceEventListener( rxLstnr );
552     }
553     return bRes;
554 }
555 
getServiceDisplayName(const Locale & rLocale)556 OUString SAL_CALL SpellChecker::getServiceDisplayName(const Locale& rLocale)
557 {
558     std::locale loc(Translate::Create("svt", LanguageTag(rLocale)));
559     return Translate::get(STR_DESCRIPTION_HUNSPELL, loc);
560 }
561 
initialize(const Sequence<Any> & rArguments)562 void SAL_CALL SpellChecker::initialize( const Sequence< Any >& rArguments )
563 {
564     MutexGuard  aGuard( GetLinguMutex() );
565 
566     if (m_pPropHelper)
567         return;
568 
569     sal_Int32 nLen = rArguments.getLength();
570     if (2 == nLen)
571     {
572         Reference< XLinguProperties >   xPropSet;
573         rArguments.getConstArray()[0] >>= xPropSet;
574         // rArguments.getConstArray()[1] >>= xDicList;
575 
576         //! Pointer allows for access of the non-UNO functions.
577         //! And the reference to the UNO-functions while increasing
578         //! the ref-count and will implicitly free the memory
579         //! when the object is no longer used.
580         m_pPropHelper.reset( new PropertyHelper_Spelling( static_cast<XSpellChecker *>(this), xPropSet ) );
581         m_pPropHelper->AddAsPropListener();   //! after a reference is established
582     }
583     else {
584         OSL_FAIL( "wrong number of arguments in sequence" );
585     }
586 }
587 
dispose()588 void SAL_CALL SpellChecker::dispose()
589 {
590     MutexGuard  aGuard( GetLinguMutex() );
591 
592     if (!m_bDisposing)
593     {
594         m_bDisposing = true;
595         EventObject aEvtObj( static_cast<XSpellChecker *>(this) );
596         m_aEvtListeners.disposeAndClear( aEvtObj );
597         if (m_pPropHelper)
598         {
599             m_pPropHelper->RemoveAsPropListener();
600             m_pPropHelper.reset();
601         }
602     }
603 }
604 
addEventListener(const Reference<XEventListener> & rxListener)605 void SAL_CALL SpellChecker::addEventListener( const Reference< XEventListener >& rxListener )
606 {
607     MutexGuard  aGuard( GetLinguMutex() );
608 
609     if (!m_bDisposing && rxListener.is())
610         m_aEvtListeners.addInterface( rxListener );
611 }
612 
removeEventListener(const Reference<XEventListener> & rxListener)613 void SAL_CALL SpellChecker::removeEventListener( const Reference< XEventListener >& rxListener )
614 {
615     MutexGuard  aGuard( GetLinguMutex() );
616 
617     if (!m_bDisposing && rxListener.is())
618         m_aEvtListeners.removeInterface( rxListener );
619 }
620 
621 // Service specific part
getImplementationName()622 OUString SAL_CALL SpellChecker::getImplementationName()
623 {
624     return u"org.openoffice.lingu.MySpellSpellChecker"_ustr;
625 }
626 
supportsService(const OUString & ServiceName)627 sal_Bool SAL_CALL SpellChecker::supportsService( const OUString& ServiceName )
628 {
629     return cppu::supportsService(this, ServiceName);
630 }
631 
getSupportedServiceNames()632 Sequence< OUString > SAL_CALL SpellChecker::getSupportedServiceNames()
633 {
634     return { SN_SPELLCHECKER };
635 }
636 
637 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
lingucomponent_SpellChecker_get_implementation(css::uno::XComponentContext *,css::uno::Sequence<css::uno::Any> const &)638 lingucomponent_SpellChecker_get_implementation(
639     css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
640 {
641     return cppu::acquire(new SpellChecker());
642 }
643 
644 
645 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
646