/* -*- 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 <bitset>

#include <tools/debug.hxx>
#include <editeng/editids.hrc>
#include <svtools/colorcfg.hxx>
#include <svtools/extcolorcfg.hxx>
#include <svx/colorbox.hxx>
#include <unotools/moduleoptions.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <svx/svxdlg.hxx>
#include <helpids.h>
#include <dialmgr.hxx>
#include "optcolor.hxx"
#include <strings.hrc>
#include <svtools/miscopt.hxx>
#include <officecfg/Office/Common.hxx>
using namespace ::com::sun::star;
using namespace ::svtools;

namespace
{

// list of default groups
enum Group
{
    Group_General,
    Group_Writer,
    Group_Html,
    Group_Calc,
    Group_Draw,
    Group_Basic,
    Group_Sql,

    nGroupCount
};

// group data
struct
{
    // group
    Group eGroup;
    // .ui group name
    OUString pGroup;
}
const vGroupInfo[] =
{
    // the groups are in the same order as in enum Group above
    { Group_General, "general" },
    { Group_Writer, "writer" },
    { Group_Html, "html" },
    { Group_Calc, "calc" },
    { Group_Draw, "draw" },
    { Group_Basic, "basic" },
    { Group_Sql, "sql" }
};

// color config entry data (see ColorConfigWindow_Impl::Entry below)
struct
{
    // group
    Group eGroup;
    //checkbox (or simple text)
    OUString pText;
    //color listbox
    OUString pColor;
    // has checkbox?
    bool bCheckBox;
}
const vEntryInfo[] =
{
    #define IDS(Name) \
        SAL_STRINGIFY(Name), SAL_STRINGIFY(Name##_lb), false

    #define IDS_CB(Name) \
        SAL_STRINGIFY(Name), SAL_STRINGIFY(Name##_lb), true

    // The list of these entries (enum ColorConfigEntry) are in colorcfg.hxx.

    { Group_General, IDS(doccolor) },
    { Group_General, IDS_CB(docboundaries) },
    { Group_General, IDS(appback) },
    { Group_General, IDS_CB(objboundaries) },
    { Group_General, IDS_CB(tblboundaries) },
    { Group_General, IDS(font) },
    { Group_General, IDS_CB(unvisitedlinks) },
    { Group_General, IDS_CB(visitedlinks) },
    { Group_General, IDS(autospellcheck) },
    { Group_General, IDS(grammarcheck) },
    { Group_General, IDS(smarttags) },
    { Group_General, IDS_CB(shadows) },

    { Group_Writer,  IDS(writergrid) },
    { Group_Writer,  IDS_CB(field) },
    { Group_Writer,  IDS_CB(index) },
    { Group_Writer,  IDS(direct) },
    { Group_Writer,  IDS(script) },
    { Group_Writer,  IDS_CB(section) },
    { Group_Writer,  IDS(hdft) },
    { Group_Writer,  IDS(pagebreak) },

    { Group_Html,    IDS(sgml) },
    { Group_Html,    IDS(htmlcomment) },
    { Group_Html,    IDS(htmlkeyword) },
    { Group_Html,    IDS(unknown) },

    { Group_Calc,    IDS(calcgrid) },
    { Group_Calc,    IDS(brk) },
    { Group_Calc,    IDS(brkmanual) },
    { Group_Calc,    IDS(brkauto) },
    { Group_Calc,    IDS_CB(hiddencolrow) },
    { Group_Calc,    IDS_CB(textoverflow) },
    { Group_Calc,    IDS(comments) },
    { Group_Calc,    IDS(det) },
    { Group_Calc,    IDS(deterror) },
    { Group_Calc,    IDS(ref) },
    { Group_Calc,    IDS(notes) },
    { Group_Calc,    IDS(values) },
    { Group_Calc,    IDS(formulas) },
    { Group_Calc,    IDS(text) },
    { Group_Calc,    IDS(protectedcells) },

    { Group_Draw,    IDS(drawgrid) },

    { Group_Basic,   IDS(basiceditor) },
    { Group_Basic,   IDS(basicid) },
    { Group_Basic,   IDS(basiccomment) },
    { Group_Basic,   IDS(basicnumber) },
    { Group_Basic,   IDS(basicstring) },
    { Group_Basic,   IDS(basicop) },
    { Group_Basic,   IDS(basickeyword) },
    { Group_Basic,   IDS(error) },

    { Group_Sql,     IDS(sqlid) },
    { Group_Sql,     IDS(sqlnumber) },
    { Group_Sql,     IDS(sqlstring) },
    { Group_Sql,     IDS(sqlop) },
    { Group_Sql,     IDS(sqlkeyword) },
    { Group_Sql,     IDS(sqlparam) },
    { Group_Sql,     IDS(sqlcomment) }

    #undef IDS
};

// Maps the names of default color schemes to the corresponding TranslateId
const std::map<OUString, OUString> &getColorSchemes()
{
    static std::map<OUString, OUString> const vColorSchemes = {
        {"COLOR_SCHEME_LIBREOFFICE_AUTOMATIC", CuiResId(RID_COLOR_SCHEME_LIBREOFFICE_AUTOMATIC)},
    };
    return vColorSchemes;
};

// If the color scheme name has a translated string, then return the translation
// Or else simply return the input string
// For non-translatable color schemes, the ID and the name are the same
OUString lcl_SchemeIdToTranslatedName(const OUString& sSchemeId)
{
    auto it = getColorSchemes().find(sSchemeId);
    if (it != getColorSchemes().end())
        return it->second;
    return sSchemeId;
}

// Given a translated color scheme name, return the scheme ID used in the UI.xcu file
// For non-translatable color schemes, the ID and the name are the same
OUString lcl_TranslatedNameToSchemeId(const OUString& sName)
{
    for (auto it = getColorSchemes().begin(); it != getColorSchemes().end(); ++it)
        if (it->second == sName)
            return it->first;
    return sName;
}

// ColorConfigWindow_Impl

class ColorConfigWindow_Impl
{
public:
    explicit ColorConfigWindow_Impl(weld::Window* pTopLevel, weld::Container* pParent);

public:
    void SetLinks(Link<weld::Toggleable&,void> const&,
                  Link<ColorListBox&,void> const&,
                  Link<weld::Widget&,void> const&,
                  weld::ScrolledWindow& rScroll);
    void Update(EditableColorConfig const*, EditableExtendedColorConfig const*);
    void UpdateEntries();
    void ClickHdl(EditableColorConfig*, const weld::Toggleable&);
    void ColorHdl(EditableColorConfig*, EditableExtendedColorConfig*, const ColorListBox*);

    weld::Widget& GetWidget1()
    {
        return *m_xWidget1;
    }

    weld::Widget& GetWidget2()
    {
        return *m_xWidget2;
    }

    weld::Widget& GetBody()
    {
        return *m_xBox;
    }

    int GetLabelIndent() const
    {
        return m_nCheckBoxLabelOffset;
    }

private:
    // Chapter -- horizontal group separator stripe with text
    class Chapter
    {
        // text
        std::unique_ptr<weld::Label> m_xText;
    public:
        Chapter(weld::Builder& rBuilder, const OUString& pLabelWidget, bool bShow);
        void SetText(const OUString& rLabel) { m_xText->set_label(rLabel); }
    };

    // Entry -- a color config entry:
    // text (checkbox) + color list box
    struct Entry
    {
        Entry(weld::Window* pTopLevel, weld::Builder& rBuilder, const OUString& pTextWidget, const OUString& pColorWidget,
              const Color& rColor, int nCheckBoxLabelOffset, const ColorListBox* pCache, bool bCheckBox, bool bShow);
        void SetText(const OUString& rLabel) { dynamic_cast<weld::Label&>(*m_xText).set_label(rLabel); }
        int get_height_request() const
        {
            return std::max(m_xText->get_preferred_size().Height(),
                            m_xColorList->get_widget().get_preferred_size().Height());
        }
        void Hide();

        void SetLinks(Link<weld::Toggleable&,void> const&,
                      Link<ColorListBox&,void> const&,
                      Link<weld::Widget&,void> const&);
        void Update (ColorConfigValue const&);
        void Update (ExtendedColorConfigValue const&);
        void ColorChanged (ColorConfigValue&);
        void ColorChanged (ExtendedColorConfigValue&);

        bool Is(const weld::Toggleable* pBox) const { return m_xText.get() == pBox; }
        bool Is(const ColorListBox* pBox) const { return m_xColorList.get() == pBox; }

        // checkbox (CheckBox) or simple text (FixedText)
        std::unique_ptr<weld::Widget> m_xText;
        // color list box
        std::unique_ptr<ColorListBox> m_xColorList;
        // default color
        Color m_aDefaultColor;
    };

private:
    weld::Window* m_pTopLevel;
    int m_nCheckBoxLabelOffset;
    std::unique_ptr<weld::Builder> m_xBuilder;
    std::unique_ptr<weld::Box> m_xBox;
    std::unique_ptr<weld::Widget> m_xWidget1;
    std::unique_ptr<weld::Widget> m_xWidget2;

    std::vector<std::unique_ptr<weld::Builder>> vExtBuilders;
    std::vector<std::unique_ptr<weld::Container>> vExtContainers;
    // vChapters -- groups (group headers)
    std::vector<std::shared_ptr<Chapter> > vChapters;
    // vEntries -- color options
    std::vector<std::shared_ptr<Entry> > vEntries;

    // module options
    SvtModuleOptions aModuleOptions;

    // initialization
    void CreateEntries();

private:

    bool IsGroupVisible (Group) const;
};

} // namespace

// ColorConfigWindow_Impl::Chapter

// ctor for default groups
// rParent: parent window (ColorConfigWindow_Impl)
// eGroup: which group is this?
ColorConfigWindow_Impl::Chapter::Chapter(weld::Builder& rBuilder, const OUString& pLabelWidget, bool bShow)
    : m_xText(rBuilder.weld_label(pLabelWidget))
{
    if (!bShow)
        m_xText->hide();
}

// ColorConfigWindow_Impl::Entry
ColorConfigWindow_Impl::Entry::Entry(weld::Window* pTopLevel, weld::Builder& rBuilder,
                                     const OUString& pTextWidget, const OUString& pColorWidget,
                                     const Color& rColor, int nCheckBoxLabelOffset,
                                     const ColorListBox* pCache, bool bCheckBox, bool bShow)
    : m_xColorList(new ColorListBox(rBuilder.weld_menu_button(pColorWidget),
                                    [pTopLevel]{ return pTopLevel; }, pCache))
    , m_aDefaultColor(rColor)
{
    if (bCheckBox)
        m_xText = rBuilder.weld_check_button(pTextWidget);
    else
        m_xText = rBuilder.weld_label(pTextWidget);

    // color list
    m_xColorList->SetSlotId(SID_ATTR_CHAR_COLOR);
    m_xColorList->SetAutoDisplayColor(m_aDefaultColor);

    if (!bCheckBox)
    {
        m_xText->set_margin_start(m_xText->get_margin_start() +
            nCheckBoxLabelOffset);
    }

    if (!bShow)
        Hide();
}

void ColorConfigWindow_Impl::Entry::Hide()
{
    m_xText->hide();
    m_xColorList->hide();
}

// SetLinks()
void ColorConfigWindow_Impl::Entry::SetLinks(Link<weld::Toggleable&,void> const& rCheckLink,
                                             Link<ColorListBox&,void> const& rColorLink,
                                             Link<weld::Widget&,void> const& rGetFocusLink)
{
    m_xColorList->SetSelectHdl(rColorLink);
    m_xColorList->connect_focus_in(rGetFocusLink);
    if (weld::Toggleable* pCheckBox = dynamic_cast<weld::Toggleable*>(m_xText.get()))
    {
        pCheckBox->connect_toggled(rCheckLink);
        pCheckBox->connect_focus_in(rGetFocusLink);
    }
}

// updates a default color config entry
void ColorConfigWindow_Impl::Entry::Update(ColorConfigValue const& rValue)
{
    Color aColor(rValue.nColor);
    m_xColorList->SelectEntry(aColor);
    if (weld::Toggleable* pCheckBox = dynamic_cast<weld::Toggleable*>(m_xText.get()))
        pCheckBox->set_active(rValue.bIsVisible);
}

// updates an extended color config entry
void ColorConfigWindow_Impl::Entry::Update(ExtendedColorConfigValue const& rValue)
{
    Color aColor(rValue.getColor());
    if (rValue.getColor() == rValue.getDefaultColor())
        m_xColorList->SelectEntry(COL_AUTO);
    else
        m_xColorList->SelectEntry(aColor);
}

// color of a default entry has changed
void ColorConfigWindow_Impl::Entry::ColorChanged(ColorConfigValue& rValue)
{
    Color aColor = m_xColorList->GetSelectEntryColor();
    rValue.nColor = aColor;
}

// color of an extended entry has changed
void ColorConfigWindow_Impl::Entry::ColorChanged(ExtendedColorConfigValue& rValue)
{
    Color aColor = m_xColorList->GetSelectEntryColor();
    rValue.setColor(aColor);
    if (aColor == COL_AUTO)
    {
        rValue.setColor(rValue.getDefaultColor());
    }
}

// ColorConfigWindow_Impl
ColorConfigWindow_Impl::ColorConfigWindow_Impl(weld::Window* pTopLevel, weld::Container* pParent)
    : m_pTopLevel(pTopLevel)
    , m_xBuilder(Application::CreateBuilder(pParent, "cui/ui/colorconfigwin.ui"))
    , m_xBox(m_xBuilder->weld_box("ColorConfigWindow"))
    , m_xWidget1(m_xBuilder->weld_widget("docboundaries"))
    , m_xWidget2(m_xBuilder->weld_widget("docboundaries_lb"))
{
    CreateEntries();
}

void ColorConfigWindow_Impl::CreateEntries()
{
    std::bitset<nGroupCount> aModulesInstalled;
    // creating group headers
    vChapters.reserve(nGroupCount);
    for (unsigned i = 0; i != nGroupCount; ++i)
    {
        aModulesInstalled[i] = IsGroupVisible(vGroupInfo[i].eGroup);
        vChapters.push_back(std::make_shared<Chapter>(*m_xBuilder, vGroupInfo[i].pGroup, aModulesInstalled[i]));
    }

    // Here we want to get the amount to add to the position of a FixedText to
    // get it to align its contents with that of a CheckBox
    {
        OUString sSampleText("XXXXXX");
        std::unique_ptr<weld::CheckButton> xCheckBox(m_xBuilder->weld_check_button("docboundaries"));
        std::unique_ptr<weld::Label> xFixedText(m_xBuilder->weld_label("doccolor"));
        OUString sOrigCheck(xCheckBox->get_label());
        OUString sOrigFixed(xFixedText->get_label());
        xCheckBox->set_label(sSampleText);
        xFixedText->set_label(sSampleText);
        Size aCheckSize(xCheckBox->get_preferred_size());
        Size aFixedSize(xFixedText->get_preferred_size());
        xCheckBox->set_label(sOrigCheck);
        xFixedText->set_label(sOrigFixed);
        m_nCheckBoxLabelOffset = aCheckSize.Width() - aFixedSize.Width();
    }

    const ColorListBox* pCache = nullptr;

    // creating entries
    vEntries.reserve(ColorConfigEntryCount);
    for (size_t i = 0; i < std::size(vEntryInfo); ++i)
    {
        vEntries.push_back(std::make_shared<Entry>(m_pTopLevel, *m_xBuilder,
            vEntryInfo[i].pText, vEntryInfo[i].pColor,
            ColorConfig::GetDefaultColor(static_cast<ColorConfigEntry>(i)),
            m_nCheckBoxLabelOffset, pCache,
            vEntryInfo[i].bCheckBox,
            aModulesInstalled[vEntryInfo[i].eGroup]));
        if (!pCache)
            pCache = vEntries.back()->m_xColorList.get();
    }

    // extended entries
    ExtendedColorConfig aExtConfig;
    unsigned const nExtGroupCount = aExtConfig.GetComponentCount();
    if (!nExtGroupCount)
        return;

    for (unsigned j = 0; j != nExtGroupCount; ++j)
    {
        vExtBuilders.emplace_back(Application::CreateBuilder(m_xBox.get(), "cui/ui/chapterfragment.ui"));
        vExtContainers.emplace_back(vExtBuilders.back()->weld_frame("ChapterFragment"));

        OUString const sComponentName = aExtConfig.GetComponentName(j);
        vChapters.push_back(std::make_shared<Chapter>(
            *vExtBuilders.back(), "chapter", true));
        vChapters.back()->SetText(aExtConfig.GetComponentDisplayName(sComponentName));

        vExtContainers.emplace_back(vExtBuilders.back()->weld_box("contents"));
        weld::Container* pChapterBox = vExtContainers.back().get();

        unsigned nColorCount = aExtConfig.GetComponentColorCount(sComponentName);
        for (unsigned i = 0; i != nColorCount; ++i)
        {
            vExtBuilders.emplace_back(Application::CreateBuilder(pChapterBox, "cui/ui/colorfragment.ui"));
            vExtContainers.emplace_back(vExtBuilders.back()->weld_container("ColorFragment"));

            ExtendedColorConfigValue const aColorEntry =
                aExtConfig.GetComponentColorConfigValue(sComponentName, i);
            vEntries.push_back(std::make_shared<Entry>(m_pTopLevel, *vExtBuilders.back(),
                "label", "button", aColorEntry.getDefaultColor(),
                m_nCheckBoxLabelOffset, pCache, false, true));
            vEntries.back()->SetText(aColorEntry.getDisplayName());
        }
    }
}

// SetLinks()
void ColorConfigWindow_Impl::SetLinks(Link<weld::Toggleable&,void> const& aCheckLink,
                                      Link<ColorListBox&,void> const& aColorLink,
                                      Link<weld::Widget&,void> const& rGetFocusLink,
                                      weld::ScrolledWindow& rScroll)
{
    if (vEntries.empty())
        return;
    for (auto const & i: vEntries)
        i->SetLinks(aCheckLink, aColorLink, rGetFocusLink);
    // 6 is the spacing set on ColorConfigWindow
    rScroll.vadjustment_set_step_increment(vEntries[0]->get_height_request() + 6);
}

// Update()
void ColorConfigWindow_Impl::Update (
    EditableColorConfig const* pConfig,
    EditableExtendedColorConfig const* pExtConfig)
{
    // updating default entries
    for (unsigned i = 0; i != ColorConfigEntryCount; ++i)
    {
        ColorConfigEntry const aColorEntry = static_cast<ColorConfigEntry>(i);
        vEntries[i]->Update(
            pConfig->GetColorValue(aColorEntry)
        );
    }

    // updating extended entries
    decltype(vEntries)::size_type i = ColorConfigEntryCount;
    unsigned const nExtCount = pExtConfig->GetComponentCount();
    for (unsigned j = 0; j != nExtCount; ++j)
    {
        OUString sComponentName = pExtConfig->GetComponentName(j);
        unsigned const nColorCount = pExtConfig->GetComponentColorCount(sComponentName);
        for (unsigned k = 0; i != vEntries.size() && k != nColorCount; ++i, ++k)
            vEntries[i]->Update(
                pExtConfig->GetComponentColorConfigValue(sComponentName, k)
            );
    }
}

void ColorConfigWindow_Impl::UpdateEntries()
{
    for (unsigned i = 0; i != ColorConfigEntryCount; ++i)
    {
        ColorConfigEntry const aEntry = static_cast<ColorConfigEntry>(i);
        Color aColor = ColorConfig::GetDefaultColor(aEntry);
        vEntries[i]->m_xColorList->SetAutoDisplayColor(aColor);
    }
}

// ClickHdl()
void ColorConfigWindow_Impl::ClickHdl(EditableColorConfig* pConfig, const weld::Toggleable& rBox)
{
    for (unsigned i = 0; i != ColorConfigEntryCount; ++i)
    {
        if (vEntries[i]->Is(&rBox))
        {
            ColorConfigEntry const aEntry = static_cast<ColorConfigEntry>(i);
            ColorConfigValue aValue = pConfig->GetColorValue(aEntry);
            aValue.bIsVisible = rBox.get_active();
            pConfig->SetColorValue(aEntry, aValue);
            break;
        }
    }
}

// ColorHdl()
void ColorConfigWindow_Impl::ColorHdl(
    EditableColorConfig* pConfig, EditableExtendedColorConfig* pExtConfig,
    const ColorListBox* pBox)
{
    unsigned i = 0;

    // default entries
    for ( ; i != ColorConfigEntryCount; ++i)
    {
        if (pBox && vEntries[i]->Is(pBox))
        {
            ColorConfigEntry const aColorEntry = static_cast<ColorConfigEntry>(i);
            ColorConfigValue aValue = pConfig->GetColorValue(aColorEntry);
            vEntries[i]->ColorChanged(aValue);
            pConfig->SetColorValue(aColorEntry, aValue);
            break;
        }
    }

    // extended entries
    unsigned const nExtCount = pExtConfig->GetComponentCount();
    i = ColorConfigEntryCount;
    for (unsigned j = 0; j != nExtCount; ++j)
    {
        OUString sComponentName = pExtConfig->GetComponentName(j);
        unsigned const nColorCount = pExtConfig->GetComponentColorCount(sComponentName);
        unsigned const nCount = vEntries.size();
        for (unsigned k = 0; i != nCount && k != nColorCount; ++i, ++k)
        {
            if (pBox && vEntries[i]->Is(pBox))
            {
                ExtendedColorConfigValue aValue =
                    pExtConfig->GetComponentColorConfigValue(sComponentName, k);
                vEntries[i]->ColorChanged(aValue);
                pExtConfig->SetColorValue(sComponentName, aValue);
                break;
            }
        }
    }
}


// IsGroupVisible()
bool ColorConfigWindow_Impl::IsGroupVisible (Group eGroup) const
{
    switch (eGroup)
    {
        case Group_Writer:
        case Group_Html:
            return aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::WRITER);
        case Group_Calc:
            return aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::CALC);
        case Group_Draw:
            return
                aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::DRAW) ||
                aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::IMPRESS);
        case Group_Sql:
            return aModuleOptions.IsModuleInstalled(SvtModuleOptions::EModule::DATABASE);
        default:
            return true;
    }
}

class ColorConfigCtrl_Impl
{
    std::unique_ptr<weld::ScrolledWindow> m_xVScroll;
    std::unique_ptr<weld::Container> m_xBody;
    std::unique_ptr<ColorConfigWindow_Impl> m_xScrollWindow;

    EditableColorConfig*            pColorConfig;
    EditableExtendedColorConfig*    pExtColorConfig;

    DECL_LINK(ClickHdl, weld::Toggleable&, void);
    DECL_LINK(ColorHdl, ColorListBox&, void);
    DECL_LINK(ControlFocusHdl, weld::Widget&, void);

public:
    explicit ColorConfigCtrl_Impl(weld::Window* pTopLevel, weld::Builder& rbuilder);

    void SetConfig (EditableColorConfig& rConfig) { pColorConfig = &rConfig; }
    void SetExtendedConfig (EditableExtendedColorConfig& rConfig) { pExtColorConfig = &rConfig; }
    void Update();
    void UpdateEntries();
    tools::Long GetScrollPosition() const
    {
        return m_xVScroll->vadjustment_get_value();
    }
    void SetScrollPosition(tools::Long nSet)
    {
        m_xVScroll->vadjustment_set_value(nSet);
    }
    weld::Widget& GetWidget1()
    {
        return m_xScrollWindow->GetWidget1();
    }
    weld::Widget& GetWidget2()
    {
        return m_xScrollWindow->GetWidget2();
    }
    int GetLabelIndent() const
    {
        return m_xScrollWindow->GetLabelIndent();
    }
};

ColorConfigCtrl_Impl::ColorConfigCtrl_Impl(weld::Window* pTopLevel, weld::Builder& rBuilder)
    : m_xVScroll(rBuilder.weld_scrolled_window("scroll"))
    , m_xBody(rBuilder.weld_container("colorconfig"))
    , m_xScrollWindow(std::make_unique<ColorConfigWindow_Impl>(pTopLevel, m_xBody.get()))
    , pColorConfig(nullptr)
    , pExtColorConfig(nullptr)
{
    m_xBody->set_stack_background();

    Link<weld::Toggleable&,void> aCheckLink = LINK(this, ColorConfigCtrl_Impl, ClickHdl);
    Link<ColorListBox&,void> aColorLink = LINK(this, ColorConfigCtrl_Impl, ColorHdl);
    Link<weld::Widget&,void> const& aGetFocusLink = LINK(this, ColorConfigCtrl_Impl, ControlFocusHdl);
    m_xScrollWindow->SetLinks(aCheckLink, aColorLink, aGetFocusLink, *m_xVScroll);
}

void ColorConfigCtrl_Impl::Update ()
{
    DBG_ASSERT(pColorConfig, "Configuration not set");
    m_xScrollWindow->Update(pColorConfig, pExtColorConfig);
}

void ColorConfigCtrl_Impl::UpdateEntries()
{
    m_xScrollWindow->UpdateEntries();
}

IMPL_LINK(ColorConfigCtrl_Impl, ClickHdl, weld::Toggleable&, rBox, void)
{
    DBG_ASSERT(pColorConfig, "Configuration not set");
    m_xScrollWindow->ClickHdl(pColorConfig, rBox);
}

// a color list has changed
IMPL_LINK(ColorConfigCtrl_Impl, ColorHdl, ColorListBox&, rBox, void)
{
    DBG_ASSERT(pColorConfig, "Configuration not set" );
    m_xScrollWindow->ColorHdl(pColorConfig, pExtColorConfig, &rBox);
}

IMPL_LINK(ColorConfigCtrl_Impl, ControlFocusHdl, weld::Widget&, rCtrl, void)
{
    // determine whether a control is completely visible
    // and make it visible
    unsigned const nWinHeight = m_xVScroll->vadjustment_get_page_size();

    // calc visible area
    auto nThumbPos = m_xVScroll->vadjustment_get_value();
    int const nWinTop = nThumbPos;
    int const nWinBottom = nWinTop + nWinHeight;

    int x, nCtrlPosY, width, nHeight;
    rCtrl.get_extents_relative_to(m_xScrollWindow->GetBody(), x, nCtrlPosY, width, nHeight);

    int const nSelectedItemTop = nCtrlPosY;
    int const nSelectedItemBottom = nCtrlPosY + nHeight;
    bool const shouldScrollDown = nSelectedItemBottom >= nWinBottom;
    bool const shouldScrollUp = nSelectedItemTop <= nWinTop;
    bool const isNeedToScroll = shouldScrollDown || shouldScrollUp || nCtrlPosY < 0;

    if (!isNeedToScroll)
        return;

    if (shouldScrollDown)
    {
        int nOffset = nSelectedItemBottom - nWinBottom;
        nThumbPos += nOffset + 2;
    }
    else
    {
        int nOffset = nWinTop - nSelectedItemTop;
        nThumbPos -= nOffset + 2;
        if(nThumbPos < 0)
            nThumbPos = 0;
    }
    m_xVScroll->vadjustment_set_value(nThumbPos);
}

// SvxColorOptionsTabPage
SvxColorOptionsTabPage::SvxColorOptionsTabPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rCoreSet)
    : SfxTabPage(pPage, pController, "cui/ui/optappearancepage.ui", "OptAppearancePage", &rCoreSet)
    , bFillItemSetCalled(false)
    , m_nSizeAllocEventId(nullptr)
    , m_xAutoColorLB(m_xBuilder->weld_combo_box("autocolorlb"))
    , m_xColorSchemeLB(m_xBuilder->weld_combo_box("colorschemelb"))
    , m_xSaveSchemePB(m_xBuilder->weld_button("save"))
    , m_xDeleteSchemePB(m_xBuilder->weld_button("delete"))
    , m_xColorConfigCT(new ColorConfigCtrl_Impl(pController->getDialog(), *m_xBuilder))
    , m_xTable(m_xBuilder->weld_widget("table"))
    , m_xOnFT(m_xBuilder->weld_label("on"))
    , m_xColorFT(m_xBuilder->weld_label("colorsetting"))
    , m_rWidget1(m_xColorConfigCT->GetWidget1())
    , m_rWidget2(m_xColorConfigCT->GetWidget2())
{
    m_xColorSchemeLB->make_sorted();
    m_xColorSchemeLB->connect_changed(LINK(this, SvxColorOptionsTabPage, SchemeChangedHdl_Impl));
    m_xAutoColorLB->connect_changed(LINK(this, SvxColorOptionsTabPage, onAutoColorChanged));
    Link<weld::Button&,void> aLk = LINK(this, SvxColorOptionsTabPage, SaveDeleteHdl_Impl );
    m_xSaveSchemePB->connect_clicked(aLk);
    m_xDeleteSchemePB->connect_clicked(aLk);

    m_rWidget1.connect_size_allocate(LINK(this, SvxColorOptionsTabPage, AdjustHeaderBar));
    m_rWidget2.connect_size_allocate(LINK(this, SvxColorOptionsTabPage, AdjustHeaderBar));
}

SvxColorOptionsTabPage::~SvxColorOptionsTabPage()
{
    if (pColorConfig)
    {
        //when the dialog is cancelled but the color scheme ListBox has been changed these
        //changes need to be undone
        if (!bFillItemSetCalled && m_xColorSchemeLB->get_value_changed_from_saved())
        {
            OUString sOldScheme = m_xColorSchemeLB->get_saved_value();
            if(!sOldScheme.isEmpty())
            {
                pColorConfig->SetCurrentSchemeName(sOldScheme);
                pExtColorConfig->SetCurrentSchemeName(sOldScheme);
            }
        }
        pColorConfig->ClearModified();
        pColorConfig->EnableBroadcast();
        pColorConfig.reset();

        pExtColorConfig->ClearModified();
        pExtColorConfig->EnableBroadcast();
        pExtColorConfig.reset();
    }
    m_xColorConfigCT.reset();
    if (m_nSizeAllocEventId)
        Application::RemoveUserEvent(m_nSizeAllocEventId);
}

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

OUString SvxColorOptionsTabPage::GetAllStrings()
{
    // buttons are excluded
    OUString sAllStrings;
    OUString labels[] = { "label2", "label3", "autocolor", "uielements", "colorsetting" };

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

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

bool SvxColorOptionsTabPage::FillItemSet( SfxItemSet*  )
{
    bFillItemSetCalled = true;
    if (m_xColorSchemeLB->get_value_changed_from_saved())
    {
        pColorConfig->SetModified();
        pExtColorConfig->SetModified();
    }
    if (pColorConfig->IsModified())
        pColorConfig->Commit();
    if (pExtColorConfig->IsModified())
        pExtColorConfig->Commit();
    return true;
}

void SvxColorOptionsTabPage::Reset( const SfxItemSet* )
{
    if(pColorConfig)
    {
        pColorConfig->ClearModified();
        pColorConfig->DisableBroadcast();
    }
    pColorConfig.reset(new EditableColorConfig);
    m_xColorConfigCT->SetConfig(*pColorConfig);

    if(pExtColorConfig)
    {
        pExtColorConfig->ClearModified();
        pExtColorConfig->DisableBroadcast();
    }
    pExtColorConfig.reset(new EditableExtendedColorConfig);
    m_xColorConfigCT->SetExtendedConfig(*pExtColorConfig);

    m_xAutoColorLB->set_active( MiscSettings::GetAppColorMode() );

    OUString sUser = GetUserData();
    //has to be called always to speed up accessibility tools
    m_xColorConfigCT->SetScrollPosition(sUser.toInt32());
    m_xColorSchemeLB->clear();
    const uno::Sequence< OUString >  aSchemes = pColorConfig->GetSchemeNames();
    for(const OUString& s : aSchemes)
        m_xColorSchemeLB->append_text(lcl_SchemeIdToTranslatedName(s));
    m_xColorSchemeLB->set_active_text(lcl_SchemeIdToTranslatedName(pColorConfig->GetCurrentSchemeName()));
    m_xColorSchemeLB->save_value();
    m_xDeleteSchemePB->set_sensitive( aSchemes.getLength() > 1 );
    UpdateColorConfig();
}

DeactivateRC SvxColorOptionsTabPage::DeactivatePage( SfxItemSet* pSet_ )
{
    if ( pSet_ )
        FillItemSet( pSet_ );
    return DeactivateRC::LeavePage;
}

void SvxColorOptionsTabPage::UpdateColorConfig()
{
    //update the color config control
    m_xColorConfigCT->Update();
}

IMPL_LINK_NOARG(SvxColorOptionsTabPage, onAutoColorChanged, weld::ComboBox&, void)
{
    MiscSettings::SetAppColorMode( m_xAutoColorLB->get_active() );

    m_xColorConfigCT->UpdateEntries();

    pColorConfig->LoadScheme(lcl_TranslatedNameToSchemeId(m_xColorSchemeLB->get_active_text()));
    pExtColorConfig->LoadScheme(lcl_TranslatedNameToSchemeId(m_xColorSchemeLB->get_active_text()));
    UpdateColorConfig();
}

IMPL_LINK(SvxColorOptionsTabPage, SchemeChangedHdl_Impl, weld::ComboBox&, rBox, void)
{
    pColorConfig->LoadScheme(lcl_TranslatedNameToSchemeId(rBox.get_active_text()));
    pExtColorConfig->LoadScheme(lcl_TranslatedNameToSchemeId(rBox.get_active_text()));
    UpdateColorConfig();
}

IMPL_LINK(SvxColorOptionsTabPage, SaveDeleteHdl_Impl, weld::Button&, rButton, void)
{
    if (m_xSaveSchemePB.get() == &rButton)
    {
        OUString sName;

        SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
        ScopedVclPtr<AbstractSvxNameDialog> aNameDlg(pFact->CreateSvxNameDialog(GetFrameWeld(),
                            sName, CuiResId(RID_CUISTR_COLOR_CONFIG_SAVE2) ));
        aNameDlg->SetCheckNameHdl( LINK(this, SvxColorOptionsTabPage, CheckNameHdl_Impl));
        aNameDlg->SetText(CuiResId(RID_CUISTR_COLOR_CONFIG_SAVE1));
        aNameDlg->SetHelpId(HID_OPTIONS_COLORCONFIG_SAVE_SCHEME);
        aNameDlg->SetCheckNameHdl( LINK(this, SvxColorOptionsTabPage, CheckNameHdl_Impl));
        if(RET_OK == aNameDlg->Execute())
        {
            aNameDlg->GetName(sName);
            pColorConfig->AddScheme(sName);
            pExtColorConfig->AddScheme(sName);
            m_xColorSchemeLB->append_text(sName);
            m_xColorSchemeLB->set_active_text(sName);
            SchemeChangedHdl_Impl(*m_xColorSchemeLB);
        }
    }
    else
    {
        DBG_ASSERT(m_xColorSchemeLB->get_count() > 1, "don't delete the last scheme");
        std::unique_ptr<weld::MessageDialog> xQuery(Application::CreateMessageDialog(GetFrameWeld(),
                                                    VclMessageType::Question, VclButtonsType::YesNo,
                                                    CuiResId(RID_CUISTR_COLOR_CONFIG_DELETE)));
        xQuery->set_title(CuiResId(RID_CUISTR_COLOR_CONFIG_DELETE_TITLE));
        if (RET_YES == xQuery->run())
        {
            OUString sDeleteScheme(m_xColorSchemeLB->get_active_text());
            m_xColorSchemeLB->remove(m_xColorSchemeLB->get_active());
            m_xColorSchemeLB->set_active(0);
            SchemeChangedHdl_Impl(*m_xColorSchemeLB);
            //first select the new scheme and then delete the old one
            pColorConfig->DeleteScheme(sDeleteScheme);
            pExtColorConfig->DeleteScheme(sDeleteScheme);
        }
    }
    m_xDeleteSchemePB->set_sensitive(m_xColorSchemeLB->get_count() > 1);
}

IMPL_LINK(SvxColorOptionsTabPage, CheckNameHdl_Impl, AbstractSvxNameDialog&, rDialog, bool )
{
    OUString sName;
    rDialog.GetName(sName);
    return !sName.isEmpty() && m_xColorSchemeLB->find_text(sName) == -1;
}

void SvxColorOptionsTabPage::FillUserData()
{
    SetUserData(OUString::number(m_xColorConfigCT->GetScrollPosition()));
}

IMPL_LINK_NOARG(SvxColorOptionsTabPage, AdjustHeaderBar, const Size&, void)
{
    if (m_nSizeAllocEventId)
        return;
    m_nSizeAllocEventId = Application::PostUserEvent(LINK(this, SvxColorOptionsTabPage, PostAdjustHeaderBar));
}

IMPL_LINK_NOARG(SvxColorOptionsTabPage, PostAdjustHeaderBar, void*, void)
{
    m_nSizeAllocEventId = nullptr;

    // horizontal positions
    int nX1, nX2, nX3, y, width, height;
    if (!m_rWidget1.get_extents_relative_to(*m_xTable, nX1, y, width, height))
        return;
    if (!m_rWidget2.get_extents_relative_to(*m_xTable, nX2, y, width, height))
        return;
    if (!m_xTable->get_extents_relative_to(*m_xTable, nX3, y, width, height))
        return;

    // 6 is the column-spacing of the parent grid of these labels
    auto nTextWidth1 = nX1 + m_xColorConfigCT->GetLabelIndent() - 6;
    m_xOnFT->set_size_request(nTextWidth1, -1);
    auto nTextWidth3 = width - nX2;
    m_xColorFT->set_size_request(nTextWidth3, -1);
}

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