/* -*- 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 <sal/config.h>

#include <i18nutil/unicode.hxx>
#include <o3tl/safeint.hxx>
#include <utility>
#include <vcl/event.hxx>
#include <vcl/keycodes.hxx>
#include <vcl/settings.hxx>
#include <vcl/transfer.hxx>
#include <sfx2/app.hxx>
#include <sfx2/objsh.hxx>
#include <sfx2/viewsh.hxx>
#include <unotools/charclass.hxx>
#include <unotools/collatorwrapper.hxx>
#include <comphelper/processfactory.hxx>
#include <vcl/svapp.hxx>
#include <sfx2/module.hxx>
#include <svl/eitem.hxx>
#include <svl/cjkoptions.hxx>
#include <svl/ctloptions.hxx>
#include <svx/SmartTagMgr.hxx>
#include <com/sun/star/smarttags/XSmartTagRecognizer.hpp>
#include <rtl/strbuf.hxx>
#include <o3tl/temporary.hxx>
#include <osl/diagnose.h>
#include <tools/debug.hxx>

#include <autocdlg.hxx>
#include <editeng/acorrcfg.hxx>
#include <editeng/svxacorr.hxx>
#include <svx/cuicharmap.hxx>
#include <strings.hrc>
#include <dialmgr.hxx>
#include <svx/svxids.hrc>

static LanguageType eLastDialogLanguage = LANGUAGE_SYSTEM;

using namespace ::com::sun::star;

OfaAutoCorrDlg::OfaAutoCorrDlg(weld::Window* pParent, const SfxItemSet* _pSet )
    : SfxTabDialogController(pParent, u"cui/ui/autocorrectdialog.ui"_ustr, u"AutoCorrectDialog"_ustr, _pSet)
    , m_xLanguageBox(m_xBuilder->weld_widget(u"langbox"_ustr))
    , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box(u"lang"_ustr)))
{
    bool bShowSWOptions = false;
    bool bOpenSmartTagOptions = false;

    if ( _pSet )
    {
        const SfxBoolItem* pItem = SfxItemSet::GetItem<SfxBoolItem>(_pSet, SID_AUTO_CORRECT_DLG, false);
        if ( pItem && pItem->GetValue() )
            bShowSWOptions = true;

        const SfxBoolItem* pItem2 = SfxItemSet::GetItem<SfxBoolItem>(_pSet, SID_OPEN_SMARTTAGOPTIONS, false);
        if ( pItem2 && pItem2->GetValue() )
            bOpenSmartTagOptions = true;
    }

    AddTabPage(u"options"_ustr, OfaAutocorrOptionsPage::Create, nullptr);
    AddTabPage(u"applypage"_ustr, OfaSwAutoFmtOptionsPage::Create, nullptr);
    AddTabPage(u"wordcompletion"_ustr, OfaAutoCompleteTabPage::Create, nullptr);
    AddTabPage(u"smarttags"_ustr, OfaSmartTagOptionsTabPage::Create, nullptr);

    if (!bShowSWOptions)
    {
        RemoveTabPage(u"applypage"_ustr);
        RemoveTabPage(u"wordcompletion"_ustr);
        RemoveTabPage(u"smarttags"_ustr);
    }
    else
    {
        // remove smart tag tab page if no extensions are installed
        SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
        SvxSwAutoFormatFlags& rOpt = pAutoCorrect->GetSwFlags();
        if (!rOpt.pSmartTagMgr || 0 == rOpt.pSmartTagMgr->NumberOfRecognizers())
            RemoveTabPage(u"smarttags"_ustr);

        RemoveTabPage(u"options"_ustr);
    }

    AddTabPage(u"replace"_ustr, OfaAutocorrReplacePage::Create, nullptr);
    AddTabPage(u"exceptions"_ustr,  OfaAutocorrExceptPage::Create, nullptr);
    AddTabPage(u"localized"_ustr, OfaQuoteTabPage::Create, nullptr);

    // initialize languages
    //! LANGUAGE_NONE is displayed as '[All]' and the LanguageType
    //! will be set to LANGUAGE_UNDETERMINED
    SvxLanguageListFlags nLangList = SvxLanguageListFlags::WESTERN;

    if( SvtCTLOptions::IsCTLFontEnabled() )
        nLangList |= SvxLanguageListFlags::CTL;
    if( SvtCJKOptions::IsCJKFontEnabled() )
        nLangList |= SvxLanguageListFlags::CJK;
    m_xLanguageLB->SetLanguageList( nLangList, true, true );
    m_xLanguageLB->set_active_id( LANGUAGE_NONE );
    int nPos = m_xLanguageLB->get_active();
    DBG_ASSERT(nPos != -1, "listbox entry missing" );
    m_xLanguageLB->set_id(nPos, LANGUAGE_UNDETERMINED);

    // Initializing doesn't work for static on linux - therefore here
    if (LANGUAGE_SYSTEM == eLastDialogLanguage)
        eLastDialogLanguage = Application::GetSettings().GetLanguageTag().getLanguageType();

    LanguageType nSelectLang = LANGUAGE_UNDETERMINED;
    nPos = m_xLanguageLB->find_id(eLastDialogLanguage);
    if (nPos != -1)
        nSelectLang = eLastDialogLanguage;
    m_xLanguageLB->set_active_id(nSelectLang);

    m_xLanguageLB->connect_changed(LINK(this, OfaAutoCorrDlg, SelectLanguageHdl));

    if ( bOpenSmartTagOptions )
        SetCurPageId(u"smarttags"_ustr);
}

OfaAutoCorrDlg::~OfaAutoCorrDlg()
{
}

void OfaAutoCorrDlg::EnableLanguage(bool bEnable)
{
    m_xLanguageBox->set_sensitive(bEnable);
}

static bool lcl_FindEntry(weld::TreeView& rLB, const OUString& rEntry,
                          CollatorWrapper const & rCmpClass)
{
    int nCount = rLB.n_children();
    int nSelPos = rLB.get_selected_index();
    for (int i = 0; i < nCount; i++)
    {
        if (0 == rCmpClass.compareString(rEntry, rLB.get_text(i)))
        {
            rLB.select(i);
            return true;
        }
    }
    if (nSelPos != -1)
        rLB.unselect(nSelPos);
    return false;
}

IMPL_LINK_NOARG(OfaAutoCorrDlg, SelectLanguageHdl, weld::ComboBox&, void)
{
    LanguageType eNewLang = m_xLanguageLB->get_active_id();
    // save old settings and fill anew
    if(eNewLang == eLastDialogLanguage)
        return;

    OUString sPageId = GetCurPageId();
    if (sPageId == "replace")
    {
        OfaAutocorrReplacePage* pPage = static_cast<OfaAutocorrReplacePage*>(GetTabPage(sPageId));
        assert(pPage);
        pPage->SetLanguage(eNewLang);
    }
    else if (sPageId == "exceptions")
    {
        OfaAutocorrExceptPage* pPage = static_cast<OfaAutocorrExceptPage*>(GetTabPage(sPageId));
        assert(pPage);
        pPage->SetLanguage(eNewLang);
    }
}

OfaAutocorrOptionsPage::OfaAutocorrOptionsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/acoroptionspage.ui"_ustr, u"AutocorrectOptionsPage"_ustr, &rSet)
    , m_sInput(CuiResId(RID_CUISTR_USE_REPLACE))
    , m_sDoubleCaps(CuiResId(RID_CUISTR_CPTL_STT_WORD))
    , m_sStartCap(CuiResId(RID_CUISTR_CPTL_STT_SENT))
    , m_sBoldUnderline(CuiResId(RID_CUISTR_BOLD_UNDER))
    , m_sURL(CuiResId(RID_CUISTR_DETECT_URL))
    , m_sDOI(CuiResId(RID_CUISTR_DETECT_DOI))
    , m_sNoDblSpaces(CuiResId(RID_CUISTR_NO_DBL_SPACES))
    , m_sDash(CuiResId(RID_CUISTR_DASH))
    , m_sAccidentalCaps(CuiResId(RID_CUISTR_CORRECT_ACCIDENTAL_CAPS_LOCK))
    , m_xCheckLB(m_xBuilder->weld_tree_view(u"checklist"_ustr))
{
    m_xCheckLB->enable_toggle_buttons(weld::ColumnToggleType::Check);
    m_xCheckLB->set_size_request(-1, m_xCheckLB->get_height_rows(10));
}

OfaAutocorrOptionsPage::~OfaAutocorrOptionsPage()
{
}

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

#define CBCOL_FIRST     0
#define CBCOL_SECOND    1
#define CBCOL_BOTH      2

bool OfaAutocorrOptionsPage::FillItemSet( SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    ACFlags nFlags = pAutoCorrect->GetFlags();

    int nPos = 0;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::Autocorrect,          m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::CapitalStartWord,     m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::CapitalStartSentence, m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgWeightUnderl,      m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::SetINetAttr,          m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::SetDOIAttr,           m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgToEnEmDash,        m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::IgnoreDoubleSpace,    m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    pAutoCorrect->SetAutoCorrFlag(ACFlags::CorrectCapsLock,      m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);

    bool bReturn = nFlags != pAutoCorrect->GetFlags();
    if(bReturn )
    {
        SvxAutoCorrCfg& rCfg = SvxAutoCorrCfg::Get();
        rCfg.SetModified();
        rCfg.Commit();
    }
    return bReturn;
}

void    OfaAutocorrOptionsPage::ActivatePage( const SfxItemSet& )
{
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage(false);
}

void OfaAutocorrOptionsPage::InsertEntry(const OUString& rTxt)
{
    m_xCheckLB->append();
    const int nRow = m_xCheckLB->n_children() - 1;
    m_xCheckLB->set_toggle(nRow, TRISTATE_FALSE);
    m_xCheckLB->set_text(nRow, rTxt, 0);
}

void OfaAutocorrOptionsPage::Reset( const SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    const ACFlags nFlags = pAutoCorrect->GetFlags();

    m_xCheckLB->freeze();
    m_xCheckLB->clear();

    InsertEntry(m_sInput);
    InsertEntry(m_sDoubleCaps);
    InsertEntry(m_sStartCap);
    InsertEntry(m_sBoldUnderline);
    InsertEntry(m_sURL);
    InsertEntry(m_sDOI);
    InsertEntry(m_sDash);
    InsertEntry(m_sNoDblSpaces);
    InsertEntry(m_sAccidentalCaps);

    int nPos = 0;
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::Autocorrect) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::CapitalStartWord) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::CapitalStartSentence) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::ChgWeightUnderl) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::SetINetAttr) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::SetDOIAttr) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::ChgToEnEmDash) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::IgnoreDoubleSpace) ? TRISTATE_TRUE : TRISTATE_FALSE );
    m_xCheckLB->set_toggle( nPos++, bool(nFlags & ACFlags::CorrectCapsLock) ? TRISTATE_TRUE : TRISTATE_FALSE );

    m_xCheckLB->thaw();
}

/*********************************************************************/
/*                                                                   */
/*  helping struct for dUserData of the Checklistbox                 */
/*                                                                   */
/*********************************************************************/

namespace {

struct ImpUserData
{
    OUString  *pString;
    vcl::Font *pFont;

    ImpUserData(OUString* pText, vcl::Font* pFnt)
        { pString = pText; pFont = pFnt;}
};


/*********************************************************************/
/*                                                                   */
/*  dialog for per cent settings                                     */
/*                                                                   */
/*********************************************************************/

class OfaAutoFmtPrcntSet : public weld::GenericDialogController
{
    std::unique_ptr<weld::MetricSpinButton> m_xPrcntMF;
public:
    explicit OfaAutoFmtPrcntSet(weld::Window* pParent)
        : GenericDialogController(pParent, u"cui/ui/percentdialog.ui"_ustr, u"PercentDialog"_ustr)
        , m_xPrcntMF(m_xBuilder->weld_metric_spin_button(u"margin"_ustr, FieldUnit::PERCENT))
    {
    }

    weld::MetricSpinButton& GetPrcntFld()
    {
        return *m_xPrcntMF;
    }
};

/*********************************************************************/
/*                                                                   */
/*  use TabPage autoformat                                           */
/*                                                                   */
/*********************************************************************/

enum OfaAutoFmtOptions
{
    USE_REPLACE_TABLE,
    CORR_UPPER,
    BEGIN_UPPER,
    BOLD_UNDERLINE,
    DETECT_URL,
    DETECT_DOI,
    REPLACE_DASHES,
    DEL_SPACES_AT_STT_END,
    DEL_SPACES_BETWEEN_LINES,
    IGNORE_DBLSPACE,
    CORRECT_CAPS_LOCK,
    APPLY_NUMBERING,
    APPLY_NUMBERING_AFTER_SPACE,
    INSERT_BORDER,
    CREATE_TABLE,
    REPLACE_STYLES,
    DEL_EMPTY_NODE,
    REPLACE_USER_COLL,
    REPLACE_BULLETS,
    MERGE_SINGLE_LINE_PARA
};

}

OfaSwAutoFmtOptionsPage::OfaSwAutoFmtOptionsPage(weld::Container* pPage, weld::DialogController* pController,
                                                 const SfxItemSet& rSet )
    : SfxTabPage(pPage, pController, u"cui/ui/applyautofmtpage.ui"_ustr, u"ApplyAutoFmtPage"_ustr, &rSet)
    , sDeleteEmptyPara(CuiResId(RID_CUISTR_DEL_EMPTY_PARA))
    , sUseReplaceTbl(CuiResId(RID_CUISTR_USE_REPLACE))
    , sCapitalStartWord(CuiResId(RID_CUISTR_CPTL_STT_WORD))
    , sCapitalStartSentence(CuiResId(RID_CUISTR_CPTL_STT_SENT))
    , sUserStyle(CuiResId(RID_CUISTR_USER_STYLE))
    , sBullet(CuiResId(RID_CUISTR_BULLET))
    , sBoldUnder(CuiResId(RID_CUISTR_BOLD_UNDER))
    , sNoDblSpaces(CuiResId(RID_CUISTR_NO_DBL_SPACES))
    , sCorrectCapsLock(CuiResId(RID_CUISTR_CORRECT_ACCIDENTAL_CAPS_LOCK))
    , sDetectURL(CuiResId(RID_CUISTR_DETECT_URL))
    , sDetectDOI(CuiResId(RID_CUISTR_DETECT_DOI))
    , sDash(CuiResId(RID_CUISTR_DASH))
    , sRightMargin(CuiResId(RID_CUISTR_RIGHT_MARGIN))
    , sNum(CuiResId(RID_CUISTR_NUM))
    , sBulletsAfterSpace(CuiResId(RID_SVXSTR_NUM_FORMAT_AFTER_SPACE))
    , sBorder(CuiResId(RID_CUISTR_BORDER))
    , sTable(CuiResId(RID_CUISTR_CREATE_TABLE))
    , sReplaceTemplates(CuiResId(RID_CUISTR_REPLACE_TEMPLATES))
    , sDelSpaceAtSttEnd(CuiResId(RID_CUISTR_DEL_SPACES_AT_STT_END))
    , sDelSpaceBetweenLines(CuiResId(RID_CUISTR_DEL_SPACES_BETWEEN_LINES))
    , nPercent(50)
    , m_xCheckLB(m_xBuilder->weld_tree_view(u"list"_ustr))
    , m_xEditPB(m_xBuilder->weld_button(u"edit"_ustr))
{
    m_xCheckLB->connect_selection_changed(LINK(this, OfaSwAutoFmtOptionsPage, SelectHdl));
    m_xCheckLB->connect_row_activated(LINK(this, OfaSwAutoFmtOptionsPage, DoubleClickEditHdl));

    std::vector<int> aWidths
    {
        o3tl::narrowing<int>(m_xCheckLB->get_pixel_size(m_xCheckLB->get_column_title(0)).Width() * 2),
        o3tl::narrowing<int>(m_xCheckLB->get_pixel_size(m_xCheckLB->get_column_title(1)).Width() * 2)
    };
    m_xCheckLB->set_column_fixed_widths(aWidths);

    m_xEditPB->connect_clicked(LINK(this, OfaSwAutoFmtOptionsPage, EditHdl));
}

void OfaSwAutoFmtOptionsPage::CreateEntry(const OUString& rTxt, sal_uInt16 nCol)
{
    m_xCheckLB->append();
    const int nRow = m_xCheckLB->n_children() - 1;
    if (nCol == CBCOL_FIRST || nCol == CBCOL_BOTH)
        m_xCheckLB->set_toggle(nRow, TRISTATE_FALSE, CBCOL_FIRST);
    if (nCol == CBCOL_SECOND || nCol == CBCOL_BOTH)
        m_xCheckLB->set_toggle(nRow, TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_text(nRow, rTxt, 2);
}

OfaSwAutoFmtOptionsPage::~OfaSwAutoFmtOptionsPage()
{
    delete weld::fromId<ImpUserData*>(m_xCheckLB->get_id(REPLACE_BULLETS));
    delete weld::fromId<ImpUserData*>(m_xCheckLB->get_id(APPLY_NUMBERING));
    delete weld::fromId<ImpUserData*>(m_xCheckLB->get_id(MERGE_SINGLE_LINE_PARA));
}

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

bool OfaSwAutoFmtOptionsPage::FillItemSet( SfxItemSet*  )
{
    bool bModified = false;
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();
    ACFlags nFlags = pAutoCorrect->GetFlags();

    bool bCheck = m_xCheckLB->get_toggle(USE_REPLACE_TABLE, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bAutoCorrect != bCheck;
    pOpt->bAutoCorrect = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::Autocorrect,
                        m_xCheckLB->get_toggle(USE_REPLACE_TABLE, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(CORR_UPPER, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bCapitalStartWord != bCheck;
    pOpt->bCapitalStartWord = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::CapitalStartWord,
                        m_xCheckLB->get_toggle(CORR_UPPER, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(BEGIN_UPPER, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bCapitalStartSentence != bCheck;
    pOpt->bCapitalStartSentence = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::CapitalStartSentence,
                        m_xCheckLB->get_toggle(BEGIN_UPPER, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(BOLD_UNDERLINE, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bChgWeightUnderl != bCheck;
    pOpt->bChgWeightUnderl = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgWeightUnderl,
                        m_xCheckLB->get_toggle(BOLD_UNDERLINE, CBCOL_SECOND) == TRISTATE_TRUE);

    pAutoCorrect->SetAutoCorrFlag(ACFlags::IgnoreDoubleSpace,
                        m_xCheckLB->get_toggle(IGNORE_DBLSPACE, CBCOL_SECOND) == TRISTATE_TRUE);

    pAutoCorrect->SetAutoCorrFlag(ACFlags::CorrectCapsLock,
                        m_xCheckLB->get_toggle(CORRECT_CAPS_LOCK, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(DETECT_URL, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bSetINetAttr != bCheck;
    pOpt->bSetINetAttr = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::SetINetAttr,
                        m_xCheckLB->get_toggle(DETECT_URL, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(DETECT_DOI, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bSetDOIAttr != bCheck;
    pOpt->bSetDOIAttr = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::SetDOIAttr,
                        m_xCheckLB->get_toggle(DETECT_DOI, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(DEL_EMPTY_NODE, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bDelEmptyNode != bCheck;
    pOpt->bDelEmptyNode = bCheck;

    bCheck = m_xCheckLB->get_toggle(REPLACE_USER_COLL, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bChgUserColl != bCheck;
    pOpt->bChgUserColl = bCheck;

    bCheck = m_xCheckLB->get_toggle(REPLACE_BULLETS, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bChgEnumNum != bCheck;
    pOpt->bChgEnumNum = bCheck;
    bModified |= aBulletFont != pOpt->aBulletFont;
    pOpt->aBulletFont = aBulletFont;
    bModified |= sBulletChar != OUString(&pOpt->cBullet, 1);
    pOpt->cBullet = sBulletChar.iterateCodePoints(&o3tl::temporary(sal_Int32(0)));

    bModified |= aByInputBulletFont != pOpt->aByInputBulletFont;
    bModified |= sByInputBulletChar != OUString(&pOpt->cByInputBullet, 1);
    pOpt->aByInputBulletFont = aByInputBulletFont;
    pOpt->cByInputBullet = sByInputBulletChar.iterateCodePoints(&o3tl::temporary(sal_Int32(0)));

    bCheck = m_xCheckLB->get_toggle(MERGE_SINGLE_LINE_PARA, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bRightMargin != bCheck;
    pOpt->bRightMargin = bCheck;
    bModified |= nPercent != pOpt->nRightMargin;
    pOpt->nRightMargin = static_cast<sal_uInt8>(nPercent);

    bCheck = m_xCheckLB->get_toggle(APPLY_NUMBERING, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bSetNumRule != bCheck;
    pOpt->bSetNumRule = bCheck;

    bCheck = m_xCheckLB->get_toggle(APPLY_NUMBERING_AFTER_SPACE, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bSetNumRuleAfterSpace != bCheck;
    pOpt->bSetNumRuleAfterSpace = bCheck;

    bCheck = m_xCheckLB->get_toggle(INSERT_BORDER, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bSetBorder != bCheck;
    pOpt->bSetBorder = bCheck;

    bCheck = m_xCheckLB->get_toggle(CREATE_TABLE, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bCreateTable != bCheck;
    pOpt->bCreateTable = bCheck;

    bCheck = m_xCheckLB->get_toggle(REPLACE_STYLES, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bReplaceStyles != bCheck;
    pOpt->bReplaceStyles = bCheck;
    bCheck = m_xCheckLB->get_toggle(REPLACE_STYLES, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bReplaceStylesByInput != bCheck;
    pOpt->bReplaceStylesByInput = bCheck;

    bCheck = m_xCheckLB->get_toggle(REPLACE_DASHES, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bChgToEnEmDash != bCheck;
    pOpt->bChgToEnEmDash = bCheck;
    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgToEnEmDash,
                        m_xCheckLB->get_toggle(REPLACE_DASHES, CBCOL_SECOND) == TRISTATE_TRUE);

    bCheck = m_xCheckLB->get_toggle(DEL_SPACES_AT_STT_END, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bAFormatDelSpacesAtSttEnd != bCheck;
    pOpt->bAFormatDelSpacesAtSttEnd = bCheck;
    bCheck = m_xCheckLB->get_toggle(DEL_SPACES_AT_STT_END, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bAFormatByInpDelSpacesAtSttEnd != bCheck;
    pOpt->bAFormatByInpDelSpacesAtSttEnd = bCheck;

    bCheck = m_xCheckLB->get_toggle(DEL_SPACES_BETWEEN_LINES, CBCOL_FIRST) == TRISTATE_TRUE;
    bModified |= pOpt->bAFormatDelSpacesBetweenLines != bCheck;
    pOpt->bAFormatDelSpacesBetweenLines = bCheck;
    bCheck = m_xCheckLB->get_toggle(DEL_SPACES_BETWEEN_LINES, CBCOL_SECOND) == TRISTATE_TRUE;
    bModified |= pOpt->bAFormatByInpDelSpacesBetweenLines != bCheck;
    pOpt->bAFormatByInpDelSpacesBetweenLines = bCheck;

    if(bModified || nFlags != pAutoCorrect->GetFlags())
    {
        SvxAutoCorrCfg& rCfg = SvxAutoCorrCfg::Get();
        rCfg.SetModified();
        rCfg.Commit();
    }

    return true;
}

void    OfaSwAutoFmtOptionsPage::ActivatePage( const SfxItemSet& )
{
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage(false);
}

void OfaSwAutoFmtOptionsPage::Reset( const SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();
    const ACFlags nFlags = pAutoCorrect->GetFlags();

    aBulletFont = pOpt->aBulletFont;
    sBulletChar = OUString(&pOpt->cBullet, 1);

    aByInputBulletFont = pOpt->aByInputBulletFont;
    sByInputBulletChar = OUString(&pOpt->cByInputBullet, 1);

    nPercent = pOpt->nRightMargin;
    sMargin = unicode::formatPercent(nPercent, Application::GetSettings().GetUILanguageTag());

    m_xCheckLB->freeze();
    m_xCheckLB->clear();

    // The following entries have to be inserted in the same order
    // as in the OfaAutoFmtOptions-enum!
    CreateEntry(sUseReplaceTbl,     CBCOL_BOTH  );
    CreateEntry(sCapitalStartWord,  CBCOL_BOTH  );
    CreateEntry(sCapitalStartSentence, CBCOL_BOTH  );
    CreateEntry(sBoldUnder,         CBCOL_BOTH  );
    CreateEntry(sDetectURL,         CBCOL_BOTH  );
    CreateEntry(sDetectDOI,         CBCOL_BOTH  );
    CreateEntry(sDash,              CBCOL_BOTH  );
    CreateEntry(sDelSpaceAtSttEnd,  CBCOL_BOTH  );
    CreateEntry(sDelSpaceBetweenLines, CBCOL_BOTH  );

    CreateEntry(sNoDblSpaces,       CBCOL_SECOND);
    CreateEntry(sCorrectCapsLock,   CBCOL_SECOND);
    CreateEntry(sNum.replaceFirst("%1", sBulletChar), CBCOL_SECOND);
    CreateEntry(sBulletsAfterSpace,  CBCOL_SECOND);
    CreateEntry(sBorder,            CBCOL_SECOND);
    CreateEntry(sTable,             CBCOL_SECOND);
    CreateEntry(sReplaceTemplates,  CBCOL_BOTH);
    CreateEntry(sDeleteEmptyPara,   CBCOL_FIRST );
    CreateEntry(sUserStyle,         CBCOL_FIRST );
    CreateEntry(sBullet.replaceFirst("%1", sByInputBulletChar), CBCOL_FIRST);
    CreateEntry(sRightMargin.replaceFirst("%1", sMargin), CBCOL_FIRST);

    m_xCheckLB->set_toggle(USE_REPLACE_TABLE, pOpt->bAutoCorrect ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(USE_REPLACE_TABLE, bool(nFlags & ACFlags::Autocorrect) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(CORR_UPPER, pOpt->bCapitalStartWord ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(CORR_UPPER, bool(nFlags & ACFlags::CapitalStartWord) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(BEGIN_UPPER, pOpt->bCapitalStartSentence ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(BEGIN_UPPER, bool(nFlags & ACFlags::CapitalStartSentence) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(BOLD_UNDERLINE, pOpt->bChgWeightUnderl ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(BOLD_UNDERLINE, bool(nFlags & ACFlags::ChgWeightUnderl) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(DETECT_URL, pOpt->bSetINetAttr ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(DETECT_URL, bool(nFlags & ACFlags::SetINetAttr) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(DETECT_DOI, pOpt->bSetDOIAttr ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(DETECT_DOI, bool(nFlags & ACFlags::SetDOIAttr) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(REPLACE_DASHES, pOpt->bChgToEnEmDash ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(REPLACE_DASHES, bool(nFlags & ACFlags::ChgToEnEmDash) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(DEL_SPACES_AT_STT_END, pOpt->bAFormatDelSpacesAtSttEnd ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(DEL_SPACES_AT_STT_END, pOpt->bAFormatByInpDelSpacesAtSttEnd ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(DEL_SPACES_BETWEEN_LINES, pOpt->bAFormatDelSpacesBetweenLines ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(DEL_SPACES_BETWEEN_LINES, pOpt->bAFormatByInpDelSpacesBetweenLines ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(IGNORE_DBLSPACE, bool(nFlags & ACFlags::IgnoreDoubleSpace) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(CORRECT_CAPS_LOCK, bool(nFlags & ACFlags::CorrectCapsLock) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(APPLY_NUMBERING, pOpt->bSetNumRule ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(APPLY_NUMBERING_AFTER_SPACE, pOpt->bSetNumRuleAfterSpace ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(INSERT_BORDER, pOpt->bSetBorder ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(CREATE_TABLE, pOpt->bCreateTable ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(REPLACE_STYLES, pOpt->bReplaceStyles ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(REPLACE_STYLES, pOpt->bReplaceStylesByInput ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
    m_xCheckLB->set_toggle(DEL_EMPTY_NODE, pOpt->bDelEmptyNode ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(REPLACE_USER_COLL, pOpt->bChgUserColl ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(REPLACE_BULLETS, pOpt->bChgEnumNum ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
    m_xCheckLB->set_toggle(MERGE_SINGLE_LINE_PARA, pOpt->bRightMargin ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);

    ImpUserData* pUserData = new ImpUserData(&sBulletChar, &aBulletFont);
    OUString sId(weld::toId(pUserData));
    m_xCheckLB->set_id(REPLACE_BULLETS, sId);

    pUserData = new ImpUserData(&sMargin, nullptr);
    sId = weld::toId(pUserData);
    m_xCheckLB->set_id(MERGE_SINGLE_LINE_PARA, sId);

    ImpUserData* pUserData2 = new ImpUserData(&sByInputBulletChar, &aByInputBulletFont);
    sId = weld::toId(pUserData2);
    m_xCheckLB->set_id(APPLY_NUMBERING, sId);

    m_xCheckLB->thaw();
}

IMPL_LINK(OfaSwAutoFmtOptionsPage, SelectHdl, weld::TreeView&, rBox, void)
{
    m_xEditPB->set_sensitive(rBox.get_selected_id().toInt64() != 0);
}

IMPL_LINK_NOARG(OfaSwAutoFmtOptionsPage, DoubleClickEditHdl, weld::TreeView&, bool)
{
    EditHdl(*m_xEditPB);
    return true;
}

IMPL_LINK_NOARG(OfaSwAutoFmtOptionsPage, EditHdl, weld::Button&, void)
{
    int nSelEntryPos = m_xCheckLB->get_selected_index();
    if (nSelEntryPos == REPLACE_BULLETS || nSelEntryPos == APPLY_NUMBERING)
    {
        SvxCharacterMap aMapDlg(GetFrameWeld(), nullptr, nullptr);
        ImpUserData* pUserData = weld::fromId<ImpUserData*>(m_xCheckLB->get_id(nSelEntryPos));
        aMapDlg.SetCharFont(*pUserData->pFont);
        aMapDlg.SetChar( (*pUserData->pString)[0] );
        if (RET_OK == aMapDlg.run())
        {
            const vcl::Font& aFont(aMapDlg.GetCharFont());
            *pUserData->pFont = aFont;
            sal_UCS4 aChar = aMapDlg.GetChar();
            // using the UCS4 constructor
            OUString aOUStr( &aChar, 1 );
            *pUserData->pString = aOUStr;
            if (nSelEntryPos == REPLACE_BULLETS)
                m_xCheckLB->set_text(nSelEntryPos, sNum.replaceFirst("%1", aOUStr), 2);
            else
                m_xCheckLB->set_text(nSelEntryPos, sBullet.replaceFirst("%1", aOUStr), 2);
        }
    }
    else if( MERGE_SINGLE_LINE_PARA == nSelEntryPos )
    {
        // dialog for per cent settings
        OfaAutoFmtPrcntSet aDlg(GetFrameWeld());
        aDlg.GetPrcntFld().set_value(nPercent, FieldUnit::PERCENT);
        if (aDlg.run() == RET_OK)
        {
            nPercent = static_cast<sal_uInt16>(aDlg.GetPrcntFld().get_value(FieldUnit::PERCENT));
            sMargin = unicode::formatPercent(nPercent, Application::GetSettings().GetUILanguageTag());
            m_xCheckLB->set_text(nSelEntryPos, sRightMargin.replaceFirst("%1", sMargin), 2);
        }
    }
}


OfaAutocorrReplacePage::OfaAutocorrReplacePage(weld::Container* pPage, weld::DialogController* pController,
                                               const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/acorreplacepage.ui"_ustr, u"AcorReplacePage"_ustr, &rSet)
    , maCompareClass(comphelper::getProcessComponentContext())
    , eLang(eLastDialogLanguage)
    , bHasSelectionText(false)
    , bFirstSelect(true)
    , bReplaceEditChanged(false)
    , bSWriter(true)
    , m_xTextOnlyCB(m_xBuilder->weld_check_button(u"textonly"_ustr))
    , m_xShortED(m_xBuilder->weld_entry(u"origtext"_ustr))
    , m_xReplaceED(m_xBuilder->weld_entry(u"newtext"_ustr))
    , m_xReplaceTLB(m_xBuilder->weld_tree_view(u"tabview"_ustr))
    , m_xNewReplacePB(m_xBuilder->weld_button(u"new"_ustr))
    , m_xReplacePB(m_xBuilder->weld_button(u"replace"_ustr))
    , m_xDeleteReplacePB(m_xBuilder->weld_button(u"delete"_ustr))
    , m_xButtonBox(m_xBuilder->weld_container(u"buttonbox"_ustr))
{
    sNew = m_xNewReplacePB->get_label();
    sModify = m_xReplacePB->get_label();

    // lock down the width of the button box to its max
    // desired width
    auto nMaxWidth = m_xButtonBox->get_preferred_size().Width();
    m_xButtonBox->set_size_request(nMaxWidth, -1);
    m_xReplacePB->hide();

    // tdf#125348 set some small but fixed initial width size, final width will
    // depend on the size of the entry boxes
    m_xReplaceTLB->set_size_request(42, m_xReplaceTLB->get_height_rows(10));

    SfxModule *pMod = SfxApplication::GetModule(SfxToolsModule::Writer);
    bSWriter = pMod == SfxModule::GetActiveModule();

    LanguageTag aLanguageTag( eLastDialogLanguage );
    maCompareClass.loadDefaultCollator( aLanguageTag.getLocale(), 0 );
    pCharClass.reset( new CharClass( std::move(aLanguageTag) ) );

    auto nColWidth = m_xReplaceTLB->get_approximate_digit_width() * 32;
    m_aReplaceFixedWidths.push_back(nColWidth);
    m_aReplaceFixedWidths.push_back(nColWidth);

    m_xReplaceTLB->connect_selection_changed(LINK(this, OfaAutocorrReplacePage, SelectHdl));
    m_xNewReplacePB->connect_clicked( LINK(this, OfaAutocorrReplacePage, NewDelButtonHdl) );
    m_xDeleteReplacePB->connect_clicked( LINK(this, OfaAutocorrReplacePage, NewDelButtonHdl) );
    m_xShortED->connect_changed( LINK(this, OfaAutocorrReplacePage, ModifyHdl) );
    m_xReplaceED->connect_changed( LINK(this, OfaAutocorrReplacePage, ModifyHdl) );
    m_xShortED->connect_activate( LINK(this, OfaAutocorrReplacePage, NewDelActionHdl) );
    m_xReplaceED->connect_activate( LINK(this, OfaAutocorrReplacePage, NewDelActionHdl) );
    m_xShortED->connect_size_allocate(LINK(this, OfaAutocorrReplacePage, EntrySizeAllocHdl));
    m_xReplaceED->connect_size_allocate(LINK(this, OfaAutocorrReplacePage, EntrySizeAllocHdl));
}

OfaAutocorrReplacePage::~OfaAutocorrReplacePage()
{
    aDoubleStringTable.clear();
    aChangesTable.clear();

    pCharClass.reset();
}

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

void OfaAutocorrReplacePage::ActivatePage( const SfxItemSet& )
{
    if(eLang != eLastDialogLanguage)
        SetLanguage(eLastDialogLanguage);
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage(true);
}

DeactivateRC OfaAutocorrReplacePage::DeactivatePage( SfxItemSet*  )
{
    return DeactivateRC::LeavePage;
}

bool OfaAutocorrReplacePage::FillItemSet( SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();

    for (StringChangeTable::reverse_iterator it = aChangesTable.rbegin(); it != aChangesTable.rend(); ++it)
    {
        LanguageType eCurrentLang = it->first;
        StringChangeList& rStringChangeList = it->second;
        std::vector<SvxAutocorrWord> aDeleteWords;
        std::vector<SvxAutocorrWord> aNewWords;

        aDeleteWords.reserve( rStringChangeList.aDeletedEntries.size() );
        for (const DoubleString & deleteEntry : rStringChangeList.aDeletedEntries)
        {
            SvxAutocorrWord aDeleteWord( deleteEntry.sShort, deleteEntry.sLong );
            aDeleteWords.push_back( aDeleteWord );
        }

        aNewWords.reserve( rStringChangeList.aNewEntries.size() );
        for (const DoubleString & newEntry : rStringChangeList.aNewEntries)
        {
            //fdo#67697 if the user data is set then we want to retain the
            //source formatting of the entry, so don't use the optimized
            //text-only MakeCombinedChanges for this entry
            bool bKeepSourceFormatting = newEntry.pUserData == &bHasSelectionText;
            if (bKeepSourceFormatting)
            {
                if (SfxObjectShell* pSh = SfxObjectShell::Current())
                    pAutoCorrect->PutText(newEntry.sShort, *pSh, eCurrentLang);
                continue;
            }

            SvxAutocorrWord aNewWord( newEntry.sShort, newEntry.sLong );
            aNewWords.push_back( aNewWord );
        }
        pAutoCorrect->MakeCombinedChanges( aNewWords, aDeleteWords, eCurrentLang );
    }
    aChangesTable.clear();
    return false;
}

void OfaAutocorrReplacePage::RefillReplaceBox(bool bFromReset,
                                        LanguageType eOldLanguage,
                                        LanguageType eNewLanguage)
{
    eLang = eNewLanguage;
    if(bFromReset)
    {
        aDoubleStringTable.clear();
        aChangesTable.clear();
    }
    else
    {
        DoubleStringArray* pArray;
        if(aDoubleStringTable.find(eOldLanguage) != aDoubleStringTable.end())
        {
            pArray = &aDoubleStringTable[eOldLanguage];
            pArray->clear();
        }
        else
        {
            pArray = &aDoubleStringTable[eOldLanguage]; // create new array
        }

        m_xReplaceTLB->all_foreach([this, &pArray](weld::TreeIter& rIter) {
            pArray->push_back(DoubleString(m_xReplaceTLB->get_text(rIter, 0),
                                           m_xReplaceTLB->get_text(rIter, 1)));
            DoubleString& rDouble = pArray->back();
            rDouble.pUserData = weld::fromId<void*>(m_xReplaceTLB->get_id(rIter));
            return false;
        });
    }

    if( !bSWriter )
        aFormatText.clear();

    if (aDoubleStringTable.find(eLang) != aDoubleStringTable.end())
    {
        DoubleStringArray& rArray = aDoubleStringTable[eNewLanguage];

        m_xReplaceTLB->bulk_insert_for_each(rArray.size(), [this, &rArray](weld::TreeIter& rIter, int nIndex) {
            DoubleString &rDouble = rArray[nIndex];
            bool bTextOnly = nullptr == rDouble.pUserData;
            // formatted text is only in Writer
            if (bSWriter || bTextOnly)
            {
                if (!bTextOnly)
                {
                    // that means: with format info or even with selection text
                    OUString sId = weld::toId(rDouble.pUserData);
                    m_xReplaceTLB->set_id(rIter, sId);
                }
                m_xReplaceTLB->set_text(rIter, rDouble.sShort, 0);
                m_xReplaceTLB->set_text(rIter, rDouble.sLong, 1);
            }
            else
            {
                aFormatText.insert(rDouble.sShort);
            }
        }, nullptr, &m_aReplaceFixedWidths);
    }
    else
    {
        SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
        SvxAutocorrWordList* pWordList = pAutoCorrect->LoadAutocorrWordList(eLang);
        const SvxAutocorrWordList::AutocorrWordSetType & rContent = pWordList->getSortedContent();
        m_xReplaceTLB->bulk_insert_for_each(rContent.size(), [this, rContent](weld::TreeIter& rIter, int nIndex) {
            auto const& elem = rContent[nIndex];
            bool bTextOnly = elem.IsTextOnly();
            // formatted text is only in Writer
            if (bSWriter || bTextOnly)
            {
                if (!bTextOnly)
                {
                    // that means: with format info or even with selection text
                    OUString sId = weld::toId(m_xTextOnlyCB.get());
                    m_xReplaceTLB->set_id(rIter, sId);
                }
                m_xReplaceTLB->set_text(rIter, elem.GetShort(), 0);
                m_xReplaceTLB->set_text(rIter, elem.GetLong(), 1);
            }
            else
            {
                aFormatText.insert(elem.GetShort());
            }
        }, nullptr, &m_aReplaceFixedWidths);
        m_xNewReplacePB->set_sensitive(false);
        m_xDeleteReplacePB->set_sensitive(false);
    }

    SfxViewShell* pViewShell = SfxViewShell::Current();
    if (pViewShell && pViewShell->HasSelection())
    {
        bHasSelectionText = true;
        const OUString sSelection( pViewShell->GetSelectionText() );
        m_xReplaceED->set_text(sSelection);
        m_xTextOnlyCB->set_active(!bSWriter);
        m_xTextOnlyCB->set_sensitive(bSWriter && !sSelection.isEmpty());
    }
    else
    {
        m_xTextOnlyCB->set_active(true);
        m_xTextOnlyCB->set_sensitive(false);
    }
}

void OfaAutocorrReplacePage::Reset( const SfxItemSet* )
{
    RefillReplaceBox(true, eLang, eLang);
    m_xShortED->grab_focus();
}

void OfaAutocorrReplacePage::SetLanguage(LanguageType eSet)
{
    //save old settings and refill
    if(eSet != eLang)
    {
        RefillReplaceBox(false, eLang, eSet);
        eLastDialogLanguage = eSet;

        LanguageTag aLanguageTag( eLastDialogLanguage );
        maCompareClass = CollatorWrapper( comphelper::getProcessComponentContext() );
        maCompareClass.loadDefaultCollator( aLanguageTag.getLocale(), 0 );
        pCharClass.reset( new CharClass( std::move(aLanguageTag) ) );
        ModifyHdl(*m_xShortED);
    }
}

IMPL_LINK(OfaAutocorrReplacePage, SelectHdl, weld::TreeView&, rBox, void)
{
    if(!bFirstSelect || !bHasSelectionText)
    {
        int nEntry = rBox.get_selected_index();
        OUString sTmpShort(rBox.get_text(nEntry, 0));
        // if the text is set via ModifyHdl, the cursor is always at the beginning
        // of a word, although you're editing here
        bool bSameContent = 0 == maCompareClass.compareString(sTmpShort, m_xShortED->get_text());
        int nStartPos, nEndPos;
        m_xShortED->get_selection_bounds(nStartPos, nEndPos);
        if (m_xShortED->get_text() != sTmpShort)
        {
            m_xShortED->set_text(sTmpShort);
            // if it was only a different notation, the selection has to be set again
            if (bSameContent)
            {
                m_xShortED->select_region(nStartPos, nEndPos);
            }
        }
        m_xReplaceED->set_text(rBox.get_text(nEntry, 1));
        // with UserData there is a Formatinfo
        m_xTextOnlyCB->set_active(rBox.get_id(nEntry).isEmpty());
    }
    else
    {
        bFirstSelect = false;
    }

    m_xNewReplacePB->set_sensitive(false);
    m_xDeleteReplacePB->set_sensitive(true);
};

void OfaAutocorrReplacePage::NewEntry(const OUString& sShort, const OUString& sLong, bool bKeepSourceFormatting)
{
    DoubleStringArray& rNewArray = aChangesTable[eLang].aNewEntries;
    for (size_t i = 0; i < rNewArray.size(); i++)
    {
        if (rNewArray[i].sShort == sShort)
        {
            rNewArray.erase(rNewArray.begin() + i);
            break;
        }
    }

    DoubleStringArray& rDeletedArray = aChangesTable[eLang].aDeletedEntries;
    for (size_t i = 0; i < rDeletedArray.size(); i++)
    {
        if (rDeletedArray[i].sShort == sShort)
        {
            rDeletedArray.erase(rDeletedArray.begin() + i);
            break;
        }
    }

    DoubleString aNewString(sShort, sLong);
    rNewArray.push_back(aNewString);
    if (bKeepSourceFormatting)
        rNewArray.back().pUserData = &bHasSelectionText;
}

void OfaAutocorrReplacePage::DeleteEntry(const OUString& sShort, const OUString& sLong)
{
    DoubleStringArray& rNewArray = aChangesTable[eLang].aNewEntries;
    for (size_t i = 0; i < rNewArray.size(); i++)
    {
        if (rNewArray[i].sShort == sShort)
        {
            rNewArray.erase(rNewArray.begin() + i);
            break;
        }
    }

    DoubleStringArray& rDeletedArray = aChangesTable[eLang].aDeletedEntries;
    for (size_t i = 0; i < rDeletedArray.size(); i++)
    {
        if (rDeletedArray[i].sShort == sShort)
        {
            rDeletedArray.erase(rDeletedArray.begin() + i);
            break;
        }
    }

    DoubleString aDeletedString(sShort, sLong);
    rDeletedArray.push_back(aDeletedString);
}

IMPL_LINK(OfaAutocorrReplacePage, NewDelButtonHdl, weld::Button&, rBtn, void)
{
    NewDelHdl(&rBtn);
}

IMPL_LINK(OfaAutocorrReplacePage, NewDelActionHdl, weld::Entry&, rEdit, bool)
{
    return NewDelHdl(&rEdit);
}

IMPL_LINK_NOARG(OfaAutocorrReplacePage, EntrySizeAllocHdl, const Size&, void)
{
    m_aReplaceFixedWidths.clear();
    int x, y, width, height;
    if (m_xReplaceED->get_extents_relative_to(*m_xReplaceTLB, x, y, width, height))
    {
        m_aReplaceFixedWidths.push_back(x);
        m_aReplaceFixedWidths.push_back(width - 1);
        m_xReplaceTLB->set_column_fixed_widths(m_aReplaceFixedWidths);
    }
}

bool OfaAutocorrReplacePage::NewDelHdl(const weld::Widget* pBtn)
{
    int nEntry = m_xReplaceTLB->get_selected_index();
    if (pBtn == m_xDeleteReplacePB.get())
    {
        DBG_ASSERT( nEntry != -1, "no entry selected" );
        if (nEntry != -1)
        {
            DeleteEntry(m_xReplaceTLB->get_text(nEntry, 0), m_xReplaceTLB->get_text(nEntry, 1));
            m_xReplaceTLB->remove(nEntry);
            ModifyHdl(*m_xShortED);
            return true;
        }
    }

    if (pBtn == m_xNewReplacePB.get() || m_xNewReplacePB->get_sensitive())
    {
        OUString sEntry(m_xShortED->get_text());
        if (!sEntry.isEmpty() && (!m_xReplaceED->get_text().isEmpty() ||
                ( bHasSelectionText && bSWriter ) ))
        {
            bool bKeepSourceFormatting = !bReplaceEditChanged && !m_xTextOnlyCB->get_active();

            NewEntry(m_xShortED->get_text(), m_xReplaceED->get_text(), bKeepSourceFormatting);
            m_xReplaceTLB->freeze();
            int nPos = -1;
            if (nEntry != -1)
            {
                nPos = nEntry;
                m_xReplaceTLB->remove(nEntry);
            }
            else
            {
                int j;
                int nCount = m_xReplaceTLB->n_children();
                for (j = 0; j < nCount; ++j)
                {
                    if (0 >= maCompareClass.compareString(sEntry, m_xReplaceTLB->get_text(j, 0)))
                        break;
                }
                nPos = j;
            }

            OUString sId;
            if (bKeepSourceFormatting)
            {
                sId = weld::toId(&bHasSelectionText); // new formatted text
            }

            m_xReplaceTLB->insert(nPos, sEntry, &sId, nullptr, nullptr);
            m_xReplaceTLB->set_text(nPos, m_xReplaceED->get_text(), 1);
            m_xReplaceTLB->thaw();
            m_xReplaceTLB->scroll_to_row(nPos);
            // if the request came from the ReplaceEdit, give focus to the ShortEdit
            if (m_xReplaceED->has_focus())
            {
                m_xShortED->grab_focus();
            }
        }
    }
    else
    {
        // this can only be an enter in one of the two edit fields
        // which means EndDialog() - has to be evaluated in KeyInput
        return false;
    }
    ModifyHdl(*m_xShortED);
    return true;
}

IMPL_LINK(OfaAutocorrReplacePage, ModifyHdl, weld::Entry&, rEdt, void)
{
    std::unique_ptr<weld::TreeIter> xFirstSel(m_xReplaceTLB->make_iterator());
    bool bFirstSelIterSet = m_xReplaceTLB->get_selected(xFirstSel.get());
    bool bShort = &rEdt == m_xShortED.get();
    const OUString rEntry = rEdt.get_text();
    const OUString rRepString = m_xReplaceED->get_text();
    OUString aWordStr(pCharClass->lowercase(rEntry));

    if(bShort)
    {
        if(!rEntry.isEmpty())
        {
            bool bFound = false;
            bool bTmpSelEntry=false;

            m_xReplaceTLB->all_foreach([this, &rEntry, &rRepString, &bFound,
                                        &bTmpSelEntry, &bFirstSelIterSet,
                                        &xFirstSel, &aWordStr](weld::TreeIter& rIter){
                OUString aTestStr = m_xReplaceTLB->get_text(rIter, 0);
                if( maCompareClass.compareString(rEntry, aTestStr ) == 0 )
                {
                    if (!rRepString.isEmpty())
                        bFirstSelect = true;
                    m_xReplaceTLB->set_cursor(rIter);
                    m_xReplaceTLB->copy_iterator(rIter, *xFirstSel);
                    bFirstSelIterSet = true;
                    m_xNewReplacePB->set_label(sModify);
                    bFound = true;
                    return true;
                }
                else
                {
                    aTestStr = pCharClass->lowercase( aTestStr );
                    if( !bTmpSelEntry && ( aTestStr.startsWith(aWordStr)
                        // find also with ".*" and between :colons:
                        || aTestStr.replaceAll(".*","").replaceAll(":", "").startsWith(aWordStr) ) )
                    {
                        m_xReplaceTLB->scroll_to_row(rIter);
                        bTmpSelEntry = true;
                    }
                }
                return false;
            });
            if( !bFound )
            {
                m_xReplaceTLB->select(-1);
                bFirstSelIterSet = false;
                m_xNewReplacePB->set_label(sNew);
                if( bReplaceEditChanged )
                    m_xTextOnlyCB->set_sensitive(false);
            }
            m_xDeleteReplacePB->set_sensitive(bFound);
        }
        else if (m_xReplaceTLB->n_children() > 0)
        {
            m_xReplaceTLB->scroll_to_row(0);
        }

    }
    else if( !bShort )
    {
        bReplaceEditChanged = true;
        if (bFirstSelIterSet)
        {
            m_xNewReplacePB->set_label(sModify);
        }
    }

    const OUString aShortTxt = m_xShortED->get_text();
    bool bEnableNew = !aShortTxt.isEmpty() &&
                        ( !rRepString.isEmpty() ||
                                ( bHasSelectionText && bSWriter )) &&
                        ( !bFirstSelIterSet || rRepString !=
                                m_xReplaceTLB->get_text(*xFirstSel, 1) );
    if( bEnableNew )
    {
        for (auto const& elem : aFormatText)
        {
            if(elem == aShortTxt)
            {
                bEnableNew = false;
                break;
            }
        }
    }
    m_xNewReplacePB->set_sensitive(bEnableNew);
}

static bool lcl_FindInArray(std::vector<OUString>& rStrings, std::u16string_view rString)
{
    for (auto const& elem : rStrings)
    {
        if(elem == rString)
        {
            return true;
        }
    }
    return false;
}

OfaAutocorrExceptPage::OfaAutocorrExceptPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/acorexceptpage.ui"_ustr, u"AcorExceptPage"_ustr, &rSet)
    , maCompareClass(comphelper::getProcessComponentContext())
    , eLang(eLastDialogLanguage)
    , m_xAbbrevED(m_xBuilder->weld_entry(u"abbrev"_ustr))
    , m_xAbbrevLB(m_xBuilder->weld_tree_view(u"abbrevlist"_ustr))
    , m_xNewAbbrevPB(m_xBuilder->weld_button(u"newabbrev"_ustr))
    , m_xDelAbbrevPB(m_xBuilder->weld_button(u"delabbrev"_ustr))
    , m_xAutoAbbrevCB(m_xBuilder->weld_check_button(u"autoabbrev"_ustr))
    , m_xDoubleCapsED(m_xBuilder->weld_entry(u"double"_ustr))
    , m_xDoubleCapsLB(m_xBuilder->weld_tree_view(u"doublelist"_ustr))
    , m_xNewDoublePB(m_xBuilder->weld_button(u"newdouble"_ustr))
    , m_xDelDoublePB(m_xBuilder->weld_button(u"deldouble"_ustr))
    , m_xAutoCapsCB(m_xBuilder->weld_check_button(u"autodouble"_ustr))
{
    m_xAbbrevLB->make_sorted();
    m_xAbbrevLB->set_size_request(-1, m_xAbbrevLB->get_height_rows(6));

    m_xDoubleCapsLB->make_sorted();
    m_xDoubleCapsLB->set_size_request(-1, m_xDoubleCapsLB->get_height_rows(6));

    css::lang::Locale aLcl( LanguageTag::convertToLocale(eLastDialogLanguage ));
    maCompareClass.loadDefaultCollator( aLcl, 0 );

    m_xNewAbbrevPB->connect_clicked(LINK(this, OfaAutocorrExceptPage, NewDelButtonHdl));
    m_xDelAbbrevPB->connect_clicked(LINK(this, OfaAutocorrExceptPage, NewDelButtonHdl));
    m_xNewDoublePB->connect_clicked(LINK(this, OfaAutocorrExceptPage, NewDelButtonHdl));
    m_xDelDoublePB->connect_clicked(LINK(this, OfaAutocorrExceptPage, NewDelButtonHdl));

    m_xAbbrevLB->connect_selection_changed(LINK(this, OfaAutocorrExceptPage, SelectHdl));
    m_xDoubleCapsLB->connect_selection_changed(LINK(this, OfaAutocorrExceptPage, SelectHdl));
    m_xAbbrevED->connect_changed(LINK(this, OfaAutocorrExceptPage, ModifyHdl));
    m_xDoubleCapsED->connect_changed(LINK(this, OfaAutocorrExceptPage, ModifyHdl));

    m_xAbbrevED->connect_activate(LINK(this, OfaAutocorrExceptPage, NewDelActionHdl));
    m_xDoubleCapsED->connect_activate(LINK(this, OfaAutocorrExceptPage, NewDelActionHdl));
}

OfaAutocorrExceptPage::~OfaAutocorrExceptPage()
{
    aStringsTable.clear();
}

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

void    OfaAutocorrExceptPage::ActivatePage( const SfxItemSet& )
{
    if(eLang != eLastDialogLanguage)
        SetLanguage(eLastDialogLanguage);
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage(true);
}

DeactivateRC OfaAutocorrExceptPage::DeactivatePage( SfxItemSet* )
{
    return DeactivateRC::LeavePage;
}

bool OfaAutocorrExceptPage::FillItemSet( SfxItemSet*  )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    for(StringsTable::reverse_iterator it1 = aStringsTable.rbegin(); it1 != aStringsTable.rend(); ++it1)
    {
        LanguageType eCurLang = it1->first;
        StringsArrays& rArrays = it1->second;
        if(eCurLang != eLang) // current language is treated later
        {
            SvStringsISortDtor* pWrdList = pAutoCorrect->LoadWordStartExceptList(eCurLang);

            if(pWrdList)
            {
                size_t nCount = pWrdList->size();
                size_t i;
                for( i = nCount; i; )
                {
                    OUString aString = (*pWrdList)[ --i ];

                    if( !lcl_FindInArray(rArrays.aDoubleCapsStrings, aString))
                    {
                      pWrdList->erase_at(i);
                    }
                }

                for (auto const& elem : rArrays.aDoubleCapsStrings)
                {
                    pWrdList->insert(elem);
                }
                pAutoCorrect->SaveWordStartExceptList(eCurLang);
            }

            SvStringsISortDtor* pCplList = pAutoCorrect->LoadCplSttExceptList(eCurLang);

            if(pCplList)
            {
                size_t nCount = pCplList->size();
                size_t i;
                for( i = nCount; i; )
                {
                    OUString aString = (*pCplList)[ --i ];
                    if( !lcl_FindInArray(rArrays.aAbbrevStrings, aString))
                    {
                        pCplList->erase_at(i);
                    }
                }

                for (auto const& elem : rArrays.aAbbrevStrings)
                {
                    pCplList->insert(elem);
                }

                pAutoCorrect->SaveCplSttExceptList(eCurLang);
            }
        }
    }
    aStringsTable.clear();

    SvStringsISortDtor* pWrdList = pAutoCorrect->LoadWordStartExceptList(eLang);

    if(pWrdList)
    {
        size_t nCount = pWrdList->size();
        size_t i;
        for( i = nCount; i; )
        {
            OUString aString = (*pWrdList)[ --i ];
            if (m_xDoubleCapsLB->find_text(aString) == -1)
            {
                pWrdList->erase_at(i);
            }
        }
        nCount = m_xDoubleCapsLB->n_children();
        for( i = 0; i < nCount; ++i )
        {
            pWrdList->insert(m_xDoubleCapsLB->get_text(i));
        }
        pAutoCorrect->SaveWordStartExceptList(eLang);
    }

    SvStringsISortDtor* pCplList = pAutoCorrect->LoadCplSttExceptList(eLang);

    if(pCplList)
    {
        size_t nCount = pCplList->size();
        for( size_t i = nCount; i; )
        {
            OUString aString = (*pCplList)[ --i ];
            if (m_xAbbrevLB->find_text(aString) == -1)
            {
                pCplList->erase_at(i);
            }
        }
        sal_Int32 nAbbrevCount = m_xAbbrevLB->n_children();
        for( sal_Int32 ia = 0; ia < nAbbrevCount; ++ia )
        {
            pCplList->insert(m_xAbbrevLB->get_text(ia));
        }
        pAutoCorrect->SaveCplSttExceptList(eLang);
    }
    if (m_xAutoAbbrevCB->get_state_changed_from_saved())
        pAutoCorrect->SetAutoCorrFlag( ACFlags::SaveWordCplSttLst, m_xAutoAbbrevCB->get_active());
    if (m_xAutoCapsCB->get_state_changed_from_saved())
        pAutoCorrect->SetAutoCorrFlag( ACFlags::SaveWordWordStartLst, m_xAutoCapsCB->get_active());
    return false;
}

void OfaAutocorrExceptPage::SetLanguage(LanguageType eSet)
{
    if(eLang != eSet)
    {
        // save old settings and fill anew
        RefillReplaceBoxes(false, eLang, eSet);
        eLastDialogLanguage = eSet;
        maCompareClass = CollatorWrapper( comphelper::getProcessComponentContext() );
        maCompareClass.loadDefaultCollator( LanguageTag::convertToLocale( eLastDialogLanguage ), 0 );
        ModifyHdl(*m_xAbbrevED);
        ModifyHdl(*m_xDoubleCapsED);
    }
}

void OfaAutocorrExceptPage::RefillReplaceBoxes(bool bFromReset,
                                        LanguageType eOldLanguage,
                                        LanguageType eNewLanguage)
{
    eLang = eNewLanguage;
    if(bFromReset)
    {
        aStringsTable.clear();
    }
    else
    {
        StringsArrays* pArrays;
        if(aStringsTable.find(eOldLanguage) != aStringsTable.end())
        {
            pArrays = &aStringsTable[eOldLanguage];
            pArrays->aAbbrevStrings.clear();
            pArrays->aDoubleCapsStrings.clear();
        }
        else
        {
            pArrays = &aStringsTable[eOldLanguage]; // create new array
        }

        sal_Int32 i, nCount;
        nCount = m_xAbbrevLB->n_children();
        for(i = 0; i < nCount; i++)
            pArrays->aAbbrevStrings.push_back(m_xAbbrevLB->get_text(i));

        nCount = m_xDoubleCapsLB->n_children();
        for(i = 0; i < nCount; i++)
            pArrays->aDoubleCapsStrings.push_back(m_xDoubleCapsLB->get_text(i));
    }
    m_xDoubleCapsLB->clear();
    m_xAbbrevLB->clear();
    m_xAbbrevED->set_text(u""_ustr);
    m_xDoubleCapsED->set_text(u""_ustr);

    if(aStringsTable.find(eLang) != aStringsTable.end())
    {
        StringsArrays& rArrays = aStringsTable[eLang];
        for (auto const& elem : rArrays.aAbbrevStrings)
            m_xAbbrevLB->append_text(elem);

        for (auto const& elem : rArrays.aDoubleCapsStrings)
            m_xDoubleCapsLB->append_text(elem);
    }
    else
    {
        SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
        const SvStringsISortDtor* pCplList = pAutoCorrect->GetCplSttExceptList(eLang);
        const SvStringsISortDtor* pWrdList = pAutoCorrect->GetWordStartExceptList(eLang);
        size_t i;
        for( i = 0; i < pCplList->size(); i++ )
        {
            m_xAbbrevLB->append_text((*pCplList)[i]);
        }
        for( i = 0; i < pWrdList->size(); i++ )
        {
            m_xDoubleCapsLB->append_text((*pWrdList)[i]);
        }
    }
}

void OfaAutocorrExceptPage::Reset( const SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    RefillReplaceBoxes(true, eLang, eLang);
    m_xAutoAbbrevCB->set_active(pAutoCorrect->IsAutoCorrFlag( ACFlags::SaveWordCplSttLst));
    m_xAutoCapsCB->set_active(pAutoCorrect->IsAutoCorrFlag( ACFlags::SaveWordWordStartLst));
    m_xAutoAbbrevCB->save_state();
    m_xAutoCapsCB->save_state();
}

IMPL_LINK(OfaAutocorrExceptPage, NewDelButtonHdl, weld::Button&, rBtn, void)
{
    NewDelHdl(&rBtn);
}

IMPL_LINK(OfaAutocorrExceptPage, NewDelActionHdl, weld::Entry&, rEdit, bool)
{
    return NewDelHdl(&rEdit);
}

bool OfaAutocorrExceptPage::NewDelHdl(const weld::Widget* pBtn)
{
    if ((pBtn == m_xNewAbbrevPB.get() || pBtn == m_xAbbrevED.get())
        && !m_xAbbrevED->get_text().isEmpty() && m_xNewAbbrevPB->get_sensitive())
    {
        m_xAbbrevLB->append_text(m_xAbbrevED->get_text());
        ModifyHdl(*m_xAbbrevED);
    }
    else if(pBtn == m_xDelAbbrevPB.get())
    {
        m_xAbbrevLB->remove_text(m_xAbbrevED->get_text());
        ModifyHdl(*m_xAbbrevED);
    }
    else if((pBtn == m_xNewDoublePB.get() || pBtn == m_xDoubleCapsED.get() )
            && !m_xDoubleCapsED->get_text().isEmpty() && m_xNewDoublePB->get_sensitive())
    {
        m_xDoubleCapsLB->append_text(m_xDoubleCapsED->get_text());
        ModifyHdl(*m_xDoubleCapsED);
    }
    else if (pBtn == m_xDelDoublePB.get())
    {
        m_xDoubleCapsLB->remove_text(m_xDoubleCapsED->get_text());
        ModifyHdl(*m_xDoubleCapsED);
    }
    else
    {
        // we didn't do anything, if this was because of 'activate' in an
        // entry then let it continue to close the dialog like the replace
        // page does
        return false;
    }
    return true;
}

IMPL_LINK(OfaAutocorrExceptPage, SelectHdl, weld::TreeView&, rBox, void)
{
    if (&rBox == m_xAbbrevLB.get())
    {
        m_xAbbrevED->set_text(rBox.get_selected_text());
        m_xNewAbbrevPB->set_sensitive(false);
        m_xDelAbbrevPB->set_sensitive(true);
    }
    else
    {
        m_xDoubleCapsED->set_text(rBox.get_selected_text());
        m_xNewDoublePB->set_sensitive(false);
        m_xDelDoublePB->set_sensitive(true);
    }
}

IMPL_LINK(OfaAutocorrExceptPage, ModifyHdl, weld::Entry&, rEdt, void)
{
    const OUString sEntry = rEdt.get_text();
    bool bEntryLen = !sEntry.isEmpty();
    if (&rEdt == m_xAbbrevED.get())
    {
        bool bSame = lcl_FindEntry(*m_xAbbrevLB, sEntry, maCompareClass);
        if(bSame && sEntry != m_xAbbrevLB->get_selected_text())
            rEdt.set_text(m_xAbbrevLB->get_selected_text());
        m_xNewAbbrevPB->set_sensitive(!bSame && bEntryLen);
        m_xDelAbbrevPB->set_sensitive(bSame && bEntryLen);
    }
    else
    {
        bool bSame = lcl_FindEntry(*m_xDoubleCapsLB, sEntry, maCompareClass);
        if(bSame && sEntry != m_xDoubleCapsLB->get_selected_text())
            rEdt.set_text(m_xDoubleCapsLB->get_selected_text());
        m_xNewDoublePB->set_sensitive(!bSame && bEntryLen);
        m_xDelDoublePB->set_sensitive(bSame && bEntryLen);
    }
}

namespace {

enum OfaQuoteOptions
{
    ADD_NONBRK_SPACE,
    REPLACE_1ST,
    TRANSLITERATE_RTL,
    REPLACE_ANGLE_QUOTES
};

}

void OfaQuoteTabPage::CreateEntry(weld::TreeView& rCheckLB, const OUString& rTxt, sal_uInt16 nCol, sal_uInt16 nTextCol)
{
    rCheckLB.append();
    const int nRow = rCheckLB.n_children() - 1;
    if (nCol == CBCOL_FIRST || nCol == CBCOL_BOTH)
        rCheckLB.set_toggle(nRow, TRISTATE_FALSE, CBCOL_FIRST);
    if (nCol == CBCOL_SECOND || nCol == CBCOL_BOTH)
        rCheckLB.set_toggle(nRow, TRISTATE_FALSE, CBCOL_SECOND);
    rCheckLB.set_text(nRow, rTxt, nTextCol);
}

OfaQuoteTabPage::OfaQuoteTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/applylocalizedpage.ui"_ustr, u"ApplyLocalizedPage"_ustr, &rSet)
    , sNonBrkSpace(CuiResId(RID_CUISTR_NON_BREAK_SPACE))
    , sOrdinal(CuiResId(RID_CUISTR_ORDINAL))
    , sTransliterateRTL(CuiResId(RID_CUISTR_OLD_HUNGARIAN))
    , sAngleQuotes(CuiResId(RID_CUISTR_ANGLE_QUOTES))
    , cSglStartQuote(0)
    , cSglEndQuote(0)
    , cStartQuote(0)
    , cEndQuote(0)
    , m_xSingleTypoCB(m_xBuilder->weld_check_button(u"singlereplace"_ustr))
    , m_xSglStartQuotePB(m_xBuilder->weld_button(u"startsingle"_ustr))
    , m_xSglStartExFT(m_xBuilder->weld_label(u"singlestartex"_ustr))
    , m_xSglEndQuotePB(m_xBuilder->weld_button(u"endsingle"_ustr))
    , m_xSglEndExFT(m_xBuilder->weld_label(u"singleendex"_ustr))
    , m_xSglStandardPB(m_xBuilder->weld_button(u"defaultsingle"_ustr))
    , m_xDoubleTypoCB(m_xBuilder->weld_check_button(u"doublereplace"_ustr))
    , m_xDblStartQuotePB(m_xBuilder->weld_button(u"startdouble"_ustr))
    , m_xDblStartExFT(m_xBuilder->weld_label(u"doublestartex"_ustr))
    , m_xDblEndQuotePB(m_xBuilder->weld_button(u"enddouble"_ustr))
    , m_xDblEndExFT(m_xBuilder->weld_label(u"doubleendex"_ustr))
    , m_xDblStandardPB(m_xBuilder->weld_button(u"defaultdouble"_ustr))
    , m_sStandard(m_xSglStartExFT->get_label())
    , m_xCheckLB(m_xBuilder->weld_tree_view(u"checklist"_ustr))
    , m_xSwCheckLB(m_xBuilder->weld_tree_view(u"list"_ustr))
{
    m_xSwCheckLB->set_size_request(m_xSwCheckLB->get_approximate_digit_width() * 50,
                                   m_xSwCheckLB->get_height_rows(6));

    bool bShowSWOptions = false;

    const SfxBoolItem* pItem = rSet.GetItem<SfxBoolItem>(SID_AUTO_CORRECT_DLG, false);
    if ( pItem && pItem->GetValue() )
        bShowSWOptions = true;

    if ( bShowSWOptions )
    {
        std::vector<int> aWidths
        {
            o3tl::narrowing<int>(m_xSwCheckLB->get_pixel_size(m_xSwCheckLB->get_column_title(0)).Width() * 2),
            o3tl::narrowing<int>(m_xSwCheckLB->get_pixel_size(m_xSwCheckLB->get_column_title(1)).Width() * 2)
        };
        m_xSwCheckLB->set_column_fixed_widths(aWidths);
        m_xCheckLB->hide();
    }
    else
    {
        m_xCheckLB->enable_toggle_buttons(weld::ColumnToggleType::Check);
        m_xSwCheckLB->hide();
    }

    m_xDblStartQuotePB->connect_clicked(LINK(this, OfaQuoteTabPage, QuoteHdl));
    m_xDblEndQuotePB->connect_clicked(LINK(this, OfaQuoteTabPage, QuoteHdl));
    m_xSglStartQuotePB->connect_clicked(LINK(this, OfaQuoteTabPage, QuoteHdl));
    m_xSglEndQuotePB->connect_clicked(LINK(this, OfaQuoteTabPage, QuoteHdl));
    m_xDblStandardPB->connect_clicked(LINK(this, OfaQuoteTabPage, StdQuoteHdl));
    m_xSglStandardPB->connect_clicked(LINK(this, OfaQuoteTabPage, StdQuoteHdl));
}

OfaQuoteTabPage::~OfaQuoteTabPage()
{
}

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

bool OfaQuoteTabPage::FillItemSet( SfxItemSet*  )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();

    ACFlags nFlags = pAutoCorrect->GetFlags();

    if (m_xCheckLB->get_visible())
    {
        int nPos = 0;
        pAutoCorrect->SetAutoCorrFlag(ACFlags::AddNonBrkSpace, m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
        pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgOrdinalNumber, m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
        pAutoCorrect->SetAutoCorrFlag(ACFlags::TransliterateRTL, m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
        pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgAngleQuotes, m_xCheckLB->get_toggle(nPos++) == TRISTATE_TRUE);
    }

    bool bModified = false;
    if (m_xSwCheckLB->get_visible())
    {
        SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();

        bool bCheck = m_xSwCheckLB->get_toggle(ADD_NONBRK_SPACE, CBCOL_FIRST) == TRISTATE_TRUE;
        bModified |= pOpt->bAddNonBrkSpace != bCheck;
        pOpt->bAddNonBrkSpace = bCheck;
        pAutoCorrect->SetAutoCorrFlag(ACFlags::AddNonBrkSpace,
                            m_xSwCheckLB->get_toggle(ADD_NONBRK_SPACE, CBCOL_SECOND) == TRISTATE_TRUE);

        bCheck = m_xSwCheckLB->get_toggle(REPLACE_1ST, CBCOL_FIRST) == TRISTATE_TRUE;
        bModified |= pOpt->bChgOrdinalNumber != bCheck;
        pOpt->bChgOrdinalNumber = bCheck;
        pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgOrdinalNumber,
                        m_xSwCheckLB->get_toggle(REPLACE_1ST, CBCOL_SECOND) == TRISTATE_TRUE);

        bCheck = m_xSwCheckLB->get_toggle(TRANSLITERATE_RTL, CBCOL_FIRST) == TRISTATE_TRUE;
        bModified |= pOpt->bTransliterateRTL != bCheck;
        pOpt->bTransliterateRTL = bCheck;
        pAutoCorrect->SetAutoCorrFlag(ACFlags::TransliterateRTL,
                        m_xSwCheckLB->get_toggle(TRANSLITERATE_RTL, CBCOL_SECOND) == TRISTATE_TRUE);

        bCheck = m_xSwCheckLB->get_toggle(REPLACE_ANGLE_QUOTES, CBCOL_FIRST) == TRISTATE_TRUE;
        bModified |= pOpt->bChgAngleQuotes != bCheck;
        pOpt->bChgAngleQuotes = bCheck;
        pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgAngleQuotes,
                        m_xSwCheckLB->get_toggle(REPLACE_ANGLE_QUOTES, CBCOL_SECOND) == TRISTATE_TRUE);
    }

    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgQuotes, m_xDoubleTypoCB->get_active());
    pAutoCorrect->SetAutoCorrFlag(ACFlags::ChgSglQuotes, m_xSingleTypoCB->get_active());
    bool bReturn = nFlags != pAutoCorrect->GetFlags();
    if(cStartQuote != pAutoCorrect->GetStartDoubleQuote())
    {
        bReturn = true;
        sal_Unicode cUCS2 = static_cast<sal_Unicode>(cStartQuote); //TODO
        pAutoCorrect->SetStartDoubleQuote(cUCS2);
    }
    if(cEndQuote != pAutoCorrect->GetEndDoubleQuote())
    {
        bReturn = true;
        sal_Unicode cUCS2 = static_cast<sal_Unicode>(cEndQuote); //TODO
        pAutoCorrect->SetEndDoubleQuote(cUCS2);
    }
    if(cSglStartQuote != pAutoCorrect->GetStartSingleQuote())
    {
        bReturn = true;
        sal_Unicode cUCS2 = static_cast<sal_Unicode>(cSglStartQuote); //TODO
        pAutoCorrect->SetStartSingleQuote(cUCS2);
    }
    if(cSglEndQuote != pAutoCorrect->GetEndSingleQuote())
    {
        bReturn = true;
        sal_Unicode cUCS2 = static_cast<sal_Unicode>(cSglEndQuote); //TODO
        pAutoCorrect->SetEndSingleQuote(cUCS2);
    }

    if( bModified || bReturn )
    {
        SvxAutoCorrCfg& rCfg = SvxAutoCorrCfg::Get();
        rCfg.SetModified();
        rCfg.Commit();
    }
    return bReturn;
}

void OfaQuoteTabPage::ActivatePage( const SfxItemSet& )
{
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage(false);
}

void OfaQuoteTabPage::Reset( const SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    const ACFlags nFlags = pAutoCorrect->GetFlags();

    // Initialize the Sw options
    if (m_xSwCheckLB->get_visible())
    {
        SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();

        m_xSwCheckLB->freeze();
        m_xSwCheckLB->clear();

        CreateEntry(*m_xSwCheckLB, sNonBrkSpace, CBCOL_BOTH, 2);
        CreateEntry(*m_xSwCheckLB, sOrdinal, CBCOL_BOTH, 2);
        CreateEntry(*m_xSwCheckLB, sTransliterateRTL, CBCOL_BOTH, 2);
        CreateEntry(*m_xSwCheckLB, sAngleQuotes, CBCOL_BOTH, 2);

        m_xSwCheckLB->set_toggle(ADD_NONBRK_SPACE, pOpt->bAddNonBrkSpace ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
        m_xSwCheckLB->set_toggle(ADD_NONBRK_SPACE, bool(nFlags & ACFlags::AddNonBrkSpace) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
        m_xSwCheckLB->set_toggle(REPLACE_1ST, pOpt->bChgOrdinalNumber ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
        m_xSwCheckLB->set_toggle(REPLACE_1ST, bool(nFlags & ACFlags::ChgOrdinalNumber) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
        m_xSwCheckLB->set_toggle(TRANSLITERATE_RTL, pOpt->bTransliterateRTL ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
        m_xSwCheckLB->set_toggle(TRANSLITERATE_RTL, bool(nFlags & ACFlags::TransliterateRTL) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);
        m_xSwCheckLB->set_toggle(REPLACE_ANGLE_QUOTES, pOpt->bChgAngleQuotes ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_FIRST);
        m_xSwCheckLB->set_toggle(REPLACE_ANGLE_QUOTES, bool(nFlags & ACFlags::ChgAngleQuotes) ? TRISTATE_TRUE : TRISTATE_FALSE, CBCOL_SECOND);

        m_xSwCheckLB->thaw();
    }

    // Initialize the non Sw options
    if (m_xCheckLB->get_visible())
    {
        m_xCheckLB->freeze();
        m_xCheckLB->clear();

        int nPos = 0;
        m_xCheckLB->append();
        m_xCheckLB->set_toggle(nPos, bool(nFlags & ACFlags::AddNonBrkSpace) ? TRISTATE_TRUE : TRISTATE_FALSE);
        m_xCheckLB->set_text(nPos++, sNonBrkSpace, 0);
        m_xCheckLB->append();
        m_xCheckLB->set_toggle(nPos, bool(nFlags & ACFlags::ChgOrdinalNumber) ? TRISTATE_TRUE : TRISTATE_FALSE);
        m_xCheckLB->set_text(nPos++, sOrdinal, 0);
        m_xCheckLB->append();
        m_xCheckLB->set_toggle(nPos, bool(nFlags & ACFlags::TransliterateRTL) ? TRISTATE_TRUE : TRISTATE_FALSE);
        m_xCheckLB->set_text(nPos++, sTransliterateRTL, 0);
        m_xCheckLB->append();
        m_xCheckLB->set_toggle(nPos, bool(nFlags & ACFlags::ChgAngleQuotes) ? TRISTATE_TRUE : TRISTATE_FALSE);
        m_xCheckLB->set_text(nPos++, sAngleQuotes, 0);

        m_xCheckLB->thaw();
    }

    // Initialize the quote stuffs
    m_xDoubleTypoCB->set_active(bool(nFlags & ACFlags::ChgQuotes));
    m_xSingleTypoCB->set_active(bool(nFlags & ACFlags::ChgSglQuotes));
    m_xDoubleTypoCB->save_state();
    m_xSingleTypoCB->save_state();

    cStartQuote = pAutoCorrect->GetStartDoubleQuote();
    cEndQuote = pAutoCorrect->GetEndDoubleQuote();
    cSglStartQuote = pAutoCorrect->GetStartSingleQuote();
    cSglEndQuote = pAutoCorrect->GetEndSingleQuote();

    m_xSglStartExFT->set_label(ChangeStringExt_Impl(cSglStartQuote));
    m_xSglEndExFT->set_label(ChangeStringExt_Impl(cSglEndQuote));
    m_xDblStartExFT->set_label(ChangeStringExt_Impl(cStartQuote));
    m_xDblEndExFT->set_label(ChangeStringExt_Impl(cEndQuote));
}

#define SGL_START       0
#define DBL_START       1
#define SGL_END         2
#define DBL_END         3


IMPL_LINK(OfaQuoteTabPage, QuoteHdl, weld::Button&, rBtn, void)
{
    sal_uInt16 nMode = SGL_START;
    if (&rBtn == m_xSglEndQuotePB.get())
        nMode = SGL_END;
    else if (&rBtn == m_xDblStartQuotePB.get())
        nMode = DBL_START;
    else if (&rBtn == m_xDblEndQuotePB.get())
        nMode = DBL_END;
    // start character selection dialog
    SvxCharacterMap aMap(GetFrameWeld(), nullptr, nullptr);
    aMap.SetCharFont( OutputDevice::GetDefaultFont(DefaultFontType::LATIN_TEXT,
                        LANGUAGE_ENGLISH_US, GetDefaultFontFlags::OnlyOne ));
    aMap.set_title(nMode < SGL_END ? CuiResId(RID_CUISTR_STARTQUOTE)  : CuiResId(RID_CUISTR_ENDQUOTE));
    sal_UCS4 cDlg;
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
    switch( nMode )
    {
        case SGL_START:
            cDlg = cSglStartQuote;
            if(cDlg == 0)
                cDlg = pAutoCorrect->GetQuote('\'', true, eLang);
        break;
        case SGL_END:
            cDlg = cSglEndQuote;
            if(cDlg == 0)
                cDlg = pAutoCorrect->GetQuote('\'', false, eLang);
        break;
        case DBL_START:
            cDlg = cStartQuote;
            if(cDlg == 0)
                cDlg = pAutoCorrect->GetQuote('\"', true, eLang);
        break;
        case DBL_END:
            cDlg = cEndQuote;
            if(cDlg == 0)
                cDlg = pAutoCorrect->GetQuote('\"', false, eLang);
        break;
        default:
            OSL_FAIL("svx::OfaQuoteTabPage::QuoteHdl(), how to initialize cDlg?" );
            cDlg = 0;
            break;

    }
    aMap.SetChar(  cDlg );
    aMap.DisableFontSelection();
    if (aMap.run() != RET_OK)
        return;

    sal_UCS4 cNewChar = aMap.GetChar();
    switch( nMode )
    {
        case SGL_START:
            cSglStartQuote = cNewChar;
            m_xSglStartExFT->set_label(ChangeStringExt_Impl(cNewChar));
        break;
        case SGL_END:
            cSglEndQuote = cNewChar;
            m_xSglEndExFT->set_label(ChangeStringExt_Impl(cNewChar));
        break;
        case DBL_START:
            cStartQuote = cNewChar;
            m_xDblStartExFT->set_label(ChangeStringExt_Impl(cNewChar));
        break;
        case DBL_END:
            cEndQuote = cNewChar;
            m_xDblEndExFT->set_label(ChangeStringExt_Impl(cNewChar));
        break;
    }
}

IMPL_LINK(OfaQuoteTabPage, StdQuoteHdl, weld::Button&, rBtn, void)
{
    if (&rBtn == m_xDblStandardPB.get())
    {
        cStartQuote = 0;
        m_xDblStartExFT->set_label(ChangeStringExt_Impl(0));
        cEndQuote = 0;
        m_xDblEndExFT->set_label(ChangeStringExt_Impl(0));

    }
    else
    {
        cSglStartQuote = 0;
        m_xSglStartExFT->set_label(ChangeStringExt_Impl(0));
        cSglEndQuote = 0;
        m_xSglEndExFT->set_label(ChangeStringExt_Impl(0));
    }
}

OUString OfaQuoteTabPage::ChangeStringExt_Impl( sal_UCS4 cChar )
{
    if (!cChar)
        return m_sStandard;

    // convert codepoint value to unicode-hex string
    sal_UCS4 aStrCodes[32] = { 0, ' ', '(', 'U', '+', '0' };
    aStrCodes[0] = cChar;
    int nFullLen = 5;
    int nHexLen = 4;
    while( (cChar >> (4*nHexLen)) != 0 )
        ++nHexLen;
    for( int i = nHexLen; --i >= 0;)
    {
        sal_UCS4 cHexDigit = ((cChar >> (4*i)) & 0x0f) + '0';
        if( cHexDigit > '9' )
            cHexDigit += 'A' - ('9' + 1);
        aStrCodes[ nFullLen++ ] = cHexDigit;
    }
    aStrCodes[ nFullLen++ ] = ')';
    // using the new UCS4 constructor
    OUString aOUStr( aStrCodes, nFullLen );
    return aOUStr;
}

OfaAutoCompleteTabPage::OfaAutoCompleteTabPage(weld::Container* pPage, weld::DialogController* pController,
    const SfxItemSet& rSet)
    : SfxTabPage(pPage, pController, u"cui/ui/wordcompletionpage.ui"_ustr,
                 u"WordCompletionPage"_ustr, &rSet)
    , m_pAutoCompleteList(nullptr)
    , m_nAutoCmpltListCnt(0)
    , m_xCBActiv(m_xBuilder->weld_check_button(u"enablewordcomplete"_ustr))
    , m_xCBAppendSpace(m_xBuilder->weld_check_button(u"appendspace"_ustr))
    , m_xCBAsTip(m_xBuilder->weld_check_button(u"showastip"_ustr))
    , m_xCBCollect(m_xBuilder->weld_check_button(u"collectwords"_ustr))
    , m_xCBRemoveList(m_xBuilder->weld_check_button(u"whenclosing"_ustr))
    , m_xDCBExpandKey(m_xBuilder->weld_combo_box(u"acceptwith"_ustr))
    , m_xNFMinWordlen(m_xBuilder->weld_spin_button(u"minwordlen"_ustr))
    , m_xNFMaxEntries(m_xBuilder->weld_spin_button(u"maxentries"_ustr))
    , m_xLBEntries(m_xBuilder->weld_tree_view(u"entries"_ustr))
    , m_xPBEntries(m_xBuilder->weld_button(u"delete"_ustr))
{
    //fdo#65595, we need height-for-width support here, but for now we can
    //bodge it
    Size aPrefSize(m_xCBRemoveList->get_preferred_size());
    int nMaxWidth = m_xCBRemoveList->get_approximate_digit_width() * 40;
    if (aPrefSize.Width() > nMaxWidth)
    {
        m_xCBRemoveList->set_label_wrap(true);
        m_xCBRemoveList->set_size_request(nMaxWidth, -1);
    }

    m_xLBEntries->set_size_request(m_xLBEntries->get_approximate_digit_width() * 30,
                                   m_xLBEntries->get_height_rows(10));
    m_xLBEntries->set_selection_mode(SelectionMode::Multiple);

    // the defined KEYs
    static const sal_uInt16 aKeyCodes[] = {
        KEY_END,
        KEY_RETURN,
        KEY_SPACE,
        KEY_RIGHT,
        KEY_TAB,
        0
    };

    for( const sal_uInt16* pKeys = aKeyCodes; *pKeys; ++pKeys )
    {
        vcl::KeyCode aKCode(*pKeys);
        m_xDCBExpandKey->append(OUString::number(static_cast<sal_Int32>(*pKeys)), aKCode.GetName());
        if (KEY_RETURN == *pKeys)      // default to RETURN
            m_xDCBExpandKey->set_active(std::distance(aKeyCodes, pKeys));
    }

    m_xPBEntries->connect_clicked(LINK(this, OfaAutoCompleteTabPage, DeleteHdl));
    m_xCBActiv->connect_toggled(LINK(this, OfaAutoCompleteTabPage, CheckHdl));
    m_xCBCollect->connect_toggled(LINK(this, OfaAutoCompleteTabPage, CheckHdl));
    m_xLBEntries->connect_key_release(LINK(this, OfaAutoCompleteTabPage, KeyReleaseHdl));
}

OfaAutoCompleteTabPage::~OfaAutoCompleteTabPage()
{
}

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

bool OfaAutoCompleteTabPage::FillItemSet( SfxItemSet* )
{
    bool bModified = false, bCheck;
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();

    bCheck = m_xCBActiv->get_active();
    bModified |= pOpt->bAutoCompleteWords != bCheck;
    pOpt->bAutoCompleteWords = bCheck;
    bCheck = m_xCBCollect->get_active();
    bModified |= pOpt->bAutoCmpltCollectWords != bCheck;
    pOpt->bAutoCmpltCollectWords = bCheck;
    bCheck = !m_xCBRemoveList->get_active(); // inverted value!
    bModified |= pOpt->bAutoCmpltKeepList != bCheck;
    pOpt->bAutoCmpltKeepList = bCheck;
    bCheck = m_xCBAppendSpace->get_active();
    bModified |= pOpt->bAutoCmpltAppendBlank != bCheck;
    pOpt->bAutoCmpltAppendBlank = bCheck;
    bCheck = m_xCBAsTip->get_active();
    bModified |= pOpt->bAutoCmpltShowAsTip != bCheck;
    pOpt->bAutoCmpltShowAsTip = bCheck;

    sal_uInt16 nVal = static_cast<sal_uInt16>(m_xNFMinWordlen->get_value());
    bModified |= nVal != pOpt->nAutoCmpltWordLen;
    pOpt->nAutoCmpltWordLen = nVal;

    sal_uInt32 nList = static_cast<sal_uInt32>(m_xNFMaxEntries->get_value());
    bModified |= nList != pOpt->nAutoCmpltListLen;
    pOpt->nAutoCmpltListLen = nList;

    const int nPos = m_xDCBExpandKey->get_active();
    if (nPos != -1)
    {
        sal_Int32 nKey = m_xDCBExpandKey->get_id(nPos).toInt32();
        bModified |= nKey != pOpt->nAutoCmpltExpandKey;
        pOpt->nAutoCmpltExpandKey = static_cast<sal_uInt16>(nKey);
    }

    if (m_pAutoCompleteList && m_nAutoCmpltListCnt != m_xLBEntries->n_children())
    {
        bModified = true;
        pOpt->m_pAutoCompleteList = m_pAutoCompleteList;
    }
    if( bModified )
    {
        SvxAutoCorrCfg& rCfg = SvxAutoCorrCfg::Get();
        rCfg.SetModified();
        rCfg.Commit();
    }
    return true;
}

void OfaAutoCompleteTabPage::Reset( const SfxItemSet*  )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();

    m_xCBActiv->set_active( pOpt->bAutoCompleteWords );
    m_xCBCollect->set_active( pOpt->bAutoCmpltCollectWords );
    m_xCBRemoveList->set_active( !pOpt->bAutoCmpltKeepList ); //inverted value!
    m_xCBAppendSpace->set_active( pOpt->bAutoCmpltAppendBlank );
    m_xCBAsTip->set_active( pOpt->bAutoCmpltShowAsTip );

    m_xNFMinWordlen->set_value( pOpt->nAutoCmpltWordLen );
    m_xNFMaxEntries->set_value( pOpt->nAutoCmpltListLen );

    // select the specific KeyCode:
    {
        sal_Int32 nKey = pOpt->nAutoCmpltExpandKey;
        for (int n = 0, nCnt = m_xDCBExpandKey->get_count(); n < nCnt; ++n)
        {
            if (nKey == m_xDCBExpandKey->get_id(n).toInt32())
            {
                m_xDCBExpandKey->set_active(n);
                break;
            }
        }
    }

    if (pOpt->m_pAutoCompleteList && !pOpt->m_pAutoCompleteList->empty())
    {
        m_pAutoCompleteList = const_cast<editeng::SortedAutoCompleteStrings*>(
                pOpt->m_pAutoCompleteList);
        pOpt->m_pAutoCompleteList = nullptr;
        m_nAutoCmpltListCnt = m_pAutoCompleteList->size();
        for (size_t n = 0; n < m_nAutoCmpltListCnt; ++n)
        {
            const OUString* pStr =
                &(*m_pAutoCompleteList)[n]->GetAutoCompleteString();
            OUString sId(weld::toId(pStr));
            m_xLBEntries->append(sId, *pStr);
        }
    }
    else
    {
        m_xLBEntries->set_sensitive(false);
        m_xPBEntries->set_sensitive(false);
    }

    CheckHdl(*m_xCBActiv);
    CheckHdl(*m_xCBCollect);
}

void OfaAutoCompleteTabPage::ActivatePage( const SfxItemSet& )
{
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage( false );
}

IMPL_LINK_NOARG(OfaAutoCompleteTabPage, DeleteHdl, weld::Button&, void)
{
    auto rows = m_xLBEntries->get_selected_rows();
    std::sort(rows.begin(), rows.end());
    while (!rows.empty())
    {
        sal_Int32 nPos = rows.back();
        OUString* pStr = weld::fromId<OUString*>(m_xLBEntries->get_id(nPos));
        m_xLBEntries->remove(nPos);
        editeng::IAutoCompleteString hack(*pStr); // UGLY
        m_pAutoCompleteList->erase(&hack);
        rows.pop_back();
    }
}

IMPL_LINK(OfaAutoCompleteTabPage, CheckHdl, weld::Toggleable&, rBox, void)
{
    bool bEnable = rBox.get_active();
    if (&rBox == m_xCBActiv.get())
    {
        m_xCBAppendSpace->set_sensitive(bEnable);
        m_xCBAppendSpace->set_sensitive(bEnable);
        m_xCBAsTip->set_sensitive(bEnable);
        m_xDCBExpandKey->set_sensitive(bEnable);
    }
    else if (&rBox == m_xCBCollect.get())
        m_xCBRemoveList->set_sensitive(bEnable);
}

void OfaAutoCompleteTabPage::CopyToClipboard() const
{
    auto rows = m_xLBEntries->get_selected_rows();
    if (!m_pAutoCompleteList || rows.empty())
        return;

    rtl::Reference<TransferDataContainer> pCntnr = new TransferDataContainer;

    OStringBuffer sData;

    rtl_TextEncoding nEncode = osl_getThreadTextEncoding();

    for (auto a : rows)
    {
        sData.append(OUStringToOString(m_xLBEntries->get_text(a), nEncode) + SAL_NEWLINE_STRING);
    }
    pCntnr->CopyByteString( SotClipboardFormatId::STRING, sData.makeStringAndClear() );
    pCntnr->CopyToClipboard(m_xLBEntries->get_clipboard());
}

IMPL_LINK(OfaAutoCompleteTabPage, KeyReleaseHdl, const KeyEvent&, rEvent, bool)
{
    bool bHandled = false;
    const vcl::KeyCode& rKeyCode = rEvent.GetKeyCode();
    switch (rKeyCode.GetModifier() | rKeyCode.GetCode())
    {
        case KEY_DELETE:
            DeleteHdl(*m_xPBEntries);
            bHandled = true;
            break;
        default:
            if (KeyFuncType::COPY == rKeyCode.GetFunction())
            {
                CopyToClipboard();
                bHandled = true;
            }
            break;
    }
    return bHandled;
}

// class OfaSmartTagOptionsTabPage ---------------------------------------------

OfaSmartTagOptionsTabPage::OfaSmartTagOptionsTabPage(weld::Container* pPage, weld::DialogController* pController,
                                                     const SfxItemSet& rSet )
    : SfxTabPage(pPage, pController, u"cui/ui/smarttagoptionspage.ui"_ustr, u"SmartTagOptionsPage"_ustr, &rSet)
    , m_xMainCB(m_xBuilder->weld_check_button(u"main"_ustr))
    , m_xSmartTagTypesLB(m_xBuilder->weld_tree_view(u"list"_ustr))
    , m_xPropertiesPB(m_xBuilder->weld_button(u"properties"_ustr))
{
    m_xSmartTagTypesLB->set_size_request(m_xSmartTagTypesLB->get_approximate_digit_width() * 50,
                                         m_xSmartTagTypesLB->get_height_rows(6));

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

    // set the handlers:
    m_xMainCB->connect_toggled(LINK(this, OfaSmartTagOptionsTabPage, CheckHdl));
    m_xPropertiesPB->connect_clicked(LINK(this, OfaSmartTagOptionsTabPage, ClickHdl));
    m_xSmartTagTypesLB->connect_selection_changed(LINK(this, OfaSmartTagOptionsTabPage, SelectHdl));
}

OfaSmartTagOptionsTabPage::~OfaSmartTagOptionsTabPage()
{
}

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

namespace {

/** This struct is used to associate list box entries with smart tag data
*/
struct ImplSmartTagLBUserData
{
    OUString maSmartTagType;
    uno::Reference< smarttags::XSmartTagRecognizer > mxRec;
    sal_Int32 mnSmartTagIdx;

    ImplSmartTagLBUserData( OUString aSmartTagType,
                            uno::Reference< smarttags::XSmartTagRecognizer >  xRec,
                            sal_Int32 nSmartTagIdx ) :
        maSmartTagType(std::move( aSmartTagType )),
        mxRec(std::move( xRec )),
        mnSmartTagIdx( nSmartTagIdx ) {}
};

}

/** Clears m_xSmartTagTypesLB
*/
void OfaSmartTagOptionsTabPage::ClearListBox()
{
    const int nCount = m_xSmartTagTypesLB->n_children();
    for (int i = 0; i < nCount; ++i)
    {
        const ImplSmartTagLBUserData* pUserData = weld::fromId<ImplSmartTagLBUserData*>(m_xSmartTagTypesLB->get_id(i));
        delete pUserData;
    }

    m_xSmartTagTypesLB->clear();
}

/** Inserts items into m_xSmartTagTypesLB
*/
void OfaSmartTagOptionsTabPage::FillListBox( const SmartTagMgr& rSmartTagMgr )
{
    // first we have to clear the list box:
    ClearListBox();

    // fill list box:
    const sal_uInt32 nNumberOfRecognizers = rSmartTagMgr.NumberOfRecognizers();
    const lang::Locale aLocale( LanguageTag::convertToLocale( eLastDialogLanguage ) );

    for ( sal_uInt32 i = 0; i < nNumberOfRecognizers; ++i )
    {
        const uno::Reference< smarttags::XSmartTagRecognizer >& xRec = rSmartTagMgr.GetRecognizer(i);

        const OUString aName = xRec->getName( aLocale );
        const sal_Int32 nNumberOfSupportedSmartTags = xRec->getSmartTagCount();

        for ( sal_Int32 j = 0; j < nNumberOfSupportedSmartTags; ++j )
        {
            const OUString aSmartTagType = xRec->getSmartTagName(j);
            OUString aSmartTagCaption = rSmartTagMgr.GetSmartTagCaption( aSmartTagType, aLocale );

            if ( aSmartTagCaption.isEmpty() )
                aSmartTagCaption = aSmartTagType;

            const OUString aLBEntry = aSmartTagCaption + " (" + aName + ")";

            m_xSmartTagTypesLB->append();
            const int nRow = m_xSmartTagTypesLB->n_children() - 1;
            const bool bCheck = rSmartTagMgr.IsSmartTagTypeEnabled( aSmartTagType );
            m_xSmartTagTypesLB->set_toggle(nRow, bCheck ? TRISTATE_TRUE : TRISTATE_FALSE);
            m_xSmartTagTypesLB->set_text(nRow, aLBEntry, 0);
            m_xSmartTagTypesLB->set_id(nRow, weld::toId(new ImplSmartTagLBUserData(aSmartTagType, xRec, j)));
        }
    }
}

/** Handler for the push button
*/
IMPL_LINK_NOARG(OfaSmartTagOptionsTabPage, ClickHdl, weld::Button&, void)
{
    const int nPos = m_xSmartTagTypesLB->get_selected_index();
    const ImplSmartTagLBUserData* pUserData = weld::fromId<ImplSmartTagLBUserData*>(m_xSmartTagTypesLB->get_id(nPos));
    uno::Reference< smarttags::XSmartTagRecognizer > xRec = pUserData->mxRec;
    const sal_Int32 nSmartTagIdx = pUserData->mnSmartTagIdx;

    const lang::Locale aLocale( LanguageTag::convertToLocale( eLastDialogLanguage ) );
    if ( xRec->hasPropertyPage( nSmartTagIdx, aLocale ) )
        xRec->displayPropertyPage( nSmartTagIdx, aLocale );
}

/** Handler for the check box
*/
IMPL_LINK_NOARG(OfaSmartTagOptionsTabPage, CheckHdl, weld::Toggleable&, void)
{
    const bool bEnable = m_xMainCB->get_active();
    m_xSmartTagTypesLB->set_sensitive(bEnable);
    m_xPropertiesPB->set_sensitive(false);

    // if the controls are currently enabled, we still have to check
    // if the properties button should be disabled because the currently
    // selected smart tag type does not have a properties dialog.
    // We do this by calling SelectHdl:
    if (bEnable)
        SelectHdl(*m_xSmartTagTypesLB);
}

/** Handler for the list box
*/
IMPL_LINK_NOARG(OfaSmartTagOptionsTabPage, SelectHdl, weld::TreeView&, void)
{
    const int nPos = m_xSmartTagTypesLB->get_selected_index();
    if (nPos == -1)
        return;
    const ImplSmartTagLBUserData* pUserData = weld::fromId<ImplSmartTagLBUserData*>(m_xSmartTagTypesLB->get_id(nPos));
    uno::Reference< smarttags::XSmartTagRecognizer > xRec = pUserData->mxRec;
    const sal_Int32 nSmartTagIdx = pUserData->mnSmartTagIdx;

    const lang::Locale aLocale( LanguageTag::convertToLocale( eLastDialogLanguage ) );
    if ( xRec->hasPropertyPage( nSmartTagIdx, aLocale ) )
        m_xPropertiesPB->set_sensitive(true);
    else
        m_xPropertiesPB->set_sensitive(false);
}

/** Propagates the current settings to the smart tag manager.
*/
bool OfaSmartTagOptionsTabPage::FillItemSet( SfxItemSet* )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();
    SmartTagMgr* pSmartTagMgr = pOpt->pSmartTagMgr;

    // robust!
    if ( !pSmartTagMgr )
        return false;

    bool bModifiedSmartTagTypes = false;
    std::vector< OUString > aDisabledSmartTagTypes;

    const int nCount = m_xSmartTagTypesLB->n_children();

    for (int i = 0; i < nCount; ++i)
    {
        const ImplSmartTagLBUserData* pUserData = weld::fromId<ImplSmartTagLBUserData*>(m_xSmartTagTypesLB->get_id(i));
        const bool bChecked = m_xSmartTagTypesLB->get_toggle(i) == TRISTATE_TRUE;
        const bool bIsCurrentlyEnabled = pSmartTagMgr->IsSmartTagTypeEnabled( pUserData->maSmartTagType );

        bModifiedSmartTagTypes = bModifiedSmartTagTypes || ( !bChecked != !bIsCurrentlyEnabled );

        if ( !bChecked )
            aDisabledSmartTagTypes.push_back( pUserData->maSmartTagType );

        delete pUserData;
    }

    const bool bModifiedRecognize = ( !m_xMainCB->get_active() != !pSmartTagMgr->IsLabelTextWithSmartTags() );
    if ( bModifiedSmartTagTypes || bModifiedRecognize )
    {
        bool bLabelTextWithSmartTags = m_xMainCB->get_active();
        pSmartTagMgr->WriteConfiguration( bModifiedRecognize     ? &bLabelTextWithSmartTags : nullptr,
                                          bModifiedSmartTagTypes ? &aDisabledSmartTagTypes : nullptr );
    }

    return true;
}

/** Sets the controls based on the current settings at SmartTagMgr.
*/
void OfaSmartTagOptionsTabPage::Reset( const SfxItemSet*  )
{
    SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
    SvxSwAutoFormatFlags *pOpt = &pAutoCorrect->GetSwFlags();
    const SmartTagMgr* pSmartTagMgr = pOpt->pSmartTagMgr;

    // robust, should not happen!
    if ( !pSmartTagMgr )
        return;

    FillListBox(*pSmartTagMgr);
    m_xSmartTagTypesLB->select(0);
    m_xMainCB->set_active(pSmartTagMgr->IsLabelTextWithSmartTags());
    CheckHdl(*m_xMainCB);
}

void OfaSmartTagOptionsTabPage::ActivatePage( const SfxItemSet& )
{
    static_cast<OfaAutoCorrDlg*>(GetDialogController())->EnableLanguage( false );
}

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