/*
 * 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 <memory>
#include <sal/config.h>
#include <tools/debug.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <comphelper/processfactory.hxx>

#include <svx/rubydialog.hxx>
#include <sfx2/dispatch.hxx>
#include <sfx2/sfxsids.hrc>
#include <sfx2/viewfrm.hxx>
#include <sfx2/viewsh.hxx>
#include <svl/eitem.hxx>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/style/XStyle.hpp>
#include <com/sun/star/text/XRubySelection.hpp>
#include <com/sun/star/beans/PropertyValues.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/beans/XPropertySetInfo.hpp>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/style/XStyleFamiliesSupplier.hpp>
#include <com/sun/star/text/RubyAdjust.hpp>
#include <com/sun/star/view/XSelectionChangeListener.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <cppuhelper/implbase.hxx>
#include <svtools/colorcfg.hxx>
#include <vcl/event.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <rtl/ustrbuf.hxx>
#include <svl/itemset.hxx>

using namespace css::uno;
using namespace css::frame;
using namespace css::text;
using namespace css::beans;
using namespace css::style;
using namespace css::view;
using namespace css::lang;
using namespace css::container;

SFX_IMPL_CHILDWINDOW(SvxRubyChildWindow, SID_RUBY_DIALOG);

namespace
{
constexpr OUString cRubyBaseText = u"RubyBaseText"_ustr;
constexpr OUString cRubyText = u"RubyText"_ustr;
constexpr OUString cRubyAdjust = u"RubyAdjust"_ustr;
constexpr OUString cRubyPosition = u"RubyPosition"_ustr;
constexpr OUString cRubyCharStyleName = u"RubyCharStyleName"_ustr;
constexpr OUString cRubySequence = u"RubySequenceNumber"_ustr;

} // end anonymous namespace

SvxRubyChildWindow::SvxRubyChildWindow(vcl::Window* _pParent, sal_uInt16 nId,
                                       SfxBindings* pBindings, SfxChildWinInfo const* pInfo)
    : SfxChildWindow(_pParent, nId)
{
    auto xDlg = std::make_shared<SvxRubyDialog>(pBindings, this, _pParent->GetFrameWeld());
    SetController(xDlg);
    xDlg->Initialize(pInfo);
}

SfxChildWinInfo SvxRubyChildWindow::GetInfo() const { return SfxChildWindow::GetInfo(); }

class SvxRubyData_Impl : public cppu::WeakImplHelper<css::view::XSelectionChangeListener>
{
    Reference<css::i18n::XBreakIterator> xBreak;
    Reference<XModel> xModel;
    Reference<XRubySelection> xSelection;
    Sequence<PropertyValues> aRubyValues;
    Reference<XController> xController;
    bool bHasSelectionChanged;
    bool bDisposing;

public:
    SvxRubyData_Impl();
    virtual ~SvxRubyData_Impl() override;

    void SetController(const Reference<XController>& xCtrl);
    Reference<XModel> const& GetModel()
    {
        if (!xController.is())
            xModel = nullptr;
        else
            xModel = xController->getModel();
        return xModel;
    }
    bool HasSelectionChanged() const { return bHasSelectionChanged; }
    bool IsDisposing() const { return bDisposing; }
    Reference<XRubySelection> const& GetRubySelection()
    {
        xSelection.set(xController, UNO_QUERY);
        return xSelection;
    }
    void UpdateRubyValues()
    {
        if (!xSelection.is())
            aRubyValues.realloc(0);
        else
            aRubyValues = xSelection->getRubyList(false);
        bHasSelectionChanged = false;
    }
    Sequence<PropertyValues>& GetRubyValues() { return aRubyValues; }
    void AssertOneEntry();

    virtual void SAL_CALL selectionChanged(const css::lang::EventObject& aEvent) override;
    virtual void SAL_CALL disposing(const css::lang::EventObject& Source) override;

    bool IsSelectionGrouped()
    {
        auto nLastSequence = std::numeric_limits<sal_Int32>::lowest();
        for (const PropertyValues& rVals : aRubyValues)
        {
            for (const PropertyValue& rVal : rVals)
            {
                sal_Int32 nValue;
                if ((rVal.Name == cRubySequence) && (rVal.Value >>= nValue))
                {
                    if (nValue <= nLastSequence)
                    {
                        return false;
                    }

                    nLastSequence = nValue;
                }
            }
        }

        return true;
    }

    void MakeSelectionGrouped()
    {
        if (IsSelectionGrouped())
        {
            return;
        }

        sal_Int32 nSequences = 0;
        for (const PropertyValue& rVal : aRubyValues[aRubyValues.size() - 1])
        {
            if (rVal.Name == cRubySequence)
            {
                rVal.Value >>= nSequences;
            }
        }

        ++nSequences;

        Sequence<PropertyValues> aNewRubyValues{ nSequences };
        PropertyValues* pNewRubyValues = aNewRubyValues.getArray();

        OUString sBaseTmp;
        OUStringBuffer aBaseString;
        OUStringBuffer aRubyString;
        sal_Int32 nCurrSequence = 0;
        bool bAtStartOfSequence = true;

        auto fnFinishSequence = [&] {
            for (PropertyValue& rVal : asNonConstRange(pNewRubyValues[nCurrSequence]))
            {
                if (rVal.Name == cRubyBaseText)
                {
                    sBaseTmp = aBaseString.makeStringAndClear();
                    rVal.Value <<= sBaseTmp;
                }
                else if (rVal.Name == cRubyText)
                {
                    sBaseTmp = aRubyString.makeStringAndClear();
                    rVal.Value <<= sBaseTmp;
                }
            }
        };

        for (const PropertyValues& rVals : aRubyValues)
        {
            // Check for advance
            for (const PropertyValue& rVal : rVals)
            {
                if (rVal.Name == cRubySequence)
                {
                    sal_Int32 nSeq = nCurrSequence;
                    rVal.Value >>= nSeq;
                    if (nSeq > nCurrSequence)
                    {
                        fnFinishSequence();
                        nCurrSequence = nSeq;
                        bAtStartOfSequence = true;
                        break;
                    }
                }
            }

            if (bAtStartOfSequence)
            {
                // Copy some reasonable style values from the previous ruby array
                pNewRubyValues[nCurrSequence] = rVals;
                bAtStartOfSequence = false;
            }

            for (const PropertyValue& rVal : rVals)
            {
                if (rVal.Name == cRubyBaseText)
                {
                    sBaseTmp.clear();
                    rVal.Value >>= sBaseTmp;
                    aBaseString.append(sBaseTmp);
                }
                else if (rVal.Name == cRubyText)
                {
                    sBaseTmp.clear();
                    rVal.Value >>= sBaseTmp;
                    aRubyString.append(sBaseTmp);
                }
            }
        }

        fnFinishSequence();

        aRubyValues = std::move(aNewRubyValues);
    }

    bool IsSelectionMono()
    {
        if (!xBreak.is())
        {
            // Cannot continue if BreakIterator is not available
            // Disable the button
            return true;
        }

        // Locale does not matter in this case; default ICU BreakIterator is sufficient
        Locale aLocale;

        OUString sBaseTmp;
        return std::all_of(
            aRubyValues.begin(), aRubyValues.end(), [&](const PropertyValues& rVals) {
                return !std::any_of(rVals.begin(), rVals.end(), [&](const PropertyValue& rVal) {
                    if (rVal.Name == cRubyBaseText)
                    {
                        rVal.Value >>= sBaseTmp;
                        sal_Int32 nDone = 0;
                        auto nPos = xBreak->nextCharacters(
                            sBaseTmp, 0, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1,
                            nDone);
                        return nPos < sBaseTmp.getLength();
                    }

                    return false;
                });
            });
    }

    void MakeSelectionMono()
    {
        if (!xBreak.is())
        {
            // Cannot continue if BreakIterator is not available
            return;
        }

        // Locale does not matter in this case; default ICU BreakIterator is sufficient
        Locale aLocale;

        OUString sBaseTmp;

        // Count the grapheme clusters
        sal_Int32 nTotalGraphemeClusters = 0;
        for (const PropertyValues& rVals : aRubyValues)
        {
            for (const PropertyValue& rVal : rVals)
            {
                if (rVal.Name == cRubyBaseText)
                {
                    rVal.Value >>= sBaseTmp;

                    sal_Int32 nPos = 0;
                    while (nPos < sBaseTmp.getLength())
                    {
                        sal_Int32 nDone = 0;
                        nPos = xBreak->nextCharacters(sBaseTmp, nPos, aLocale,
                                                      css::i18n::CharacterIteratorMode::SKIPCELL, 1,
                                                      nDone);
                        ++nTotalGraphemeClusters;
                    }
                }
            }
        }

        // Put each grapheme cluster in its own entry
        Sequence<PropertyValues> aNewRubyValues{ nTotalGraphemeClusters };
        PropertyValues* pNewRubyValues = aNewRubyValues.getArray();

        sal_Int32 nCurrGraphemeCluster = 0;
        for (const PropertyValues& rVals : aRubyValues)
        {
            for (const PropertyValue& rVal : rVals)
            {
                if (rVal.Name == cRubyBaseText)
                {
                    rVal.Value >>= sBaseTmp;

                    sal_Int32 nPos = 0;
                    while (nPos < sBaseTmp.getLength())
                    {
                        sal_Int32 nDone = 0;
                        auto nNextPos = xBreak->nextCharacters(
                            sBaseTmp, nPos, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1,
                            nDone);

                        PropertyValues& rNewVals = pNewRubyValues[nCurrGraphemeCluster++];

                        // Initialize new property values with values from current run
                        rNewVals = rVals;

                        PropertyValue* aNewVals = rNewVals.getArray();
                        for (sal_Int32 i = 0; i < rNewVals.getLength(); ++i)
                        {
                            PropertyValue& rNewVal = aNewVals[i];

                            if (rNewVal.Name == cRubyText)
                            {
                                rNewVal.Value <<= OUString{};
                            }
                            else if (rNewVal.Name == cRubyBaseText)
                            {
                                rNewVal.Value <<= sBaseTmp.copy(nPos, nNextPos - nPos);
                            }
                        }

                        nPos = nNextPos;
                    }
                }
            }
        }

        aRubyValues = std::move(aNewRubyValues);
    }
};

SvxRubyData_Impl::SvxRubyData_Impl()
    : bHasSelectionChanged(false)
    , bDisposing(false)
{
    const Reference<XComponentContext>& xContext = ::comphelper::getProcessComponentContext();
    xBreak = css::i18n::BreakIterator::create(xContext);
}

SvxRubyData_Impl::~SvxRubyData_Impl() {}

void SvxRubyData_Impl::SetController(const Reference<XController>& xCtrl)
{
    if (xCtrl.get() == xController.get())
        return;

    try
    {
        Reference<XSelectionSupplier> xSelSupp(xController, UNO_QUERY);
        if (xSelSupp.is())
            xSelSupp->removeSelectionChangeListener(this);

        bHasSelectionChanged = true;
        xController = xCtrl;
        xSelSupp.set(xController, UNO_QUERY);
        if (xSelSupp.is())
            xSelSupp->addSelectionChangeListener(this);
    }
    catch (const Exception&)
    {
    }
}

void SvxRubyData_Impl::selectionChanged(const EventObject&) { bHasSelectionChanged = true; }

void SvxRubyData_Impl::disposing(const EventObject&)
{
    try
    {
        Reference<XSelectionSupplier> xSelSupp(xController, UNO_QUERY);
        if (xSelSupp.is())
            xSelSupp->removeSelectionChangeListener(this);
    }
    catch (const Exception&)
    {
    }
    xController = nullptr;
    bDisposing = true;
}

void SvxRubyData_Impl::AssertOneEntry()
{
    //create one entry
    if (!aRubyValues.hasElements())
    {
        aRubyValues.realloc(1);
        Sequence<PropertyValue>& rValues = aRubyValues.getArray()[0];
        rValues.realloc(6);
        PropertyValue* pValues = rValues.getArray();
        pValues[0].Name = cRubyBaseText;
        pValues[1].Name = cRubyText;
        pValues[2].Name = cRubyAdjust;
        pValues[3].Name = cRubyPosition;
        pValues[4].Name = cRubyCharStyleName;
        pValues[5].Name = cRubySequence;
    }
}

SvxRubyDialog::SvxRubyDialog(SfxBindings* pBind, SfxChildWindow* pCW, weld::Window* pParent)
    : SfxModelessDialogController(pBind, pCW, pParent, u"svx/ui/asianphoneticguidedialog.ui"_ustr,
                                  u"AsianPhoneticGuideDialog"_ustr)
    , nLastPos(0)
    , nCurrentEdit(0)
    , bModified(false)
    , pBindings(pBind)
    , m_pImpl(new SvxRubyData_Impl)
    , m_xLeft1ED(m_xBuilder->weld_entry(u"Left1ED"_ustr))
    , m_xRight1ED(m_xBuilder->weld_entry(u"Right1ED"_ustr))
    , m_xLeft2ED(m_xBuilder->weld_entry(u"Left2ED"_ustr))
    , m_xRight2ED(m_xBuilder->weld_entry(u"Right2ED"_ustr))
    , m_xLeft3ED(m_xBuilder->weld_entry(u"Left3ED"_ustr))
    , m_xRight3ED(m_xBuilder->weld_entry(u"Right3ED"_ustr))
    , m_xLeft4ED(m_xBuilder->weld_entry(u"Left4ED"_ustr))
    , m_xRight4ED(m_xBuilder->weld_entry(u"Right4ED"_ustr))
    , m_xScrolledWindow(m_xBuilder->weld_scrolled_window(u"scrolledwindow"_ustr, true))
    , m_xAdjustLB(m_xBuilder->weld_combo_box(u"adjustlb"_ustr))
    , m_xPositionLB(m_xBuilder->weld_combo_box(u"positionlb"_ustr))
    , m_xCharStyleFT(m_xBuilder->weld_label(u"styleft"_ustr))
    , m_xCharStyleLB(m_xBuilder->weld_combo_box(u"stylelb"_ustr))
    , m_xStylistPB(m_xBuilder->weld_button(u"styles"_ustr))
    , m_xSelectionGroupPB(m_xBuilder->weld_button(u"selection-group"_ustr))
    , m_xSelectionMonoPB(m_xBuilder->weld_button(u"selection-mono"_ustr))
    , m_xApplyPB(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xClosePB(m_xBuilder->weld_button(u"close"_ustr))
    , m_xContentArea(m_xDialog->weld_content_area())
    , m_xGrid(m_xBuilder->weld_widget(u"grid"_ustr))
    , m_xPreviewWin(new RubyPreview)
    , m_xPreview(new weld::CustomWeld(*m_xBuilder, u"preview"_ustr, *m_xPreviewWin))
{
    m_xCharStyleLB->make_sorted();
    m_xPreviewWin->setRubyDialog(this);
    m_xScrolledWindow->set_size_request(-1, m_xGrid->get_preferred_size().Height());
    m_xScrolledWindow->set_vpolicy(VclPolicyType::NEVER);

    aEditArr[0] = m_xLeft1ED.get();
    aEditArr[1] = m_xRight1ED.get();
    aEditArr[2] = m_xLeft2ED.get();
    aEditArr[3] = m_xRight2ED.get();
    aEditArr[4] = m_xLeft3ED.get();
    aEditArr[5] = m_xRight3ED.get();
    aEditArr[6] = m_xLeft4ED.get();
    aEditArr[7] = m_xRight4ED.get();

    m_xSelectionGroupPB->connect_clicked(LINK(this, SvxRubyDialog, SelectionGroup_Impl));
    m_xSelectionMonoPB->connect_clicked(LINK(this, SvxRubyDialog, SelectionMono_Impl));
    m_xApplyPB->connect_clicked(LINK(this, SvxRubyDialog, ApplyHdl_Impl));
    m_xClosePB->connect_clicked(LINK(this, SvxRubyDialog, CloseHdl_Impl));
    m_xStylistPB->connect_clicked(LINK(this, SvxRubyDialog, StylistHdl_Impl));
    m_xAdjustLB->connect_changed(LINK(this, SvxRubyDialog, AdjustHdl_Impl));
    m_xPositionLB->connect_changed(LINK(this, SvxRubyDialog, PositionHdl_Impl));
    m_xCharStyleLB->connect_changed(LINK(this, SvxRubyDialog, CharStyleHdl_Impl));

    Link<weld::ScrolledWindow&, void> aScrLk(LINK(this, SvxRubyDialog, ScrollHdl_Impl));
    m_xScrolledWindow->connect_vadjustment_value_changed(aScrLk);

    Link<weld::Entry&, void> aEditLk(LINK(this, SvxRubyDialog, EditModifyHdl_Impl));
    Link<weld::Widget&, void> aFocusLk(LINK(this, SvxRubyDialog, EditFocusHdl_Impl));
    Link<const KeyEvent&, bool> aKeyUpDownLk(LINK(this, SvxRubyDialog, KeyUpDownHdl_Impl));
    Link<const KeyEvent&, bool> aKeyTabUpDownLk(LINK(this, SvxRubyDialog, KeyUpDownTabHdl_Impl));
    for (sal_uInt16 i = 0; i < 8; i++)
    {
        aEditArr[i]->connect_changed(aEditLk);
        aEditArr[i]->connect_focus_in(aFocusLk);
        if (!i || 7 == i)
            aEditArr[i]->connect_key_press(aKeyTabUpDownLk);
        else
            aEditArr[i]->connect_key_press(aKeyUpDownLk);
    }
}

SvxRubyDialog::~SvxRubyDialog()
{
    ClearCharStyleList();
    EventObject aEvent;
    m_pImpl->disposing(aEvent);
}

void SvxRubyDialog::ClearCharStyleList() { m_xCharStyleLB->clear(); }

void SvxRubyDialog::Close()
{
    if (IsClosing())
        return;
    SfxViewFrame* pViewFrame = SfxViewFrame::Current();
    if (pViewFrame)
        pViewFrame->ToggleChildWindow(SID_RUBY_DIALOG);
}

void SvxRubyDialog::Activate()
{
    SfxModelessDialogController::Activate();
    if (m_pImpl->IsDisposing())
    {
        // tdf#141967/tdf#152495 if Activate is called during tear down bail early
        return;
    }

    //get selection from current view frame
    SfxViewFrame* pCurFrm = SfxViewFrame::Current();
    Reference<XController> xCtrl(pCurFrm ? pCurFrm->GetFrame().GetController() : nullptr);
    m_pImpl->SetController(xCtrl);
    if (!m_pImpl->HasSelectionChanged())
        return;

    Reference<XRubySelection> xRubySel = m_pImpl->GetRubySelection();
    m_pImpl->UpdateRubyValues();
    EnableControls(xRubySel.is());
    if (xRubySel.is())
    {
        Reference<XModel> xModel = m_pImpl->GetModel();
        const OUString sCharStyleSelect = m_xCharStyleLB->get_active_text();
        ClearCharStyleList();
        Reference<XStyleFamiliesSupplier> xSupplier(xModel, UNO_QUERY);
        if (xSupplier.is())
        {
            try
            {
                Reference<XNameAccess> xFam = xSupplier->getStyleFamilies();
                Any aChar = xFam->getByName(u"CharacterStyles"_ustr);
                Reference<XNameContainer> xChar;
                aChar >>= xChar;
                Reference<XIndexAccess> xCharIdx(xChar, UNO_QUERY);
                if (xCharIdx.is())
                {
                    OUString sUIName(u"DisplayName"_ustr);
                    for (sal_Int32 nStyle = 0; nStyle < xCharIdx->getCount(); nStyle++)
                    {
                        Any aStyle = xCharIdx->getByIndex(nStyle);
                        Reference<XStyle> xStyle;
                        aStyle >>= xStyle;
                        Reference<XPropertySet> xPrSet(xStyle, UNO_QUERY);
                        OUString sName, sCoreName;
                        if (xPrSet.is())
                        {
                            Reference<XPropertySetInfo> xInfo = xPrSet->getPropertySetInfo();
                            if (xInfo->hasPropertyByName(sUIName))
                            {
                                Any aName = xPrSet->getPropertyValue(sUIName);
                                aName >>= sName;
                            }
                        }
                        if (xStyle.is())
                        {
                            sCoreName = xStyle->getName();
                            if (sName.isEmpty())
                                sName = sCoreName;
                        }
                        if (!sName.isEmpty())
                        {
                            m_xCharStyleLB->append(sCoreName, sName);
                        }
                    }
                }
            }
            catch (const Exception&)
            {
                TOOLS_WARN_EXCEPTION("svx.dialog", "exception in style access");
            }
            if (!sCharStyleSelect.isEmpty())
                m_xCharStyleLB->set_active_text(sCharStyleSelect);
        }
        m_xCharStyleLB->set_sensitive(xSupplier.is());
        m_xCharStyleFT->set_sensitive(xSupplier.is());
    }
    Update();
    m_xPreviewWin->Invalidate();
}

void SvxRubyDialog::SetRubyText(sal_Int32 nPos, weld::Entry& rLeft, weld::Entry& rRight)
{
    OUString sLeft, sRight;
    const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
    bool bEnable = aRubyValues.getLength() > nPos;
    if (bEnable)
    {
        const Sequence<PropertyValue> aProps = aRubyValues.getConstArray()[nPos];
        for (const PropertyValue& rProp : aProps)
        {
            if (rProp.Name == cRubyBaseText)
                rProp.Value >>= sLeft;
            else if (rProp.Name == cRubyText)
                rProp.Value >>= sRight;
        }
    }
    else if (!nPos)
    {
        bEnable = true;
    }
    rLeft.set_sensitive(bEnable);
    rRight.set_sensitive(bEnable);
    rLeft.set_text(sLeft);
    rRight.set_text(sRight);
    rLeft.save_value();
    rRight.save_value();
}

void SvxRubyDialog::GetRubyText()
{
    tools::Long nTempLastPos = GetLastPos();
    Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
    auto aRubyValuesRange = asNonConstRange(aRubyValues);
    for (int i = 0; i < 8; i += 2)
    {
        if (aEditArr[i]->get_sensitive()
            && (aEditArr[i]->get_value_changed_from_saved()
                || aEditArr[i + 1]->get_value_changed_from_saved()))
        {
            DBG_ASSERT(aRubyValues.getLength() > (i / 2 + nTempLastPos), "wrong index");
            SetModified(true);
            for (PropertyValue& propVal : asNonConstRange(aRubyValuesRange[i / 2 + nTempLastPos]))
            {
                if (propVal.Name == cRubyBaseText)
                    propVal.Value <<= aEditArr[i]->get_text();
                else if (propVal.Name == cRubyText)
                    propVal.Value <<= aEditArr[i + 1]->get_text();
            }
        }
    }
}

void SvxRubyDialog::Update()
{
    // Only enable selection grouping options when they can be applied
    m_xSelectionGroupPB->set_sensitive(!m_pImpl->IsSelectionGrouped());
    m_xSelectionMonoPB->set_sensitive(!m_pImpl->IsSelectionMono());

    const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
    sal_Int32 nLen = aRubyValues.getLength();
    m_xScrolledWindow->vadjustment_configure(0, !nLen ? 1 : nLen, 1, 4, 4);
    if (nLen > 4)
        m_xScrolledWindow->set_vpolicy(VclPolicyType::ALWAYS);
    else
        m_xScrolledWindow->set_vpolicy(VclPolicyType::NEVER);
    SetLastPos(0);
    SetModified(false);

    sal_Int16 nAdjust = -1;
    sal_Int16 nPosition = -1;
    OUString sCharStyleName, sTmp;
    bool bCharStyleEqual = true;
    for (sal_Int32 nRuby = 0; nRuby < nLen; nRuby++)
    {
        const Sequence<PropertyValue>& rProps = aRubyValues.getConstArray()[nRuby];
        for (const PropertyValue& rProp : rProps)
        {
            if (nAdjust > -2 && rProp.Name == cRubyAdjust)
            {
                sal_Int16 nTmp = sal_Int16();
                rProp.Value >>= nTmp;
                if (!nRuby)
                    nAdjust = nTmp;
                else if (nAdjust != nTmp)
                    nAdjust = -2;
            }
            if (nPosition > -2 && rProp.Name == cRubyPosition)
            {
                sal_Int16 nTmp = sal_Int16();
                rProp.Value >>= nTmp;
                if (!nRuby)
                    nPosition = nTmp;
                else if (nPosition != nTmp)
                    nPosition = -2;
            }
            if (bCharStyleEqual && rProp.Name == cRubyCharStyleName)
            {
                rProp.Value >>= sTmp;
                if (!nRuby)
                    sCharStyleName = sTmp;
                else if (sCharStyleName != sTmp)
                    bCharStyleEqual = false;
            }
        }
    }
    if (!nLen)
    {
        //enable selection if the ruby list is empty
        nAdjust = 0;
        nPosition = 0;
    }
    if (nAdjust > -1)
        m_xAdjustLB->set_active(nAdjust);
    else
        m_xAdjustLB->set_active(-1);
    if (nPosition > -1)
        m_xPositionLB->set_active(nPosition);
    if (!nLen || (bCharStyleEqual && sCharStyleName.isEmpty()))
        sCharStyleName = "Rubies";
    if (!sCharStyleName.isEmpty())
    {
        for (int i = 0, nEntryCount = m_xCharStyleLB->get_count(); i < nEntryCount; i++)
        {
            OUString sCoreName = m_xCharStyleLB->get_id(i);
            if (sCharStyleName == sCoreName)
            {
                m_xCharStyleLB->set_active(i);
                break;
            }
        }
    }
    else
        m_xCharStyleLB->set_active(-1);

    ScrollHdl_Impl(*m_xScrolledWindow);
}

void SvxRubyDialog::GetCurrentText(OUString& rBase, OUString& rRuby)
{
    rBase = aEditArr[nCurrentEdit * 2]->get_text();
    rRuby = aEditArr[nCurrentEdit * 2 + 1]->get_text();
}

IMPL_LINK(SvxRubyDialog, ScrollHdl_Impl, weld::ScrolledWindow&, rScroll, void)
{
    int nPos = rScroll.vadjustment_get_value();
    if (GetLastPos() != nPos)
    {
        GetRubyText();
    }
    SetRubyText(nPos++, *m_xLeft1ED, *m_xRight1ED);
    SetRubyText(nPos++, *m_xLeft2ED, *m_xRight2ED);
    SetRubyText(nPos++, *m_xLeft3ED, *m_xRight3ED);
    SetRubyText(nPos, *m_xLeft4ED, *m_xRight4ED);
    SetLastPos(nPos - 3);
    m_xPreviewWin->Invalidate();
}

IMPL_LINK_NOARG(SvxRubyDialog, SelectionGroup_Impl, weld::Button&, void)
{
    m_pImpl->MakeSelectionGrouped();
    Update();
}

IMPL_LINK_NOARG(SvxRubyDialog, SelectionMono_Impl, weld::Button&, void)
{
    m_pImpl->MakeSelectionMono();
    Update();
}

IMPL_LINK_NOARG(SvxRubyDialog, ApplyHdl_Impl, weld::Button&, void)
{
    const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues();
    if (!aRubyValues.hasElements())
    {
        AssertOneEntry();
        PositionHdl_Impl(*m_xPositionLB);
        AdjustHdl_Impl(*m_xAdjustLB);
        CharStyleHdl_Impl(*m_xCharStyleLB);
    }
    GetRubyText();
    //reset all edit fields - SaveValue is called
    ScrollHdl_Impl(*m_xScrolledWindow);

    Reference<XRubySelection> xSelection = m_pImpl->GetRubySelection();
    if (IsModified() && xSelection.is())
    {
        try
        {
            xSelection->setRubyList(aRubyValues, false);
        }
        catch (const Exception&)
        {
            TOOLS_WARN_EXCEPTION("svx.dialog", "");
        }
    }
}

IMPL_LINK_NOARG(SvxRubyDialog, CloseHdl_Impl, weld::Button&, void) { Close(); }

IMPL_LINK_NOARG(SvxRubyDialog, StylistHdl_Impl, weld::Button&, void)
{
    std::unique_ptr<SfxBoolItem> pState;
    SfxItemState eState = pBindings->QueryState(SID_STYLE_DESIGNER, pState);
    if (eState <= SfxItemState::SET || !pState || !pState->GetValue())
    {
        pBindings->GetDispatcher()->Execute(SID_STYLE_DESIGNER,
                                            SfxCallMode::ASYNCHRON | SfxCallMode::RECORD);
    }
}

IMPL_LINK(SvxRubyDialog, AdjustHdl_Impl, weld::ComboBox&, rBox, void)
{
    AssertOneEntry();
    sal_Int16 nAdjust = rBox.get_active();
    for (PropertyValues& rProps : asNonConstRange(m_pImpl->GetRubyValues()))
    {
        for (PropertyValue& propVal : asNonConstRange(rProps))
        {
            if (propVal.Name == cRubyAdjust)
                propVal.Value <<= nAdjust;
        }
        SetModified(true);
    }
    m_xPreviewWin->Invalidate();
}

IMPL_LINK(SvxRubyDialog, PositionHdl_Impl, weld::ComboBox&, rBox, void)
{
    AssertOneEntry();
    sal_Int16 nPosition = rBox.get_active();
    for (PropertyValues& rProps : asNonConstRange(m_pImpl->GetRubyValues()))
    {
        for (PropertyValue& propVal : asNonConstRange(rProps))
        {
            if (propVal.Name == cRubyPosition)
                propVal.Value <<= nPosition;
        }
        SetModified(true);
    }
    m_xPreviewWin->Invalidate();
}

IMPL_LINK_NOARG(SvxRubyDialog, CharStyleHdl_Impl, weld::ComboBox&, void)
{
    AssertOneEntry();
    OUString sStyleName;
    if (m_xCharStyleLB->get_active() != -1)
        sStyleName = m_xCharStyleLB->get_active_id();
    for (PropertyValues& rProps : asNonConstRange(m_pImpl->GetRubyValues()))
    {
        for (PropertyValue& propVal : asNonConstRange(rProps))
        {
            if (propVal.Name == cRubyCharStyleName)
            {
                propVal.Value <<= sStyleName;
            }
        }
        SetModified(true);
    }
}

IMPL_LINK(SvxRubyDialog, EditFocusHdl_Impl, weld::Widget&, rEdit, void)
{
    for (sal_uInt16 i = 0; i < 8; i++)
    {
        if (&rEdit == aEditArr[i])
        {
            nCurrentEdit = i / 2;
            break;
        }
    }
    m_xPreviewWin->Invalidate();
}

IMPL_LINK(SvxRubyDialog, EditModifyHdl_Impl, weld::Entry&, rEdit, void)
{
    EditFocusHdl_Impl(rEdit);
}

bool SvxRubyDialog::EditScrollHdl_Impl(sal_Int32 nParam)
{
    bool bRet = false;
    //scroll forward
    if (nParam > 0 && (aEditArr[7]->has_focus() || aEditArr[6]->has_focus()))
    {
        if (m_xScrolledWindow->vadjustment_get_upper()
            > m_xScrolledWindow->vadjustment_get_value()
                  + m_xScrolledWindow->vadjustment_get_page_size())
        {
            m_xScrolledWindow->vadjustment_set_value(m_xScrolledWindow->vadjustment_get_value()
                                                     + 1);
            aEditArr[6]->grab_focus();
            bRet = true;
        }
    }
    //scroll backward
    else if (m_xScrolledWindow->vadjustment_get_value()
             && (aEditArr[0]->has_focus() || aEditArr[1]->has_focus()))
    {
        m_xScrolledWindow->vadjustment_set_value(m_xScrolledWindow->vadjustment_get_value() - 1);
        aEditArr[1]->grab_focus();
        bRet = true;
    }
    if (bRet)
        ScrollHdl_Impl(*m_xScrolledWindow);
    return bRet;
}

bool SvxRubyDialog::EditJumpHdl_Impl(sal_Int32 nParam)
{
    bool bHandled = false;
    sal_uInt16 nIndex = USHRT_MAX;
    for (sal_uInt16 i = 0; i < 8; i++)
    {
        if (aEditArr[i]->has_focus())
            nIndex = i;
    }
    if (nIndex < 8)
    {
        if (nParam > 0)
        {
            if (nIndex < 6)
                aEditArr[nIndex + 2]->grab_focus();
            else if (EditScrollHdl_Impl(nParam))
                aEditArr[nIndex]->grab_focus();
        }
        else
        {
            if (nIndex > 1)
                aEditArr[nIndex - 2]->grab_focus();
            else if (EditScrollHdl_Impl(nParam))
                aEditArr[nIndex]->grab_focus();
        }
        bHandled = true;
    }
    return bHandled;
}

void SvxRubyDialog::AssertOneEntry() { m_pImpl->AssertOneEntry(); }

void SvxRubyDialog::EnableControls(bool bEnable)
{
    m_xContentArea->set_sensitive(bEnable);
    m_xApplyPB->set_sensitive(bEnable);
}

RubyPreview::RubyPreview()
    : m_pParentDlg(nullptr)
{
}

RubyPreview::~RubyPreview() {}

void RubyPreview::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& /*rRect*/)
{
    auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::ALL);

    rRenderContext.SetMapMode(MapMode(MapUnit::MapTwip));

    Size aWinSize = rRenderContext.GetOutputSize();

    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    svtools::ColorConfig aColorConfig;

    Color aNewTextColor(aColorConfig.GetColorValue(svtools::FONTCOLOR).nColor);
    Color aNewFillColor(rStyleSettings.GetWindowColor());

    vcl::Font aFont = rRenderContext.GetFont();
    aFont.SetFontHeight(aWinSize.Height() / 4);
    aFont.SetFillColor(aNewFillColor);
    aFont.SetColor(aNewTextColor);
    rRenderContext.SetFont(aFont);

    tools::Rectangle aRect(Point(0, 0), aWinSize);
    rRenderContext.SetLineColor();
    rRenderContext.SetFillColor(aFont.GetFillColor());
    rRenderContext.DrawRect(aRect);

    OUString sBaseText, sRubyText;
    m_pParentDlg->GetCurrentText(sBaseText, sRubyText);

    tools::Long nTextHeight = rRenderContext.GetTextHeight();
    tools::Long nBaseWidth = rRenderContext.GetTextWidth(sBaseText);

    vcl::Font aRubyFont(aFont);
    aRubyFont.SetFontHeight(aRubyFont.GetFontHeight() * 70 / 100);
    rRenderContext.SetFont(aRubyFont);
    tools::Long nRubyWidth = rRenderContext.GetTextWidth(sRubyText);
    rRenderContext.SetFont(aFont);

    RubyAdjust nAdjust = static_cast<RubyAdjust>(m_pParentDlg->m_xAdjustLB->get_active());
    //use center if no adjustment is available
    if (nAdjust > RubyAdjust_INDENT_BLOCK)
        nAdjust = RubyAdjust_CENTER;

    //which part is stretched ?
    bool bRubyStretch = nBaseWidth >= nRubyWidth;

    tools::Long nCenter = aWinSize.Width() / 2;
    tools::Long nHalfWidth = std::max(nBaseWidth, nRubyWidth) / 2;
    tools::Long nLeftStart = nCenter - nHalfWidth;
    tools::Long nRightEnd = nCenter + nHalfWidth;

    // Default values for TOP or no selection
    tools::Long nYRuby = aWinSize.Height() / 4 - nTextHeight / 2;
    tools::Long nYBase = aWinSize.Height() * 3 / 4 - nTextHeight / 2;

    sal_Int16 nRubyPos = m_pParentDlg->m_xPositionLB->get_active();
    if (nRubyPos == 1) // BOTTOM
        std::swap(nYRuby, nYBase);
    else if (nRubyPos == 2) // RIGHT ( vertically )
    {
        // Align the ruby text and base text to the vertical center.
        nYBase = (aWinSize.Height() - nTextHeight) / 2;
        nYRuby = (aWinSize.Height() - nRubyWidth) / 2;

        // Align the ruby text at the right side of the base text
        nAdjust = RubyAdjust_RIGHT;
        nHalfWidth = nBaseWidth / 2;
        nLeftStart = nCenter - nHalfWidth;
        nRightEnd = nCenter + nHalfWidth + nRubyWidth + nTextHeight;
        // Render base text first, then render ruby text on the right.
        bRubyStretch = true;

        aRubyFont.SetVertical(true);
        aRubyFont.SetOrientation(2700_deg10);
    }

    tools::Long nYOutput;
    tools::Long nOutTextWidth;
    OUString sOutputText;

    if (bRubyStretch)
    {
        rRenderContext.DrawText(Point(nLeftStart, nYBase), sBaseText);
        nYOutput = nYRuby;
        sOutputText = sRubyText;
        nOutTextWidth = nRubyWidth;
        rRenderContext.SetFont(aRubyFont);
    }
    else
    {
        rRenderContext.SetFont(aRubyFont);
        rRenderContext.DrawText(Point(nLeftStart, nYRuby), sRubyText);
        nYOutput = nYBase;
        sOutputText = sBaseText;
        nOutTextWidth = nBaseWidth;
        rRenderContext.SetFont(aFont);
    }

    switch (nAdjust)
    {
        case RubyAdjust_LEFT:
            rRenderContext.DrawText(Point(nLeftStart, nYOutput), sOutputText);
            break;
        case RubyAdjust_RIGHT:
            rRenderContext.DrawText(Point(nRightEnd - nOutTextWidth, nYOutput), sOutputText);
            break;
        case RubyAdjust_INDENT_BLOCK:
        {
            tools::Long nCharWidth = rRenderContext.GetTextWidth(u"X"_ustr);
            if (nOutTextWidth < (nRightEnd - nLeftStart - nCharWidth))
            {
                nCharWidth /= 2;
                nLeftStart += nCharWidth;
                nRightEnd -= nCharWidth;
            }
            [[fallthrough]];
        }
        case RubyAdjust_BLOCK:
        {
            if (sOutputText.getLength() > 1)
            {
                sal_Int32 nCount = sOutputText.getLength();
                tools::Long nSpace
                    = ((nRightEnd - nLeftStart) - rRenderContext.GetTextWidth(sOutputText))
                      / (nCount - 1);
                for (sal_Int32 i = 0; i < nCount; i++)
                {
                    OUString sChar(sOutputText[i]);
                    rRenderContext.DrawText(Point(nLeftStart, nYOutput), sChar);
                    tools::Long nCharWidth = rRenderContext.GetTextWidth(sChar);
                    nLeftStart += nCharWidth + nSpace;
                }
                break;
            }
            [[fallthrough]];
        }
        case RubyAdjust_CENTER:
            rRenderContext.DrawText(Point(nCenter - nOutTextWidth / 2, nYOutput), sOutputText);
            break;
        default:
            break;
    }
}

void RubyPreview::SetDrawingArea(weld::DrawingArea* pDrawingArea)
{
    pDrawingArea->set_size_request(pDrawingArea->get_approximate_digit_width() * 40,
                                   pDrawingArea->get_text_height() * 7);
    CustomWidgetController::SetDrawingArea(pDrawingArea);
}

IMPL_LINK(SvxRubyDialog, KeyUpDownHdl_Impl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = false;
    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
    sal_uInt16 nCode = rKeyCode.GetCode();
    if (KEY_UP == nCode || KEY_DOWN == nCode)
    {
        sal_Int32 nParam = KEY_UP == nCode ? -1 : 1;
        bHandled = EditJumpHdl_Impl(nParam);
    }
    return bHandled;
}

IMPL_LINK(SvxRubyDialog, KeyUpDownTabHdl_Impl, const KeyEvent&, rKEvt, bool)
{
    bool bHandled = false;
    const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode();
    sal_uInt16 nMod = rKeyCode.GetModifier();
    sal_uInt16 nCode = rKeyCode.GetCode();
    if (nCode == KEY_TAB && (!nMod || KEY_SHIFT == nMod))
    {
        sal_Int32 nParam = KEY_SHIFT == nMod ? -1 : 1;
        if (EditScrollHdl_Impl(nParam))
            bHandled = true;
    }
    if (!bHandled)
        bHandled = KeyUpDownHdl_Impl(rKEvt);
    return bHandled;
}

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