/* -*- 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 <sfx2/sidebar/SidebarController.hxx>

#include <boost/property_tree/json_parser.hpp>

#include <sfx2/sidebar/Deck.hxx>
#include <sidebar/DeckDescriptor.hxx>
#include <sidebar/DeckTitleBar.hxx>
#include <sfx2/sidebar/Panel.hxx>
#include <sidebar/PanelDescriptor.hxx>
#include <sidebar/PanelTitleBar.hxx>
#include <sfx2/sidebar/TabBar.hxx>
#include <sfx2/sidebar/Theme.hxx>
#include <sfx2/sidebar/SidebarChildWindow.hxx>
#include <sidebar/Tools.hxx>
#include <sfx2/sidebar/SidebarDockingWindow.hxx>
#include <com/sun/star/ui/XSidebarProvider.hpp>
#include <com/sun/star/frame/XController2.hpp>
#include <sfx2/sidebar/Context.hxx>
#include <sfx2/viewsh.hxx>


#include <framework/ContextChangeEventMultiplexerTunnel.hxx>
#include <vcl/EnumContext.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <vcl/svapp.hxx>
#include <splitwin.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <tools/json_writer.hxx>
#include <tools/link.hxx>
#include <toolkit/helper/vclunohelper.hxx>
#include <comphelper/processfactory.hxx>
#include <comphelper/namedvaluecollection.hxx>
#include <comphelper/lok.hxx>
#include <sal/log.hxx>
#include <officecfg/Office/UI/Sidebar.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <o3tl/string_view.hxx>

#include <com/sun/star/awt/XWindowPeer.hpp>
#include <com/sun/star/frame/XDispatch.hpp>
#include <com/sun/star/ui/ContextChangeEventMultiplexer.hpp>
#include <com/sun/star/ui/ContextChangeEventObject.hpp>
#include <com/sun/star/ui/theUIElementFactoryManager.hpp>
#include <com/sun/star/util/URL.hpp>
#include <com/sun/star/rendering/XSpriteCanvas.hpp>

#include <bitmaps.hlst>

using namespace css;
using namespace css::uno;

namespace
{
    constexpr OUString gsReadOnlyCommandName = u".uno:EditDoc"_ustr;
    const sal_Int32 gnWidthCloseThreshold (70);
    const sal_Int32 gnWidthOpenThreshold (40);

    std::string UnoNameFromDeckId(std::u16string_view rsDeckId, const sfx2::sidebar::Context& context)
    {
        if (rsDeckId == u"SdCustomAnimationDeck")
            return ".uno:CustomAnimation";

        if (rsDeckId == u"PropertyDeck")
            return vcl::EnumContext::Application::Impress == vcl::EnumContext::GetApplicationEnum(context.msApplication) ? ".uno:ModifyPage" : ".uno:Sidebar";

        if (rsDeckId == u"SdLayoutsDeck")
            return ".uno:ModifyPage";

        if (rsDeckId == u"SdSlideTransitionDeck")
            return ".uno:SlideChangeWindow";

        if (rsDeckId == u"SdAllMasterPagesDeck")
            return ".uno:MasterSlidesPanel";

        if (rsDeckId == u"SdMasterPagesDeck")
            return ".uno:MasterSlidesPanel";

        if (rsDeckId == u"GalleryDeck")
            return ".uno:Gallery";

        OString sUno = ".uno:SidebarDeck." + OUStringToOString(rsDeckId, RTL_TEXTENCODING_ASCII_US);
        return std::string(sUno);
    }
}

namespace sfx2::sidebar {

namespace {

    /** When in doubt, show this deck.
    */
    constexpr OUString gsDefaultDeckId(u"PropertyDeck"_ustr);
}

SidebarController::SidebarController (
    SidebarDockingWindow* pParentWindow,
    const SfxViewFrame* pViewFrame)
    : mpParentWindow(pParentWindow),
      mpViewFrame(pViewFrame),
      mxFrame(pViewFrame->GetFrame().GetFrameInterface()),
      mpTabBar(VclPtr<TabBar>::Create(
              mpParentWindow,
              mxFrame,
              [this](const OUString& rsDeckId) { return this->OpenThenToggleDeck(rsDeckId); },
              [this](weld::Menu& rMainMenu, weld::Menu& rSubMenu) { return this->ConnectMenuActivateHandlers(rMainMenu, rSubMenu); },
              *this)),
      maCurrentContext(OUString(), OUString()),
      maRequestedContext(OUString(), OUString()),
      mnRequestedForceFlags(SwitchFlag_NoForce),
      mbMinimumSidebarWidth(officecfg::Office::UI::Sidebar::General::MinimumWidth::get()),
      msCurrentDeckId(gsDefaultDeckId),
      maPropertyChangeForwarder(mpViewFrame, [this](){ return this->BroadcastPropertyChange(); }),
      maContextChangeUpdate(mpViewFrame, [this](){ return this->UpdateConfigurations(); }),
      mbFloatingDeckClosed(!pParentWindow->IsFloatingMode()),
      mnSavedSidebarWidth(pParentWindow->GetSizePixel().Width()),
      maFocusManager([this](const Panel& rPanel){ return this->ShowPanel(rPanel); }),
      mbIsDocumentReadOnly(false),
      mpSplitWindow(nullptr),
      mnWidthOnSplitterButtonDown(0)
{
    mnMaximumSidebarWidth = officecfg::Office::UI::Sidebar::General::MaximumWidth::get() * mpTabBar->GetDPIScaleFactor();
    // Decks and panel collections for this sidebar
    mpResourceManager = std::make_unique<ResourceManager>();
}

rtl::Reference<SidebarController> SidebarController::create(SidebarDockingWindow* pParentWindow,
                                                            const SfxViewFrame* pViewFrame)
{
    rtl::Reference<SidebarController> instance(new SidebarController(pParentWindow, pViewFrame));

    const css::uno::Reference<css::frame::XFrame>& rxFrame = pViewFrame->GetFrame().GetFrameInterface();
    instance->registerSidebarForFrame(rxFrame->getController());
    rxFrame->addFrameActionListener(instance);
    // Listen for window events.
    instance->mpParentWindow->AddEventListener(LINK(instance.get(), SidebarController, WindowEventHandler));

    // Listen for theme property changes.
    instance->mxThemePropertySet = Theme::GetPropertySet();
    instance->mxThemePropertySet->addPropertyChangeListener(
        u""_ustr,
        static_cast<css::beans::XPropertyChangeListener*>(instance.get()));

    // Get the dispatch object as preparation to listen for changes of
    // the read-only state.
    const util::URL aURL (Tools::GetURL(gsReadOnlyCommandName));
    instance->mxReadOnlyModeDispatch = Tools::GetDispatch(rxFrame, aURL);
    if (instance->mxReadOnlyModeDispatch.is())
        instance->mxReadOnlyModeDispatch->addStatusListener(instance, aURL);

    //first UpdateConfigurations call will SwitchToDeck

    return instance;
}

SidebarController::~SidebarController()
{
}

SidebarController* SidebarController::GetSidebarControllerForFrame (
    const css::uno::Reference<css::frame::XFrame>& rxFrame)
{
    uno::Reference<frame::XController> const xController(rxFrame->getController());
    if (!xController.is()) // this may happen during dispose of Draw controller but perhaps it's a bug
    {
        SAL_WARN("sfx.sidebar", "GetSidebarControllerForFrame: frame has no XController");
        return nullptr;
    }
    uno::Reference<ui::XContextChangeEventListener> const xListener(
        framework::GetFirstListenerWith(
            ::comphelper::getProcessComponentContext(),
            xController,
            [] (uno::Reference<uno::XInterface> const& xRef)
            { return nullptr != dynamic_cast<SidebarController*>(xRef.get()); }
        ));

    return dynamic_cast<SidebarController*>(xListener.get());
}

void SidebarController::registerSidebarForFrame(const css::uno::Reference<css::frame::XController>& xController)
{
    // Listen for context change events.
    css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
        css::ui::ContextChangeEventMultiplexer::get(
            ::comphelper::getProcessComponentContext()));
    xMultiplexer->addContextChangeEventListener(
        static_cast<css::ui::XContextChangeEventListener*>(this),
        xController);
}

void SidebarController::unregisterSidebarForFrame(const css::uno::Reference<css::frame::XController>& xController)
{
    saveDeckState();
    disposeDecks();

    css::uno::Reference<css::ui::XContextChangeEventMultiplexer> xMultiplexer (
        css::ui::ContextChangeEventMultiplexer::get(
            ::comphelper::getProcessComponentContext()));
    xMultiplexer->removeContextChangeEventListener(
        static_cast<css::ui::XContextChangeEventListener*>(this),
        xController);
}

void SidebarController::disposeDecks()
{
    SolarMutexGuard aSolarMutexGuard;

    if (comphelper::LibreOfficeKit::isActive())
    {
        if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
        {
            const std::string hide = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
            if (!hide.empty())
            {
                // Be consistent with SwitchToDeck(), so both places emit JSON.
                boost::property_tree::ptree aTree;
                aTree.put("commandName", hide);
                aTree.put("state", "false");
                std::stringstream aStream;
                boost::property_tree::write_json(aStream, aTree);
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
                                                       OString(aStream.str()));
            }
        }

        if (mpParentWindow)
            mpParentWindow->ReleaseLOKNotifier();
    }

    mpCurrentDeck.reset();
    maFocusManager.Clear();
    mpResourceManager->disposeDecks();
}

namespace
{
    class CloseIndicator final : public InterimItemWindow
    {
    public:
        CloseIndicator(vcl::Window* pParent)
            : InterimItemWindow(pParent, u"svt/ui/fixedimagecontrol.ui"_ustr, u"FixedImageControl"_ustr)
            , m_xWidget(m_xBuilder->weld_image(u"image"_ustr))
        {
            InitControlBase(m_xWidget.get());

            m_xWidget->set_from_icon_name(SIDEBAR_CLOSE_INDICATOR);

            SetSizePixel(get_preferred_size());

            SetBackground(Theme::GetColor(Theme::Color_DeckBackground));
        }

        virtual ~CloseIndicator() override
        {
            disposeOnce();
        }

        virtual void dispose() override
        {
            m_xWidget.reset();
            InterimItemWindow::dispose();
        }

    private:
        std::unique_ptr<weld::Image> m_xWidget;
    };
}

void SidebarController::disposing(std::unique_lock<std::mutex>&)
{
    SolarMutexGuard aSolarMutexGuard;

    mpCloseIndicator.disposeAndClear();

    maFocusManager.Clear();
    mpTabBar.disposeAndClear();

    saveDeckState();

    // clear decks
    ResourceManager::DeckContextDescriptorContainer aDecks;

    mpResourceManager->GetMatchingDecks (
            aDecks,
            GetCurrentContext(),
            IsDocumentReadOnly(),
            mxFrame->getController());

    for (const auto& rDeck : aDecks)
    {
        std::shared_ptr<DeckDescriptor> deckDesc = mpResourceManager->GetDeckDescriptor(rDeck.msId);

        VclPtr<Deck> aDeck = deckDesc->mpDeck;
        if (aDeck)
            aDeck.disposeAndClear();
    }

    maContextChangeUpdate.CancelRequest();

    if (mxReadOnlyModeDispatch.is())
        mxReadOnlyModeDispatch->removeStatusListener(this, Tools::GetURL(gsReadOnlyCommandName));

    if (mxThemePropertySet.is())
        mxThemePropertySet->removePropertyChangeListener(
            u""_ustr,
            static_cast<css::beans::XPropertyChangeListener*>(this));

    if (mpParentWindow != nullptr)
    {
        mpParentWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
        mpParentWindow = nullptr;
    }

    if (mpSplitWindow != nullptr)
    {
        mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));
        mpSplitWindow = nullptr;
    }

    mxFrame->removeFrameActionListener(this);

    uno::Reference<css::frame::XController> xController = mxFrame->getController();
    if (!xController.is())
        xController = mxCurrentController;

    unregisterSidebarForFrame(xController);
}

void SAL_CALL SidebarController::notifyContextChangeEvent (const css::ui::ContextChangeEventObject& rEvent)
{
    SolarMutexGuard aSolarMutexGuard;

    // Update to the requested new context asynchronously to avoid
    // subtle errors caused by SFX2 which in rare cases can not
    // properly handle a synchronous update.

    maRequestedContext = Context(
        rEvent.ApplicationName,
        rEvent.ContextName);

    if (maRequestedContext != maCurrentContext)
    {
        mxCurrentController.set(rEvent.Source, css::uno::UNO_QUERY);
        maContextChangeUpdate.RequestCall(); // async call, not a prob
                                             // calling with held
                                             // solarmutex

        bool bSwitchedApp = maRequestedContext.msApplication != maCurrentContext.msApplication;
        // Happens on reattach of sidebar to frame or context change
        // LOK performance impact: prevents to switch sidebar on every keypress in multi user case
        // Allow when enters embedded OLE (eg. Math formula editor second time)
        if (!comphelper::LibreOfficeKit::isActive() || bSwitchedApp)
            UpdateConfigurations();
    }
}

void SAL_CALL SidebarController::disposing (const css::lang::EventObject& )
{
    dispose();
}

void SAL_CALL SidebarController::propertyChange (const css::beans::PropertyChangeEvent& )
{
    SolarMutexGuard aSolarMutexGuard;

    maPropertyChangeForwarder.RequestCall(); // async call, not a prob
                                             // to call with held
                                             // solarmutex
}

void SAL_CALL SidebarController::statusChanged (const css::frame::FeatureStateEvent& rEvent)
{
    SolarMutexGuard aSolarMutexGuard;

    bool bIsReadWrite (true);
    if (rEvent.IsEnabled)
        rEvent.State >>= bIsReadWrite;

    if (mbIsDocumentReadOnly != !bIsReadWrite)
    {
        mbIsDocumentReadOnly = !bIsReadWrite;

        // Force the current deck to update its panel list.
        if ( ! mbIsDocumentReadOnly)
            SwitchToDefaultDeck();

        mnRequestedForceFlags |= SwitchFlag_ForceSwitch;
        maContextChangeUpdate.RequestCall(); // async call, ok to call
                                             // with held solarmutex
    }
}

void SAL_CALL SidebarController::requestLayout()
{
    SolarMutexGuard aSolarMutexGuard;

    sal_Int32 nMinimalWidth = 0;
    if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
    {
        mpCurrentDeck->RequestLayout();
        nMinimalWidth = mbMinimumSidebarWidth ? mpCurrentDeck->GetMinimalWidth() : 0;
    }
    RestrictWidth(nMinimalWidth);
}

void SidebarController::BroadcastPropertyChange()
{
    mpParentWindow->Invalidate(InvalidateFlags::Children);
}

void SidebarController::NotifyResize()
{
    if (!mpTabBar)
    {
        OSL_ASSERT(mpTabBar!=nullptr);
        return;
    }

    const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();

    const sal_Int32 nWidth(mpParentWindow->GetSizePixel().Width());
    const sal_Int32 nHeight(mpParentWindow->GetSizePixel().Height());

    mbIsDeckOpen = (nWidth > nTabBarDefaultWidth);

    if (mnSavedSidebarWidth <= 0)
        mnSavedSidebarWidth = nWidth;

    bool bIsDeckVisible;
    const bool bIsOpening (nWidth > mnWidthOnSplitterButtonDown);
    if (bIsOpening)
        bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthOpenThreshold;
    else
        bIsDeckVisible = nWidth >= nTabBarDefaultWidth + gnWidthCloseThreshold;
    mbIsDeckRequestedOpen = bIsDeckVisible;
    UpdateCloseIndicator(!bIsDeckVisible);

    if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
    {
        SfxSplitWindow* pSplitWindow = GetSplitWindow();
        WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
        tools::Long nDeckX, nTabX;
        if (eAlign == WindowAlign::Left)     // attach the Sidebar towards the left-side of screen
        {
            nDeckX = nTabBarDefaultWidth;
            nTabX = 0;
        }
        else   // attach the Sidebar towards the right-side of screen
        {
            nDeckX = 0;
            nTabX = nWidth - nTabBarDefaultWidth;
        }

        // Place the deck first.
        if (bIsDeckVisible)
        {
            if (comphelper::LibreOfficeKit::isActive())
            {
                // We want to let the layouter use up as much of the
                // height as necessary to make sure no scrollbar is
                // visible. This only works when there are no greedy
                // panes that fill up all available area. So we only
                // use this for the PropertyDeck, which has no such
                // panes, while most other do. This is fine, since
                // it's the PropertyDeck that really has many panes
                // that can collapse or expand. For others, limit
                // the height to something sensible.
                const sal_Int32 nExtHeight = (msCurrentDeckId == "PropertyDeck" ? 2000 : 600);
                // No TabBar in LOK (use nWidth in full).
                mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth, nExtHeight);
            }
            else
                mpCurrentDeck->setPosSizePixel(nDeckX, 0, nWidth - nTabBarDefaultWidth, nHeight);
            mpCurrentDeck->Show();
            mpCurrentDeck->RequestLayout();
            mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
        }
        else
            mpCurrentDeck->Hide();

        // Now place the tab bar.
        mpTabBar->setPosSizePixel(nTabX, 0, nTabBarDefaultWidth, nHeight);
        if (!comphelper::LibreOfficeKit::isActive())
            mpTabBar->Show(); // Don't show TabBar in LOK.
    }

    // Determine if the closer of the deck can be shown.
    sal_Int32 nMinimalWidth = 0;
    if (mpCurrentDeck && !mpCurrentDeck->isDisposed())
    {
        DeckTitleBar* pTitleBar = mpCurrentDeck->GetTitleBar();
        if (pTitleBar && pTitleBar->GetVisible())
            pTitleBar->SetCloserVisible(CanModifyChildWindowWidth());
        nMinimalWidth = mbMinimumSidebarWidth ? mpCurrentDeck->GetMinimalWidth() : 0;
    }

    RestrictWidth(nMinimalWidth);
}

void SidebarController::ProcessNewWidth (const sal_Int32 nNewWidth)
{
    if ( ! mbIsDeckRequestedOpen.has_value())
        return;

    if (*mbIsDeckRequestedOpen)
    {
        // Deck became large enough to be shown.  Show it.
        mnSavedSidebarWidth = nNewWidth;
        // Store nNewWidth to mnWidthOnSplitterButtonDown when dragging sidebar Splitter
        mnWidthOnSplitterButtonDown = nNewWidth;
        if (!*mbIsDeckOpen)
            RequestOpenDeck();
    }
    else
    {
        // Deck became too small.  Close it completely.
        // If window is wider than the tab bar then mark the deck as being visible, even when it is not.
        // This is to trigger an adjustment of the width to the width of the tab bar.
        mbIsDeckOpen = true;
        RequestCloseDeck();

        if (mnWidthOnSplitterButtonDown > TabBar::GetDefaultWidth())
            mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
    }
}

void SidebarController::SyncUpdate()
{
    maPropertyChangeForwarder.Sync();
    maContextChangeUpdate.Sync();
}

void SidebarController::UpdateConfigurations()
{
    if (maCurrentContext == maRequestedContext
        && mnRequestedForceFlags == SwitchFlag_NoForce)
        return;

    bool bIsLOK = comphelper::LibreOfficeKit::isActive();

    if (!bIsLOK && maCurrentContext.msApplication != "none" &&
        !maCurrentContext.msApplication.isEmpty())
    {
        mpResourceManager->SaveDecksSettings(maCurrentContext);
        mpResourceManager->SetLastActiveDeck(maCurrentContext, msCurrentDeckId);
    }

    // get last active deck for this application on first update
    if (!maRequestedContext.msApplication.isEmpty() &&
        (maCurrentContext.msApplication != maRequestedContext.msApplication))
    {
        if (bIsLOK)
        {
            // LOK has no last-used memory
            const auto& rOverrides = mpResourceManager->GetDeckOverrides();
            const auto aOverride = rOverrides.find(maRequestedContext.msApplication);
            if (aOverride != rOverrides.end())
                msCurrentDeckId = aOverride->second;
        }
        else
        {
            OUString sLastActiveDeck = mpResourceManager->GetLastActiveDeck( maRequestedContext );
            if (!sLastActiveDeck.isEmpty())
                msCurrentDeckId = sLastActiveDeck;
        }
    }

    maCurrentContext = maRequestedContext;

    mpResourceManager->InitDeckContext(GetCurrentContext());

    // Find the set of decks that could be displayed for the new context.
    ResourceManager::DeckContextDescriptorContainer aDecks;

    css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();

    mpResourceManager->GetMatchingDecks (
        aDecks,
        maCurrentContext,
        mbIsDocumentReadOnly,
        xController);

    maFocusManager.Clear();

    // Notify the tab bar about the updated set of decks.
    mpTabBar->SetDecks(aDecks);

    // Find the new deck.  By default that is the same as the old
    // one.  If that is not set or not enabled, then choose the
    // first enabled deck (which is PropertyDeck).
    OUString sNewDeckId;
    for (const auto& rDeck : aDecks)
    {
        if (rDeck.mbIsEnabled)
        {
            if (rDeck.msId == msCurrentDeckId)
            {
                sNewDeckId = msCurrentDeckId;
                break;
            }
            else if (sNewDeckId.getLength() == 0)
                sNewDeckId = rDeck.msId;
        }
    }

    if (sNewDeckId.getLength() == 0)
    {
        // We did not find a valid deck.
        RequestCloseDeck();
        return;
    }

    std::shared_ptr<DeckDescriptor> xDescriptor = mpResourceManager->GetDeckDescriptor(sNewDeckId);

    if (xDescriptor)
    {
        SwitchToDeck(*xDescriptor, maCurrentContext);
    }
}

namespace {

void collectUIInformation(const OUString& rDeckId)
{
    EventDescription aDescription;
    aDescription.aAction = "SIDEBAR";
    aDescription.aParent = "MainWindow";
    aDescription.aParameters = {{"PANEL", rDeckId}};
    aDescription.aKeyWord = "CurrentApp";

    UITestLogger::getInstance().logEvent(aDescription);
}

}

bool SidebarController::IsDocked() const { return !mpParentWindow->IsFloatingMode(); }

void SidebarController::OpenThenToggleDeck (
    const OUString& rsDeckId)
{
    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
        // tdf#83546 Collapsed sidebar should expand first
        pSplitWindow->FadeIn();
    else if ( IsDeckVisible( rsDeckId ) )
    {
        if( !WasFloatingDeckClosed() )
        {
            // tdf#88241 Summoning an undocked sidebar a second time should close sidebar
            mpParentWindow->Close();
            return;
        }
        else
        {
            // tdf#67627 Clicking a second time on a Deck icon will close the Deck
            RequestCloseDeck();
            return;
        }
    }
    RequestOpenDeck();
    // before SwitchToDeck which may cause the rsDeckId string to be released
    collectUIInformation(rsDeckId);
    SwitchToDeck(rsDeckId);

    // Make sure the sidebar is wide enough to fit the requested content
    if (mpCurrentDeck && mpTabBar)
    {
        sal_Int32 nRequestedWidth = mpCurrentDeck->GetMinimalWidth() + TabBar::GetDefaultWidth();
        // if sidebar was dragged
        if(mnWidthOnSplitterButtonDown > 0 && mnWidthOnSplitterButtonDown > nRequestedWidth){
            SetChildWindowWidth(mnWidthOnSplitterButtonDown);
        }else{
            // tdf#150639 The mnWidthOnSplitterButtonDown is initialized to 0 at program start.
            // This makes every call to take the else case until the user manually changes the
            // width, but some decks such as Master Slides have the mnMinimalWidth too low which
            // makes them too narrow for the content they should display to the user.
            SetChildWindowWidth(nRequestedWidth > mnSavedSidebarWidth ? nRequestedWidth
                                                                      : mnSavedSidebarWidth);
        }
    }
}

void SidebarController::OpenThenSwitchToDeck (
    std::u16string_view rsDeckId)
{
    RequestOpenDeck();
    SwitchToDeck(rsDeckId);

}

void SidebarController::SwitchToDefaultDeck()
{
    SwitchToDeck(gsDefaultDeckId);
}

void SidebarController::SwitchToDeck (
    std::u16string_view rsDeckId)
{
    if (  msCurrentDeckId != rsDeckId
        || ! mbIsDeckOpen.has_value()
        || mnRequestedForceFlags!=SwitchFlag_NoForce)
    {
        std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rsDeckId);

        if (xDeckDescriptor)
        {
            SwitchToDeck(*xDeckDescriptor, maCurrentContext);
            Deck::LOKSendSidebarFullUpdate();
        }
    }
}

void SidebarController::CreateDeck(std::u16string_view rDeckId) {
    CreateDeck(rDeckId, maCurrentContext);
}

void SidebarController::CreateDeck(std::u16string_view rDeckId, const Context& rContext, bool bForceCreate)
{
    std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);

    if (!xDeckDescriptor)
        return;

    VclPtr<Deck> aDeck = xDeckDescriptor->mpDeck;
    if (!aDeck || bForceCreate)
    {
        if (aDeck)
            aDeck.disposeAndClear();

        aDeck = VclPtr<Deck>::Create(
                        *xDeckDescriptor,
                        mpParentWindow,
                        [this]() { return this->RequestCloseDeck(); });
    }
    xDeckDescriptor->mpDeck = std::move(aDeck);
    CreatePanels(rDeckId, rContext);
}

void SidebarController::CreatePanels(std::u16string_view rDeckId, const Context& rContext)
{
    std::shared_ptr<DeckDescriptor> xDeckDescriptor = mpResourceManager->GetDeckDescriptor(rDeckId);

    // init panels bounded to that deck, do not wait them being displayed as may be accessed through API

    VclPtr<Deck> pDeck = xDeckDescriptor->mpDeck;

    ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;

    css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();

    mpResourceManager->GetMatchingPanels(
                                        aPanelContextDescriptors,
                                        rContext,
                                        rDeckId,
                                        xController);

    // Update the panel list.
    const sal_Int32 nNewPanelCount (aPanelContextDescriptors.size());
    SharedPanelContainer aNewPanels;
    sal_Int32 nWriteIndex (0);

    aNewPanels.resize(nNewPanelCount);

    for (sal_Int32 nReadIndex=0; nReadIndex<nNewPanelCount; ++nReadIndex)
    {
        const ResourceManager::PanelContextDescriptor& rPanelContexDescriptor (
            aPanelContextDescriptors[nReadIndex]);

        // Determine if the panel can be displayed.
        const bool bIsPanelVisible (!mbIsDocumentReadOnly || rPanelContexDescriptor.mbShowForReadOnlyDocuments);
        if ( ! bIsPanelVisible)
            continue;

        auto xOldPanel(pDeck->GetPanel(rPanelContexDescriptor.msId));
        if (xOldPanel)
        {
            xOldPanel->SetLurkMode(false);
            aNewPanels[nWriteIndex] = xOldPanel;
            xOldPanel->SetExpanded(rPanelContexDescriptor.mbIsInitiallyVisible);
            ++nWriteIndex;
        }
        else
        {
            auto aPanel = CreatePanel(rPanelContexDescriptor.msId,
                                      pDeck->GetPanelParentWindow(),
                                      rPanelContexDescriptor.mbIsInitiallyVisible,
                                      rContext,
                                      pDeck);
            if (aPanel)
            {
                aNewPanels[nWriteIndex] = std::move(aPanel);

                // Depending on the context we have to change the command
                // for the "more options" dialog.
                PanelTitleBar* pTitleBar = aNewPanels[nWriteIndex]->GetTitleBar();
                if (pTitleBar)
                {
                    pTitleBar->SetMoreOptionsCommand(
                        rPanelContexDescriptor.msMenuCommand,
                        mxFrame, xController);
                }
                ++nWriteIndex;
            }
        }
    }

    // mpCurrentPanels - may miss stuff (?)
    aNewPanels.resize(nWriteIndex);
    pDeck->ResetPanels(std::move(aNewPanels));
}

void SidebarController::SwitchToDeck (
    const DeckDescriptor& rDeckDescriptor,
    const Context& rContext)
{
    if (comphelper::LibreOfficeKit::isActive())
    {
        if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
        {
            std::vector<std::pair<std::string, std::string>> aStateChanges;
            if (msCurrentDeckId != rDeckDescriptor.msId)
            {
                const std::string hide = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
                if (!hide.empty())
                {
                    aStateChanges.push_back({hide, std::string("false")});
                }
            }

            const std::string show = UnoNameFromDeckId(rDeckDescriptor.msId, GetCurrentContext());
            if (!show.empty())
            {
                aStateChanges.push_back({show, std::string("true")});
            }

            for (const auto& rStateChange : aStateChanges)
            {
                boost::property_tree::ptree aTree;
                aTree.put("locale", comphelper::LibreOfficeKit::getLocale().getBcp47());
                aTree.put("commandName", rStateChange.first);
                aTree.put("state", rStateChange.second);
                std::stringstream aStream;
                boost::property_tree::write_json(aStream, aTree);
                pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
                                                       OString(aStream.str()));
            }
        }
    }

    maFocusManager.Clear();

    const bool bForceNewDeck ((mnRequestedForceFlags&SwitchFlag_ForceNewDeck)!=0);
    const bool bForceNewPanels ((mnRequestedForceFlags&SwitchFlag_ForceNewPanels)!=0);
    mnRequestedForceFlags = SwitchFlag_NoForce;

    if (   msCurrentDeckId != rDeckDescriptor.msId
        || bForceNewDeck)
    {
        if (mpCurrentDeck)
            mpCurrentDeck->Hide();

        msCurrentDeckId = rDeckDescriptor.msId;
    }

    // Determine the panels to display in the deck.
    ResourceManager::PanelContextDescriptorContainer aPanelContextDescriptors;

    css::uno::Reference<css::frame::XController> xController = mxCurrentController.is() ? mxCurrentController : mxFrame->getController();

    mpResourceManager->GetMatchingPanels(
        aPanelContextDescriptors,
        rContext,
        rDeckDescriptor.msId,
        xController);

    if (aPanelContextDescriptors.empty())
    {
        // There are no panels to be displayed in the current context.
        if (vcl::EnumContext::GetContextEnum(rContext.msContext) != vcl::EnumContext::Context::Empty)
        {
            // Switch to the "empty" context and try again.
            SwitchToDeck(
                rDeckDescriptor,
                Context(
                    rContext.msApplication,
                    vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Empty)));
            return;
        }
        else
        {
            // This is already the "empty" context. Looks like we have
            // to live with an empty deck.
        }
    }

    // Provide a configuration and Deck object.

    CreateDeck(rDeckDescriptor.msId, rContext, bForceNewDeck);

    if (bForceNewPanels && !bForceNewDeck) // already forced if bForceNewDeck
        CreatePanels(rDeckDescriptor.msId, rContext);

    if (mpCurrentDeck && mpCurrentDeck != rDeckDescriptor.mpDeck)
        mpCurrentDeck->Hide();
    mpCurrentDeck.reset(rDeckDescriptor.mpDeck);

    if ( ! mpCurrentDeck)
        return;

#if OSL_DEBUG_LEVEL >= 2
    // Show the context name in the deck title bar.
    DeckTitleBar* pDebugTitleBar = mpCurrentDeck->GetTitleBar();
    if (pDebugTitleBar)
        pDebugTitleBar->SetTitle(rDeckDescriptor.msTitle + " (" + maCurrentContext.msContext + ")");
#endif

    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();
    WindowAlign eAlign = pSplitWindow ? pSplitWindow->GetAlign() : WindowAlign::Right;
    tools::Long nDeckX;
    if (eAlign == WindowAlign::Left)     // attach the Sidebar towards the left-side of screen
    {
        nDeckX = nTabBarDefaultWidth;
    }
    else   // attach the Sidebar towards the right-side of screen
    {
        nDeckX = 0;
    }

    // Activate the deck and the new set of panels.
    mpCurrentDeck->setPosSizePixel(
        nDeckX,
        0,
        mpParentWindow->GetSizePixel().Width() - nTabBarDefaultWidth,
        mpParentWindow->GetSizePixel().Height());

    mpCurrentDeck->Show();

    mpParentWindow->SetText(rDeckDescriptor.msTitle);

    NotifyResize();

    // Tell the focus manager about the new panels and tab bar
    // buttons.
    maFocusManager.SetDeck(mpCurrentDeck);
    maFocusManager.SetPanels(mpCurrentDeck->GetPanels());

    mpTabBar->UpdateFocusManager(maFocusManager);
    UpdateTitleBarIcons();
}

void SidebarController::notifyDeckTitle(std::u16string_view targetDeckId)
{
    if (msCurrentDeckId == targetDeckId)
    {
        maFocusManager.SetDeck(mpCurrentDeck);
        mpTabBar->UpdateFocusManager(maFocusManager);
        UpdateTitleBarIcons();
    }
}

std::shared_ptr<Panel> SidebarController::CreatePanel (
    std::u16string_view rsPanelId,
    weld::Widget* pParentWindow,
    const bool bIsInitiallyExpanded,
    const Context& rContext,
    const VclPtr<Deck>& pDeck)
{
    std::shared_ptr<PanelDescriptor> xPanelDescriptor = mpResourceManager->GetPanelDescriptor(rsPanelId);

    if (!xPanelDescriptor)
        return nullptr;

    // Create the panel which is the parent window of the UIElement.
    auto xPanel = std::make_shared<Panel>(
        *xPanelDescriptor,
        pParentWindow,
        bIsInitiallyExpanded,
        pDeck,
        [this]() { return this->GetCurrentContext(); },
        mxFrame);

    // Create the XUIElement.
    Reference<ui::XUIElement> xUIElement (CreateUIElement(
            xPanel->GetElementParentWindow(),
            xPanelDescriptor->msImplementationURL,
            xPanelDescriptor->mbWantsCanvas,
            rContext));
    if (xUIElement.is())
    {
        // Initialize the panel and add it to the active deck.
        xPanel->SetUIElement(xUIElement);
    }
    else
    {
        xPanel.reset();
    }

    return xPanel;
}

Reference<ui::XUIElement> SidebarController::CreateUIElement (
    const Reference<awt::XWindow>& rxWindow,
    const OUString& rsImplementationURL,
    const bool bWantsCanvas,
    const Context& rContext)
{
    try
    {
        const Reference<XComponentContext>& xComponentContext (::comphelper::getProcessComponentContext() );
        const Reference<ui::XUIElementFactory> xUIElementFactory =
               ui::theUIElementFactoryManager::get( xComponentContext );

       // Create the XUIElement.
        ::comphelper::NamedValueCollection aCreationArguments;
        aCreationArguments.put(u"Frame"_ustr, Any(mxFrame));
        aCreationArguments.put(u"ParentWindow"_ustr, Any(rxWindow));
        SidebarDockingWindow* pSfxDockingWindow = mpParentWindow.get();
        if (pSfxDockingWindow != nullptr)
            aCreationArguments.put(u"SfxBindings"_ustr, Any(reinterpret_cast<sal_uInt64>(&pSfxDockingWindow->GetBindings())));
        aCreationArguments.put(u"Theme"_ustr, Theme::GetPropertySet());
        aCreationArguments.put(u"Sidebar"_ustr, Any(Reference<ui::XSidebar>(static_cast<ui::XSidebar*>(this))));
        if (bWantsCanvas)
        {
            Reference<rendering::XSpriteCanvas> xCanvas (VCLUnoHelper::GetWindow(rxWindow)->GetOutDev()->GetSpriteCanvas());
            aCreationArguments.put(u"Canvas"_ustr, Any(xCanvas));
        }

        if (mxCurrentController.is())
        {
            OUString aModule = Tools::GetModuleName(mxCurrentController);
            if (!aModule.isEmpty())
                aCreationArguments.put(u"Module"_ustr, Any(aModule));
            // See also sfx2::sidebar::GetFrame. Maybe we should always create
            // uielements with the Controller's getFrame as the XFrame. For now
            // set both XFrame and Controller and selectively get the XFrame
            // from the Controller.
            aCreationArguments.put(u"Controller"_ustr, Any(mxCurrentController));
        }

        aCreationArguments.put(u"ApplicationName"_ustr, Any(rContext.msApplication));
        aCreationArguments.put(u"ContextName"_ustr, Any(rContext.msContext));

        Reference<ui::XUIElement> xUIElement(
            xUIElementFactory->createUIElement(
                rsImplementationURL,
                aCreationArguments.getPropertyValues()),
            UNO_SET_THROW);

        return xUIElement;
    }
    catch(const Exception&)
    {
        TOOLS_WARN_EXCEPTION("sfx.sidebar", "Cannot create panel " << rsImplementationURL);
        return nullptr;
    }
}

IMPL_LINK(SidebarController, WindowEventHandler, VclWindowEvent&, rEvent, void)
{
    if (rEvent.GetWindow() == mpParentWindow)
    {
        switch (rEvent.GetId())
        {
            case VclEventId::WindowShow:
            case VclEventId::WindowResize:
                NotifyResize();
                break;

            case VclEventId::WindowDataChanged:
                // Force an update of deck and tab bar to reflect
                // changes in theme (high contrast mode).
                Theme::HandleDataChange();
                UpdateTitleBarIcons();
                mpParentWindow->Invalidate();
                mnRequestedForceFlags |= SwitchFlag_ForceNewDeck | SwitchFlag_ForceNewPanels;
                maContextChangeUpdate.RequestCall();
                break;

            case VclEventId::WindowToggleFloating:
                // make sure the appropriate "Dock" or "Undock" menu entry is shown
                mpTabBar->UpdateMenus();
                break;

            case VclEventId::ObjectDying:
                dispose();
                break;

            case VclEventId::WindowPaint:
                SAL_INFO("sfx.sidebar", "Paint");
                break;

            default:
                break;
        }
    }
    else if (rEvent.GetWindow()==mpSplitWindow && mpSplitWindow!=nullptr)
    {
        switch (rEvent.GetId())
        {
            case VclEventId::WindowMouseButtonDown:
                mnWidthOnSplitterButtonDown = mpParentWindow->GetSizePixel().Width();
                break;

            case VclEventId::WindowMouseButtonUp:
            {
                ProcessNewWidth(mpParentWindow->GetSizePixel().Width());
                break;
            }

            case VclEventId::ObjectDying:
                dispose();
                break;

            default: break;
         }
    }
}

void SidebarController::ConnectMenuActivateHandlers(weld::Menu& rMainMenu, weld::Menu& rSubMenu) const
{
    rMainMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnMenuItemSelected));
    rSubMenu.connect_activate(LINK(const_cast<SidebarController*>(this), SidebarController, OnSubMenuItemSelected));
}

IMPL_LINK(SidebarController, OnMenuItemSelected, const OUString&, rCurItemId, void)
{
    if (rCurItemId == "unlocktaskpanel")
    {
        mpParentWindow->SetFloatingMode(true);
        if (mpParentWindow->IsFloatingMode())
            mpParentWindow->ToTop(ToTopFlags::GrabFocusOnly);
    }
    else if (rCurItemId == "locktaskpanel")
    {
        mpParentWindow->SetFloatingMode(false);
    }
    else if (rCurItemId == "hidesidebar")
    {
        if (!comphelper::LibreOfficeKit::isActive())
        {
            const util::URL aURL(Tools::GetURL(u".uno:Sidebar"_ustr));
            Reference<frame::XDispatch> xDispatch(Tools::GetDispatch(mxFrame, aURL));
            if (xDispatch.is())
                xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>());
        }
        else
        {
            // In LOK we don't really destroy the sidebar when "closing";
            // we simply hide it. This is because recreating it is problematic
            // See notes in SidebarDockingWindow::NotifyResize().
            RequestCloseDeck();
        }
    }
    else
    {
        try
        {
            std::u16string_view sNumber;
            if (rCurItemId.startsWith("select", &sNumber))
            {
                RequestOpenDeck();
                SwitchToDeck(mpTabBar->GetDeckIdForIndex(o3tl::toInt32(sNumber)));
            }
            mpParentWindow->GrabFocusToDocument();
        }
        catch (RuntimeException&)
        {
        }
    }
}

IMPL_LINK(SidebarController, OnSubMenuItemSelected, const OUString&, rCurItemId, void)
{
        try
        {
            std::u16string_view sNumber;
            if (rCurItemId.startsWith("customize", &sNumber))
            {
                mpTabBar->ToggleHideFlag(o3tl::toInt32(sNumber));

                // Find the set of decks that could be displayed for the new context.
                ResourceManager::DeckContextDescriptorContainer aDecks;
                mpResourceManager->GetMatchingDecks (
                                            aDecks,
                                            GetCurrentContext(),
                                            IsDocumentReadOnly(),
                                            mxFrame->getController());
                // Notify the tab bar about the updated set of decks.
                maFocusManager.Clear();
                mpTabBar->SetDecks(aDecks);
                mpTabBar->HighlightDeck(mpCurrentDeck->GetId());
                mpTabBar->UpdateFocusManager(maFocusManager);
            }
            mpParentWindow->GrabFocusToDocument();
        }
        catch (RuntimeException&)
        {
        }
}


void SidebarController::RequestCloseDeck()
{
    if (comphelper::LibreOfficeKit::isActive() && mpCurrentDeck)
    {
        const SfxViewShell* pViewShell = SfxViewShell::Current();
        if (pViewShell && pViewShell->isLOKMobilePhone())
        {
            // Mobile phone - TODO: unify with desktop
            tools::JsonWriter aJsonWriter;
            aJsonWriter.put("id", mpParentWindow->get_id());
            aJsonWriter.put("type", "dockingwindow");
            aJsonWriter.put("text", mpParentWindow->GetText());
            aJsonWriter.put("enabled", false);
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, aJsonWriter.finishAndGetAsOString());
        }
        else if (pViewShell)
        {
            tools::JsonWriter aJsonWriter;
            aJsonWriter.put("id", mpParentWindow->get_id());
            aJsonWriter.put("action", "close");
            aJsonWriter.put("jsontype", "sidebar");
            pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_JSDIALOG, aJsonWriter.finishAndGetAsOString());
        }
    }

    mbIsDeckRequestedOpen = false;
    UpdateDeckOpenState();

    mpTabBar->RemoveDeckHighlight();
}

void SidebarController::RequestOpenDeck()
{
    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    if ( pSplitWindow && !pSplitWindow->IsFadeIn() )
        // tdf#83546 Collapsed sidebar should expand first
        pSplitWindow->FadeIn();

    mbIsDeckRequestedOpen = true;
    UpdateDeckOpenState();
}

bool SidebarController::IsDeckOpen(const sal_Int32 nIndex)
{
    if (nIndex >= 0)
    {
        OUString asDeckId(mpTabBar->GetDeckIdForIndex(nIndex));
        return IsDeckVisible(asDeckId);
    }
    return mbIsDeckOpen.has_value() && *mbIsDeckOpen;
}

bool SidebarController::IsDeckVisible(std::u16string_view rsDeckId)
{
    return mbIsDeckOpen.has_value() && *mbIsDeckOpen && msCurrentDeckId == rsDeckId;
}

void SidebarController::UpdateDeckOpenState()
{
    if ( ! mbIsDeckRequestedOpen.has_value() )
        // No state requested.
        return;

    const sal_Int32 nTabBarDefaultWidth = TabBar::GetDefaultWidth();

    // Update (change) the open state when it either has not yet been initialized
    // or when its value differs from the requested state.
    if ( mbIsDeckOpen.has_value() && *mbIsDeckOpen == *mbIsDeckRequestedOpen )
        return;

    if (*mbIsDeckRequestedOpen)
    {
        if (!mpParentWindow->IsFloatingMode())
        {
            if (mnSavedSidebarWidth <= nTabBarDefaultWidth)
                SetChildWindowWidth(SidebarChildWindow::GetDefaultWidth(mpParentWindow));
            else
                SetChildWindowWidth(mnSavedSidebarWidth);
        }
        else
        {
            // Show the Deck by resizing back to the original size (before hiding).
            Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
            Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());

            aNewPos.setX(aNewPos.X() - mnSavedSidebarWidth + nTabBarDefaultWidth);
            aNewSize.setWidth(mnSavedSidebarWidth);

            mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);

            if (comphelper::LibreOfficeKit::isActive())
            {
                // Sidebar wide enough to render the menu; enable it.
                mpTabBar->EnableMenuButton(true);

                if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
                {
                    const std::string uno = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
                    if (!uno.empty())
                        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
                                                                OString(uno + "=true"));
                }
            }
        }
    }
    else
    {
        if ( ! mpParentWindow->IsFloatingMode())
            mnSavedSidebarWidth = SetChildWindowWidth(nTabBarDefaultWidth);
        else
        {
            // Hide the Deck by resizing to the width of the TabBar.
            Size aNewSize(mpParentWindow->GetFloatingWindow()->GetSizePixel());
            Point aNewPos(mpParentWindow->GetFloatingWindow()->GetPosPixel());
            mnSavedSidebarWidth = aNewSize.Width(); // Save the current width to restore.

            aNewPos.setX(aNewPos.X() + mnSavedSidebarWidth - nTabBarDefaultWidth);
            if (comphelper::LibreOfficeKit::isActive())
            {
                // Hide by collapsing, otherwise with 0x0 the client might expect
                // to get valid dimensions on rendering and not collapse the sidebar.
                aNewSize.setWidth(1);
            }
            else
                aNewSize.setWidth(nTabBarDefaultWidth);

            mpParentWindow->GetFloatingWindow()->SetPosSizePixel(aNewPos, aNewSize);

            if (comphelper::LibreOfficeKit::isActive())
            {
                // Sidebar too narrow to render the menu; disable it.
                mpTabBar->EnableMenuButton(false);

                if (const SfxViewShell* pViewShell = mpViewFrame->GetViewShell())
                {
                    const std::string uno = UnoNameFromDeckId(msCurrentDeckId, GetCurrentContext());
                    if (!uno.empty())
                        pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED,
                                                                OString(uno + "=false"));
                }
            }
        }

        if (mnWidthOnSplitterButtonDown > nTabBarDefaultWidth)
            mnSavedSidebarWidth = mnWidthOnSplitterButtonDown;
        mpParentWindow->SetStyle(mpParentWindow->GetStyle() & ~WB_SIZEABLE);
    }

    NotifyResize();
}

bool SidebarController::CanModifyChildWindowWidth()
{
    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    if (pSplitWindow == nullptr)
        return false;

    sal_uInt16 nRow (0xffff);
    sal_uInt16 nColumn (0xffff);
    if (pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow))
    {
        sal_uInt16 nRowCount (pSplitWindow->GetWindowCount(nColumn));
        return nRowCount==1;
    }
    else
        return false;
}

sal_Int32 SidebarController::SetChildWindowWidth (const sal_Int32 nNewWidth)
{
    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    if (pSplitWindow == nullptr)
        return 0;

    sal_uInt16 nRow (0xffff);
    sal_uInt16 nColumn (0xffff);
    pSplitWindow->GetWindowPos(mpParentWindow, nColumn, nRow);
    const tools::Long nColumnWidth (pSplitWindow->GetLineSize(nColumn));

    vcl::Window* pWindow = mpParentWindow;
    const Size aWindowSize (pWindow->GetSizePixel());

    pSplitWindow->MoveWindow(
        mpParentWindow,
        Size(nNewWidth, aWindowSize.Height()),
        nColumn,
        nRow,
        false);
    static_cast<SplitWindow*>(pSplitWindow)->Split();

    return static_cast<sal_Int32>(nColumnWidth);
}

void SidebarController::RestrictWidth (sal_Int32 nWidth)
{
    SfxSplitWindow* pSplitWindow = GetSplitWindow();
    if (pSplitWindow != nullptr)
    {
        const sal_uInt16 nId (pSplitWindow->GetItemId(mpParentWindow.get()));
        const sal_uInt16 nSetId (pSplitWindow->GetSet(nId));
        const sal_Int32 nRequestedWidth = TabBar::GetDefaultWidth() + nWidth;

        pSplitWindow->SetItemSizeRange(
            nSetId,
            Range(nRequestedWidth, std::max(nRequestedWidth, getMaximumWidth())));
    }
}

SfxSplitWindow* SidebarController::GetSplitWindow()
{
    if (mpParentWindow != nullptr)
    {
        SfxSplitWindow* pSplitWindow = dynamic_cast<SfxSplitWindow*>(mpParentWindow->GetParent());
        if (pSplitWindow != mpSplitWindow)
        {
            if (mpSplitWindow != nullptr)
                mpSplitWindow->RemoveEventListener(LINK(this, SidebarController, WindowEventHandler));

            mpSplitWindow = pSplitWindow;

            if (mpSplitWindow != nullptr)
                mpSplitWindow->AddEventListener(LINK(this, SidebarController, WindowEventHandler));
        }
        return mpSplitWindow;
    }
    else
        return nullptr;
}

void SidebarController::UpdateCloseIndicator (const bool bCloseAfterDrag)
{
    if (mpParentWindow == nullptr)
        return;

    if (bCloseAfterDrag)
    {
        // Make sure that the indicator exists.
        if (!mpCloseIndicator)
            mpCloseIndicator.reset(VclPtr<CloseIndicator>::Create(mpParentWindow));

        // Place and show the indicator.
        const Size aWindowSize (mpParentWindow->GetSizePixel());
        const Size aImageSize (mpCloseIndicator->GetSizePixel());
        mpCloseIndicator->SetPosPixel(
            Point(
                aWindowSize.Width() - TabBar::GetDefaultWidth() - aImageSize.Width(),
                (aWindowSize.Height() - aImageSize.Height())/2));
        mpCloseIndicator->Show();
    }
    else
    {
        // Hide but don't delete the indicator.
        if (mpCloseIndicator)
            mpCloseIndicator->Hide();
    }
}

void SidebarController::UpdateTitleBarIcons()
{
    if ( ! mpCurrentDeck)
        return;

    const bool bIsHighContrastModeActive (Theme::IsHighContrastMode());

    const ResourceManager& rResourceManager = *mpResourceManager;

    // Update the deck icon.
    std::shared_ptr<DeckDescriptor> xDeckDescriptor = rResourceManager.GetDeckDescriptor(mpCurrentDeck->GetId());
    if (xDeckDescriptor && mpCurrentDeck->GetTitleBar())
    {
        const OUString sIconURL(
            bIsHighContrastModeActive
                ? xDeckDescriptor->msHighContrastTitleBarIconURL
                : xDeckDescriptor->msTitleBarIconURL);
        mpCurrentDeck->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
    }

    // Update the panel icons.
    const SharedPanelContainer& rPanels (mpCurrentDeck->GetPanels());
    for (const auto& rxPanel : rPanels)
    {
        if ( ! rxPanel)
            continue;
        if (!rxPanel->GetTitleBar())
            continue;
        std::shared_ptr<PanelDescriptor> xPanelDescriptor = rResourceManager.GetPanelDescriptor(rxPanel->GetId());
        if (!xPanelDescriptor)
            continue;
        const OUString sIconURL (
            bIsHighContrastModeActive
               ? xPanelDescriptor->msHighContrastTitleBarIconURL
               : xPanelDescriptor->msTitleBarIconURL);
        rxPanel->GetTitleBar()->SetIcon(Tools::GetImage(sIconURL, mxFrame));
    }
}

void SidebarController::ShowPanel (const Panel& rPanel)
{
    if (mpCurrentDeck)
    {
        if (!IsDeckOpen())
            RequestOpenDeck();
        mpCurrentDeck->ShowPanel(rPanel);
    }
}

ResourceManager::DeckContextDescriptorContainer SidebarController::GetMatchingDecks()
{
    ResourceManager::DeckContextDescriptorContainer aDecks;
    mpResourceManager->GetMatchingDecks (aDecks,
                                        GetCurrentContext(),
                                        IsDocumentReadOnly(),
                                        mxFrame->getController());
    return aDecks;
}

ResourceManager::PanelContextDescriptorContainer SidebarController::GetMatchingPanels(std::u16string_view rDeckId)
{
    ResourceManager::PanelContextDescriptorContainer aPanels;

    mpResourceManager->GetMatchingPanels(aPanels,
                                        GetCurrentContext(),
                                        rDeckId,
                                        mxFrame->getController());
    return aPanels;
}

void SidebarController::updateModel(const css::uno::Reference<css::frame::XModel>& xModel)
{
    mpResourceManager->UpdateModel(xModel);
}

void SidebarController::FadeOut()
{
    if (mpSplitWindow)
        mpSplitWindow->FadeOut();
}

void SidebarController::FadeIn()
{
    if (mpSplitWindow)
        mpSplitWindow->FadeIn();
}

tools::Rectangle SidebarController::GetDeckDragArea() const
{
    tools::Rectangle aRect;
    if (mpCurrentDeck)
    {
        if (DeckTitleBar* pTitleBar = mpCurrentDeck->GetTitleBar())
        {
            aRect = pTitleBar->GetDragArea();
        }
    }
    return aRect;
}

void SidebarController::frameAction(const css::frame::FrameActionEvent& rEvent)
{
    if (rEvent.Frame == mxFrame)
    {
        if (rEvent.Action == css::frame::FrameAction_COMPONENT_DETACHING)
            unregisterSidebarForFrame(mxFrame->getController());
        else if (rEvent.Action == css::frame::FrameAction_COMPONENT_REATTACHED)
            registerSidebarForFrame(mxFrame->getController());
    }
}

void SidebarController::saveDeckState()
{
    // Impress shutdown : context (frame) is disposed before sidebar disposing
    // calc writer : context (frame) is disposed after sidebar disposing
    // so need to test if GetCurrentContext is still valid regarding msApplication
    if (GetCurrentContext().msApplication != "none")
    {
        mpResourceManager->SaveDecksSettings(GetCurrentContext());
        mpResourceManager->SaveLastActiveDeck(GetCurrentContext(), msCurrentDeckId);
    }
}

static bool isChartOrMathContext(const Context& context)
{
    return context.msApplication == "com.sun.star.chart2.ChartDocument"
           || context.msApplication == "com.sun.star.formula.FormulaProperties";
}

bool SidebarController::hasChartOrMathContextCurrently() const
{
    if ((maRequestedContext != maCurrentContext) && isChartOrMathContext(maRequestedContext))
        return true; // We are not yet changed, but in the process

    return isChartOrMathContext(maCurrentContext);
}

sfx2::sidebar::SidebarController* SidebarController::GetSidebarControllerForView(const SfxViewShell* pViewShell)
{
    if (!pViewShell)
        return nullptr;

    Reference<css::frame::XController2> xController(pViewShell->GetController(), UNO_QUERY);
    if (!xController.is())
        return nullptr;

    // Make sure there is a model behind the controller, otherwise getSidebar() can crash.
    if (!xController->getModel().is())
        return nullptr;

    Reference<css::ui::XSidebarProvider> xSidebarProvider = xController->getSidebar();
    if (!xSidebarProvider.is())
        return nullptr;

    Reference<css::ui::XSidebar> xSidebar = xSidebarProvider->getSidebar();
    if (!xSidebar.is())
        return nullptr;

    return dynamic_cast<sfx2::sidebar::SidebarController*>(xSidebar.get());
}

} // end of namespace sfx2::sidebar

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