/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <utility>
#include <vcl/settings.hxx>
#include <vcl/weld.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <o3tl/safeint.hxx>
#include <officecfg/Office/Security.hxx>
#include <officecfg/Office/Linguistic.hxx>
#include <unotools/lingucfg.hxx>
#include <unotools/linguprops.hxx>
#include <editeng/unolingu.hxx>
#include <linguistic/misc.hxx>
#include <sfx2/sfxsids.hrc>
#include <tools/debug.hxx>
#include <tools/urlobj.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/dispatchcommand.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/processfactory.hxx>
#include <com/sun/star/linguistic2/LinguServiceManager.hpp>
#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
#include <com/sun/star/linguistic2/XSpellChecker.hpp>
#include <com/sun/star/linguistic2/XProofreader.hpp>
#include <com/sun/star/linguistic2/XHyphenator.hpp>
#include <com/sun/star/linguistic2/XThesaurus.hpp>
#include <com/sun/star/linguistic2/XDictionary.hpp>
#include <com/sun/star/linguistic2/XDictionaryList.hpp>
#include <com/sun/star/linguistic2/XLinguProperties.hpp>
#include <com/sun/star/lang/XServiceDisplayName.hpp>
#include <com/sun/star/frame/XStorable.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/beans/PropertyAttribute.hpp>
#include <unotools/extendedsecurityoptions.hxx>
#include <svl/eitem.hxx>
#include <vcl/svapp.hxx>
#include <sal/log.hxx>
#include <osl/diagnose.h>

#include <svx/svxdlg.hxx>
#include <editeng/optitems.hxx>
#include <optlingu.hxx>
#include <dialmgr.hxx>
#include <strings.hrc>

#include <ucbhelper/content.hxx>

#include <set>
#include <vector>
#include <map>

using namespace ::ucbhelper;
using namespace ::com::sun::star;
using namespace css::lang;
using namespace css::uno;
using namespace css::linguistic2;
using namespace css::beans;

constexpr OUString cSpell(SN_SPELLCHECKER);
constexpr OUString cGrammar(SN_GRAMMARCHECKER);
constexpr OUString cHyph(SN_HYPHENATOR);
constexpr OUString cThes(SN_THESAURUS);

// static ----------------------------------------------------------------

static sal_Int32 lcl_SeqGetEntryPos(
    const Sequence< OUString > &rSeq, std::u16string_view rEntry )
{
    auto it = std::find(rSeq.begin(), rSeq.end(), rEntry);
    return it == rSeq.end() ? -1 : std::distance(rSeq.begin(), it);
}

static bool KillFile_Impl( const OUString& rURL )
{
    bool bRet = true;
    try
    {
        Content aCnt( rURL, uno::Reference< css::ucb::XCommandEnvironment >(), comphelper::getProcessComponentContext() );
        aCnt.executeCommand( u"delete"_ustr, Any( true ) );
    }
    catch( ... )
    {
        TOOLS_WARN_EXCEPTION( "cui.options", "KillFile" );
        bRet = false;
    }

    return bRet;
}

// 0x 0p 0t 0c nn
// p: 1 -> parent
// t: 1 -> spell, 2 -> hyph, 3 -> thes, 4 -> grammar
// c: 1 -> checked 0 -> unchecked
// n: index

#define TYPE_SPELL      sal_uInt8(1)
#define TYPE_GRAMMAR    sal_uInt8(2)
#define TYPE_HYPH       sal_uInt8(3)
#define TYPE_THES       sal_uInt8(4)

namespace {

class ModuleUserData_Impl
{
    bool bParent;
    bool bIsChecked;
    sal_uInt8 nType;
    sal_uInt8 nIndex;
    OUString  sImplName;

public:
    ModuleUserData_Impl( OUString sImpName, bool bIsParent, bool bChecked, sal_uInt8 nSetType, sal_uInt8 nSetIndex ) :
        bParent(bIsParent),
        bIsChecked(bChecked),
        nType(nSetType),
        nIndex(nSetIndex),
        sImplName(std::move(sImpName))
        {
        }
    bool IsParent() const {return bParent;}
    sal_uInt8 GetType() const {return nType;}
    bool IsChecked() const {return bIsChecked;}
    sal_uInt8 GetIndex() const {return nIndex;}
    const OUString& GetImplName() const {return sImplName;}

};


// User for user-dictionaries (XDictionary interface)

class DicUserData
{
    sal_uInt32 nVal;

public:
    explicit DicUserData(sal_uInt32 nUserData) : nVal( nUserData ) {}
    DicUserData( sal_uInt16 nEID,
                 bool bChecked, bool bEditable, bool bDeletable );

    sal_uInt32  GetUserData() const         { return nVal; }
    sal_uInt16  GetEntryId() const          { return static_cast<sal_uInt16>(nVal >> 16); }
    bool        IsChecked() const           { return static_cast<bool>((nVal >>  8) & 0x01); }
    bool        IsDeletable() const         { return static_cast<bool>((nVal >> 10) & 0x01); }
};

}

DicUserData::DicUserData(
        sal_uInt16 nEID,
        bool bChecked, bool bEditable, bool bDeletable )
{
    DBG_ASSERT( nEID < 65000, "Entry Id out of range" );
    nVal =  (static_cast<sal_uInt32>(0xFFFF & nEID)         << 16) |
            (static_cast<sal_uInt32>(bChecked ? 1 : 0)      <<  8) |
            (static_cast<sal_uInt32>(bEditable ? 1 : 0)     <<  9) |
            (static_cast<sal_uInt32>(bDeletable ? 1 : 0)    << 10);
}

/*--------------------------------------------------
    Entry IDs for options listbox of dialog
--------------------------------------------------*/

namespace {

enum EID_OPTIONS
{
    EID_SPELL_AUTO,
    EID_GRAMMAR_AUTO,
    EID_CAPITAL_WORDS,
    EID_WORDS_WITH_DIGITS,
    EID_SPELL_SPECIAL,
    EID_NUM_MIN_WORDLEN,
    EID_NUM_PRE_BREAK,
    EID_NUM_POST_BREAK,
    EID_HYPH_AUTO,
    EID_HYPH_SPECIAL,
    EID_SPELL_CLOSED_COMPOUND,
    EID_SPELL_HYPHENATED_COMPOUND
};

}

static const OUString & lcl_GetPropertyName( EID_OPTIONS eEntryId )
{
    switch (eEntryId)
    {
        case EID_SPELL_AUTO: return UPN_IS_SPELL_AUTO;
        case EID_GRAMMAR_AUTO: return UPN_IS_GRAMMAR_AUTO;
        case EID_CAPITAL_WORDS: return UPN_IS_SPELL_UPPER_CASE;
        case EID_SPELL_CLOSED_COMPOUND: return UPN_IS_SPELL_CLOSED_COMPOUND;
        case EID_SPELL_HYPHENATED_COMPOUND: return UPN_IS_SPELL_HYPHENATED_COMPOUND;
        case EID_WORDS_WITH_DIGITS: return UPN_IS_SPELL_WITH_DIGITS;
        case EID_SPELL_SPECIAL: return UPN_IS_SPELL_SPECIAL;
        case EID_NUM_MIN_WORDLEN: return UPN_HYPH_MIN_WORD_LENGTH;
        case EID_NUM_PRE_BREAK: return UPN_HYPH_MIN_LEADING;
        case EID_NUM_POST_BREAK: return UPN_HYPH_MIN_TRAILING;
        case EID_HYPH_AUTO: return UPN_IS_HYPH_AUTO;
        case EID_HYPH_SPECIAL: return UPN_IS_HYPH_SPECIAL;
        default: assert (false); abort();
    }
}

namespace {

class OptionsBreakSet : public weld::GenericDialogController
{
    std::unique_ptr<weld::Widget> m_xBeforeFrame;
    std::unique_ptr<weld::Widget> m_xAfterFrame;
    std::unique_ptr<weld::Widget> m_xMinimalFrame;
    std::unique_ptr<weld::SpinButton> m_xBreakNF;

public:
    OptionsBreakSet(weld::Window* pParent, sal_uInt16 nRID)
        : GenericDialogController(pParent, u"cui/ui/breaknumberoption.ui"_ustr, u"BreakNumberOption"_ustr)
        , m_xBeforeFrame(m_xBuilder->weld_widget(u"beforeframe"_ustr))
        , m_xAfterFrame(m_xBuilder->weld_widget(u"afterframe"_ustr))
        , m_xMinimalFrame(m_xBuilder->weld_widget(u"miniframe"_ustr))
    {
        assert(EID_NUM_PRE_BREAK == nRID || EID_NUM_POST_BREAK == nRID || EID_NUM_MIN_WORDLEN == nRID); //unexpected ID

        if (nRID == EID_NUM_PRE_BREAK)
        {
            m_xBeforeFrame->show();
            m_xBreakNF = m_xBuilder->weld_spin_button(u"beforebreak"_ustr);
        }
        else if(nRID == EID_NUM_POST_BREAK)
        {
            m_xAfterFrame->show();
            m_xBreakNF = m_xBuilder->weld_spin_button(u"afterbreak"_ustr);
        }
        else if(nRID == EID_NUM_MIN_WORDLEN)
        {
            m_xMinimalFrame->show();
            m_xBreakNF = m_xBuilder->weld_spin_button(u"wordlength"_ustr);
        }
    }

    weld::SpinButton& GetNumericFld()
    {
        return *m_xBreakNF;
    }
};

// class OptionsUserData -------------------------------------------------

class OptionsUserData
{
    sal_uInt32   nVal;

public:
    explicit OptionsUserData( sal_uInt32 nUserData ) : nVal( nUserData ) {}
    OptionsUserData( sal_uInt16 nEID,
                     bool bHasNV, sal_uInt16 nNumVal,
                     bool bCheckable, bool bChecked );

    sal_uInt32  GetUserData() const         { return nVal; }
    sal_uInt16  GetEntryId() const          { return static_cast<sal_uInt16>(nVal >> 16); }
    bool        HasNumericValue() const     { return static_cast<bool>((nVal >> 10) & 0x01); }
    sal_uInt16  GetNumericValue() const     { return static_cast<sal_uInt16>(nVal & 0xFF); }
    bool        IsCheckable() const         { return static_cast<bool>((nVal >> 9) & 0x01); }
    bool        IsModified() const          { return static_cast<bool>((nVal >> 11) & 0x01); }

    void        SetNumericValue( sal_uInt8 nNumVal );
};

}

OptionsUserData::OptionsUserData( sal_uInt16 nEID,
        bool bHasNV, sal_uInt16 nNumVal,
        bool bCheckable, bool bChecked )
{
    DBG_ASSERT( nEID < 65000, "Entry Id out of range" );
    DBG_ASSERT( nNumVal < 256, "value out of range" );
    nVal =  (static_cast<sal_uInt32>(0xFFFF & nEID)         << 16) |
            (static_cast<sal_uInt32>(bHasNV ? 1 : 0)        << 10) |
            (static_cast<sal_uInt32>(bCheckable ? 1 : 0)    <<  9) |
            (static_cast<sal_uInt32>(bChecked ? 1 : 0)      <<  8) |
            static_cast<sal_uInt32>(0xFF & nNumVal);
}

void OptionsUserData::SetNumericValue( sal_uInt8 nNumVal )
{
    if (HasNumericValue()  &&  (GetNumericValue() != nNumVal))
    {
        nVal &= 0xffffff00;
        nVal |= nNumVal;
        nVal |= sal_uInt32(1) << 11; // mark as modified
    }
}

// ServiceInfo_Impl ----------------------------------------------------

namespace {

struct ServiceInfo_Impl
{
    OUString                    sDisplayName;
    OUString                    sSpellImplName;
    OUString                    sHyphImplName;
    OUString                    sThesImplName;
    OUString                    sGrammarImplName;
    uno::Reference< XSpellChecker >     xSpell;
    uno::Reference< XHyphenator >       xHyph;
    uno::Reference< XThesaurus >        xThes;
    uno::Reference< XProofreader >      xGrammar;
    bool                        bConfigured;

    ServiceInfo_Impl() : bConfigured(false) {}
};

struct Locale_less
{
    bool operator()(const css::lang::Locale& lhs, const css::lang::Locale& rhs) const
    {
        if (lhs.Language < rhs.Language)
            return true;
        if (lhs.Language > rhs.Language)
            return false;
        if (lhs.Country < rhs.Country)
            return true;
        if (lhs.Country > rhs.Country)
            return false;
        return lhs.Variant < rhs.Variant;
    }
};

}

typedef std::vector< ServiceInfo_Impl >                   ServiceInfoArr;
typedef std::map< LanguageType, Sequence< OUString > >    LangImplNameTable;


// SvxLinguData_Impl ----------------------------------------------------

class SvxLinguData_Impl
{
    //contains services and implementation names sorted by implementation names
    ServiceInfoArr                      aDisplayServiceArr;
    sal_uInt32                          nDisplayServices;

    std::set<Locale, Locale_less>       aAllServiceLocales;
    LangImplNameTable                   aCfgSpellTable;
    LangImplNameTable                   aCfgHyphTable;
    LangImplNameTable                   aCfgThesTable;
    LangImplNameTable                   aCfgGrammarTable;
    uno::Reference< XLinguServiceManager2 >  xLinguSrvcMgr;


    static bool AddRemove( Sequence< OUString > &rConfigured,
                           const OUString &rImplName, bool bAdd );

public:
    SvxLinguData_Impl();

    uno::Reference<XLinguServiceManager2> &   GetManager() { return xLinguSrvcMgr; }

    void SetChecked( const Sequence< OUString > &rConfiguredServices );
    void Reconfigure( std::u16string_view rDisplayName, bool bEnable );

    const auto&                 GetAllSupportedLocales() const { return aAllServiceLocales; }

    LangImplNameTable &         GetSpellTable()         { return aCfgSpellTable; }
    LangImplNameTable &         GetHyphTable()          { return aCfgHyphTable; }
    LangImplNameTable &         GetThesTable()          { return aCfgThesTable; }
    LangImplNameTable &         GetGrammarTable()       { return aCfgGrammarTable; }

    ServiceInfoArr &            GetDisplayServiceArray()        { return aDisplayServiceArr; }

    const sal_uInt32 &   GetDisplayServiceCount() const          { return nDisplayServices; }
    void            SetDisplayServiceCount( sal_uInt32 nVal )    { nDisplayServices = nVal; }

    // returns the list of service implementation names for the specified
    // language and service (TYPE_SPELL, TYPE_HYPH, TYPE_THES) sorted in
    // the proper order for the SvxEditModulesDlg (the ones from the
    // configuration (keeping that order!) first and then the other ones.
    // I.e. the ones available but not configured in arbitrary order).
    // They available ones may contain names that do not(!) support that
    // language.
    Sequence< OUString > GetSortedImplNames( LanguageType nLang, sal_uInt8 nType );

    ServiceInfo_Impl * GetInfoByImplName( std::u16string_view rSvcImplName );
};


Sequence< OUString > SvxLinguData_Impl::GetSortedImplNames( LanguageType nLang, sal_uInt8 nType )
{
    LangImplNameTable *pTable = nullptr;
    switch (nType)
    {
        case TYPE_SPELL     : pTable = &aCfgSpellTable; break;
        case TYPE_HYPH      : pTable = &aCfgHyphTable; break;
        case TYPE_THES      : pTable = &aCfgThesTable; break;
        case TYPE_GRAMMAR   : pTable = &aCfgGrammarTable; break;
    }
    Sequence< OUString > aRes;
    if (!pTable)
    {
        SAL_WARN( "cui.options", "unknown linguistic type" );
        return aRes;
    }
    if (pTable->count( nLang ))
        aRes = (*pTable)[ nLang ];      // add configured services
    sal_Int32 nIdx = aRes.getLength();
    DBG_ASSERT( nDisplayServices >= o3tl::make_unsigned(nIdx), "size mismatch" );
    aRes.realloc( nDisplayServices );
    OUString *pRes = aRes.getArray();

    // add not configured services
    for (sal_uInt32 i = 0;  i < nDisplayServices;  ++i)
    {
        const ServiceInfo_Impl &rInfo = aDisplayServiceArr[ i ];
        OUString aImplName;
        switch (nType)
        {
            case TYPE_SPELL     : aImplName = rInfo.sSpellImplName; break;
            case TYPE_HYPH      : aImplName = rInfo.sHyphImplName; break;
            case TYPE_THES      : aImplName = rInfo.sThesImplName; break;
            case TYPE_GRAMMAR   : aImplName = rInfo.sGrammarImplName; break;
        }

        if (!aImplName.isEmpty()  &&  (lcl_SeqGetEntryPos( aRes, aImplName) == -1))    // name not yet added
        {
            DBG_ASSERT( nIdx < aRes.getLength(), "index out of range" );
            if (nIdx < aRes.getLength())
                pRes[ nIdx++ ] = aImplName;
        }
    }
    // don't forget to put aRes back to its actual size just in case you allocated too much
    // since all of the names may have already been added
    // otherwise you get duplicate entries in the edit dialog
    aRes.realloc( nIdx );
    return aRes;
}


ServiceInfo_Impl * SvxLinguData_Impl::GetInfoByImplName( std::u16string_view rSvcImplName )
{
    for (sal_uInt32 i = 0;  i < nDisplayServices;  ++i)
    {
        ServiceInfo_Impl &rTmp = aDisplayServiceArr[ i ];
        if (rTmp.sSpellImplName == rSvcImplName ||
            rTmp.sHyphImplName  == rSvcImplName ||
            rTmp.sThesImplName  == rSvcImplName ||
            rTmp.sGrammarImplName == rSvcImplName)
        {
            return &rTmp;
        }
    }
    return nullptr;
}

static void lcl_MergeDisplayArray(
        SvxLinguData_Impl &rData,
        const ServiceInfo_Impl &rToAdd )
{
    sal_uInt32 nCnt = 0;

    ServiceInfoArr &rSvcInfoArr = rData.GetDisplayServiceArray();
    sal_uInt32 nEntries = rData.GetDisplayServiceCount();

    for (sal_uInt32 i = 0;  i < nEntries;  ++i)
    {
        ServiceInfo_Impl& rEntry = rSvcInfoArr[i];
        if (rEntry.sDisplayName == rToAdd.sDisplayName)
        {
            if(rToAdd.xSpell.is())
            {
                DBG_ASSERT( !rEntry.xSpell.is() &&
                            rEntry.sSpellImplName.isEmpty(),
                            "merge conflict" );
                rEntry.sSpellImplName = rToAdd.sSpellImplName;
                rEntry.xSpell = rToAdd.xSpell;
            }
            if(rToAdd.xGrammar.is())
            {
                DBG_ASSERT( !rEntry.xGrammar.is() &&
                            rEntry.sGrammarImplName.isEmpty(),
                            "merge conflict" );
                rEntry.sGrammarImplName = rToAdd.sGrammarImplName;
                rEntry.xGrammar = rToAdd.xGrammar;
            }
            if(rToAdd.xHyph.is())
            {
                DBG_ASSERT( !rEntry.xHyph.is() &&
                            rEntry.sHyphImplName.isEmpty(),
                            "merge conflict" );
                rEntry.sHyphImplName = rToAdd.sHyphImplName;
                rEntry.xHyph = rToAdd.xHyph;
            }
            if(rToAdd.xThes.is())
            {
                DBG_ASSERT( !rEntry.xThes.is() &&
                            rEntry.sThesImplName.isEmpty(),
                            "merge conflict" );
                rEntry.sThesImplName = rToAdd.sThesImplName;
                rEntry.xThes = rToAdd.xThes;
            }
            return ;
        }
        ++nCnt;
    }
    rData.GetDisplayServiceArray().push_back( rToAdd );
    rData.SetDisplayServiceCount( nCnt + 1 );
}

SvxLinguData_Impl::SvxLinguData_Impl() :
    nDisplayServices    (0)
{
    const uno::Reference< XComponentContext >& xContext = ::comphelper::getProcessComponentContext();
    xLinguSrvcMgr = LinguServiceManager::create(xContext);

    const Locale& rCurrentLocale = Application::GetSettings().GetUILanguageTag().getLocale();
    Sequence<Any> aArgs
    {
        Any(LinguMgr::GetLinguPropertySet()),
        Any() // second argument has to be empty!
    };

    //read spell checker
    const Sequence< OUString > aSpellNames = xLinguSrvcMgr->getAvailableServices(
                    cSpell,    Locale() );

    for(const OUString& spellName : aSpellNames)
    {
        ServiceInfo_Impl aInfo;
        aInfo.sSpellImplName = spellName;
        aInfo.xSpell.set(
                        xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sSpellImplName, aArgs, xContext), UNO_QUERY);

        uno::Reference<XServiceDisplayName> xDispName(aInfo.xSpell, UNO_QUERY);
        if(xDispName.is())
            aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );

        const Sequence< Locale > aLocales( aInfo.xSpell->getLocales() );
        //! suppress display of entries with no supported languages (see feature 110994)
        if (aLocales.hasElements())
        {
            aAllServiceLocales.insert(aLocales.begin(), aLocales.end());
            lcl_MergeDisplayArray( *this, aInfo );
        }
    }

    //read grammar checker
    const Sequence< OUString > aGrammarNames = xLinguSrvcMgr->getAvailableServices(
                    cGrammar, Locale() );
    for(const OUString& grammarName : aGrammarNames)
    {
        ServiceInfo_Impl aInfo;
        aInfo.sGrammarImplName = grammarName;
        aInfo.xGrammar.set(
                        xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sGrammarImplName, aArgs, xContext), UNO_QUERY);

        uno::Reference<XServiceDisplayName> xDispName(aInfo.xGrammar, UNO_QUERY);
        if(xDispName.is())
            aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );

        const Sequence< Locale > aLocales( aInfo.xGrammar->getLocales() );
        //! suppress display of entries with no supported languages (see feature 110994)
        if (aLocales.hasElements())
        {
            aAllServiceLocales.insert(aLocales.begin(), aLocales.end());
            lcl_MergeDisplayArray( *this, aInfo );
        }
    }

    //read hyphenator
    const Sequence< OUString > aHyphNames = xLinguSrvcMgr->getAvailableServices(
                    cHyph, Locale() );
    for(const OUString& hyphName : aHyphNames)
    {
        ServiceInfo_Impl aInfo;
        aInfo.sHyphImplName = hyphName;
        aInfo.xHyph.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sHyphImplName, aArgs, xContext), UNO_QUERY);

        uno::Reference<XServiceDisplayName> xDispName(aInfo.xHyph, UNO_QUERY);
        if(xDispName.is())
            aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );

        const Sequence< Locale > aLocales( aInfo.xHyph->getLocales() );
        //! suppress display of entries with no supported languages (see feature 110994)
        if (aLocales.hasElements())
        {
            aAllServiceLocales.insert(aLocales.begin(), aLocales.end());
            lcl_MergeDisplayArray( *this, aInfo );
        }
    }

    //read thesauri
    const Sequence< OUString > aThesNames = xLinguSrvcMgr->getAvailableServices(
                    cThes,     Locale() );
    for(const OUString& thesName : aThesNames)
    {
        ServiceInfo_Impl aInfo;
        aInfo.sThesImplName = thesName;
        aInfo.xThes.set( xContext->getServiceManager()->createInstanceWithArgumentsAndContext(aInfo.sThesImplName, aArgs, xContext), UNO_QUERY);

        uno::Reference<XServiceDisplayName> xDispName(aInfo.xThes, UNO_QUERY);
        if(xDispName.is())
            aInfo.sDisplayName = xDispName->getServiceDisplayName( rCurrentLocale );

        const Sequence< Locale > aLocales( aInfo.xThes->getLocales() );
        //! suppress display of entries with no supported languages (see feature 110994)
        if (aLocales.hasElements())
        {
            aAllServiceLocales.insert(aLocales.begin(), aLocales.end());
            lcl_MergeDisplayArray( *this, aInfo );
        }
    }

    Sequence< OUString > aCfgSvcs;
    for (auto const& locale : aAllServiceLocales)
    {
        LanguageType nLang = LanguageTag::convertToLanguageType( locale );

        aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cSpell, locale);
        SetChecked( aCfgSvcs );
        if (aCfgSvcs.hasElements())
            aCfgSpellTable[ nLang ] = aCfgSvcs;

        aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cGrammar, locale);
        SetChecked( aCfgSvcs );
        if (aCfgSvcs.hasElements())
            aCfgGrammarTable[ nLang ] = aCfgSvcs;

        aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cHyph, locale);
        SetChecked( aCfgSvcs );
        if (aCfgSvcs.hasElements())
            aCfgHyphTable[ nLang ] = aCfgSvcs;

        aCfgSvcs = xLinguSrvcMgr->getConfiguredServices(cThes, locale);
        SetChecked( aCfgSvcs );
        if (aCfgSvcs.hasElements())
            aCfgThesTable[ nLang ] = aCfgSvcs;
    }
}

void SvxLinguData_Impl::SetChecked(const Sequence<OUString>& rConfiguredServices)
{
    for(OUString const & configService : rConfiguredServices)
    {
        for (sal_uInt32 i = 0;  i < nDisplayServices;  ++i)
        {
            ServiceInfo_Impl& rEntry = aDisplayServiceArr[i];
            if (!rEntry.bConfigured)
            {
                const OUString &rSrvcImplName = configService;
                if (!rSrvcImplName.isEmpty()  &&
                    (rEntry.sSpellImplName == rSrvcImplName ||
                        rEntry.sGrammarImplName == rSrvcImplName ||
                        rEntry.sHyphImplName == rSrvcImplName ||
                        rEntry.sThesImplName == rSrvcImplName))
                {
                    rEntry.bConfigured = true;
                    break;
                }
            }
        }
    }
}

bool SvxLinguData_Impl::AddRemove(
            Sequence< OUString > &rConfigured,
            const OUString &rImplName, bool bAdd )
{
    bool bRet = false;  // modified?

    sal_Int32 nEntries = rConfigured.getLength();
    sal_Int32 nPos = lcl_SeqGetEntryPos(rConfigured, rImplName);
    if (bAdd  &&  nPos < 0)         // add new entry
    {
        rConfigured.realloc( ++nEntries );
        OUString *pConfigured = rConfigured.getArray();
        pConfigured[nEntries - 1] = rImplName;
        bRet = true;
    }
    else if (!bAdd  &&  nPos >= 0)  // remove existing entry
    {
        OUString *pConfigured = rConfigured.getArray();
        for (sal_Int32 i = nPos;  i < nEntries - 1;  ++i)
            pConfigured[i] = pConfigured[i + 1];
        rConfigured.realloc(--nEntries);
        bRet = true;
    }

    return bRet;
}


void SvxLinguData_Impl::Reconfigure( std::u16string_view rDisplayName, bool bEnable )
{
    DBG_ASSERT( !rDisplayName.empty(), "empty DisplayName" );

    ServiceInfo_Impl *pInfo = nullptr;
    for (sal_uInt32 i = 0;  i < nDisplayServices;  ++i)
    {
        ServiceInfo_Impl& rTmp = aDisplayServiceArr[i];
        if (rTmp.sDisplayName == rDisplayName)
        {
            pInfo = &rTmp;
            break;
        }
    }
    DBG_ASSERT( pInfo, "DisplayName entry not found" );
    if (!pInfo)
        return;

    pInfo->bConfigured = bEnable;

    // update configured spellchecker entries
    if (pInfo->xSpell.is())
    {
        for (auto& locale : pInfo->xSpell->getLocales())
        {
            LanguageType nLang = LanguageTag::convertToLanguageType(locale);
            if (!aCfgSpellTable.count( nLang ) && bEnable)
                aCfgSpellTable[ nLang ] = Sequence< OUString >();
            if (aCfgSpellTable.count( nLang ))
                AddRemove( aCfgSpellTable[ nLang ], pInfo->sSpellImplName, bEnable );
        }
    }

    // update configured grammar checker entries
    if (pInfo->xGrammar.is())
    {
        for (auto& locale : pInfo->xGrammar->getLocales())
        {
            LanguageType nLang = LanguageTag::convertToLanguageType(locale);
            if (!aCfgGrammarTable.count( nLang ) && bEnable)
                aCfgGrammarTable[ nLang ] = Sequence< OUString >();
            if (aCfgGrammarTable.count( nLang ))
                AddRemove( aCfgGrammarTable[ nLang ], pInfo->sGrammarImplName, bEnable );
        }
    }

    // update configured hyphenator entries
    if (pInfo->xHyph.is())
    {
        for (auto& locale : pInfo->xHyph->getLocales())
        {
            LanguageType nLang = LanguageTag::convertToLanguageType(locale);
            if (!aCfgHyphTable.count( nLang ) && bEnable)
                aCfgHyphTable[ nLang ] = Sequence< OUString >();
            if (aCfgHyphTable.count( nLang ))
                AddRemove( aCfgHyphTable[ nLang ], pInfo->sHyphImplName, bEnable );
        }
    }

    // update configured spellchecker entries
    if (!pInfo->xThes.is())
        return;

    for (auto& locale : pInfo->xThes->getLocales())
    {
        LanguageType nLang = LanguageTag::convertToLanguageType(locale);
        if (!aCfgThesTable.count( nLang ) && bEnable)
            aCfgThesTable[ nLang ] = Sequence< OUString >();
        if (aCfgThesTable.count( nLang ))
            AddRemove( aCfgThesTable[ nLang ], pInfo->sThesImplName, bEnable );
    }
}


// class SvxLinguTabPage -------------------------------------------------

SvxLinguTabPage::SvxLinguTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/optlingupage.ui"_ustr, u"OptLinguPage"_ustr, &rSet)
    , sCapitalWords   (CuiResId(RID_CUISTR_CAPITAL_WORDS))
    , sWordsWithDigits(CuiResId(RID_CUISTR_WORDS_WITH_DIGITS))
    , sSpellSpecial   (CuiResId(RID_CUISTR_SPELL_SPECIAL))
    , sSpellAuto      (CuiResId(RID_CUISTR_SPELL_AUTO))
    , sSpellClosedCompound (CuiResId(RID_CUISTR_SPELL_CLOSED_COMPOUND))
    , sSpellHyphenatedCompound (CuiResId(RID_CUISTR_SPELL_HYPHENATED_COMPOUND))
    , sGrammarAuto    (CuiResId(RID_CUISTR_GRAMMAR_AUTO))
    , sNumMinWordlen  (CuiResId(RID_CUISTR_NUM_MIN_WORDLEN))
    , sNumPreBreak    (CuiResId(RID_CUISTR_NUM_PRE_BREAK))
    , sNumPostBreak   (CuiResId(RID_CUISTR_NUM_POST_BREAK))
    , sHyphAuto       (CuiResId(RID_CUISTR_HYPH_AUTO))
    , sHyphSpecial    (CuiResId(RID_CUISTR_HYPH_SPECIAL))
    , nUPN_HYPH_MIN_WORD_LENGTH(-1)
    , nUPN_HYPH_MIN_LEADING(-1)
    , nUPN_HYPH_MIN_TRAILING(-1)
    , m_nDlbClickEventId(nullptr)
    , m_xLinguModulesFrame(m_xBuilder->weld_container(u"lingumodulesframe"_ustr))
    , m_xLinguModulesCLB(m_xBuilder->weld_tree_view(u"lingumodules"_ustr))
    , m_xLinguModulesEditPB(m_xBuilder->weld_button(u"lingumodulesedit"_ustr))
    , m_xLinguDicsFT(m_xBuilder->weld_label(u"lingudictsft"_ustr))
    , m_xLinguDicsCLB(m_xBuilder->weld_tree_view(u"lingudicts"_ustr))
    , m_xLinguDicsNewPB(m_xBuilder->weld_button(u"lingudictsnew"_ustr))
    , m_xLinguDicsEditPB(m_xBuilder->weld_button(u"lingudictsedit"_ustr))
    , m_xLinguDicsDelPB(m_xBuilder->weld_button(u"lingudictsdelete"_ustr))
    , m_xLinguOptionsCLB(m_xBuilder->weld_tree_view(u"linguoptions"_ustr))
    , m_xLinguOptionsEditPB(m_xBuilder->weld_button(u"linguoptionsedit"_ustr))
    , m_xMoreDictsBox(m_xBuilder->weld_box(u"moredictsbox"_ustr))
    , m_xMoreDictsLink(m_xBuilder->weld_link_button(u"moredictslink"_ustr))
{
    m_xLinguModulesCLB->enable_toggle_buttons(weld::ColumnToggleType::Check);
    m_xLinguDicsCLB->enable_toggle_buttons(weld::ColumnToggleType::Check);
    m_xLinguOptionsCLB->enable_toggle_buttons(weld::ColumnToggleType::Check);

    m_xLinguModulesCLB->connect_selection_changed(LINK(this, SvxLinguTabPage, SelectHdl_Impl));
    m_xLinguModulesCLB->connect_row_activated(LINK(this, SvxLinguTabPage, BoxDoubleClickHdl_Impl));
    m_xLinguModulesCLB->connect_toggled(LINK(this, SvxLinguTabPage, ModulesBoxCheckButtonHdl_Impl));

    m_xLinguModulesEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
    m_xLinguOptionsEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));

    m_xLinguDicsCLB->connect_selection_changed(LINK(this, SvxLinguTabPage, SelectHdl_Impl));
    m_xLinguDicsCLB->connect_toggled(LINK(this, SvxLinguTabPage, DicsBoxCheckButtonHdl_Impl));

    m_xLinguDicsNewPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
    m_xLinguDicsEditPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));
    m_xLinguDicsDelPB->connect_clicked( LINK( this, SvxLinguTabPage, ClickHdl_Impl ));

    m_xLinguOptionsCLB->connect_selection_changed(LINK(this, SvxLinguTabPage, SelectHdl_Impl));
    m_xLinguOptionsCLB->connect_row_activated(LINK(this, SvxLinguTabPage, BoxDoubleClickHdl_Impl));

    m_xMoreDictsLink->connect_activate_link(LINK(this, SvxLinguTabPage, OnLinkClick));
    if (officecfg::Office::Security::Hyperlinks::Open::get() == SvtExtendedSecurityOptions::OPEN_NEVER)
        m_xMoreDictsBox->hide();

    if (comphelper::LibreOfficeKit::isActive())
    {
        // hide User-defined Dictionaries part
        m_xBuilder->weld_frame(u"dictsframe"_ustr)->hide();
        // hide Get more dictionaries URL + icon
        m_xMoreDictsBox->hide();
    }

    xProp = LinguMgr::GetLinguPropertySet();
    xDicList.set( LinguMgr::GetDictionaryList() );
    if (xDicList.is())
    {
        // keep references to all **currently** available dictionaries,
        // since the diclist may get changed meanwhile (e.g. through the API).
        // We want the dialog to operate on the same set of dictionaries it
        // was started with.
        // Also we have to take care to not lose the last reference when
        // someone else removes a dictionary from the list.
        // removed dics will be replaced by NULL new entries be added to the end
        // Thus we may use indices as consistent references.
        aDics = xDicList->getDictionaries();

        UpdateDicBox_Impl();
    }
    else
    {
        m_xLinguDicsFT->set_sensitive(false);
        m_xLinguDicsCLB->set_sensitive(false);
        m_xLinguDicsNewPB->set_sensitive(false);
        m_xLinguDicsEditPB->set_sensitive(false);
        m_xLinguDicsDelPB->set_sensitive(false);
    }
}

SvxLinguTabPage::~SvxLinguTabPage()
{
    if (m_nDlbClickEventId)
    {
        Application::RemoveUserEvent(m_nDlbClickEventId);
        m_nDlbClickEventId = nullptr;
    }
    pLinguData.reset();
}

std::unique_ptr<SfxTabPage> SvxLinguTabPage::Create( weld::Container* pPage, weld::DialogController* pController,
                                            const SfxItemSet* rAttrSet )
{
    return std::make_unique<SvxLinguTabPage>( pPage, pController, *rAttrSet );
}

OUString SvxLinguTabPage::GetAllStrings()
{
    OUString sAllStrings;
    OUString labels[] = { u"lingumodulesft"_ustr, u"lingudictsft"_ustr, u"label4"_ustr };

    for (const auto& label : labels)
    {
        if (const auto pString = m_xBuilder->weld_label(label))
            sAllStrings += pString->get_label() + " ";
    }

    sAllStrings += m_xMoreDictsLink->get_label() + " ";

    return sAllStrings.replaceAll("_", "");
}

bool SvxLinguTabPage::FillItemSet( SfxItemSet* rCoreSet )
{
    bool bModified = true; // !!!!

    // if not HideGroups was called with GROUP_MODULES...
    if (m_xLinguModulesCLB->get_visible())
    {
        DBG_ASSERT( pLinguData, "pLinguData not yet initialized" );
        if (!pLinguData)
            pLinguData.reset( new SvxLinguData_Impl );

        // update spellchecker configuration entries
        const LangImplNameTable *pTable = &pLinguData->GetSpellTable();
        for (auto const& elem : *pTable)
        {
            LanguageType nLang = elem.first;
            const Sequence< OUString > aImplNames(elem.second);
            uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
            Locale aLocale( LanguageTag::convertToLocale(nLang) );
            if (xMgr.is())
                xMgr->setConfiguredServices( cSpell, aLocale, aImplNames );
        }

        // update grammar checker configuration entries
        pTable = &pLinguData->GetGrammarTable();
        for (auto const& elem : *pTable)
        {
            LanguageType nLang = elem.first;
            const Sequence< OUString > aImplNames(elem.second);
            uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
            Locale aLocale( LanguageTag::convertToLocale(nLang) );
            if (xMgr.is())
                xMgr->setConfiguredServices( cGrammar, aLocale, aImplNames );
        }

        // update hyphenator configuration entries
        pTable = &pLinguData->GetHyphTable();
        for (auto const& elem : *pTable)
        {
            LanguageType nLang = elem.first;
            const Sequence< OUString > aImplNames(elem.second);
            uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
            Locale aLocale( LanguageTag::convertToLocale(nLang) );
            if (xMgr.is())
                xMgr->setConfiguredServices( cHyph, aLocale, aImplNames );
        }

        // update thesaurus configuration entries
        pTable = &pLinguData->GetThesTable();
        for (auto const& elem : *pTable)
        {
            LanguageType nLang = elem.first;
            const Sequence< OUString > aImplNames(elem.second);
            uno::Reference< XLinguServiceManager2 > xMgr( pLinguData->GetManager() );
            Locale aLocale( LanguageTag::convertToLocale(nLang) );
            if (xMgr.is())
                xMgr->setConfiguredServices( cThes, aLocale, aImplNames );
        }
    }


    // activate dictionaries according to checkbox state

    Sequence< OUString > aActiveDics;
    sal_Int32 nActiveDics = 0;
    int nEntries = m_xLinguDicsCLB->n_children();
    for (int i = 0; i < nEntries; ++i)
    {
        sal_Int32 nDics = aDics.getLength();

        aActiveDics.realloc( nDics );
        OUString *pActiveDic = aActiveDics.getArray();

        DicUserData aData(m_xLinguDicsCLB->get_id(i).toUInt32());
        if (aData.GetEntryId() < nDics)
        {
            bool bChecked = m_xLinguDicsCLB->get_toggle(i) == TRISTATE_TRUE;
            uno::Reference<XDictionary> xDic(aDics[i]);
            if (xDic.is())
            {
                if (LinguMgr::GetIgnoreAllList() == xDic)
                    bChecked = true;
                xDic->setActive( bChecked );
                if (bChecked)
                    pActiveDic[nActiveDics++] = xDic->getName();
            }
        }
    }

    aActiveDics.realloc( nActiveDics );
    Any aTmp;
    aTmp <<= aActiveDics;
    SvtLinguConfig aLngCfg;
    aLngCfg.SetProperty( UPH_ACTIVE_DICTIONARIES, aTmp );


    nEntries = m_xLinguOptionsCLB->n_children();
    for (int j = 0; j < nEntries; ++j)
    {
        OptionsUserData aData(m_xLinguOptionsCLB->get_id(j).toUInt32());
        OUString aPropName( lcl_GetPropertyName( static_cast<EID_OPTIONS>(aData.GetEntryId()) ) );

        Any aAny;
        if (aData.IsCheckable())
        {
            bool bChecked = m_xLinguOptionsCLB->get_toggle(j) == TRISTATE_TRUE;
            aAny <<= bChecked;
        }
        else if (aData.HasNumericValue())
        {
            sal_Int16 nVal = aData.GetNumericValue();
            aAny <<= nVal;
        }

        if (xProp.is())
            xProp->setPropertyValue( aPropName, aAny );
        aLngCfg.SetProperty( aPropName, aAny );
    }

    OptionsUserData aPreBreakData(m_xLinguOptionsCLB->get_id(EID_NUM_PRE_BREAK).toUInt32());
    OptionsUserData aPostBreakData(m_xLinguOptionsCLB->get_id(EID_NUM_POST_BREAK).toUInt32());
    if ( aPreBreakData.IsModified() || aPostBreakData.IsModified() )
    {
        SfxHyphenRegionItem aHyp( SID_ATTR_HYPHENREGION );
        aHyp.GetMinLead()  = static_cast<sal_uInt8>(aPreBreakData.GetNumericValue());
        aHyp.GetMinTrail() = static_cast<sal_uInt8>(aPostBreakData.GetNumericValue());
        rCoreSet->Put( aHyp );
    }

    // automatic spell checking
    bool bNewAutoCheck = m_xLinguOptionsCLB->get_toggle(EID_SPELL_AUTO) == TRISTATE_TRUE;
    const SfxPoolItem* pOld = GetOldItem( *rCoreSet, SID_AUTOSPELL_CHECK );
    if ( !pOld || static_cast<const SfxBoolItem*>(pOld)->GetValue() != bNewAutoCheck )
    {
        rCoreSet->Put( SfxBoolItem( SID_AUTOSPELL_CHECK, bNewAutoCheck ) );
        bModified = true;
    }

    return bModified;
}

sal_uInt32 SvxLinguTabPage::GetDicUserData( const uno::Reference< XDictionary > &rxDic, sal_uInt16 nIdx )
{
    sal_uInt32 nRes = 0;
    DBG_ASSERT( rxDic.is(), "dictionary not supplied" );
    if (rxDic.is())
    {
        uno::Reference< frame::XStorable > xStor( rxDic, UNO_QUERY );

        bool bChecked = rxDic->isActive();
        bool bEditable = !xStor.is() || !xStor->isReadonly();
        bool bDeletable = bEditable;

        nRes = DicUserData( nIdx,
                bChecked, bEditable, bDeletable ).GetUserData();
    }
    return nRes;
}


void SvxLinguTabPage::AddDicBoxEntry(
        const uno::Reference< XDictionary > &rxDic,
        sal_uInt16 nIdx )
{
    m_xLinguDicsCLB->freeze();

    OUString aTxt( ::GetDicInfoStr( rxDic->getName(),
                        LanguageTag( rxDic->getLocale() ).getLanguageType(),
                        DictionaryType_NEGATIVE == rxDic->getDictionaryType() ) );
    m_xLinguDicsCLB->append();  // append at end
    int nEntry = m_xLinguDicsCLB->n_children() - 1;
    DicUserData aData( GetDicUserData( rxDic, nIdx ) );
    m_xLinguDicsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
    m_xLinguDicsCLB->set_toggle(nEntry, aData.IsChecked() ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguDicsCLB->set_text(nEntry, aTxt, 0);  // append at end

    m_xLinguDicsCLB->thaw();
}

void SvxLinguTabPage::UpdateDicBox_Impl()
{
    m_xLinguDicsCLB->freeze();
    m_xLinguDicsCLB->clear();

    for (sal_Int32 i = 0;  i < aDics.getLength();  ++i)
    {
        const uno::Reference<XDictionary>& rDic = aDics[i];
        if (rDic.is())
            AddDicBoxEntry( rDic, static_cast<sal_uInt16>(i) );
    }

    m_xLinguDicsCLB->thaw();
    if (m_xLinguDicsCLB->n_children())
    {
        m_xLinguDicsCLB->select(0);
        SelectHdl_Impl(*m_xLinguDicsCLB);
    }
}

void SvxLinguTabPage::UpdateModulesBox_Impl()
{
    if (!pLinguData)
        return;

    const ServiceInfoArr &rAllDispSrvcArr = pLinguData->GetDisplayServiceArray();
    const sal_uInt32 nDispSrvcCount = pLinguData->GetDisplayServiceCount();

    m_xLinguModulesCLB->clear();

    for (sal_uInt32 i = 0;  i < nDispSrvcCount;  ++i)
    {
        const ServiceInfo_Impl &rInfo = rAllDispSrvcArr[i];
        m_xLinguModulesCLB->append();
        m_xLinguModulesCLB->set_id(i, weld::toId(&rInfo));
        m_xLinguModulesCLB->set_toggle(i, rInfo.bConfigured ? TRISTATE_TRUE : TRISTATE_FALSE);
        m_xLinguModulesCLB->set_text(i, rInfo.sDisplayName, 0);
    }
    if (nDispSrvcCount)
    {
        m_xLinguModulesCLB->select(0);
        SelectHdl_Impl(*m_xLinguModulesCLB);
    }
    m_xLinguModulesEditPB->set_sensitive( nDispSrvcCount > 0 );
}

void SvxLinguTabPage::Reset( const SfxItemSet* rSet )
{
    // if not HideGroups was called with GROUP_MODULES...
    if (m_xLinguModulesCLB->get_visible())
    {
        if (!pLinguData)
            pLinguData.reset( new SvxLinguData_Impl );
        UpdateModulesBox_Impl();
    }


    //  get data from configuration
    SvtLinguConfig aLngCfg;

    m_xLinguOptionsCLB->freeze();
    m_xLinguOptionsCLB->clear();

    sal_Int16 nVal = 0;
    bool  bVal  = false;
    sal_uInt32 nUserData = 0;

    m_xLinguOptionsCLB->append();
    int nEntry = 0;

    aLngCfg.GetProperty( UPN_IS_SPELL_AUTO ) >>= bVal;
    const SfxPoolItem* pItem = GetItem( *rSet, SID_AUTOSPELL_CHECK );
    if (pItem)
        bVal = static_cast<const SfxBoolItem *>(pItem)->GetValue();
    nUserData = OptionsUserData( EID_SPELL_AUTO, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sSpellAuto, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_AUTO));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_GRAMMAR_AUTO ) >>= bVal;
    nUserData = OptionsUserData( EID_GRAMMAR_AUTO, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sGrammarAuto, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_GRAMMAR_AUTO));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_SPELL_UPPER_CASE ) >>= bVal;
    nUserData = OptionsUserData( EID_CAPITAL_WORDS, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sCapitalWords, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_UPPER_CASE));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_SPELL_WITH_DIGITS ) >>= bVal;
    nUserData = OptionsUserData( EID_WORDS_WITH_DIGITS, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sWordsWithDigits, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_WITH_DIGITS));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_SPELL_CLOSED_COMPOUND ) >>= bVal;
    nUserData = OptionsUserData( EID_SPELL_CLOSED_COMPOUND, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sSpellClosedCompound, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_CLOSED_COMPOUND));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_SPELL_HYPHENATED_COMPOUND ) >>= bVal;
    nUserData = OptionsUserData( EID_SPELL_HYPHENATED_COMPOUND, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sSpellHyphenatedCompound, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_HYPHENATED_COMPOUND));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_SPELL_SPECIAL ) >>= bVal;
    nUserData = OptionsUserData( EID_SPELL_SPECIAL, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sSpellSpecial, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_SPELL_SPECIAL));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_HYPH_MIN_WORD_LENGTH ) >>= nVal;
    nUserData = OptionsUserData( EID_NUM_MIN_WORDLEN, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
    m_xLinguOptionsCLB->set_text(nEntry, sNumMinWordlen + " " + OUString::number(nVal), 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_HYPH_MIN_WORD_LENGTH));
    nUPN_HYPH_MIN_WORD_LENGTH = nEntry;

    const SfxHyphenRegionItem *pHyp = nullptr;
    if ( rSet->GetItemState( SID_ATTR_HYPHENREGION, false ) == SfxItemState::SET )
        pHyp = & rSet->Get( SID_ATTR_HYPHENREGION );

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_HYPH_MIN_LEADING ) >>= nVal;
    if (pHyp)
        nVal = static_cast<sal_Int16>(pHyp->GetMinLead());
    nUserData = OptionsUserData( EID_NUM_PRE_BREAK, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
    m_xLinguOptionsCLB->set_text(nEntry, sNumPreBreak + " " + OUString::number(nVal), 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_HYPH_MIN_LEADING));
    nUPN_HYPH_MIN_LEADING = nEntry;

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_HYPH_MIN_TRAILING ) >>= nVal;
    if (pHyp)
        nVal = static_cast<sal_Int16>(pHyp->GetMinTrail());
    nUserData = OptionsUserData( EID_NUM_POST_BREAK, true, static_cast<sal_uInt16>(nVal), false, false).GetUserData();
    m_xLinguOptionsCLB->set_text(nEntry, sNumPostBreak + " " + OUString::number(nVal), 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_HYPH_MIN_TRAILING));
    nUPN_HYPH_MIN_TRAILING = nEntry;

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_HYPH_AUTO ) >>= bVal;
    nUserData = OptionsUserData( EID_HYPH_AUTO, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sHyphAuto, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_HYPH_AUTO));

    m_xLinguOptionsCLB->append();
    ++nEntry;

    aLngCfg.GetProperty( UPN_IS_HYPH_SPECIAL ) >>= bVal;
    nUserData = OptionsUserData( EID_HYPH_SPECIAL, false, 0, true, bVal).GetUserData();
    m_xLinguOptionsCLB->set_toggle(nEntry, bVal ? TRISTATE_TRUE : TRISTATE_FALSE);
    m_xLinguOptionsCLB->set_text(nEntry, sHyphSpecial, 0);
    m_xLinguOptionsCLB->set_id(nEntry, OUString::number(nUserData));
    m_xLinguOptionsCLB->set_sensitive(nEntry, !aLngCfg.IsReadOnly(UPN_IS_HYPH_SPECIAL));

    m_xLinguOptionsCLB->thaw();

    m_xLinguOptionsCLB->select(0);
    SelectHdl_Impl(*m_xLinguOptionsCLB);

    m_xLinguModulesCLB->set_size_request(m_xLinguModulesCLB->get_preferred_size().Width(),
                                         m_xLinguModulesCLB->get_height_rows(3));
    m_xLinguDicsCLB->set_size_request(m_xLinguDicsCLB->get_preferred_size().Width(),
                                      m_xLinguDicsCLB->get_height_rows(5));
    m_xLinguOptionsCLB->set_size_request(m_xLinguOptionsCLB->get_preferred_size().Width(),
                                         m_xLinguOptionsCLB->get_height_rows(5));

    if (officecfg::Office::Linguistic::General::DictionaryList::ActiveDictionaries::isReadOnly())
    {
        m_xLinguDicsFT->set_sensitive(false);
        m_xLinguDicsCLB->set_sensitive(false);
        m_xLinguDicsNewPB->set_sensitive(false);
        m_xLinguDicsEditPB->set_sensitive(false);
        m_xLinguDicsDelPB->set_sensitive(false);
    }
}

IMPL_LINK(SvxLinguTabPage, BoxDoubleClickHdl_Impl, weld::TreeView&, rBox, bool)
{
    if (&rBox == m_xLinguModulesCLB.get() && !m_nDlbClickEventId)
    {
        //! in order to avoid a bug causing a GPF when double clicking
        //! on a module entry and exiting the "Edit Modules" dialog
        //! after that.
        m_nDlbClickEventId = Application::PostUserEvent(LINK(this, SvxLinguTabPage, PostDblClickHdl_Impl));
    }
    else if (&rBox == m_xLinguOptionsCLB.get())
    {
        ClickHdl_Impl(*m_xLinguOptionsEditPB);
    }
    return true;
}

IMPL_LINK_NOARG(SvxLinguTabPage, PostDblClickHdl_Impl, void*, void)
{
    m_nDlbClickEventId = nullptr;
    ClickHdl_Impl(*m_xLinguModulesEditPB);
}

IMPL_LINK(SvxLinguTabPage, ModulesBoxCheckButtonHdl_Impl, const weld::TreeView::iter_col&, rRowCol, void)
{
    if (!pLinguData)
        return;
    pLinguData->Reconfigure(m_xLinguModulesCLB->get_text(rRowCol.first),
                            m_xLinguModulesCLB->get_toggle(rRowCol.first) == TRISTATE_TRUE);
}

IMPL_LINK(SvxLinguTabPage, DicsBoxCheckButtonHdl_Impl, const weld::TreeView::iter_col&, rRowCol, void)
{
    const uno::Reference<XDictionary> &rDic = aDics[m_xLinguDicsCLB->get_iter_index_in_parent(rRowCol.first)];
    if (LinguMgr::GetIgnoreAllList() == rDic)
        m_xLinguDicsCLB->set_toggle(rRowCol.first, TRISTATE_TRUE);
}

IMPL_LINK(SvxLinguTabPage, ClickHdl_Impl, weld::Button&, rBtn, void)
{
    if (m_xLinguModulesEditPB.get() == &rBtn)
    {
        if (!pLinguData)
            pLinguData.reset( new SvxLinguData_Impl );

        SvxLinguData_Impl aOldLinguData(*pLinguData);
        SvxEditModulesDlg aDlg(GetFrameWeld(), *pLinguData);
        if (aDlg.run() != RET_OK)
            *pLinguData = std::move(aOldLinguData);

        // evaluate new status of 'bConfigured' flag
        sal_uInt32 nLen = pLinguData->GetDisplayServiceCount();
        for (sal_uInt32 i = 0;  i < nLen;  ++i)
            pLinguData->GetDisplayServiceArray()[i].bConfigured = false;
        for (const auto& locale : pLinguData->GetAllSupportedLocales())
        {
            LanguageType nLang = LanguageTag::convertToLanguageType(locale);
            if (pLinguData->GetSpellTable().count( nLang ))
                pLinguData->SetChecked( pLinguData->GetSpellTable()[ nLang ] );
            if (pLinguData->GetGrammarTable().count( nLang ))
                pLinguData->SetChecked( pLinguData->GetGrammarTable()[ nLang ] );
            if (pLinguData->GetHyphTable().count( nLang ))
                pLinguData->SetChecked( pLinguData->GetHyphTable()[ nLang ] );
            if (pLinguData->GetThesTable().count( nLang ))
                pLinguData->SetChecked( pLinguData->GetThesTable()[ nLang ] );
        }

        // show new status of modules
        UpdateModulesBox_Impl();
    }
    else if (m_xLinguDicsNewPB.get() == &rBtn)
    {
        SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
        ScopedVclPtr<AbstractSvxNewDictionaryDialog> aDlg(pFact->CreateSvxNewDictionaryDialog(GetFrameWeld()));
        uno::Reference< XDictionary >  xNewDic;
        if ( aDlg->Execute() == RET_OK )
            xNewDic = aDlg->GetNewDictionary();
        if ( xNewDic.is() )
        {
            // add new dics to the end
            sal_Int32 nLen = aDics.getLength();
            aDics.realloc( nLen + 1 );

            aDics.getArray()[ nLen ] = xNewDic;

            AddDicBoxEntry( xNewDic, static_cast<sal_uInt16>(nLen) );
        }
    }
    else if (m_xLinguDicsEditPB.get() == &rBtn)
    {
        int nEntry = m_xLinguDicsCLB->get_selected_index();
        if (nEntry != -1)
        {
            DicUserData aData(m_xLinguDicsCLB->get_id(nEntry).toUInt32());
            sal_uInt16 nDicPos = aData.GetEntryId();
            sal_Int32 nDics = aDics.getLength();
            if (nDicPos < nDics)
            {
                uno::Reference<XDictionary> xDic = aDics[nDicPos];
                if (xDic.is())
                {
                    SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
                    ScopedVclPtr<VclAbstractDialog> aDlg(pFact->CreateSvxEditDictionaryDialog(GetFrameWeld(), xDic->getName()));
                    aDlg->Execute();
                }
            }
        }
    }
    else if (m_xLinguDicsDelPB.get() == &rBtn)
    {
        std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(GetFrameWeld(), u"cui/ui/querydeletedictionarydialog.ui"_ustr));
        std::unique_ptr<weld::MessageDialog> xQuery(xBuilder->weld_message_dialog(u"QueryDeleteDictionaryDialog"_ustr));
        if (RET_NO == xQuery->run())
            return;

        int nEntry = m_xLinguDicsCLB->get_selected_index();
        if (nEntry != -1)
        {
            DicUserData aData(m_xLinguDicsCLB->get_id(nEntry).toUInt32());
            sal_uInt16 nDicPos = aData.GetEntryId();
            sal_Int32 nDics = aDics.getLength();
            if (nDicPos < nDics)
            {
                uno::Reference<XDictionary> xDic = aDics[nDicPos];
                if (xDic.is())
                {
                    if (LinguMgr::GetIgnoreAllList() == xDic)
                        xDic->clear();
                    else
                    {
                        if (xDicList.is())
                            xDicList->removeDictionary( xDic );

                        uno::Reference< frame::XStorable > xStor( xDic, UNO_QUERY );
                        if ( xStor->hasLocation() && !xStor->isReadonly() )
                        {
                            OUString sURL = xStor->getLocation();
                            INetURLObject aObj(sURL);
                            DBG_ASSERT( aObj.GetProtocol() == INetProtocol::File,
                                    "non-file URLs cannot be deleted" );
                            if ( aObj.GetProtocol() == INetProtocol::File )
                            {
                                KillFile_Impl( aObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ) );
                            }
                        }

                        aDics.getArray()[ nDicPos ] = nullptr;

                        // remove entry from checklistbox
                        int nCnt = m_xLinguDicsCLB->n_children();
                        for (int i = 0;  i < nCnt;  ++i)
                        {
                            DicUserData aDicData(m_xLinguDicsCLB->get_id(i).toUInt32());
                            if (aDicData.GetEntryId() == nDicPos )
                            {
                                m_xLinguDicsCLB->remove(i);
                                break;
                            }
                        }
                        DBG_ASSERT( nCnt > m_xLinguDicsCLB->n_children(),
                                "remove failed ?");
                    }
                }
            }
        }
    }
    else if (m_xLinguOptionsEditPB.get() == &rBtn)
    {
        int nEntry = m_xLinguOptionsCLB->get_selected_index();
        DBG_ASSERT(nEntry != -1, "no entry selected");
        if (nEntry != -1)
        {
            OptionsUserData aData(m_xLinguOptionsCLB->get_id(nEntry).toUInt32());
            if (aData.HasNumericValue())
            {
                sal_uInt16 nRID = aData.GetEntryId();
                OptionsBreakSet aDlg(GetFrameWeld(), nRID);
                aDlg.GetNumericFld().set_value(aData.GetNumericValue());
                if (RET_OK == aDlg.run())
                {
                    int nVal = aDlg.GetNumericFld().get_value();
                    if (-1 != nVal && aData.GetNumericValue() != nVal)
                    {
                        aData.SetNumericValue( static_cast<sal_uInt8>(nVal) ); //! sets IsModified !
                        m_xLinguOptionsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
                        if (nEntry == nUPN_HYPH_MIN_WORD_LENGTH)
                            m_xLinguOptionsCLB->set_text(nEntry, sNumMinWordlen + " " + OUString::number(nVal), 0);
                        else if (nEntry == nUPN_HYPH_MIN_LEADING)
                            m_xLinguOptionsCLB->set_text(nEntry, sNumPreBreak + " " + OUString::number(nVal), 0);
                        else if (nEntry == nUPN_HYPH_MIN_TRAILING)
                            m_xLinguOptionsCLB->set_text(nEntry, sNumPostBreak + " " + OUString::number(nVal), 0);
                        m_xLinguOptionsCLB->set_id(nEntry, OUString::number(aData.GetUserData()));
                    }
                }
            }
        }
    }
    else
    {
        SAL_WARN("cui.options", "rBtn unexpected value");
    }
}

IMPL_LINK(SvxLinguTabPage, SelectHdl_Impl, weld::TreeView&, rBox, void)
{
    if (m_xLinguModulesCLB.get() == &rBox)
    {
    }
    else if (m_xLinguDicsCLB.get() == &rBox)
    {
        int nEntry = rBox.get_selected_index();
        if (nEntry != -1)
        {
            DicUserData aData(rBox.get_id(nEntry).toUInt32());

            // always allow to edit (i.e. at least view the content of the dictionary)
            m_xLinguDicsEditPB->set_sensitive( true );
            m_xLinguDicsDelPB->set_sensitive( aData.IsDeletable() );
        }
    }
    else if (m_xLinguOptionsCLB.get() == &rBox)
    {
        int nEntry = rBox.get_selected_index();
        if (nEntry != -1)
        {
            OptionsUserData aData(rBox.get_id(nEntry).toUInt32());
            m_xLinguOptionsEditPB->set_sensitive( aData.HasNumericValue() );
        }
    }
    else
    {
        SAL_WARN("cui.options", "rBtn unexpected value");
    }
}

void SvxLinguTabPage::HideModulesGroup()
{
    m_xLinguModulesFrame->hide();

    if (officecfg::Office::Security::Hyperlinks::Open::get() != SvtExtendedSecurityOptions::OPEN_NEVER &&
        !comphelper::LibreOfficeKit::isActive())
    {
        m_xMoreDictsBox->show();
    }
}

IMPL_STATIC_LINK_NOARG(SvxLinguTabPage, OnLinkClick, weld::LinkButton&, bool)
{
    comphelper::dispatchCommand(u".uno:MoreDictionaries"_ustr, {});
    return true;
}

SvxEditModulesDlg::SvxEditModulesDlg(weld::Window* pParent, SvxLinguData_Impl& rData)
    : GenericDialogController(pParent, u"cui/ui/editmodulesdialog.ui"_ustr, u"EditModulesDialog"_ustr)
    , sSpell(CuiResId(RID_CUISTR_SPELL))
    , sHyph(CuiResId(RID_CUISTR_HYPH))
    , sThes(CuiResId(RID_CUISTR_THES))
    , sGrammar(CuiResId(RID_CUISTR_GRAMMAR))
    , rLinguData(rData)
    , m_xModulesCLB(m_xBuilder->weld_tree_view(u"lingudicts"_ustr))
    , m_xPrioUpPB(m_xBuilder->weld_button(u"up"_ustr))
    , m_xPrioDownPB(m_xBuilder->weld_button(u"down"_ustr))
    , m_xBackPB(m_xBuilder->weld_button(u"back"_ustr))
    , m_xMoreDictsLink(m_xBuilder->weld_link_button(u"moredictslink"_ustr))
    , m_xClosePB(m_xBuilder->weld_button(u"close"_ustr))
    , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"language"_ustr)))
{
    m_xModulesCLB->set_size_request(m_xModulesCLB->get_approximate_digit_width() * 40,
                                    m_xModulesCLB->get_height_rows(12));

    m_xModulesCLB->enable_toggle_buttons(weld::ColumnToggleType::Check);

    pDefaultLinguData.reset( new SvxLinguData_Impl( rLinguData ) );

    m_xModulesCLB->connect_selection_changed(LINK(this, SvxEditModulesDlg, SelectHdl_Impl));
    m_xModulesCLB->connect_toggled(LINK(this, SvxEditModulesDlg, BoxCheckButtonHdl_Impl));

    m_xClosePB->connect_clicked( LINK( this, SvxEditModulesDlg, ClickHdl_Impl ));
    m_xPrioUpPB->connect_clicked( LINK( this, SvxEditModulesDlg, UpDownHdl_Impl ));
    m_xPrioDownPB->connect_clicked( LINK( this, SvxEditModulesDlg, UpDownHdl_Impl ));
    m_xBackPB->connect_clicked( LINK( this, SvxEditModulesDlg, BackHdl_Impl ));
    // in case of not installed language modules
    m_xPrioUpPB->set_sensitive( false );
    m_xPrioDownPB->set_sensitive( false );

    m_xMoreDictsLink->connect_activate_link(LINK(this, SvxEditModulesDlg, OnLinkClick));
    if (officecfg::Office::Security::Hyperlinks::Open::get() == SvtExtendedSecurityOptions::OPEN_NEVER)
        m_xMoreDictsLink->hide();

    // set that we want the checkbox shown if spellchecking is available
    m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::EMPTY, false, false, true);

    //fill language box
    const auto& rLoc = rLinguData.GetAllSupportedLocales();
    std::vector<LanguageType> aLanguages;
    aLanguages.reserve(rLoc.size());
    std::transform(rLoc.begin(), rLoc.end(), std::back_inserter(aLanguages),
                   [](Locale const& locale) { return LanguageTag::convertToLanguageType(locale); });
    m_xLanguageLB->InsertLanguages(aLanguages);
    LanguageType eSysLang = MsLangId::getConfiguredSystemLanguage();
    m_xLanguageLB->set_active_id( eSysLang );
    if (m_xLanguageLB->get_active_id() != eSysLang)
        m_xLanguageLB->set_active(0);

    const css::uno::Reference < css::uno::XComponentContext >& xContext(::comphelper::getProcessComponentContext());
    m_xReadWriteAccess = css::configuration::ReadWriteAccess::create(xContext, u"*"_ustr);

    m_xLanguageLB->connect_changed( LINK( this, SvxEditModulesDlg, LangSelectListBoxHdl_Impl ));
    LangSelectHdl_Impl(m_xLanguageLB.get());
}

SvxEditModulesDlg::~SvxEditModulesDlg()
{
    for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
        delete weld::fromId<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i));
}

IMPL_LINK( SvxEditModulesDlg, SelectHdl_Impl, weld::TreeView&, rBox, void )
{
    int nCurPos = rBox.get_selected_index();
    if (nCurPos == -1)
        return;

    bool bDisableUp = true;
    bool bDisableDown = true;
    ModuleUserData_Impl* pData = weld::fromId<ModuleUserData_Impl*>(rBox.get_id(nCurPos));
    if (!pData->IsParent() && pData->GetType() != TYPE_HYPH)
    {
        if (nCurPos < rBox.n_children() - 1)
        {
            bDisableDown = weld::fromId<ModuleUserData_Impl*>(rBox.get_id(nCurPos + 1))->IsParent();
        }
        if (nCurPos > 1)
        {
            bDisableUp = weld::fromId<ModuleUserData_Impl*>(rBox.get_id(nCurPos - 1))->IsParent();
        }
    }
    m_xPrioUpPB->set_sensitive(!bDisableUp);
    m_xPrioDownPB->set_sensitive(!bDisableDown);
}

IMPL_LINK( SvxEditModulesDlg, BoxCheckButtonHdl_Impl, const weld::TreeView::iter_col&, rRowCol, void )
{
    ModuleUserData_Impl* pData = weld::fromId<ModuleUserData_Impl*>(m_xModulesCLB->get_id(rRowCol.first));
    if (pData->IsParent() || pData->GetType() != TYPE_HYPH)
        return;

    // make hyphenator checkboxes function as radio-buttons
    // (at most one box may be checked)
    auto nPos = m_xModulesCLB->get_iter_index_in_parent(rRowCol.first);
    for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
    {
        pData = weld::fromId<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i));
        if (!pData->IsParent() && pData->GetType() == TYPE_HYPH && i != nPos)
        {
            m_xModulesCLB->set_toggle(i, TRISTATE_FALSE);
        }
    }
}

IMPL_LINK_NOARG(SvxEditModulesDlg, LangSelectListBoxHdl_Impl, weld::ComboBox&, void)
{
    LangSelectHdl_Impl(m_xLanguageLB.get());
}

void SvxEditModulesDlg::LangSelectHdl_Impl(const SvxLanguageBox* pBox)
{
    LanguageType  eCurLanguage = m_xLanguageLB->get_active_id();
    static Locale aLastLocale;
    Locale aCurLocale( LanguageTag::convertToLocale( eCurLanguage));

    if (pBox)
    {
        // save old probably changed settings
        // before switching to new language entries

        LanguageType nLang = LanguageTag::convertToLanguageType( aLastLocale );

        sal_Int32 nStart = 0, nLocalIndex = 0;
        Sequence< OUString > aChange;
        bool bChanged = false;
        for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
        {
            ModuleUserData_Impl* pData = weld::fromId<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i));
            if (pData->IsParent())
            {
                if (bChanged)
                {
                    LangImplNameTable *pTable = nullptr;
                    sal_uInt8 nType = pData->GetType();
                    switch (nType - 1)
                    {
                        case  TYPE_SPELL    : pTable = &rLinguData.GetSpellTable(); break;
                        case  TYPE_GRAMMAR  : pTable = &rLinguData.GetGrammarTable();  break;
                        case  TYPE_HYPH     : pTable = &rLinguData.GetHyphTable();  break;
                        case  TYPE_THES     : pTable = &rLinguData.GetThesTable();  break;
                    }
                    if (pTable)
                    {
                        aChange.realloc(nStart);
                        (*pTable)[ nLang ] = aChange;
                    }
                }
                nLocalIndex = nStart = 0;
                aChange.realloc(nEntryCount);
                bChanged = false;
            }
            else
            {
                OUString* pChange = aChange.getArray();
                pChange[nStart] = pData->GetImplName();
                bChanged |= pData->GetIndex() != nLocalIndex ||
                    static_cast<TriState>(pData->IsChecked()) != m_xModulesCLB->get_toggle(i);
                if (m_xModulesCLB->get_toggle(i))
                    nStart++;
                ++nLocalIndex;
            }
        }
        if(bChanged)
        {
            aChange.realloc(nStart);
            rLinguData.GetThesTable()[ nLang ] = std::move(aChange);
        }
    }

    for (int i = 0, nEntryCount = m_xModulesCLB->n_children(); i < nEntryCount; ++i)
        delete weld::fromId<ModuleUserData_Impl*>(m_xModulesCLB->get_id(i));
    m_xModulesCLB->clear();

    // display entries for new selected language

    if (LANGUAGE_DONTKNOW != eCurLanguage)
    {
        bool bReadOnly = false;

        int nRow = 0;
        // spellchecker entries

        ModuleUserData_Impl* pUserData = new ModuleUserData_Impl(
                                         OUString(), true, false, TYPE_SPELL, 0 );
        OUString sId(weld::toId(pUserData));
        m_xModulesCLB->append(nullptr);
        m_xModulesCLB->set_id(nRow, sId);
        m_xModulesCLB->set_text(nRow, sSpell, 0);
        m_xModulesCLB->set_text_emphasis(nRow, true, 0);
        ++nRow;

        OUString aLangNodeName = LanguageTag::convertToBcp47(aCurLocale);
        OUString aConfigPath = officecfg::Office::Linguistic::ServiceManager::path() + "/SpellCheckerList/" + aLangNodeName;
        if (m_xReadWriteAccess->hasPropertyByHierarchicalName(aConfigPath))
        {
            css::beans::Property aProperty = m_xReadWriteAccess->getPropertyByHierarchicalName(aConfigPath);
            bReadOnly = (aProperty.Attributes & css::beans::PropertyAttribute::READONLY) != 0;
        }

        sal_Int32 nLocalIndex = 0;  // index relative to parent
        for (auto& name : rLinguData.GetSortedImplNames(eCurLanguage, TYPE_SPELL))
        {
            OUString aImplName;
            bool     bIsSuppLang = false;

            ServiceInfo_Impl* pInfo = rLinguData.GetInfoByImplName(name);
            if (pInfo)
            {
                bIsSuppLang = pInfo->xSpell.is()  &&
                              pInfo->xSpell->hasLocale( aCurLocale );
                aImplName = pInfo->sSpellImplName;
            }
            if (!aImplName.isEmpty() && bIsSuppLang)
            {
                OUString aTxt( pInfo->sDisplayName );

                LangImplNameTable &rTable = rLinguData.GetSpellTable();
                const bool bHasLang = rTable.count( eCurLanguage );
                if (!bHasLang)
                {
                    SAL_INFO( "cui.options", "language entry missing" );    // only relevant if all languages found should be supported
                }
                const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
                pUserData = new ModuleUserData_Impl( aImplName, false,
                                        bCheck, TYPE_SPELL, static_cast<sal_uInt8>(nLocalIndex++) );
                sId = weld::toId(pUserData);

                m_xModulesCLB->append(nullptr);
                m_xModulesCLB->set_id(nRow, sId);
                m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
                m_xModulesCLB->set_text(nRow, aTxt, 0);
                m_xModulesCLB->set_text_emphasis(nRow, false, 0);
                m_xModulesCLB->set_sensitive(nRow, !bReadOnly);
                ++nRow;
            }
        }

        // grammar checker entries

        pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_GRAMMAR, 0 );
        sId = weld::toId(pUserData);
        m_xModulesCLB->append(nullptr);
        m_xModulesCLB->set_id(nRow, sId);
        m_xModulesCLB->set_text(nRow, sGrammar, 0);
        m_xModulesCLB->set_text_emphasis(nRow, true, 0);
        ++nRow;

        aConfigPath = officecfg::Office::Linguistic::ServiceManager::path() + "/GrammarCheckerList/" + aLangNodeName;
        if (m_xReadWriteAccess->hasPropertyByHierarchicalName(aConfigPath))
        {
            css::beans::Property aProperty = m_xReadWriteAccess->getPropertyByHierarchicalName(aConfigPath);
            bReadOnly = (aProperty.Attributes & css::beans::PropertyAttribute::READONLY) != 0;
        }

        nLocalIndex = 0;
        for (auto& name : rLinguData.GetSortedImplNames(eCurLanguage, TYPE_GRAMMAR))
        {
            OUString aImplName;
            bool     bIsSuppLang = false;

            ServiceInfo_Impl* pInfo = rLinguData.GetInfoByImplName(name);
            if (pInfo)
            {
                bIsSuppLang = pInfo->xGrammar.is()  &&
                              pInfo->xGrammar->hasLocale( aCurLocale );
                aImplName = pInfo->sGrammarImplName;
            }
            if (!aImplName.isEmpty() && bIsSuppLang)
            {
                OUString aTxt( pInfo->sDisplayName );

                LangImplNameTable &rTable = rLinguData.GetGrammarTable();
                const bool bHasLang = rTable.count( eCurLanguage );
                if (!bHasLang)
                {
                    SAL_INFO( "cui.options", "language entry missing" );    // only relevant if all languages found should be supported
                }
                const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
                pUserData = new ModuleUserData_Impl( aImplName, false,
                                        bCheck, TYPE_GRAMMAR, static_cast<sal_uInt8>(nLocalIndex++) );

                sId = weld::toId(pUserData);

                m_xModulesCLB->append(nullptr);
                m_xModulesCLB->set_id(nRow, sId);
                m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
                m_xModulesCLB->set_text(nRow, aTxt, 0);
                m_xModulesCLB->set_text_emphasis(nRow, false, 0);
                m_xModulesCLB->set_sensitive(nRow, !bReadOnly);
                ++nRow;
            }
        }

        // hyphenator entries

        pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_HYPH, 0 );
        sId = weld::toId(pUserData);
        m_xModulesCLB->append(nullptr);
        m_xModulesCLB->set_id(nRow, sId);
        m_xModulesCLB->set_text(nRow, sHyph, 0);
        m_xModulesCLB->set_text_emphasis(nRow, true, 0);
        ++nRow;

        aConfigPath = officecfg::Office::Linguistic::ServiceManager::path() + "/HyphenatorList/" + aLangNodeName;
        if (m_xReadWriteAccess->hasPropertyByHierarchicalName(aConfigPath))
        {
            css::beans::Property aProperty = m_xReadWriteAccess->getPropertyByHierarchicalName(aConfigPath);
            bReadOnly = (aProperty.Attributes & css::beans::PropertyAttribute::READONLY) != 0;
        }

        nLocalIndex = 0;
        for (auto& name : rLinguData.GetSortedImplNames(eCurLanguage, TYPE_HYPH))
        {
            OUString aImplName;
            bool     bIsSuppLang = false;

            ServiceInfo_Impl* pInfo = rLinguData.GetInfoByImplName(name);
            if (pInfo)
            {
                bIsSuppLang = pInfo->xHyph.is()  &&
                              pInfo->xHyph->hasLocale( aCurLocale );
                aImplName = pInfo->sHyphImplName;
            }
            if (!aImplName.isEmpty() && bIsSuppLang)
            {
                OUString aTxt( pInfo->sDisplayName );

                LangImplNameTable &rTable = rLinguData.GetHyphTable();
                const bool bHasLang = rTable.count( eCurLanguage );
                if (!bHasLang)
                {
                    SAL_INFO( "cui.options", "language entry missing" );    // only relevant if all languages found should be supported
                }
                const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
                pUserData = new ModuleUserData_Impl( aImplName, false,
                                        bCheck, TYPE_HYPH, static_cast<sal_uInt8>(nLocalIndex++) );
                sId = weld::toId(pUserData);

                m_xModulesCLB->append(nullptr);
                m_xModulesCLB->set_id(nRow, sId);
                m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
                m_xModulesCLB->set_text(nRow, aTxt, 0);
                m_xModulesCLB->set_text_emphasis(nRow, false, 0);
                m_xModulesCLB->set_sensitive(nRow, !bReadOnly);
                ++nRow;
            }
        }

        // thesaurus entries

        pUserData = new ModuleUserData_Impl( OUString(), true, false, TYPE_THES, 0 );
        sId = weld::toId(pUserData);
        m_xModulesCLB->append(nullptr);
        m_xModulesCLB->set_id(nRow, sId);
        m_xModulesCLB->set_text(nRow, sThes, 0);
        m_xModulesCLB->set_text_emphasis(nRow, true, 0);
        ++nRow;

        aConfigPath = officecfg::Office::Linguistic::ServiceManager::path() + "/ThesaurusList/" + aLangNodeName;
        if (m_xReadWriteAccess->hasPropertyByHierarchicalName(aConfigPath))
        {
            css::beans::Property aProperty = m_xReadWriteAccess->getPropertyByHierarchicalName(aConfigPath);
            bReadOnly = (aProperty.Attributes & css::beans::PropertyAttribute::READONLY) != 0;
        }

        nLocalIndex = 0;
        for (auto& name : rLinguData.GetSortedImplNames(eCurLanguage, TYPE_THES))
        {
            OUString aImplName;
            bool     bIsSuppLang = false;

            ServiceInfo_Impl* pInfo = rLinguData.GetInfoByImplName(name);
            if (pInfo)
            {
                bIsSuppLang = pInfo->xThes.is()  &&
                              pInfo->xThes->hasLocale( aCurLocale );
                aImplName = pInfo->sThesImplName;
            }
            if (!aImplName.isEmpty() && bIsSuppLang)
            {
                OUString aTxt( pInfo->sDisplayName );

                LangImplNameTable &rTable = rLinguData.GetThesTable();
                const bool bHasLang = rTable.count( eCurLanguage );
                if (!bHasLang)
                {
                    SAL_INFO( "cui.options", "language entry missing" );    // only relevant if all languages found should be supported
                }
                const bool bCheck = bHasLang && lcl_SeqGetEntryPos( rTable[ eCurLanguage ], aImplName ) >= 0;
                pUserData = new ModuleUserData_Impl( aImplName, false,
                                        bCheck, TYPE_THES, static_cast<sal_uInt8>(nLocalIndex++) );
                sId = weld::toId(pUserData);

                m_xModulesCLB->append(nullptr);
                m_xModulesCLB->set_id(nRow, sId);
                m_xModulesCLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
                m_xModulesCLB->set_text(nRow, aTxt, 0);
                m_xModulesCLB->set_text_emphasis(nRow, false, 0);
                m_xModulesCLB->set_sensitive(nRow, !bReadOnly);
                ++nRow;
            }
        }
    }
    aLastLocale = std::move(aCurLocale);
}

IMPL_LINK( SvxEditModulesDlg, UpDownHdl_Impl, weld::Button&, rBtn, void )
{
    bool bUp = m_xPrioUpPB.get() == &rBtn;
    int nCurPos = m_xModulesCLB->get_selected_index();
    if (nCurPos == -1)
        return;

    m_xModulesCLB->freeze();

    OUString sId(m_xModulesCLB->get_id(nCurPos));
    OUString sStr(m_xModulesCLB->get_text(nCurPos));
    bool bIsChecked = m_xModulesCLB->get_toggle(nCurPos);

    m_xModulesCLB->remove(nCurPos);

    int nDestPos = bUp ? nCurPos - 1 : nCurPos + 1;

    m_xModulesCLB->insert_text(nDestPos, sStr);
    m_xModulesCLB->set_id(nDestPos, sId);
    m_xModulesCLB->set_toggle(nDestPos, bIsChecked ? TRISTATE_TRUE : TRISTATE_FALSE);

    m_xModulesCLB->thaw();

    m_xModulesCLB->select(nDestPos);
    SelectHdl_Impl(*m_xModulesCLB);
}

IMPL_LINK_NOARG(SvxEditModulesDlg, ClickHdl_Impl, weld::Button&, void)
{
    // store language config
    LangSelectHdl_Impl(m_xLanguageLB.get());
    m_xDialog->response(RET_OK);
}

IMPL_LINK_NOARG(SvxEditModulesDlg, BackHdl_Impl, weld::Button&, void)
{
    rLinguData = *pDefaultLinguData;
    LangSelectHdl_Impl(nullptr);
}

IMPL_STATIC_LINK_NOARG(SvxEditModulesDlg, OnLinkClick, weld::LinkButton&, bool)
{
    comphelper::dispatchCommand(u".uno:MoreDictionaries"_ustr, {});
    return true;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
