/* -*- 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 <stdlib.h>
#include <algorithm>
#include <string_view>

#include <sfx2/tabdlg.hxx>
#include <sfx2/app.hxx>
#include <sfx2/sfxresid.hxx>
#include <sfx2/sfxdlg.hxx>
#include <sfx2/viewsh.hxx>
#include <unotools/viewoptions.hxx>
#include <utility>
#include <vcl/virdev.hxx>
#include <sal/log.hxx>
#include <tools/debug.hxx>
#include <comphelper/lok.hxx>

#include <sfx2/strings.hrc>
#include <helpids.h>

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

constexpr OUString USERITEM_NAME = u"UserItem"_ustr;


struct TabPageImpl
{
    bool                        mbStandard;
    SfxOkDialogController*      mpSfxDialogController;
    css::uno::Reference< css::frame::XFrame > mxFrame;

    TabPageImpl() : mbStandard(false), mpSfxDialogController(nullptr) {}
};

namespace {

struct Data_Impl
{
    OUString sId;                 // The ID
    CreateTabPage fnCreatePage;   // Pointer to Factory
    GetTabPageRanges fnGetRanges; // Pointer to Ranges-Function
    std::unique_ptr<SfxTabPage> xTabPage;         // The TabPage itself
    bool bRefresh;                // Flag: Page must be re-initialized

    // Constructor
    Data_Impl( const OUString& rId, CreateTabPage fnPage,
               GetTabPageRanges fnRanges ) :

        sId         ( rId ),
        fnCreatePage( fnPage ),
        fnGetRanges ( fnRanges ),
        bRefresh    ( false )
    {
    }
};

}

SfxTabDialogItem::SfxTabDialogItem( const SfxTabDialogItem& rAttr, SfxItemPool* pItemPool )
    : SfxSetItem( rAttr, pItemPool )
{
}

SfxTabDialogItem::SfxTabDialogItem( sal_uInt16 nId, const SfxItemSet& rItemSet )
    : SfxSetItem( nId, rItemSet )
{
}

SfxTabDialogItem* SfxTabDialogItem::Clone(SfxItemPool* pToPool) const
{
    return new SfxTabDialogItem( *this, pToPool );
}

typedef std::vector<Data_Impl*> SfxTabDlgData_Impl;

struct TabDlg_Impl
{
    bool                bHideResetBtn : 1;
    bool                bStarted : 1;
    SfxTabDlgData_Impl  aData;

    explicit TabDlg_Impl(sal_uInt8 nCnt)
        : bHideResetBtn(false)
        , bStarted(false)
    {
        aData.reserve( nCnt );
    }
};

static auto Find(const SfxTabDlgData_Impl& rArr, std::u16string_view rId)
{
    return std::find_if(rArr.begin(), rArr.end(),
                        [rId](const auto& item) { return item->sId == rId; });
}

void SfxTabPage::SetFrame(const css::uno::Reference< css::frame::XFrame >& xFrame)
{
    if (mpImpl)
        mpImpl->mxFrame = xFrame;
}

css::uno::Reference< css::frame::XFrame > SfxTabPage::GetFrame() const
{
    if (mpImpl)
        return mpImpl->mxFrame;
    return css::uno::Reference< css::frame::XFrame >();
}

static bool isLOKMobilePhone()
{
    if (!comphelper::LibreOfficeKit::isActive())
        return false;
    const SfxViewShell* pCurrentShell = SfxViewShell::Current();
    return pCurrentShell && pCurrentShell->isLOKMobilePhone();
}

SfxTabPage::SfxTabPage(weld::Container* pPage, weld::DialogController* pController, const OUString& rUIXMLDescription, const OUString& rID, const SfxItemSet *rAttrSet)
    : BuilderPage(pPage, pController, rUIXMLDescription, rID, isLOKMobilePhone())
    , mpSet(rAttrSet)
    , mbHasExchangeSupport(false)
    , mbCancel(false)
    , mpImpl(new TabPageImpl)
{
    mpImpl->mpSfxDialogController = dynamic_cast<SfxOkDialogController*>(m_pDialogController);
}

SfxTabPage::~SfxTabPage()
{
    if (m_xContainer)
    {
        std::unique_ptr<weld::Container> xParent(m_xContainer->weld_parent());
        if (xParent)
            xParent->move(m_xContainer.get(), nullptr);
    }
    m_xContainer.reset();
    mpImpl.reset();
    m_xBuilder.reset();
}

bool SfxTabPage::FillItemSet( SfxItemSet* )
{
    return false;
}

/*
Returns the visible strings of a dialog.

Supported items:
- label
- check button
- radio button
- toggle button
- link button
- button
*/
OUString SfxTabPage::GetAllStrings() { return OUString(); }

void SfxTabPage::Reset( const SfxItemSet* )
{
}

bool SfxTabPage::DeferResetToFirstActivation() { return false; }

void SfxTabPage::ActivatePage( const SfxItemSet& )
/*  [Description]

    Default implementation of the virtual ActivatePage method. This method is
    called when a page of dialogue supports the exchange of data between pages.
    <SfxTabPage::DeactivatePage(SfxItemSet *)>
*/
{
}

DeactivateRC SfxTabPage::DeactivatePage( SfxItemSet* )

/*  [Description]

    Default implementation of the virtual DeactivatePage method. This method is
    called by Sfx when leaving a page; the application can, through the return
    value, control whether to leave the page. If the page is displayed through
    bHasExchangeSupport which supports data exchange between pages, then a
    pointer to the exchange set is passed as parameter. This takes on data for
    the exchange, then the set is available as a parameter in
    <SfxTabPage::ActivatePage(const SfxItemSet &)>.

    [Return value]

    DeactivateRC::LeavePage; Allow leaving the page
*/

{
    return DeactivateRC::LeavePage;
}


void SfxTabPage::FillUserData()

/*  [Description]

    Virtual method is called by the base class in the destructor to save
    specific information of the TabPage in the ini-file. When overriding a
    string must be compiled, which is then flushed with the <SetUserData()>.
*/

{
}


bool SfxTabPage::IsReadOnly() const
{
    return false;
}


const SfxPoolItem* SfxTabPage::GetItem( const SfxItemSet& rSet, sal_uInt16 nSlot, bool bDeep )

/*  [Description]

    static Method: hereby are the implementations of the TabPage code
    being simplified.
*/

{
    const SfxItemPool* pPool = rSet.GetPool();
    sal_uInt16 nWh = pPool->GetWhichIDFromSlotID( nSlot, bDeep );
    const SfxPoolItem* pItem = nullptr;
    rSet.GetItemState( nWh, true, &pItem );

    if ( !pItem && nWh != nSlot )
        pItem = &pPool->GetUserOrPoolDefaultItem( nWh );
    return pItem;
}


const SfxPoolItem* SfxTabPage::GetOldItem( const SfxItemSet& rSet,
                                           sal_uInt16 nSlot, bool bDeep )

/*  [Description]

    This method returns an attribute for comparison of the old value.
*/

{
    const SfxItemSet& rOldSet = GetItemSet();
    sal_uInt16 nWh = GetWhich( nSlot, bDeep );
    const SfxPoolItem* pItem = nullptr;

    if (mpImpl->mbStandard && rOldSet.GetParent())
        pItem = GetItem( *rOldSet.GetParent(), nSlot );
    else if ( rSet.GetParent() &&
              SfxItemState::INVALID == rSet.GetItemState( nWh ) )
        pItem = GetItem( *rSet.GetParent(), nSlot );
    else
        pItem = GetItem( rOldSet, nSlot );
    return pItem;
}

void SfxTabPage::PageCreated( const SfxAllItemSet& /*aSet*/ )
{
    SAL_WARN( "sfx.dialog", "SfxTabPage::PageCreated should not be called");
}

void SfxTabPage::ChangesApplied()
{
}

void SfxTabPage::SetDialogController(SfxOkDialogController* pDialog)
{
    mpImpl->mpSfxDialogController = pDialog;
    m_pDialogController = mpImpl->mpSfxDialogController;
}

SfxOkDialogController* SfxTabPage::GetDialogController() const
{
    return mpImpl->mpSfxDialogController;
}

OUString SfxTabPage::GetHelpId() const
{
    if (m_xContainer)
        return m_xContainer->get_help_id();
    return {};
}

weld::Window* SfxTabPage::GetFrameWeld() const
{
    if (m_pDialogController)
        return m_pDialogController->getDialog();
    return nullptr;
}

const SfxItemSet* SfxTabPage::GetDialogExampleSet() const
{
    if (mpImpl->mpSfxDialogController)
        return mpImpl->mpSfxDialogController->GetExampleSet();
    return nullptr;
}

SfxTabDialogController::SfxTabDialogController
(
    weld::Widget* pParent,              // Parent Window
    const OUString& rUIXMLDescription, const OUString& rID, // Dialog .ui path, Dialog Name
    const SfxItemSet* pItemSet,   // Itemset with the data;
                                  // can be NULL, when Pages are onDemand
    bool bEditFmt                 // when yes -> additional Button for standard
)
    : SfxOkDialogController(pParent, rUIXMLDescription, rID)
    , m_xTabCtrl(m_xBuilder->weld_notebook(u"tabcontrol"_ustr))
    , m_xOKBtn(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xApplyBtn(m_xBuilder->weld_button(u"apply"_ustr))
    , m_xUserBtn(m_xBuilder->weld_button(u"user"_ustr))
    , m_xCancelBtn(m_xBuilder->weld_button(u"cancel"_ustr))
    , m_xResetBtn(m_xBuilder->weld_button(u"reset"_ustr))
    , m_xBaseFmtBtn(m_xBuilder->weld_button(u"standard"_ustr))
    , m_pSet(pItemSet ? new SfxItemSet(*pItemSet) : nullptr)
    , m_bStandardPushed(false)
{
    m_pImpl.reset(new TabDlg_Impl(m_xTabCtrl->get_n_pages()));
    m_pImpl->bHideResetBtn = !m_xResetBtn->get_visible();
    m_xOKBtn->connect_clicked(LINK(this, SfxTabDialogController, OkHdl));
    m_xCancelBtn->connect_clicked(LINK(this, SfxTabDialogController, CancelHdl));
    m_xResetBtn->connect_clicked(LINK(this, SfxTabDialogController, ResetHdl));
    m_xResetBtn->set_label(SfxResId(STR_RESET));
    m_xTabCtrl->connect_enter_page(LINK(this, SfxTabDialogController, ActivatePageHdl));
    m_xTabCtrl->connect_leave_page(LINK(this, SfxTabDialogController, DeactivatePageHdl));
    m_xResetBtn->set_help_id(HID_TABDLG_RESET_BTN);

    if (bEditFmt)
    {
        m_xBaseFmtBtn->set_label(SfxResId(STR_STANDARD_SHORTCUT));
        m_xBaseFmtBtn->connect_clicked(LINK(this, SfxTabDialogController, BaseFmtHdl));
        m_xBaseFmtBtn->set_help_id(HID_TABDLG_STANDARD_BTN);
        m_xBaseFmtBtn->show();
    }

    if (m_xUserBtn)
        m_xUserBtn->connect_clicked(LINK(this, SfxTabDialogController, UserHdl));

    if (m_pSet)
    {
        m_xExampleSet.reset(new SfxItemSet(*m_pSet));
        m_pOutSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges()));
    }

    // The reset functionality seems to be confusing to many; disable in LOK.
    if (comphelper::LibreOfficeKit::isActive())
        RemoveResetButton();
}

IMPL_LINK_NOARG(SfxTabDialogController, OkHdl, weld::Button&, void)

/*  [Description]

    Handler of the Ok-Buttons
    This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>.
    Returns <DeactivateRC::LeavePage>, <SfxTabDialog::Ok()> is called
    and the Dialog is ended.
*/

{
    if (PrepareLeaveCurrentPage())
        m_xDialog->response(Ok());
}

IMPL_LINK_NOARG(SfxTabDialogController, UserHdl, weld::Button&, void)

/*  [Description]

    Handler of the User-Buttons
    This calls the current page <SfxTabPage::DeactivatePage(SfxItemSet *)>.
    returns this <DeactivateRC::LeavePage> and  <SfxTabDialog::Ok()> is called.
    Then the Dialog is ended with the Return value <SfxTabDialog::Ok()>
*/

{
    if (PrepareLeaveCurrentPage())
    {
        short nRet = Ok();
        if (RET_OK == nRet)
            nRet = RET_USER;
        else
            nRet = RET_CANCEL;
        m_xDialog->response(nRet);
    }
}

IMPL_LINK_NOARG(SfxTabDialogController, CancelHdl, weld::Button&, void)
{
    m_xDialog->response(RET_CANCEL);
}

IMPL_LINK_NOARG(SfxTabDialogController, ResetHdl, weld::Button&, void)

/*  [Description]

    Handler behind the reset button.
    The Current Page is new initialized with their initial data, all the
    settings that the user has made on this page are repealed.
*/

{
    auto it = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident());
    assert(it != m_pImpl->aData.end() && "Id not known");

    (*it)->xTabPage->Reset(m_pSet.get());
    // Also reset relevant items of ExampleSet and OutSet to initial state
    if (!(*it)->fnGetRanges)
        return;

    if (!m_xExampleSet)
        m_xExampleSet.reset(new SfxItemSet(*m_pSet));

    const SfxItemPool* pPool = m_pSet->GetPool();
    const WhichRangesContainer aTmpRanges = ((*it)->fnGetRanges)();

    for (const auto & rPair : aTmpRanges)
    {
        // Correct Range with multiple values
        sal_uInt16 nTmp = rPair.first, nTmpEnd = rPair.second;
        DBG_ASSERT(nTmp <= nTmpEnd, "Range is sorted the wrong way");

        if (nTmp > nTmpEnd)
        {
            // If really sorted wrongly, then set new
            std::swap(nTmp, nTmpEnd);
        }

        while (nTmp && nTmp <= nTmpEnd)
        {
            // Iterate over the Range and set the Items
            sal_uInt16 nWh = pPool->GetWhichIDFromSlotID(nTmp);
            const SfxPoolItem* pItem;
            if (SfxItemState::SET == m_pSet->GetItemState(nWh, false, &pItem))
            {
                m_xExampleSet->Put(*pItem);
            }
            else
            {
                m_xExampleSet->ClearItem(nWh);
            }
            m_pOutSet->ClearItem(nWh);
            nTmp++;
        }
    }
}

/*  [Description]

    Handler behind the Standard-Button.
    This button is available when editing style sheets. All the set attributes
    in the edited stylesheet are deleted.
*/
IMPL_LINK_NOARG(SfxTabDialogController, BaseFmtHdl, weld::Button&, void)
{
    m_bStandardPushed = true;

    auto it = Find(m_pImpl->aData, m_xTabCtrl->get_current_page_ident());
    assert(it != m_pImpl->aData.end() && "Id not known");

    if (!(*it)->fnGetRanges)
        return;

    if (!m_xExampleSet)
        m_xExampleSet.reset(new SfxItemSet(*m_pSet));

    const SfxItemPool* pPool = m_pSet->GetPool();
    const WhichRangesContainer aTmpRanges = ((*it)->fnGetRanges)();
    SfxItemSet aTmpSet(*m_xExampleSet);

    for (const auto& rPair : aTmpRanges)
    {
        // Correct Range with multiple values
        sal_uInt16 nTmp = rPair.first, nTmpEnd = rPair.second;
        DBG_ASSERT( nTmp <= nTmpEnd, "Range is sorted the wrong way" );

        if ( nTmp > nTmpEnd )
        {
            // If really sorted wrongly, then set new
            std::swap(nTmp, nTmpEnd);
        }

        while ( nTmp && nTmp <= nTmpEnd ) // guard against overflow
        {
            // Iterate over the Range and set the Items
            sal_uInt16 nWh = pPool->GetWhichIDFromSlotID(nTmp);
            m_xExampleSet->ClearItem(nWh);
            aTmpSet.ClearItem(nWh);
            // At the Outset of InvalidateItem,
            // so that the change takes effect
            m_pOutSet->InvalidateItem(nWh);
            nTmp++;
        }
    }
    // Set all Items as new  -> the call the current Page Reset()
    assert((*it)->xTabPage && "the Page is gone");
    (*it)->xTabPage->Reset(&aTmpSet);
    (*it)->xTabPage->mpImpl->mbStandard = true;
}

IMPL_LINK(SfxTabDialogController, ActivatePageHdl, const OUString&, rPage, void)
{
    ActivatePage(rPage);
}

void SfxTabDialogController::ActivatePage(const OUString& rPage)
/*  [Description]

    Handler that is called by StarView for switching to a different page.
    If possible the <SfxTabPage::Reset(const SfxItemSet &)> or
    <SfxTabPage::ActivatePage(const SfxItemSet &)> is called on the new page
*/

{
    assert(!m_pImpl->aData.empty() && "no Pages registered");
    auto it = Find(m_pImpl->aData, rPage);
    if (it == m_pImpl->aData.end())
    {
        SAL_WARN("sfx.dialog", "Tab Page ID '" << rPage << "' not known, this is pretty serious and needs investigation");
        return;
    }

    SfxTabPage* pTabPage = (*it)->xTabPage.get();
    if (!pTabPage)
        return;

    if ((*it)->bRefresh)
        pTabPage->Reset(m_pSet.get());
    (*it)->bRefresh = false;

    if (m_xExampleSet)
        pTabPage->ActivatePage(*m_xExampleSet);

    if (pTabPage->IsReadOnly() || m_pImpl->bHideResetBtn)
        m_xResetBtn->hide();
    else
        m_xResetBtn->show();
}

IMPL_LINK(SfxTabDialogController, DeactivatePageHdl, const OUString&, rPage, bool)
{
    return DeactivatePage(rPage);
}

bool SfxTabDialogController::DeactivatePage(std::u16string_view aPage)
/*  [Description]

    Handler that is called by StarView before leaving a page.

    [Cross-reference]

    <SfxTabPage::DeactivatePage(SfxItemSet *)>
*/

{
    assert(!m_pImpl->aData.empty() && "no Pages registered");
    auto it = Find(m_pImpl->aData, aPage);
    if (it == m_pImpl->aData.end())
    {
        SAL_WARN("sfx.dialog", "Tab Page ID not known, this is pretty serious and needs investigation");
        return false;
    }

    SfxTabPage* pPage = (*it)->xTabPage.get();
    if (!pPage)
        return true;

    DeactivateRC nRet = DeactivateRC::LeavePage;

    if (!m_xExampleSet && pPage->HasExchangeSupport() && m_pSet)
        m_xExampleSet.reset(new SfxItemSet(*m_pSet->GetPool(), m_pSet->GetRanges()));

    if (m_pSet)
    {
        SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );

        if (pPage->HasExchangeSupport())
            nRet = pPage->DeactivatePage(&aTmp);
        else
            nRet = pPage->DeactivatePage(nullptr);
        if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage &&
             aTmp.Count() && m_xExampleSet)
        {
            m_xExampleSet->Put( aTmp );
            m_pOutSet->Put( aTmp );
        }
    }
    else
    {
        if ( pPage->HasExchangeSupport() ) //!!!
        {
            if (!m_xExampleSet)
            {
                SfxItemPool* pPool = pPage->GetItemSet().GetPool();
                m_xExampleSet.reset(new SfxItemSet(*pPool, GetInputRanges(*pPool)));
            }
            nRet = pPage->DeactivatePage(m_xExampleSet.get());
        }
        else
            nRet = pPage->DeactivatePage( nullptr );
    }

    if ( nRet & DeactivateRC::RefreshSet )
    {
        RefreshInputSet();
        // Flag all Pages as to be initialized as new

        for (auto const& elem : m_pImpl->aData)
        {
            elem->bRefresh = ( elem->xTabPage.get() != pPage ); // Do not refresh own Page anymore
        }
    }
    return static_cast<bool>(nRet & DeactivateRC::LeavePage);
}

bool SfxTabDialogController::PrepareLeaveCurrentPage()
{
    const OUString sId = m_xTabCtrl->get_current_page_ident();
    auto it = Find(m_pImpl->aData, sId);
    DBG_ASSERT(it != m_pImpl->aData.end(), "Id not known");
    SfxTabPage* pPage = it != m_pImpl->aData.end() ? (*it)->xTabPage.get() : nullptr;

    bool bEnd = !pPage;

    if ( pPage )
    {
        DeactivateRC nRet = DeactivateRC::LeavePage;
        if ( m_pSet )
        {
            SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );

            if ( pPage->HasExchangeSupport() )
                nRet = pPage->DeactivatePage( &aTmp );
            else
                nRet = pPage->DeactivatePage( nullptr );

            if ( ( DeactivateRC::LeavePage & nRet ) == DeactivateRC::LeavePage
                 && aTmp.Count() )
            {
                m_xExampleSet->Put( aTmp );
                m_pOutSet->Put( aTmp );
            }
        }
        else
            nRet = pPage->DeactivatePage( nullptr );
        bEnd = nRet != DeactivateRC::KeepPage;
    }

    return bEnd;
}

void SfxTabDialogController::PrepareCancel()
{
    for (auto pDataObject : m_pImpl->aData)
    {
        if (!pDataObject->xTabPage)
            continue;

        SfxTabPage* pPage = pDataObject->xTabPage.get();
        pPage->SetCancelMode(true);
        pPage->DeactivatePage(nullptr);
    }
}

const WhichRangesContainer & SfxTabDialogController::GetInputRanges(const SfxItemPool& rPool)

/*  [Description]

    Makes the set over the range of all pages of the dialogue. Pages have the
    static method for querying their range in AddTabPage, ie deliver their
    sets onDemand.

    [Return value]

    Pointer to a null-terminated array of sal_uInt16. This array belongs to the
    dialog and is deleted when the dialogue is destroy.

    [Cross-reference]

    <SfxTabDialog::AddTabPage(sal_uInt16, CreateTabPage, GetTabPageRanges, bool)>
    <SfxTabDialog::AddTabPage(sal_uInt16, const String &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)>
    <SfxTabDialog::AddTabPage(sal_uInt16, const Bitmap &, CreateTabPage, GetTabPageRanges, bool, sal_uInt16)>
*/

{
    if ( m_pSet )
    {
        SAL_WARN( "sfx.dialog", "Set already exists!" );
        return m_pSet->GetRanges();
    }

    if ( !m_pRanges.empty() )
        return m_pRanges;
    SfxItemSet aUS(const_cast<SfxItemPool&>(rPool));

    for (auto const& elem : m_pImpl->aData)
    {

        if ( elem->fnGetRanges )
        {
            const WhichRangesContainer aTmpRanges = (elem->fnGetRanges)();

            for (const auto & rPair : aTmpRanges)
            {
                sal_uInt16 nWidFrom = rPool.GetWhichIDFromSlotID(rPair.first);
                sal_uInt16 nWidTo = rPool.GetWhichIDFromSlotID(rPair.second);
                aUS.MergeRange(nWidFrom, nWidTo); // Keep it valid
            }
        }
    }

    m_pRanges = aUS.GetRanges();
    return m_pRanges;
}

SfxTabDialogController::~SfxTabDialogController()
{
    SavePosAndId();

    for (auto & elem : m_pImpl->aData)
    {
        if ( elem->xTabPage )
        {
            // save settings of all pages (user data)
            elem->xTabPage->FillUserData();
            OUString aPageData( elem->xTabPage->GetUserData() );
            if ( !aPageData.isEmpty() )
            {
                // save settings of all pages (user data)
                SvtViewOptions aPageOpt(EViewType::TabPage, elem->xTabPage->GetConfigId());
                aPageOpt.SetUserItem( USERITEM_NAME, Any( aPageData ) );
            }

            elem->xTabPage.reset();
        }
        delete elem;
        elem = nullptr;
    }
}

short SfxTabDialogController::Ok()

/*  [Description]

    Ok handler for the Dialogue.

    Dialog's current location and current page are saved for the next time
    the dialog is shown.

    The OutputSet is created and for each page this or the special OutputSet
    is set by calling the method <SfxTabPage::FillItemSet(SfxItemSet &)>, to
    insert the entered data by the user into the set.

    [Return value]

    RET_OK:       if at least one page has returned from FillItemSet,
                  otherwise RET_CANCEL.
*/
{
    SavePosAndId(); //See fdo#38828 "Apply" resetting window position

    if ( !m_pOutSet )
    {
        if ( m_xExampleSet )
            m_pOutSet.reset(new SfxItemSet( *m_xExampleSet ));
        else if ( m_pSet )
            m_pOutSet = m_pSet->Clone( false );  // without Items
    }
    bool bModified = false;

    for (auto const& elem : m_pImpl->aData)
    {
        SfxTabPage* pTabPage = elem->xTabPage.get();

        if ( pTabPage )
        {
            if ( m_pSet && !pTabPage->HasExchangeSupport() )
            {
                SfxItemSet aTmp( *m_pSet->GetPool(), m_pSet->GetRanges() );

                if ( pTabPage->FillItemSet( &aTmp ) )
                {
                    bModified = true;
                    if (m_xExampleSet)
                        m_xExampleSet->Put( aTmp );
                    m_pOutSet->Put( aTmp );
                }
            }
        }
    }

    if (m_pOutSet && m_pOutSet->Count() > 0)
        bModified = true;

    if (m_bStandardPushed)
        bModified = true;

    return bModified ? RET_OK : RET_CANCEL;
}

void SfxTabDialogController::RefreshInputSet()

/*  [Description]

    Default implementation of the virtual Method.
    This is called, when <SfxTabPage::DeactivatePage(SfxItemSet *)>
    returns <DeactivateRC::RefreshSet>.
*/

{
    SAL_INFO ( "sfx.dialog", "RefreshInputSet not implemented" );
}

void SfxTabDialogController::PageCreated

/*  [Description]

    Default implementation of the virtual method. This is called immediately
    after creating a page. Here the dialogue can call the TabPage Method
    directly.
*/

(
    const OUString&, // Id of the created page
    SfxTabPage&     // Reference to the created page
)
{
}

void SfxTabDialogController::SavePosAndId()
{
    // save settings (screen position and current page)
    SvtViewOptions aDlgOpt(EViewType::TabDialog, m_xDialog->get_help_id());
    aDlgOpt.SetPageID(m_xTabCtrl->get_current_page_ident());
}

/*
    Adds a page to the dialog. The Name must correspond to an entry in the
    TabControl in the dialog .ui
*/
void SfxTabDialogController::AddTabPage(const OUString &rName /* Page ID */,
                                        CreateTabPage pCreateFunc  /* Pointer to the Factory Method */,
                                        GetTabPageRanges pRangesFunc /* Pointer to the Method for querying Ranges onDemand */)
{
    m_pImpl->aData.push_back(new Data_Impl(rName, pCreateFunc, pRangesFunc));
}

void SfxTabDialogController::AddTabPage(const OUString &rName /* Page ID */,
                                        sal_uInt16 nPageCreateId /* Identifier of the Factory Method to create the page */)
{
    SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create();
    CreateTabPage pCreateFunc = pFact->GetTabPageCreatorFunc(nPageCreateId);
    GetTabPageRanges pRangesFunc = pFact->GetTabPageRangesFunc(nPageCreateId);
    AddTabPage(rName, pCreateFunc, pRangesFunc);
}

/*  [Description]

    Add a page to the dialog. The Rider text is passed on, the page has no
    counterpart in the TabControl in the resource of the dialogue.
*/

void SfxTabDialogController::AddTabPage(const OUString &rName, /* Page ID */
                                        const OUString& rRiderText,
                                        CreateTabPage pCreateFunc, /* Pointer to the Factory Method */
                                        const OUString* pIconName)
{
    assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
    AddTabPage(rName, pCreateFunc, nullptr);
    m_xTabCtrl->append_page(rName, rRiderText, pIconName);
}

void SfxTabDialogController::AddTabPage(const OUString &rName, /* Page ID */
                                        const OUString& rRiderText,
                                        CreateTabPage pCreateFunc, /* Pointer to the Factory Method */
                                        GetTabPageRanges pRangesFunc,
                                        const OUString& rIconName)
{
    assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
    AddTabPage(rName, pCreateFunc, pRangesFunc);
    m_xTabCtrl->append_page(rName, rRiderText, &rIconName);
}

void SfxTabDialogController::AddTabPage(const OUString &rName,
                                        const OUString& rRiderText,
                                        CreateTabPage pCreateFunc,
                                        const OUString& rIconName)
{
    AddTabPage(rName, rRiderText, pCreateFunc, &rIconName);
}

void SfxTabDialogController::AddTabPage(const OUString &rName, const OUString& rRiderText,
                                        sal_uInt16 nPageCreateId, /* Identifier of the Factory Method to create the page */
                                        const OUString* pIconName)
{
    assert(!m_xTabCtrl->get_page(rName) && "Double Page-Ids in the Tabpage");
    AddTabPage(rName, nPageCreateId);
    m_xTabCtrl->append_page(rName, rRiderText, pIconName);
}

void SfxTabDialogController::AddTabPage(const OUString &rName, const OUString& rRiderText,
                                        sal_uInt16 nPageCreateId,
                                        const OUString& rIconName)
{
    AddTabPage(rName, rRiderText, nPageCreateId, &rIconName);
}

/*  [Description]

    Default implementation of the virtual Method.
    This is called when pages create their sets onDemand.
*/
SfxItemSet* SfxTabDialogController::CreateInputItemSet(const OUString&)
{
    SAL_WARN( "sfx.dialog", "CreateInputItemSet not implemented" );
    m_xItemSet = std::make_unique<SfxAllItemSet>(SfxGetpApp()->GetPool());
    return m_xItemSet.get();
}

void SfxTabDialogController::CreatePages()
{
    for (auto pDataObject : m_pImpl->aData)
    {
        if (pDataObject->xTabPage)
           continue;
        weld::Container* pPage = m_xTabCtrl->get_page(pDataObject->sId);
        if (m_pSet)
            pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, m_pSet.get());
        else
            pDataObject->xTabPage = (pDataObject->fnCreatePage)(pPage, this, CreateInputItemSet(pDataObject->sId));
        pDataObject->xTabPage->SetDialogController(this);
        SvtViewOptions aPageOpt(EViewType::TabPage, pDataObject->xTabPage->GetConfigId());
        OUString sUserData;
        Any aUserItem = aPageOpt.GetUserItem(USERITEM_NAME);
        OUString aTemp;
        if ( aUserItem >>= aTemp )
            sUserData = aTemp;
        pDataObject->xTabPage->SetUserData(sUserData);

        PageCreated(pDataObject->sId, *pDataObject->xTabPage);
        if (pDataObject->xTabPage->DeferResetToFirstActivation())
            pDataObject->bRefresh = true; // Reset will be called in ActivatePageHdl
        else
            pDataObject->xTabPage->Reset(m_pSet.get());
    }
}

void SfxTabDialogController::setPreviewsToSamePlace()
{
    //where tab pages have the same basic layout with a preview on the right,
    //get both of their non-preview areas to request the same size so that the
    //preview appears in the same place in each one so flipping between tabs
    //isn't distracting as it jumps around
    std::vector<std::unique_ptr<weld::Widget>> aGrids;
    for (auto pDataObject : m_pImpl->aData)
    {
        if (!pDataObject->xTabPage)
            continue;
        if (!pDataObject->xTabPage->m_xBuilder)
            continue;
        std::unique_ptr<weld::Widget> pGrid = pDataObject->xTabPage->m_xBuilder->weld_widget(u"maingrid"_ustr);
        if (!pGrid)
            continue;
        aGrids.emplace_back(std::move(pGrid));
    }

    m_xSizeGroup.reset();

    if (aGrids.size() <= 1)
        return;

    m_xSizeGroup = m_xBuilder->create_size_group();
    m_xSizeGroup->set_mode(VclSizeGroupMode::Both);
    for (auto& rGrid : aGrids)
        m_xSizeGroup->add_widget(rGrid.get());
}

void SfxTabDialogController::RemoveTabPage(const OUString& rId)

/*  [Description]

    Delete the TabPage with ID nId
*/

{
    m_xTabCtrl->remove_page(rId);
    auto it = Find(m_pImpl->aData, rId);

    if (it != m_pImpl->aData.end())
    {
        if ((*it)->xTabPage)
        {
            (*it)->xTabPage->FillUserData();
            OUString aPageData((*it)->xTabPage->GetUserData());
            if ( !aPageData.isEmpty() )
            {
                // save settings of this page (user data)
                SvtViewOptions aPageOpt(EViewType::TabPage, (*it)->xTabPage->GetConfigId());
                aPageOpt.SetUserItem( USERITEM_NAME, Any( aPageData ) );
            }

            (*it)->xTabPage.reset();
        }

        delete *it;
        m_pImpl->aData.erase(it);
    }
    else
    {
        SAL_INFO( "sfx.dialog", "TabPage-Id not known" );
    }
}

void SfxTabDialogController::Start_Impl()
{
    CreatePages();

    setPreviewsToSamePlace();

    assert(m_pImpl->aData.size() == static_cast<size_t>(m_xTabCtrl->get_n_pages())
            && "not all pages registered");

    // load old settings, when exists, setting SetCurPageId will override the settings,
    // something that the sort dialog in calc depends on
    if (m_sAppPageId.isEmpty())
    {
        int nInitialPage = 0;
        SvtViewOptions aDlgOpt(EViewType::TabDialog, m_xDialog->get_help_id());
        if (aDlgOpt.Exists())
        {
            const int nIndex = m_xTabCtrl->get_page_index(aDlgOpt.GetPageID());
            if (nIndex >= 0)
                nInitialPage = nIndex;
        }
        m_xTabCtrl->set_current_page(nInitialPage);
    }

    ActivatePage(m_xTabCtrl->get_current_page_ident());

    m_pImpl->bStarted = true;
}

void SfxTabDialogController::SetCurPageId(const OUString& rIdent)
{
    m_sAppPageId = rIdent;
    m_xTabCtrl->set_current_page(m_sAppPageId);
}

/*  [Description]

    The TabPage is activated with the specified Id.
*/
void SfxTabDialogController::ShowPage(const OUString& rIdent)
{
    SetCurPageId(rIdent);
    ActivatePage(rIdent);
}

OUString SfxTabDialogController::GetCurPageId() const
{
    return m_xTabCtrl->get_current_page_ident();
}

short SfxTabDialogController::run()
{
    Start_Impl();
    return SfxDialogController::run();
}

bool SfxTabDialogController::runAsync(const std::shared_ptr<SfxTabDialogController>& rController,
                                      const std::function<void(sal_Int32)>& rFunc)
{
    rController->Start_Impl();
    return weld::DialogController::runAsync(rController, rFunc);
}

void SfxTabDialogController::SetInputSet( const SfxItemSet* pInSet )

/*  [Description]

    With this method the Input-Set can subsequently be set initially or re-set.
*/

{
    bool bSet = ( m_pSet != nullptr );
    m_pSet.reset(pInSet ? new SfxItemSet(*pInSet) : nullptr);

    if (!bSet && !m_xExampleSet && !m_pOutSet && m_pSet)
    {
        m_xExampleSet.reset(new SfxItemSet(*m_pSet));
        m_pOutSet.reset(new SfxItemSet( *m_pSet->GetPool(), m_pSet->GetRanges() ));
    }
}

SfxItemSet* SfxTabDialogController::GetInputSetImpl()

/*  [Description]

    Derived classes may create new storage for the InputSet. This has to be
    released in the Destructor. To do this, this method must be called.
*/

{
    return m_pSet.get();
}

void SfxTabDialogController::RemoveResetButton()
{
    m_xResetBtn->hide();
    m_pImpl->bHideResetBtn = true;
}

void SfxTabDialogController::RemoveStandardButton()
{
    m_xBaseFmtBtn->hide();
}

SfxTabPage* SfxTabDialogController::GetTabPage(std::u16string_view rPageId) const

/*  [Description]

    Return TabPage with the specified Id.
*/

{
    auto it = Find(m_pImpl->aData, rPageId);
    if (it != m_pImpl->aData.end())
        return (*it)->xTabPage.get();
    return nullptr;
}

void SfxTabDialogController::SetApplyHandler(const Link<weld::Button&, void>& _rHdl)
{
    DBG_ASSERT( m_xApplyBtn, "SfxTabDialog::GetApplyHandler: no apply button enabled!" );
    if (m_xApplyBtn)
        m_xApplyBtn->connect_clicked(_rHdl);
}

bool SfxTabDialogController::Apply()
{
    bool bApplied = false;
    if (PrepareLeaveCurrentPage())
    {
        bApplied = (Ok() == RET_OK);
        //let the pages update their saved values
        GetInputSetImpl()->Put(*GetOutputItemSet());
        for (auto pDataObject : m_pImpl->aData)
        {
            if (!pDataObject->xTabPage)
                continue;
            pDataObject->xTabPage->ChangesApplied();
        }
    }
    return bApplied;
}

std::vector<OUString> SfxTabDialogController::getAllPageUIXMLDescriptions() const
{
    int nPages = m_xTabCtrl->get_n_pages();
    std::vector<OUString> aRet;
    aRet.reserve(nPages);
    for (int i = 0; i < nPages; ++i)
        aRet.push_back(m_xTabCtrl->get_page_ident(i));
    return aRet;
}

bool SfxTabDialogController::selectPageByUIXMLDescription(const OUString& rUIXMLDescription)
{
    ShowPage(rUIXMLDescription);
    return m_xTabCtrl->get_current_page_ident() == rUIXMLDescription;
}

Bitmap SfxTabDialogController::createScreenshot() const
{
    // if we haven't run Start_Impl yet, do so now to create the initial pages
    if (!m_pImpl->bStarted)
    {
        const_cast<SfxTabDialogController*>(this)->Start_Impl();
    }

    VclPtr<VirtualDevice> xDialogSurface(m_xDialog->screenshot());
    return xDialogSurface->GetBitmap(Point(), xDialogSurface->GetOutputSizePixel());
}

OUString SfxTabDialogController::GetScreenshotId() const
{
    const OUString sId = m_xTabCtrl->get_current_page_ident();
    auto it = Find(m_pImpl->aData, sId);
    SfxTabPage* pPage = it != m_pImpl->aData.end() ? (*it)->xTabPage.get() : nullptr;
    if (pPage)
    {
        OUString sHelpId(pPage->GetHelpId());
        if (!sHelpId.isEmpty())
            return sHelpId;
    }
    return m_xDialog->get_help_id();
}

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