Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / linguistic / source / lngsvcmgr.cxx
blob5dd758ebbf8d1b8de8e616fab999f3c298acba02
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 <sal/config.h>
21 #include <sal/log.hxx>
23 #include <com/sun/star/deployment/DeploymentException.hpp>
24 #include <com/sun/star/deployment/ExtensionManager.hpp>
25 #include <com/sun/star/container/XContentEnumerationAccess.hpp>
26 #include <com/sun/star/container/XEnumeration.hpp>
27 #include <com/sun/star/lang/XSingleComponentFactory.hpp>
28 #include <com/sun/star/lang/XSingleServiceFactory.hpp>
29 #include <com/sun/star/linguistic2/XSupportedLocales.hpp>
30 #include <com/sun/star/linguistic2/DictionaryListEventFlags.hpp>
31 #include <com/sun/star/linguistic2/LinguServiceEventFlags.hpp>
32 #include <com/sun/star/linguistic2/ProofreadingIterator.hpp>
34 #include <tools/debug.hxx>
35 #include <unotools/lingucfg.hxx>
36 #include <vcl/svapp.hxx>
37 #include <comphelper/processfactory.hxx>
38 #include <comphelper/sequence.hxx>
39 #include <i18nlangtag/lang.h>
40 #include <i18nlangtag/languagetag.hxx>
41 #include <cppuhelper/factory.hxx>
42 #include <cppuhelper/implbase.hxx>
43 #include <cppuhelper/supportsservice.hxx>
44 #include <cppuhelper/weak.hxx>
46 #include "lngsvcmgr.hxx"
47 #include <linguistic/misc.hxx>
48 #include "spelldsp.hxx"
49 #include "hyphdsp.hxx"
50 #include "thesdsp.hxx"
51 #include "gciterator.hxx"
53 using namespace com::sun::star;
54 using namespace linguistic;
56 uno::Sequence< OUString > static GetLangSvcList( const uno::Any &rVal );
57 uno::Sequence< OUString > static GetLangSvc( const uno::Any &rVal );
59 static bool lcl_SeqHasString( const uno::Sequence< OUString > &rSeq, const OUString &rText )
61 return !rText.isEmpty()
62 && comphelper::findValue(rSeq, rText) != -1;
66 static uno::Sequence< lang::Locale > GetAvailLocales(
67 const uno::Sequence< OUString > &rSvcImplNames )
69 uno::Sequence< lang::Locale > aRes;
71 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
72 if( rSvcImplNames.hasElements() )
74 std::set< LanguageType > aLanguages;
76 // All of these services only use one arg, but need two args for compat reasons
77 uno::Sequence< uno::Any > aArgs(2);
78 aArgs.getArray()[0] <<= GetLinguProperties();
80 // check all services for the supported languages and new
81 // languages to the result
83 for (const OUString& rImplName : rSvcImplNames)
85 uno::Reference< linguistic2::XSupportedLocales > xSuppLoc;
86 try
88 xSuppLoc.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(
89 rImplName, aArgs, xContext ),
90 uno::UNO_QUERY );
92 catch (uno::Exception &)
94 SAL_WARN( "linguistic", "createInstanceWithArguments failed" );
97 if (xSuppLoc.is())
99 const uno::Sequence< lang::Locale > aLoc( xSuppLoc->getLocales() );
100 for (const lang::Locale& rLoc : aLoc)
102 LanguageType nLang = LinguLocaleToLanguage( rLoc );
104 // It's a set, so insertion fails if language was already added.
105 aLanguages.insert( nLang );
108 else
110 SAL_WARN( "linguistic", "interface not supported by service" );
114 // build return sequence
115 std::vector<lang::Locale> aVec;
116 aVec.reserve(aLanguages.size());
118 std::transform(aLanguages.begin(), aLanguages.end(), std::back_inserter(aVec),
119 [](const LanguageType& rLang) -> lang::Locale { return LanguageTag::convertToLocale(rLang); });
121 aRes = comphelper::containerToSequence(aVec);
124 return aRes;
128 struct SvcInfo
130 const OUString aSvcImplName;
131 const std::vector< LanguageType > aSuppLanguages;
133 SvcInfo( const OUString &rSvcImplName,
134 const std::vector< LanguageType > &rSuppLanguages ) :
135 aSvcImplName (rSvcImplName),
136 aSuppLanguages (rSuppLanguages)
140 bool HasLanguage( LanguageType nLanguage ) const;
144 bool SvcInfo::HasLanguage( LanguageType nLanguage ) const
146 for ( auto const & i : aSuppLanguages)
148 if (nLanguage == i)
149 return true;
151 return false;
154 class LngSvcMgrListenerHelper :
155 public cppu::WeakImplHelper
157 linguistic2::XLinguServiceEventListener,
158 linguistic2::XDictionaryListEventListener
161 LngSvcMgr &rMyManager;
163 ::comphelper::OInterfaceContainerHelper2 aLngSvcMgrListeners;
164 ::comphelper::OInterfaceContainerHelper2 aLngSvcEvtBroadcasters;
165 uno::Reference< linguistic2::XSearchableDictionaryList > xDicList;
167 sal_Int16 nCombinedLngSvcEvt;
169 void LaunchEvent( sal_Int16 nLngSvcEvtFlags );
171 void Timeout();
173 public:
174 LngSvcMgrListenerHelper( LngSvcMgr &rLngSvcMgr,
175 const uno::Reference< linguistic2::XSearchableDictionaryList > &rxDicList );
177 LngSvcMgrListenerHelper(const LngSvcMgrListenerHelper&) = delete;
178 LngSvcMgrListenerHelper& operator=(const LngSvcMgrListenerHelper&) = delete;
180 // lang::XEventListener
181 virtual void SAL_CALL
182 disposing( const lang::EventObject& rSource ) override;
184 // linguistic2::XLinguServiceEventListener
185 virtual void SAL_CALL
186 processLinguServiceEvent( const linguistic2::LinguServiceEvent& aLngSvcEvent ) override;
188 // linguistic2::XDictionaryListEventListener
189 virtual void SAL_CALL
190 processDictionaryListEvent(
191 const linguistic2::DictionaryListEvent& rDicListEvent ) override;
193 inline void AddLngSvcMgrListener(
194 const uno::Reference< lang::XEventListener >& rxListener );
195 inline void RemoveLngSvcMgrListener(
196 const uno::Reference< lang::XEventListener >& rxListener );
197 void DisposeAndClear( const lang::EventObject &rEvtObj );
198 void AddLngSvcEvtBroadcaster(
199 const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster );
200 void RemoveLngSvcEvtBroadcaster(
201 const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster );
203 void AddLngSvcEvt( sal_Int16 nLngSvcEvt );
207 LngSvcMgrListenerHelper::LngSvcMgrListenerHelper(
208 LngSvcMgr &rLngSvcMgr,
209 const uno::Reference< linguistic2::XSearchableDictionaryList > &rxDicList ) :
210 rMyManager ( rLngSvcMgr ),
211 aLngSvcMgrListeners ( GetLinguMutex() ),
212 aLngSvcEvtBroadcasters ( GetLinguMutex() ),
213 xDicList ( rxDicList )
215 if (xDicList.is())
217 xDicList->addDictionaryListEventListener(
218 static_cast<linguistic2::XDictionaryListEventListener *>(this), false );
221 nCombinedLngSvcEvt = 0;
225 void SAL_CALL LngSvcMgrListenerHelper::disposing( const lang::EventObject& rSource )
227 osl::MutexGuard aGuard( GetLinguMutex() );
229 uno::Reference< uno::XInterface > xRef( rSource.Source );
230 if ( xRef.is() )
232 aLngSvcMgrListeners .removeInterface( xRef );
233 aLngSvcEvtBroadcasters.removeInterface( xRef );
234 if (xDicList == xRef)
235 xDicList = nullptr;
239 void LngSvcMgrListenerHelper::Timeout()
241 osl::MutexGuard aGuard( GetLinguMutex() );
244 // change event source to LinguServiceManager since the listeners
245 // probably do not know (and need not to know) about the specific
246 // SpellChecker's or Hyphenator's.
247 linguistic2::LinguServiceEvent aEvtObj(
248 static_cast<css::linguistic2::XLinguServiceManager*>(&rMyManager), nCombinedLngSvcEvt );
249 nCombinedLngSvcEvt = 0;
251 if (rMyManager.mxSpellDsp.is())
252 rMyManager.mxSpellDsp->FlushSpellCache();
254 // pass event on to linguistic2::XLinguServiceEventListener's
255 aLngSvcMgrListeners.notifyEach( &linguistic2::XLinguServiceEventListener::processLinguServiceEvent, aEvtObj );
260 void LngSvcMgrListenerHelper::AddLngSvcEvt( sal_Int16 nLngSvcEvt )
262 nCombinedLngSvcEvt |= nLngSvcEvt;
263 Timeout();
267 void SAL_CALL
268 LngSvcMgrListenerHelper::processLinguServiceEvent(
269 const linguistic2::LinguServiceEvent& rLngSvcEvent )
271 osl::MutexGuard aGuard( GetLinguMutex() );
272 AddLngSvcEvt( rLngSvcEvent.nEvent );
276 void SAL_CALL
277 LngSvcMgrListenerHelper::processDictionaryListEvent(
278 const linguistic2::DictionaryListEvent& rDicListEvent )
280 osl::MutexGuard aGuard( GetLinguMutex() );
282 sal_Int16 nDlEvt = rDicListEvent.nCondensedEvent;
283 if (0 == nDlEvt)
284 return;
286 // we do keep the original event source here though...
288 // pass event on to linguistic2::XDictionaryListEventListener's
289 aLngSvcMgrListeners.notifyEach( &linguistic2::XDictionaryListEventListener::processDictionaryListEvent, rDicListEvent );
291 // "translate" DictionaryList event into linguistic2::LinguServiceEvent
292 sal_Int16 nLngSvcEvt = 0;
293 sal_Int16 const nSpellCorrectFlags =
294 linguistic2::DictionaryListEventFlags::ADD_NEG_ENTRY |
295 linguistic2::DictionaryListEventFlags::DEL_POS_ENTRY |
296 linguistic2::DictionaryListEventFlags::ACTIVATE_NEG_DIC |
297 linguistic2::DictionaryListEventFlags::DEACTIVATE_POS_DIC;
298 if (0 != (nDlEvt & nSpellCorrectFlags))
299 nLngSvcEvt |= linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN;
301 sal_Int16 const nSpellWrongFlags =
302 linguistic2::DictionaryListEventFlags::ADD_POS_ENTRY |
303 linguistic2::DictionaryListEventFlags::DEL_NEG_ENTRY |
304 linguistic2::DictionaryListEventFlags::ACTIVATE_POS_DIC |
305 linguistic2::DictionaryListEventFlags::DEACTIVATE_NEG_DIC;
306 if (0 != (nDlEvt & nSpellWrongFlags))
307 nLngSvcEvt |= linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN;
309 sal_Int16 const nHyphenateFlags =
310 linguistic2::DictionaryListEventFlags::ADD_POS_ENTRY |
311 linguistic2::DictionaryListEventFlags::DEL_POS_ENTRY |
312 linguistic2::DictionaryListEventFlags::ACTIVATE_POS_DIC |
313 linguistic2::DictionaryListEventFlags::ACTIVATE_NEG_DIC;
314 if (0 != (nDlEvt & nHyphenateFlags))
315 nLngSvcEvt |= linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN;
317 if (rMyManager.mxSpellDsp.is())
318 rMyManager.mxSpellDsp->FlushSpellCache();
319 if (nLngSvcEvt)
320 LaunchEvent( nLngSvcEvt );
324 void LngSvcMgrListenerHelper::LaunchEvent( sal_Int16 nLngSvcEvtFlags )
326 linguistic2::LinguServiceEvent aEvt(
327 static_cast<css::linguistic2::XLinguServiceManager*>(&rMyManager), nLngSvcEvtFlags );
329 // pass event on to linguistic2::XLinguServiceEventListener's
330 aLngSvcMgrListeners.notifyEach( &linguistic2::XLinguServiceEventListener::processLinguServiceEvent, aEvt );
334 inline void LngSvcMgrListenerHelper::AddLngSvcMgrListener(
335 const uno::Reference< lang::XEventListener >& rxListener )
337 aLngSvcMgrListeners.addInterface( rxListener );
341 inline void LngSvcMgrListenerHelper::RemoveLngSvcMgrListener(
342 const uno::Reference< lang::XEventListener >& rxListener )
344 aLngSvcMgrListeners.removeInterface( rxListener );
348 void LngSvcMgrListenerHelper::DisposeAndClear( const lang::EventObject &rEvtObj )
350 // call "disposing" for all listeners and clear list
351 aLngSvcMgrListeners .disposeAndClear( rEvtObj );
353 // remove references to this object hold by the broadcasters
354 comphelper::OInterfaceIteratorHelper2 aIt( aLngSvcEvtBroadcasters );
355 while (aIt.hasMoreElements())
357 uno::Reference< linguistic2::XLinguServiceEventBroadcaster > xRef( aIt.next(), uno::UNO_QUERY );
358 if (xRef.is())
359 RemoveLngSvcEvtBroadcaster( xRef );
362 // remove reference to this object hold by the dictionary-list
363 if (xDicList.is())
365 xDicList->removeDictionaryListEventListener(
366 static_cast<linguistic2::XDictionaryListEventListener *>(this) );
367 xDicList = nullptr;
372 void LngSvcMgrListenerHelper::AddLngSvcEvtBroadcaster(
373 const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
375 if (rxBroadcaster.is())
377 aLngSvcEvtBroadcasters.addInterface( rxBroadcaster );
378 rxBroadcaster->addLinguServiceEventListener(
379 static_cast<linguistic2::XLinguServiceEventListener *>(this) );
384 void LngSvcMgrListenerHelper::RemoveLngSvcEvtBroadcaster(
385 const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
387 if (rxBroadcaster.is())
389 aLngSvcEvtBroadcasters.removeInterface( rxBroadcaster );
390 rxBroadcaster->removeLinguServiceEventListener(
391 static_cast<linguistic2::XLinguServiceEventListener *>(this) );
396 LngSvcMgr::LngSvcMgr()
397 : utl::ConfigItem("Office.Linguistic")
398 , aEvtListeners(GetLinguMutex())
400 bDisposing = false;
402 // request notify events when properties (i.e. something in the subtree) changes
403 uno::Sequence< OUString > aNames{
404 "ServiceManager/SpellCheckerList",
405 "ServiceManager/GrammarCheckerList",
406 "ServiceManager/HyphenatorList",
407 "ServiceManager/ThesaurusList"
409 EnableNotification( aNames );
411 UpdateAll();
413 aUpdateIdle.SetPriority(TaskPriority::LOWEST);
414 aUpdateIdle.SetInvokeHandler(LINK(this, LngSvcMgr, updateAndBroadcast));
416 // request to be notified if an extension has been added/removed
417 uno::Reference<uno::XComponentContext> xContext(comphelper::getProcessComponentContext());
419 uno::Reference<deployment::XExtensionManager> xExtensionManager;
420 try {
421 xExtensionManager = deployment::ExtensionManager::get(xContext);
422 } catch ( const uno::DeploymentException & ) {
423 SAL_WARN( "linguistic", "no extension manager - should fire on mobile only" );
424 } catch ( const deployment::DeploymentException & ) {
425 SAL_WARN( "linguistic", "no extension manager - should fire on mobile only" );
427 if (xExtensionManager.is())
429 xMB.set(xExtensionManager, uno::UNO_QUERY_THROW);
431 uno::Reference<util::XModifyListener> xListener(this);
432 xMB->addModifyListener( xListener );
436 // css::util::XModifyListener
437 void LngSvcMgr::modified(const lang::EventObject&)
440 osl::MutexGuard aGuard(GetLinguMutex());
441 //assume that if an extension has been added/removed that
442 //it might be a dictionary extension, so drop our cache
444 pAvailSpellSvcs.reset();
445 pAvailGrammarSvcs.reset();
446 pAvailHyphSvcs.reset();
447 pAvailThesSvcs.reset();
451 SolarMutexGuard aGuard;
452 //schedule in an update to execute in the main thread
453 aUpdateIdle.Start();
457 //run update, and inform everyone that dictionaries (may) have changed, this
458 //needs to be run in the main thread because
459 //utl::ConfigChangeListener_Impl::changesOccurred grabs the SolarMutex and we
460 //get notified that an extension was added from an extension manager thread
461 IMPL_LINK_NOARG(LngSvcMgr, updateAndBroadcast, Timer *, void)
463 osl::MutexGuard aGuard( GetLinguMutex() );
465 UpdateAll();
467 if (mxListenerHelper.is())
469 mxListenerHelper->AddLngSvcEvt(
470 linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN |
471 linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN |
472 linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN |
473 linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN );
477 void LngSvcMgr::stopListening()
479 osl::MutexGuard aGuard(GetLinguMutex());
481 if (!xMB.is())
482 return;
486 uno::Reference<util::XModifyListener> xListener(this);
487 xMB->removeModifyListener(xListener);
489 catch (const uno::Exception&)
493 xMB.clear();
496 void LngSvcMgr::disposing(const lang::EventObject&)
498 stopListening();
501 LngSvcMgr::~LngSvcMgr()
503 stopListening();
505 // memory for pSpellDsp, pHyphDsp, pThesDsp, pListenerHelper
506 // will be freed in the destructor of the respective Reference's
507 // xSpellDsp, xGrammarDsp, xHyphDsp, xThesDsp
509 pAvailSpellSvcs.reset();
510 pAvailGrammarSvcs.reset();
511 pAvailHyphSvcs.reset();
512 pAvailThesSvcs.reset();
515 namespace
517 using lang::Locale;
518 using uno::Any;
519 using uno::Sequence;
521 bool lcl_FindEntry( const OUString &rEntry, const Sequence< OUString > &rCfgSvcs )
523 return comphelper::findValue(rCfgSvcs, rEntry) != -1;
526 bool lcl_FindEntry( const OUString &rEntry, const std::vector< OUString > &rCfgSvcs )
528 return std::find(rCfgSvcs.begin(), rCfgSvcs.end(), rEntry) != rCfgSvcs.end();
531 Sequence< OUString > lcl_GetLastFoundSvcs(
532 SvtLinguConfig const &rCfg,
533 const OUString &rLastFoundList ,
534 const OUString& rCfgLocaleStr )
536 Sequence< OUString > aRes;
538 Sequence< OUString > aNodeNames( rCfg.GetNodeNames(rLastFoundList) );
539 bool bFound = lcl_FindEntry( rCfgLocaleStr, aNodeNames);
541 if (bFound)
543 Sequence< OUString > aNames { rLastFoundList + "/" + rCfgLocaleStr };
544 Sequence< Any > aValues( rCfg.GetProperties( aNames ) );
545 if (aValues.hasElements())
547 SAL_WARN_IF( aValues.getLength() != 1, "linguistic", "unexpected length of sequence" );
548 Sequence< OUString > aSvcImplNames;
549 if (aValues.getConstArray()[0] >>= aSvcImplNames)
550 aRes = aSvcImplNames;
551 else
553 SAL_WARN( "linguistic", "type mismatch" );
558 return aRes;
561 Sequence< OUString > lcl_RemoveMissingEntries(
562 const Sequence< OUString > &rCfgSvcs,
563 const Sequence< OUString > &rAvailSvcs )
565 std::vector<OUString> aRes;
566 aRes.reserve(rCfgSvcs.getLength());
568 std::copy_if(rCfgSvcs.begin(), rCfgSvcs.end(), std::back_inserter(aRes),
569 [&rAvailSvcs](const OUString& entry) { return lcl_SeqHasString(rAvailSvcs, entry); });
571 return comphelper::containerToSequence(aRes);
574 Sequence< OUString > lcl_GetNewEntries(
575 const Sequence< OUString > &rLastFoundSvcs,
576 const Sequence< OUString > &rAvailSvcs )
578 std::vector<OUString> aRes;
579 aRes.reserve(rAvailSvcs.getLength());
581 std::copy_if(rAvailSvcs.begin(), rAvailSvcs.end(), std::back_inserter(aRes),
582 [&rLastFoundSvcs](const OUString& rEntry) {
583 return !rEntry.isEmpty() && !lcl_FindEntry( rEntry, rLastFoundSvcs ); });
585 return comphelper::containerToSequence(aRes);
588 Sequence< OUString > lcl_MergeSeq(
589 const Sequence< OUString > &rCfgSvcs,
590 const Sequence< OUString > &rNewSvcs )
592 std::vector<OUString> aRes;
593 aRes.reserve(rCfgSvcs.getLength() + rNewSvcs.getLength());
595 auto lVecNotHasString = [&aRes](const OUString& rEntry)
596 { return !rEntry.isEmpty() && !lcl_FindEntry(rEntry, aRes); };
598 // add previously configured service first and append
599 // new found services at the end
600 for (const Sequence< OUString > &rSeq : { rCfgSvcs, rNewSvcs })
602 std::copy_if(rSeq.begin(), rSeq.end(), std::back_inserter(aRes), lVecNotHasString);
605 return comphelper::containerToSequence(aRes);
609 void LngSvcMgr::UpdateAll()
611 using beans::PropertyValue;
612 using lang::Locale;
613 using uno::Sequence;
615 typedef std::map< OUString, Sequence< OUString > > list_entry_map_t;
617 SvtLinguConfig aCfg;
619 const int nNumServices = 4;
620 const char * const apServices[nNumServices] = { SN_SPELLCHECKER, SN_GRAMMARCHECKER, SN_HYPHENATOR, SN_THESAURUS };
621 const char * const apCurLists[nNumServices] = { "ServiceManager/SpellCheckerList", "ServiceManager/GrammarCheckerList", "ServiceManager/HyphenatorList", "ServiceManager/ThesaurusList" };
622 const char * const apLastFoundLists[nNumServices] = { "ServiceManager/LastFoundSpellCheckers", "ServiceManager/LastFoundGrammarCheckers", "ServiceManager/LastFoundHyphenators", "ServiceManager/LastFoundThesauri" };
624 // usage of indices as above: 0 = spell checker, 1 = grammar checker, 2 = hyphenator, 3 = thesaurus
625 std::vector< list_entry_map_t > aLastFoundSvcs(nNumServices);
626 std::vector< list_entry_map_t > aCurSvcs(nNumServices);
628 for (int k = 0; k < nNumServices; ++k)
630 OUString aService( OUString::createFromAscii( apServices[k] ) );
631 OUString aActiveList( OUString::createFromAscii( apCurLists[k] ) );
632 OUString aLastFoundList( OUString::createFromAscii( apLastFoundLists[k] ) );
635 // remove configured but not available language/services entries
637 const Sequence< OUString > aNodeNames( aCfg.GetNodeNames( aActiveList ) ); // list of configured locales
638 for (const OUString& rNodeName : aNodeNames)
640 Locale aLocale( LanguageTag::convertToLocale( rNodeName));
641 Sequence< OUString > aCfgSvcs( getConfiguredServices( aService, aLocale ));
642 Sequence< OUString > aAvailSvcs( getAvailableServices( aService, aLocale ));
644 aCfgSvcs = lcl_RemoveMissingEntries( aCfgSvcs, aAvailSvcs );
646 aCurSvcs[k][ rNodeName ] = aCfgSvcs;
650 // add new available language/service entries
651 // and
652 // set last found services to currently available ones
654 const Sequence< Locale > aAvailLocales( getAvailableLocales(aService) );
655 for (const Locale& rAvailLocale : aAvailLocales)
657 OUString aCfgLocaleStr( LanguageTag::convertToBcp47( rAvailLocale));
659 Sequence< OUString > aAvailSvcs( getAvailableServices( aService, rAvailLocale ));
661 aLastFoundSvcs[k][ aCfgLocaleStr ] = aAvailSvcs;
663 Sequence< OUString > aLastSvcs(
664 lcl_GetLastFoundSvcs( aCfg, aLastFoundList , aCfgLocaleStr ));
665 Sequence< OUString > aNewSvcs =
666 lcl_GetNewEntries( aLastSvcs, aAvailSvcs );
668 Sequence< OUString > aCfgSvcs( aCurSvcs[k][ aCfgLocaleStr ] );
670 // merge services list (previously configured to be listed first).
671 aCfgSvcs = lcl_MergeSeq( aCfgSvcs, aNewSvcs );
673 aCurSvcs[k][ aCfgLocaleStr ] = aCfgSvcs;
678 // write new data back to configuration
680 for (int k = 0; k < nNumServices; ++k)
682 for (int i = 0; i < 2; ++i)
684 const char *pSubNodeName = (i == 0) ? apCurLists[k] : apLastFoundLists[k];
685 OUString aSubNodeName( OUString::createFromAscii(pSubNodeName) );
687 list_entry_map_t &rCurMap = (i == 0) ? aCurSvcs[k] : aLastFoundSvcs[k];
688 sal_Int32 nVals = static_cast< sal_Int32 >( rCurMap.size() );
689 Sequence< PropertyValue > aNewValues( nVals );
690 PropertyValue *pNewValue = aNewValues.getArray();
691 for (auto const& elem : rCurMap)
693 pNewValue->Name = aSubNodeName + "/" + elem.first;
694 pNewValue->Value <<= elem.second;
695 ++pNewValue;
697 OSL_ENSURE( pNewValue - aNewValues.getConstArray() == nVals,
698 "possible mismatch of sequence size and property number" );
701 // add new or replace existing entries.
702 bool bRes = aCfg.ReplaceSetProperties( aSubNodeName, aNewValues );
703 SAL_WARN_IF(!bRes, "linguistic", "failed to set new configuration values");
708 //The new settings in the configuration get applied ! because we are
709 //listening to the configuration for changes of the relevant ! properties
710 //and Notify applies the new settings.
713 void LngSvcMgr::Notify( const uno::Sequence< OUString > &rPropertyNames )
715 const OUString aSpellCheckerList( "ServiceManager/SpellCheckerList" );
716 const OUString aGrammarCheckerList( "ServiceManager/GrammarCheckerList" );
717 const OUString aHyphenatorList( "ServiceManager/HyphenatorList" );
718 const OUString aThesaurusList( "ServiceManager/ThesaurusList" );
720 const uno::Sequence< OUString > aSpellCheckerListEntries( GetNodeNames( aSpellCheckerList ) );
721 const uno::Sequence< OUString > aGrammarCheckerListEntries( GetNodeNames( aGrammarCheckerList ) );
722 const uno::Sequence< OUString > aHyphenatorListEntries( GetNodeNames( aHyphenatorList ) );
723 const uno::Sequence< OUString > aThesaurusListEntries( GetNodeNames( aThesaurusList ) );
725 uno::Sequence< uno::Any > aValues;
726 uno::Sequence< OUString > aNames( 1 );
727 OUString *pNames = aNames.getArray();
729 for (const OUString& rName : rPropertyNames)
731 // property names look like
732 // "ServiceManager/ThesaurusList/de-CH"
734 sal_Int32 nKeyStart;
735 nKeyStart = rName.lastIndexOf( '/' );
736 OUString aKeyText;
737 if (nKeyStart != -1)
738 aKeyText = rName.copy( nKeyStart + 1 );
739 SAL_WARN_IF( aKeyText.isEmpty(), "linguistic", "unexpected key (lang::Locale) string" );
740 if (rName.startsWith( aSpellCheckerList ))
742 osl::MutexGuard aGuard(GetLinguMutex());
744 // delete old cached data, needs to be acquired new on demand
745 pAvailSpellSvcs.reset();
747 if (lcl_SeqHasString( aSpellCheckerListEntries, aKeyText ))
749 pNames[0] = aSpellCheckerList + "/" + aKeyText;
750 aValues = /*aCfg.*/GetProperties( aNames );
751 uno::Sequence< OUString > aSvcImplNames;
752 if (aValues.hasElements())
753 aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
755 LanguageType nLang = LANGUAGE_NONE;
756 if (!aKeyText.isEmpty())
757 nLang = LanguageTag::convertToLanguageType( aKeyText );
759 GetSpellCheckerDsp_Impl( false ); // don't set service list, it will be done below
760 mxSpellDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
763 else if (rName.startsWith( aGrammarCheckerList ))
765 osl::MutexGuard aGuard(GetLinguMutex());
767 // delete old cached data, needs to be acquired new on demand
768 pAvailGrammarSvcs.reset();
770 if (lcl_SeqHasString( aGrammarCheckerListEntries, aKeyText ))
772 pNames[0] = aGrammarCheckerList + "/" + aKeyText;
773 aValues = /*aCfg.*/GetProperties( aNames );
774 uno::Sequence< OUString > aSvcImplNames;
775 if (aValues.hasElements())
776 aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
778 LanguageType nLang = LANGUAGE_NONE;
779 if (!aKeyText.isEmpty())
780 nLang = LanguageTag::convertToLanguageType( aKeyText );
782 if (SvtLinguConfig().HasGrammarChecker())
784 GetGrammarCheckerDsp_Impl( false ); // don't set service list, it will be done below
785 mxGrammarDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
789 else if (rName.startsWith( aHyphenatorList ))
791 osl::MutexGuard aGuard(GetLinguMutex());
793 // delete old cached data, needs to be acquired new on demand
794 pAvailHyphSvcs.reset();
796 if (lcl_SeqHasString( aHyphenatorListEntries, aKeyText ))
798 pNames[0] = aHyphenatorList + "/" + aKeyText;
799 aValues = /*aCfg.*/GetProperties( aNames );
800 uno::Sequence< OUString > aSvcImplNames;
801 if (aValues.hasElements())
802 aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
804 LanguageType nLang = LANGUAGE_NONE;
805 if (!aKeyText.isEmpty())
806 nLang = LanguageTag::convertToLanguageType( aKeyText );
808 GetHyphenatorDsp_Impl( false ); // don't set service list, it will be done below
809 mxHyphDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
812 else if (rName.startsWith( aThesaurusList ))
814 osl::MutexGuard aGuard(GetLinguMutex());
816 // delete old cached data, needs to be acquired new on demand
817 pAvailThesSvcs.reset();
819 if (lcl_SeqHasString( aThesaurusListEntries, aKeyText ))
821 pNames[0] = aThesaurusList + "/" + aKeyText;
822 aValues = /*aCfg.*/GetProperties( aNames );
823 uno::Sequence< OUString > aSvcImplNames;
824 if (aValues.hasElements())
825 aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
827 LanguageType nLang = LANGUAGE_NONE;
828 if (!aKeyText.isEmpty())
829 nLang = LanguageTag::convertToLanguageType( aKeyText );
831 GetThesaurusDsp_Impl( false ); // don't set service list, it will be done below
832 mxThesDsp->SetServiceList( LanguageTag::convertToLocale(nLang), aSvcImplNames );
835 else
837 SAL_WARN( "linguistic", "notified for unexpected property" );
843 void LngSvcMgr::ImplCommit()
845 // everything necessary should have already been done by 'SaveCfgSvcs'
846 // called from within 'setConfiguredServices'.
847 // Also this class usually exits only when the Office is being shutdown.
851 void LngSvcMgr::GetListenerHelper_Impl()
853 if (!mxListenerHelper.is())
855 mxListenerHelper = new LngSvcMgrListenerHelper( *this, linguistic::GetDictionaryList() );
860 void LngSvcMgr::GetSpellCheckerDsp_Impl( bool bSetSvcList )
862 if (!mxSpellDsp.is())
864 mxSpellDsp = new SpellCheckerDispatcher( *this );
865 if (bSetSvcList)
866 SetCfgServiceLists( *mxSpellDsp );
871 void LngSvcMgr::GetGrammarCheckerDsp_Impl( bool bSetSvcList )
873 if (mxGrammarDsp.is() || !SvtLinguConfig().HasGrammarChecker())
874 return;
876 //! since the grammar checking iterator needs to be a one instance service
877 //! we need to create it the correct way!
878 uno::Reference< linguistic2::XProofreadingIterator > xGCI;
881 xGCI = linguistic2::ProofreadingIterator::create( comphelper::getProcessComponentContext() );
883 catch (const uno::Exception &)
886 SAL_WARN_IF( !xGCI.is(), "linguistic", "instantiating grammar checking iterator failed" );
888 if (xGCI.is())
890 mxGrammarDsp = dynamic_cast< GrammarCheckingIterator * >(xGCI.get());
891 SAL_WARN_IF( mxGrammarDsp == nullptr, "linguistic", "failed to get implementation" );
892 if (bSetSvcList && mxGrammarDsp.is())
893 SetCfgServiceLists( *mxGrammarDsp );
898 void LngSvcMgr::GetHyphenatorDsp_Impl( bool bSetSvcList )
900 if (!mxHyphDsp.is())
902 mxHyphDsp = new HyphenatorDispatcher( *this );
903 if (bSetSvcList)
904 SetCfgServiceLists( *mxHyphDsp );
909 void LngSvcMgr::GetThesaurusDsp_Impl( bool bSetSvcList )
911 if (!mxThesDsp.is())
913 mxThesDsp = new ThesaurusDispatcher;
914 if (bSetSvcList)
915 SetCfgServiceLists( *mxThesDsp );
920 void LngSvcMgr::GetAvailableSpellSvcs_Impl()
922 if (pAvailSpellSvcs)
923 return;
925 pAvailSpellSvcs.reset(new SvcInfoArray);
927 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
929 uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
930 uno::Reference< container::XEnumeration > xEnum;
931 if (xEnumAccess.is())
932 xEnum = xEnumAccess->createContentEnumeration( SN_SPELLCHECKER );
934 if (!xEnum.is())
935 return;
937 while (xEnum->hasMoreElements())
939 uno::Any aCurrent = xEnum->nextElement();
940 uno::Reference< lang::XSingleComponentFactory > xCompFactory;
941 uno::Reference< lang::XSingleServiceFactory > xFactory;
943 uno::Reference< linguistic2::XSpellChecker > xSvc;
944 xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
945 if (!xCompFactory.is())
947 xFactory.set(aCurrent, css::uno::UNO_QUERY);
949 if ( xCompFactory.is() || xFactory.is() )
953 xSvc.set( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY );
955 catch (const uno::Exception &)
957 SAL_WARN( "linguistic", "createInstance failed" );
961 if (xSvc.is())
963 OUString aImplName;
964 std::vector< LanguageType > aLanguages;
965 uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
966 if (xInfo.is())
967 aImplName = xInfo->getImplementationName();
968 SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
969 uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
970 aLanguages = LocaleSeqToLangVec( aLocaleSequence );
972 pAvailSpellSvcs->push_back( std::make_unique<SvcInfo>( aImplName, aLanguages ) );
978 void LngSvcMgr::GetAvailableGrammarSvcs_Impl()
980 if (pAvailGrammarSvcs)
981 return;
983 pAvailGrammarSvcs.reset(new SvcInfoArray);
985 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
987 uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
988 uno::Reference< container::XEnumeration > xEnum;
989 if (xEnumAccess.is())
990 xEnum = xEnumAccess->createContentEnumeration( SN_GRAMMARCHECKER );
992 if (!xEnum.is())
993 return;
995 while (xEnum->hasMoreElements())
997 uno::Any aCurrent = xEnum->nextElement();
998 uno::Reference< lang::XSingleComponentFactory > xCompFactory;
999 uno::Reference< lang::XSingleServiceFactory > xFactory;
1001 uno::Reference< linguistic2::XProofreader > xSvc;
1002 xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
1003 if (!xCompFactory.is())
1005 xFactory.set(aCurrent, css::uno::UNO_QUERY);
1007 if ( xCompFactory.is() || xFactory.is() )
1011 if (xCompFactory.is())
1013 xSvc.set(xCompFactory->createInstanceWithContext(xContext), uno::UNO_QUERY);
1015 else
1017 xSvc.set(xFactory->createInstance(), uno::UNO_QUERY);
1020 catch (const uno::Exception &)
1022 SAL_WARN( "linguistic", "createInstance failed" );
1026 if (xSvc.is() && pAvailGrammarSvcs)
1028 OUString aImplName;
1029 std::vector< LanguageType > aLanguages;
1030 uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
1031 if (xInfo.is())
1032 aImplName = xInfo->getImplementationName();
1033 SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
1034 uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
1035 aLanguages = LocaleSeqToLangVec( aLocaleSequence );
1037 pAvailGrammarSvcs->push_back( std::make_unique<SvcInfo>( aImplName, aLanguages ) );
1043 void LngSvcMgr::GetAvailableHyphSvcs_Impl()
1045 if (pAvailHyphSvcs)
1046 return;
1048 pAvailHyphSvcs.reset(new SvcInfoArray);
1049 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
1051 uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
1052 uno::Reference< container::XEnumeration > xEnum;
1053 if (xEnumAccess.is())
1054 xEnum = xEnumAccess->createContentEnumeration( SN_HYPHENATOR );
1056 if (!xEnum.is())
1057 return;
1059 while (xEnum->hasMoreElements())
1061 uno::Any aCurrent = xEnum->nextElement();
1062 uno::Reference< lang::XSingleComponentFactory > xCompFactory;
1063 uno::Reference< lang::XSingleServiceFactory > xFactory;
1065 uno::Reference< linguistic2::XHyphenator > xSvc;
1066 xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
1067 if (!xCompFactory.is())
1069 xFactory.set(aCurrent, css::uno::UNO_QUERY);
1071 if ( xCompFactory.is() || xFactory.is() )
1075 xSvc.set( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY );
1077 catch (const uno::Exception &)
1079 SAL_WARN( "linguistic", "createInstance failed" );
1082 if (xSvc.is())
1084 OUString aImplName;
1085 std::vector< LanguageType > aLanguages;
1086 uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
1087 if (xInfo.is())
1088 aImplName = xInfo->getImplementationName();
1089 SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
1090 uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
1091 aLanguages = LocaleSeqToLangVec( aLocaleSequence );
1092 pAvailHyphSvcs->push_back( std::make_unique<SvcInfo>( aImplName, aLanguages ) );
1098 void LngSvcMgr::GetAvailableThesSvcs_Impl()
1100 if (pAvailThesSvcs)
1101 return;
1103 pAvailThesSvcs.reset(new SvcInfoArray);
1105 uno::Reference< uno::XComponentContext > xContext( comphelper::getProcessComponentContext() );
1107 uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xContext->getServiceManager(), uno::UNO_QUERY );
1108 uno::Reference< container::XEnumeration > xEnum;
1109 if (xEnumAccess.is())
1110 xEnum = xEnumAccess->createContentEnumeration( SN_THESAURUS );
1112 if (!xEnum.is())
1113 return;
1115 while (xEnum->hasMoreElements())
1117 uno::Any aCurrent = xEnum->nextElement();
1118 uno::Reference< lang::XSingleComponentFactory > xCompFactory;
1119 uno::Reference< lang::XSingleServiceFactory > xFactory;
1121 uno::Reference< linguistic2::XThesaurus > xSvc;
1122 xCompFactory.set(aCurrent, css::uno::UNO_QUERY);
1123 if (!xCompFactory.is())
1125 xFactory.set(aCurrent, css::uno::UNO_QUERY);
1127 if ( xCompFactory.is() || xFactory.is() )
1131 xSvc.set( ( xCompFactory.is() ? xCompFactory->createInstanceWithContext( xContext ) : xFactory->createInstance() ), uno::UNO_QUERY );
1133 catch (const uno::Exception &)
1135 SAL_WARN( "linguistic", "createInstance failed" );
1138 if (xSvc.is())
1140 OUString aImplName;
1141 std::vector< LanguageType > aLanguages;
1142 uno::Reference< XServiceInfo > xInfo( xSvc, uno::UNO_QUERY );
1143 if (xInfo.is())
1144 aImplName = xInfo->getImplementationName();
1145 SAL_WARN_IF( aImplName.isEmpty(), "linguistic", "empty implementation name" );
1146 uno::Sequence<lang::Locale> aLocaleSequence(xSvc->getLocales());
1147 aLanguages = LocaleSeqToLangVec( aLocaleSequence );
1149 pAvailThesSvcs->push_back( std::make_unique<SvcInfo>( aImplName, aLanguages ) );
1155 void LngSvcMgr::SetCfgServiceLists( SpellCheckerDispatcher &rSpellDsp )
1157 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Spell" );
1159 OUString aNode("ServiceManager/SpellCheckerList");
1160 uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
1162 // append path prefix need for 'GetProperties' call below
1163 OUString aPrefix = aNode + "/";
1164 for (OUString & name : aNames)
1166 name = aPrefix + name;
1169 const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
1170 if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
1171 return;
1173 const OUString *pNames = aNames.getConstArray();
1174 for (const uno::Any& rValue : aValues)
1176 uno::Sequence< OUString > aSvcImplNames;
1177 if (rValue >>= aSvcImplNames)
1179 OUString aLocaleStr( *pNames++ );
1180 sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
1181 aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
1182 rSpellDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
1188 void LngSvcMgr::SetCfgServiceLists( GrammarCheckingIterator &rGrammarDsp )
1190 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Grammar" );
1192 OUString aNode("ServiceManager/GrammarCheckerList");
1193 uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
1195 // append path prefix need for 'GetProperties' call below
1196 OUString aPrefix = aNode + "/";
1197 for (OUString & name : aNames)
1199 name = aPrefix + name;
1202 const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
1203 if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
1204 return;
1206 const OUString *pNames = aNames.getConstArray();
1207 for (const uno::Any& rValue : aValues)
1209 uno::Sequence< OUString > aSvcImplNames;
1210 if (rValue >>= aSvcImplNames)
1212 // there should only be one grammar checker in use per language...
1213 if (aSvcImplNames.getLength() > 1)
1214 aSvcImplNames.realloc(1);
1216 OUString aLocaleStr( *pNames++ );
1217 sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
1218 aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
1219 rGrammarDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
1225 void LngSvcMgr::SetCfgServiceLists( HyphenatorDispatcher &rHyphDsp )
1227 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Hyph" );
1229 OUString aNode("ServiceManager/HyphenatorList");
1230 uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
1232 // append path prefix need for 'GetProperties' call below
1233 OUString aPrefix = aNode + "/";
1234 for (OUString & name : aNames)
1236 name = aPrefix + name;
1239 const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
1240 if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
1241 return;
1243 const OUString *pNames = aNames.getConstArray();
1244 for (const uno::Any& rValue : aValues)
1246 uno::Sequence< OUString > aSvcImplNames;
1247 if (rValue >>= aSvcImplNames)
1249 // there should only be one hyphenator in use per language...
1250 if (aSvcImplNames.getLength() > 1)
1251 aSvcImplNames.realloc(1);
1253 OUString aLocaleStr( *pNames++ );
1254 sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
1255 aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
1256 rHyphDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
1262 void LngSvcMgr::SetCfgServiceLists( ThesaurusDispatcher &rThesDsp )
1264 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SetCfgServiceLists - Thes" );
1266 OUString aNode("ServiceManager/ThesaurusList");
1267 uno::Sequence< OUString > aNames( /*aCfg.*/GetNodeNames( aNode ) );
1269 // append path prefix need for 'GetProperties' call below
1270 OUString aPrefix = aNode + "/";
1271 for (OUString & name : aNames)
1273 name = aPrefix + name;
1276 const uno::Sequence< uno::Any > aValues( /*aCfg.*/GetProperties( aNames ) );
1277 if (!(aNames.hasElements() && aNames.getLength() == aValues.getLength()))
1278 return;
1280 const OUString *pNames = aNames.getConstArray();
1281 for (const uno::Any& rValue : aValues)
1283 uno::Sequence< OUString > aSvcImplNames;
1284 if (rValue >>= aSvcImplNames)
1286 OUString aLocaleStr( *pNames++ );
1287 sal_Int32 nSeparatorPos = aLocaleStr.lastIndexOf( '/' );
1288 aLocaleStr = aLocaleStr.copy( nSeparatorPos + 1 );
1289 rThesDsp.SetServiceList( LanguageTag::convertToLocale(aLocaleStr), aSvcImplNames );
1295 uno::Reference< linguistic2::XSpellChecker > SAL_CALL
1296 LngSvcMgr::getSpellChecker()
1298 osl::MutexGuard aGuard( GetLinguMutex() );
1299 #if OSL_DEBUG_LEVEL > 0
1300 getAvailableLocales(SN_SPELLCHECKER);
1301 #endif
1303 uno::Reference< linguistic2::XSpellChecker > xRes;
1304 if (!bDisposing)
1306 if (!mxSpellDsp.is())
1307 GetSpellCheckerDsp_Impl();
1308 xRes = mxSpellDsp.get();
1310 return xRes;
1314 uno::Reference< linguistic2::XHyphenator > SAL_CALL
1315 LngSvcMgr::getHyphenator()
1317 osl::MutexGuard aGuard( GetLinguMutex() );
1318 #if OSL_DEBUG_LEVEL > 0
1319 getAvailableLocales(SN_HYPHENATOR);
1320 #endif
1321 uno::Reference< linguistic2::XHyphenator > xRes;
1322 if (!bDisposing)
1324 if (!mxHyphDsp.is())
1325 GetHyphenatorDsp_Impl();
1326 xRes = mxHyphDsp.get();
1328 return xRes;
1332 uno::Reference< linguistic2::XThesaurus > SAL_CALL
1333 LngSvcMgr::getThesaurus()
1335 osl::MutexGuard aGuard( GetLinguMutex() );
1336 #if OSL_DEBUG_LEVEL > 0
1337 getAvailableLocales(SN_THESAURUS);
1338 #endif
1339 uno::Reference< linguistic2::XThesaurus > xRes;
1340 if (!bDisposing)
1342 if (!mxThesDsp.is())
1343 GetThesaurusDsp_Impl();
1344 xRes = mxThesDsp.get();
1346 return xRes;
1350 sal_Bool SAL_CALL
1351 LngSvcMgr::addLinguServiceManagerListener(
1352 const uno::Reference< lang::XEventListener >& xListener )
1354 osl::MutexGuard aGuard( GetLinguMutex() );
1356 if (bDisposing || !xListener.is())
1357 return false;
1359 if (!mxListenerHelper.is())
1360 GetListenerHelper_Impl();
1361 mxListenerHelper->AddLngSvcMgrListener( xListener );
1362 return true;
1366 sal_Bool SAL_CALL
1367 LngSvcMgr::removeLinguServiceManagerListener(
1368 const uno::Reference< lang::XEventListener >& xListener )
1370 osl::MutexGuard aGuard( GetLinguMutex() );
1372 if (bDisposing || !xListener.is())
1373 return false;
1375 DBG_ASSERT( mxListenerHelper.is(), "listener removed without being added" );
1376 if (!mxListenerHelper.is())
1377 GetListenerHelper_Impl();
1378 mxListenerHelper->RemoveLngSvcMgrListener( xListener );
1379 return true;
1383 uno::Sequence< OUString > SAL_CALL
1384 LngSvcMgr::getAvailableServices(
1385 const OUString& rServiceName,
1386 const lang::Locale& rLocale )
1388 osl::MutexGuard aGuard( GetLinguMutex() );
1390 uno::Sequence< OUString > aRes;
1391 const SvcInfoArray *pInfoArray = nullptr;
1393 if (rServiceName == SN_SPELLCHECKER)
1395 GetAvailableSpellSvcs_Impl();
1396 pInfoArray = pAvailSpellSvcs.get();
1398 else if (rServiceName == SN_GRAMMARCHECKER)
1400 GetAvailableGrammarSvcs_Impl();
1401 pInfoArray = pAvailGrammarSvcs.get();
1403 else if (rServiceName == SN_HYPHENATOR)
1405 GetAvailableHyphSvcs_Impl();
1406 pInfoArray = pAvailHyphSvcs.get();
1408 else if (rServiceName == SN_THESAURUS)
1410 GetAvailableThesSvcs_Impl();
1411 pInfoArray = pAvailThesSvcs.get();
1414 if (pInfoArray)
1416 std::vector<OUString> aVec;
1417 aVec.reserve(pInfoArray->size());
1419 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
1420 for (const auto& pInfo : *pInfoArray)
1422 if (LinguIsUnspecified( nLanguage )
1423 || pInfo->HasLanguage( nLanguage ))
1425 aVec.push_back(pInfo->aSvcImplName);
1429 aRes = comphelper::containerToSequence(aVec);
1432 return aRes;
1436 uno::Sequence< lang::Locale > SAL_CALL
1437 LngSvcMgr::getAvailableLocales(
1438 const OUString& rServiceName )
1440 osl::MutexGuard aGuard( GetLinguMutex() );
1442 uno::Sequence< lang::Locale > aRes;
1444 uno::Sequence< lang::Locale > *pAvailLocales = nullptr;
1445 if (rServiceName == SN_SPELLCHECKER)
1446 pAvailLocales = &aAvailSpellLocales;
1447 else if (rServiceName == SN_GRAMMARCHECKER)
1448 pAvailLocales = &aAvailGrammarLocales;
1449 else if (rServiceName == SN_HYPHENATOR)
1450 pAvailLocales = &aAvailHyphLocales;
1451 else if (rServiceName == SN_THESAURUS)
1452 pAvailLocales = &aAvailThesLocales;
1454 // Nowadays (with OOo lingu in SO) we want to know immediately about
1455 // new downloaded dictionaries and have them ready right away if the Tools/Options...
1456 // is used to activate them. Thus we can not rely anymore on buffered data.
1457 if (pAvailLocales)
1459 *pAvailLocales = GetAvailLocales(getAvailableServices(rServiceName, lang::Locale()));
1460 aRes = *pAvailLocales;
1463 return aRes;
1466 static bool IsEqSvcList( const uno::Sequence< OUString > &rList1,
1467 const uno::Sequence< OUString > &rList2 )
1469 // returns true if both sequences are equal
1470 return rList1.getLength() == rList2.getLength()
1471 && std::equal(rList1.begin(), rList1.end(), rList2.begin(), rList2.end());
1475 void SAL_CALL
1476 LngSvcMgr::setConfiguredServices(
1477 const OUString& rServiceName,
1478 const lang::Locale& rLocale,
1479 const uno::Sequence< OUString >& rServiceImplNames )
1481 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::setConfiguredServices" );
1483 osl::MutexGuard aGuard( GetLinguMutex() );
1485 LanguageType nLanguage = LinguLocaleToLanguage( rLocale );
1486 if (LinguIsUnspecified( nLanguage))
1487 return;
1489 if (rServiceName == SN_SPELLCHECKER)
1491 if (!mxSpellDsp.is())
1492 GetSpellCheckerDsp_Impl();
1493 bool bChanged = !IsEqSvcList( rServiceImplNames,
1494 mxSpellDsp->GetServiceList( rLocale ) );
1495 if (bChanged)
1497 mxSpellDsp->SetServiceList( rLocale, rServiceImplNames );
1498 SaveCfgSvcs( SN_SPELLCHECKER );
1500 if (mxListenerHelper)
1501 mxListenerHelper->AddLngSvcEvt(
1502 linguistic2::LinguServiceEventFlags::SPELL_CORRECT_WORDS_AGAIN |
1503 linguistic2::LinguServiceEventFlags::SPELL_WRONG_WORDS_AGAIN );
1506 else if (rServiceName == SN_GRAMMARCHECKER)
1508 if (!mxGrammarDsp.is())
1509 GetGrammarCheckerDsp_Impl();
1510 bool bChanged = !IsEqSvcList( rServiceImplNames,
1511 mxGrammarDsp->GetServiceList( rLocale ) );
1512 if (bChanged)
1514 mxGrammarDsp->SetServiceList( rLocale, rServiceImplNames );
1515 SaveCfgSvcs( SN_GRAMMARCHECKER );
1517 if (mxListenerHelper)
1518 mxListenerHelper->AddLngSvcEvt(
1519 linguistic2::LinguServiceEventFlags::PROOFREAD_AGAIN );
1522 else if (rServiceName == SN_HYPHENATOR)
1524 if (!mxHyphDsp.is())
1525 GetHyphenatorDsp_Impl();
1526 bool bChanged = !IsEqSvcList( rServiceImplNames,
1527 mxHyphDsp->GetServiceList( rLocale ) );
1528 if (bChanged)
1530 mxHyphDsp->SetServiceList( rLocale, rServiceImplNames );
1531 SaveCfgSvcs( SN_HYPHENATOR );
1533 if (mxListenerHelper)
1534 mxListenerHelper->AddLngSvcEvt(
1535 linguistic2::LinguServiceEventFlags::HYPHENATE_AGAIN );
1538 else if (rServiceName == SN_THESAURUS)
1540 if (!mxThesDsp.is())
1541 GetThesaurusDsp_Impl();
1542 bool bChanged = !IsEqSvcList( rServiceImplNames,
1543 mxThesDsp->GetServiceList( rLocale ) );
1544 if (bChanged)
1546 mxThesDsp->SetServiceList( rLocale, rServiceImplNames );
1547 SaveCfgSvcs( SN_THESAURUS );
1553 bool LngSvcMgr::SaveCfgSvcs( const OUString &rServiceName )
1555 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SaveCfgSvcs" );
1557 bool bRes = false;
1559 LinguDispatcher *pDsp = nullptr;
1560 uno::Sequence< lang::Locale > aLocales;
1562 if (rServiceName == SN_SPELLCHECKER)
1564 if (!mxSpellDsp)
1565 GetSpellCheckerDsp_Impl();
1566 pDsp = mxSpellDsp.get();
1567 aLocales = getAvailableLocales( SN_SPELLCHECKER );
1569 else if (rServiceName == SN_GRAMMARCHECKER)
1571 if (!mxGrammarDsp.is())
1572 GetGrammarCheckerDsp_Impl();
1573 pDsp = mxGrammarDsp.get();
1574 aLocales = getAvailableLocales( SN_GRAMMARCHECKER );
1576 else if (rServiceName == SN_HYPHENATOR)
1578 if (!mxHyphDsp.is())
1579 GetHyphenatorDsp_Impl();
1580 pDsp = mxHyphDsp.get();
1581 aLocales = getAvailableLocales( SN_HYPHENATOR );
1583 else if (rServiceName == SN_THESAURUS)
1585 if (!mxThesDsp.is())
1586 GetThesaurusDsp_Impl();
1587 pDsp = mxThesDsp.get();
1588 aLocales = getAvailableLocales( SN_THESAURUS );
1591 if (pDsp && aLocales.hasElements())
1593 uno::Sequence< beans::PropertyValue > aValues( aLocales.getLength() );
1594 beans::PropertyValue *pValue = aValues.getArray();
1596 // get node name to be used
1597 const char *pNodeName = nullptr;
1598 if (pDsp == mxSpellDsp.get())
1599 pNodeName = "ServiceManager/SpellCheckerList";
1600 else if (pDsp == mxGrammarDsp.get())
1601 pNodeName = "ServiceManager/GrammarCheckerList";
1602 else if (pDsp == mxHyphDsp.get())
1603 pNodeName = "ServiceManager/HyphenatorList";
1604 else if (pDsp == mxThesDsp.get())
1605 pNodeName = "ServiceManager/ThesaurusList";
1606 else
1608 SAL_WARN( "linguistic", "node name missing" );
1610 OUString aNodeName( OUString::createFromAscii(pNodeName) );
1612 for (const lang::Locale& rLocale : std::as_const(aLocales))
1614 uno::Sequence< OUString > aSvcImplNames = pDsp->GetServiceList( rLocale );
1616 // build value to be written back to configuration
1617 uno::Any aCfgAny;
1618 if ((pDsp == mxHyphDsp.get() || pDsp == mxGrammarDsp.get()) && aSvcImplNames.getLength() > 1)
1619 aSvcImplNames.realloc(1); // there should be only one entry for hyphenators or grammar checkers (because they are not chained)
1620 aCfgAny <<= aSvcImplNames;
1621 DBG_ASSERT( aCfgAny.hasValue(), "missing value for 'Any' type" );
1623 OUString aCfgLocaleStr( LanguageTag::convertToBcp47( rLocale));
1624 pValue->Value = aCfgAny;
1625 pValue->Name = aNodeName + "/" + aCfgLocaleStr;
1626 pValue++;
1629 SAL_INFO( "linguistic", "linguistic: LngSvcMgr::SaveCfgSvcs - ReplaceSetProperties" );
1630 // change, add new or replace existing entries.
1631 bRes |= /*aCfg.*/ReplaceSetProperties( aNodeName, aValues );
1635 return bRes;
1639 static uno::Sequence< OUString > GetLangSvcList( const uno::Any &rVal )
1641 uno::Sequence< OUString > aRes;
1643 if (rVal.hasValue())
1645 rVal >>= aRes;
1646 #if OSL_DEBUG_LEVEL > 0
1647 for (const OUString& rSvcName : std::as_const(aRes))
1649 SAL_WARN_IF( rSvcName.isEmpty(), "linguistic", "service impl-name missing" );
1651 #endif
1654 return aRes;
1658 static uno::Sequence< OUString > GetLangSvc( const uno::Any &rVal )
1660 uno::Sequence< OUString > aRes;
1661 if (!rVal.hasValue())
1662 return aRes;
1664 // allowing for a sequence here as well (even though it should only
1665 // be a string) makes coding easier in other places since one needs
1666 // not make a special case for writing a string only and not a
1667 // sequence of strings.
1668 if (rVal >>= aRes)
1670 // but only the first string should be used.
1671 if (aRes.getLength() > 1)
1672 aRes.realloc(1);
1674 else
1676 OUString aImplName;
1677 if ((rVal >>= aImplName) && !aImplName.isEmpty())
1679 aRes.realloc(1);
1680 aRes.getArray()[0] = aImplName;
1682 else
1684 SAL_WARN( "linguistic", "GetLangSvc: unexpected type encountered" );
1688 return aRes;
1692 uno::Sequence< OUString > SAL_CALL
1693 LngSvcMgr::getConfiguredServices(
1694 const OUString& rServiceName,
1695 const lang::Locale& rLocale )
1697 osl::MutexGuard aGuard( GetLinguMutex() );
1699 uno::Sequence< OUString > aSvcImplNames;
1701 OUString aCfgLocale( LanguageTag::convertToBcp47( rLocale) );
1703 uno::Sequence< uno::Any > aValues;
1704 uno::Sequence< OUString > aNames( 1 );
1705 OUString *pNames = aNames.getArray();
1706 if ( rServiceName == SN_SPELLCHECKER )
1708 OUString aNode( "ServiceManager/SpellCheckerList");
1709 const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
1710 if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
1712 pNames[0] = aNode + "/" + aCfgLocale;
1713 aValues = /*aCfg.*/GetProperties( aNames );
1714 if (aValues.hasElements())
1715 aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
1718 else if ( rServiceName == SN_GRAMMARCHECKER )
1720 OUString aNode( "ServiceManager/GrammarCheckerList");
1721 const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
1722 if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
1724 pNames[0] = aNode + "/" + aCfgLocale;
1725 aValues = /*aCfg.*/GetProperties( aNames );
1726 if (aValues.hasElements())
1727 aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
1730 else if ( rServiceName == SN_HYPHENATOR )
1732 OUString aNode( "ServiceManager/HyphenatorList");
1733 const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
1734 if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
1736 pNames[0] = aNode + "/" + aCfgLocale;
1737 aValues = /*aCfg.*/GetProperties( aNames );
1738 if (aValues.hasElements())
1739 aSvcImplNames = GetLangSvc( aValues.getConstArray()[0] );
1742 else if ( rServiceName == SN_THESAURUS )
1744 OUString aNode( "ServiceManager/ThesaurusList");
1745 const uno::Sequence< OUString > aNodeEntries( GetNodeNames( aNode ) );
1746 if (lcl_SeqHasString( aNodeEntries, aCfgLocale ))
1748 pNames[0] = aNode + "/" + aCfgLocale;
1749 aValues = /*aCfg.*/GetProperties( aNames );
1750 if (aValues.hasElements())
1751 aSvcImplNames = GetLangSvcList( aValues.getConstArray()[0] );
1755 return aSvcImplNames;
1759 void SAL_CALL
1760 LngSvcMgr::dispose()
1762 osl::MutexGuard aGuard( GetLinguMutex() );
1764 if (!bDisposing)
1766 bDisposing = true;
1768 // require listeners to release this object
1769 lang::EventObject aEvtObj( static_cast<XLinguServiceManager*>(this) );
1770 aEvtListeners.disposeAndClear( aEvtObj );
1772 if (mxListenerHelper.is())
1773 mxListenerHelper->DisposeAndClear( aEvtObj );
1778 void SAL_CALL
1779 LngSvcMgr::addEventListener(
1780 const uno::Reference< lang::XEventListener >& xListener )
1782 osl::MutexGuard aGuard( GetLinguMutex() );
1784 if (!bDisposing && xListener.is())
1786 aEvtListeners.addInterface( xListener );
1791 void SAL_CALL
1792 LngSvcMgr::removeEventListener(
1793 const uno::Reference< lang::XEventListener >& xListener )
1795 osl::MutexGuard aGuard( GetLinguMutex() );
1797 if (xListener.is())
1799 aEvtListeners.removeInterface( xListener );
1804 bool LngSvcMgr::AddLngSvcEvtBroadcaster(
1805 const uno::Reference< linguistic2::XLinguServiceEventBroadcaster > &rxBroadcaster )
1807 if (!rxBroadcaster.is())
1808 return false;
1809 if (!mxListenerHelper.is())
1810 GetListenerHelper_Impl();
1811 mxListenerHelper->AddLngSvcEvtBroadcaster( rxBroadcaster );
1812 return true;
1816 OUString SAL_CALL
1817 LngSvcMgr::getImplementationName()
1819 return "com.sun.star.lingu2.LngSvcMgr";
1823 sal_Bool SAL_CALL
1824 LngSvcMgr::supportsService( const OUString& ServiceName )
1826 return cppu::supportsService(this, ServiceName);
1830 uno::Sequence< OUString > SAL_CALL
1831 LngSvcMgr::getSupportedServiceNames()
1833 return { "com.sun.star.linguistic2.LinguServiceManager" };
1836 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
1837 linguistic_LngSvcMgr_get_implementation(
1838 css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&)
1840 return cppu::acquire(static_cast<cppu::OWeakObject*>(new LngSvcMgr()));
1844 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */