/* -*- 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 <utility>
#include <vcl/settings.hxx>
#include "PresenterWindowManager.hxx"
#include "PresenterController.hxx"
#include "PresenterGeometryHelper.hxx"
#include "PresenterPaintManager.hxx"
#include "PresenterPaneBorderPainter.hxx"
#include "PresenterPaneContainer.hxx"
#include "PresenterPaneFactory.hxx"
#include "PresenterToolBar.hxx"
#include "PresenterViewFactory.hxx"
#include "PresenterTheme.hxx"
#include <com/sun/star/awt/InvalidateStyle.hpp>
#include <com/sun/star/awt/PosSize.hpp>
#include <com/sun/star/awt/XWindow2.hpp>
#include <com/sun/star/awt/XWindowPeer.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/FillRule.hpp>
#include <com/sun/star/rendering/Texture.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>
#include <math.h>

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

namespace sdext::presenter {

//===== PresenterWindowManager ================================================

PresenterWindowManager::PresenterWindowManager (
    const Reference<XComponentContext>& rxContext,
    ::rtl::Reference<PresenterPaneContainer> pPaneContainer,
    ::rtl::Reference<PresenterController> pPresenterController)
    : PresenterWindowManagerInterfaceBase(m_aMutex),
      mxComponentContext(rxContext),
      mpPresenterController(std::move(pPresenterController)),
      mpPaneContainer(std::move(pPaneContainer)),
      mbIsLayoutPending(true),
      mbIsLayouting(false),
      meLayoutMode(LayoutMode::Generic),
      mbIsSlideSorterActive(false),
      mbIsHelpViewActive(false),
      mbisPaused(false),
      mbIsMouseClickPending(false)
{

}

PresenterWindowManager::~PresenterWindowManager()
{
}

void SAL_CALL PresenterWindowManager::disposing()
{
    NotifyDisposing();

    SetParentPane(nullptr);

    Reference<lang::XComponent> xComponent (mxPaneBorderManager, UNO_QUERY);
    if (xComponent.is())
        xComponent->dispose();
    mxPaneBorderManager = nullptr;

    for (const auto& rxPane : mpPaneContainer->maPanes)
    {
        if (rxPane->mxBorderWindow.is())
        {
            rxPane->mxBorderWindow->removeWindowListener(this);
            rxPane->mxBorderWindow->removeFocusListener(this);
            rxPane->mxBorderWindow->removeMouseListener(this);
        }
    }
}

void PresenterWindowManager::SetParentPane (
    const rtl::Reference<sd::framework::AbstractPane>& rxPane)
{
    if (mxParentWindow.is())
    {
        mxParentWindow->removeWindowListener(this);
        mxParentWindow->removePaintListener(this);
        mxParentWindow->removeMouseListener(this);
        mxParentWindow->removeFocusListener(this);
    }
    mxParentWindow = nullptr;
    mxParentCanvas = nullptr;

    if (rxPane.is())
    {
        mxParentWindow = rxPane->getWindow();
        mxParentCanvas = rxPane->getCanvas();
    }
    else
    {
        mxParentWindow = nullptr;
    }

    if (mxParentWindow.is())
    {
        mxParentWindow->addWindowListener(this);
        mxParentWindow->addPaintListener(this);
        mxParentWindow->addMouseListener(this);
        mxParentWindow->addFocusListener(this);

        // We paint our own background, make that of the parent window transparent.
        Reference<awt::XWindowPeer> xPeer (mxParentWindow, UNO_QUERY);
        if (xPeer.is())
            xPeer->setBackground(util::Color(0xff000000));
    }
}

void PresenterWindowManager::SetTheme (const std::shared_ptr<PresenterTheme>& rpTheme)
{
    mpTheme = rpTheme;

    // Get background bitmap or background color from the theme.

    if (mpTheme != nullptr)
    {
        mpBackgroundBitmap = mpTheme->GetBitmap(OUString(), u"Background"_ustr);
    }
}

void PresenterWindowManager::NotifyViewCreation (const rtl::Reference<sd::framework::AbstractView>& rxView)
{
    PresenterPaneContainer::SharedPaneDescriptor pDescriptor (
        mpPaneContainer->FindPaneId(rxView->getResourceId()->getAnchor()));
    OSL_ASSERT(pDescriptor);
    if (pDescriptor)
    {
        Layout();

        mpPresenterController->GetPaintManager()->Invalidate(
            pDescriptor->mxContentWindow,
            sal_Int16(awt::InvalidateStyle::TRANSPARENT
            | awt::InvalidateStyle::CHILDREN));
    }
}

void PresenterWindowManager::SetPanePosSizeAbsolute (
    const OUString& rsPaneURL,
    const double nX,
    const double nY,
    const double nWidth,
    const double nHeight)
{
    PresenterPaneContainer::SharedPaneDescriptor pDescriptor (
        mpPaneContainer->FindPaneURL(rsPaneURL));
    if (pDescriptor)
    {
        if (pDescriptor->mxBorderWindow.is())
            pDescriptor->mxBorderWindow->setPosSize(
                ::sal::static_int_cast<sal_Int32>(nX),
                ::sal::static_int_cast<sal_Int32>(nY),
                ::sal::static_int_cast<sal_Int32>(nWidth),
                ::sal::static_int_cast<sal_Int32>(nHeight),
                awt::PosSize::POSSIZE);
    }
}

void PresenterWindowManager::SetPaneBorderPainter (
    const ::rtl::Reference<PresenterPaneBorderPainter>& rPainter)
{
    mpPaneBorderPainter = rPainter;
}

//----- XWindowListener -------------------------------------------------------

void SAL_CALL PresenterWindowManager::windowResized (const awt::WindowEvent& rEvent)
{
    ThrowIfDisposed();
    if (rEvent.Source == mxParentWindow)
    {
        Layout();
    }
    else
    {
        Reference<awt::XWindow> xWindow (rEvent.Source,UNO_QUERY);
        if (xWindow.is())
        {
            UpdateWindowSize(xWindow);

            // Make sure the background of a transparent window is painted.
            mpPresenterController->GetPaintManager()->Invalidate(mxParentWindow);
        }
    }
}

void SAL_CALL PresenterWindowManager::windowMoved (const awt::WindowEvent& rEvent)
{
    ThrowIfDisposed();
    if (rEvent.Source != mxParentWindow)
    {
        Reference<awt::XWindow> xWindow (rEvent.Source,UNO_QUERY);
        UpdateWindowSize(xWindow);

        // Make sure the background of a transparent window is painted.
        mpPresenterController->GetPaintManager()->Invalidate(xWindow);
    }
}

void SAL_CALL PresenterWindowManager::windowShown (const lang::EventObject&) {}

void SAL_CALL PresenterWindowManager::windowHidden (const lang::EventObject&) {}

//----- XPaintListener --------------------------------------------------------

void SAL_CALL PresenterWindowManager::windowPaint (const awt::PaintEvent& rEvent)
{
    ThrowIfDisposed();

    if ( ! mxParentWindow.is())
        return;
    if ( ! mxParentCanvas.is())
        return;

    if (mpTheme == nullptr)
        return;

    try
    {
        if (mbIsLayoutPending)
            Layout();
        PaintBackground(rEvent.UpdateRect);
        PaintChildren(rEvent);
    }
    catch (RuntimeException&)
    {
        OSL_FAIL("paint failed!");
    }
}

//----- XMouseListener --------------------------------------------------------

void SAL_CALL PresenterWindowManager::mousePressed (const css::awt::MouseEvent&)
{
    if (!mbIsSlideSorterActive) // tdf#127921
        mbIsMouseClickPending = true;
}

void SAL_CALL PresenterWindowManager::mouseReleased (const css::awt::MouseEvent& rEvent)
{
    if (mbIsMouseClickPending)
    {
        mbIsMouseClickPending = false;
        mpPresenterController->HandleMouseClick(rEvent);
    }
}

void SAL_CALL PresenterWindowManager::mouseEntered (const css::awt::MouseEvent&)
{
    mbIsMouseClickPending = false;
}

void SAL_CALL PresenterWindowManager::mouseExited (const css::awt::MouseEvent&)
{
    mbIsMouseClickPending = false;
}

//----- XFocusListener --------------------------------------------------------

void SAL_CALL PresenterWindowManager::focusGained (const css::awt::FocusEvent& /*rEvent*/)
{
    ThrowIfDisposed();
}

void SAL_CALL PresenterWindowManager::focusLost (const css::awt::FocusEvent&)
{
    ThrowIfDisposed();
}

//----- XEventListener --------------------------------------------------------

void SAL_CALL PresenterWindowManager::disposing (const lang::EventObject& rEvent)
{
    if (rEvent.Source == mxParentWindow)
        mxParentWindow = nullptr;
}


void PresenterWindowManager::PaintChildren (const awt::PaintEvent& rEvent) const
{
    // Call windowPaint on all children that lie in or touch the
    // update rectangle.
    for (const auto& rxPane : mpPaneContainer->maPanes)
    {
        try
        {
            // Make sure that the pane shall and can be painted.
            if ( ! rxPane->mbIsActive)
                continue;
            if (rxPane->mbIsSprite)
                continue;
            if ( ! rxPane->mxPane.is())
                continue;
            if ( ! rxPane->mxBorderWindow.is())
                continue;
            Reference<awt::XWindow> xBorderWindow (rxPane->mxBorderWindow);
            if ( ! xBorderWindow.is())
                continue;

            // Get the area in which the border of the pane has to be painted.
            const awt::Rectangle aBorderBox (xBorderWindow->getPosSize());
            const awt::Rectangle aBorderUpdateBox(
                PresenterGeometryHelper::Intersection(
                    rEvent.UpdateRect,
                    aBorderBox));
            if (aBorderUpdateBox.Width<=0 || aBorderUpdateBox.Height<=0)
                continue;

            const awt::Rectangle aLocalBorderUpdateBox(
                PresenterGeometryHelper::TranslateRectangle(
                    aBorderUpdateBox,
                    -aBorderBox.X,
                    -aBorderBox.Y));

            // Invalidate the area of the content window.
            mpPresenterController->GetPaintManager()->Invalidate(
                xBorderWindow,
                aLocalBorderUpdateBox,
                sal_Int16(awt::InvalidateStyle::CHILDREN
                    | awt::InvalidateStyle::NOTRANSPARENT));
        }
        catch (RuntimeException&)
        {
            OSL_FAIL("paint children failed!");
        }
    }
}

void PresenterWindowManager::SetLayoutMode (const LayoutMode eMode)
{
    OSL_ASSERT(mpPresenterController);

    if (meLayoutMode == eMode
        && !mbIsSlideSorterActive
        && !mbIsHelpViewActive)
        return;

    meLayoutMode = eMode;
    mbIsSlideSorterActive = false;
    mbIsHelpViewActive = false;

    mpPresenterController->RequestViews(
        mbIsSlideSorterActive,
        meLayoutMode == LayoutMode::Notes,
        mbIsHelpViewActive);
    Layout();
    NotifyLayoutModeChange();
}

void PresenterWindowManager::SetSlideSorterState (bool bIsActive)
{
    if (mbIsSlideSorterActive == bIsActive)
        return;

    mbIsSlideSorterActive = bIsActive;
    if (mbIsSlideSorterActive)
        mbIsHelpViewActive = false;
    StoreViewMode(GetViewMode());

    mpPresenterController->RequestViews(
        mbIsSlideSorterActive,
        meLayoutMode == LayoutMode::Notes,
        mbIsHelpViewActive);
    Layout();
    NotifyLayoutModeChange();
}

void PresenterWindowManager::SetHelpViewState (bool bIsActive)
{
    if (mbIsHelpViewActive == bIsActive)
        return;

    mbIsHelpViewActive = bIsActive;
    if (mbIsHelpViewActive)
        mbIsSlideSorterActive = false;
    StoreViewMode(GetViewMode());

    mpPresenterController->RequestViews(
        mbIsSlideSorterActive,
        meLayoutMode == LayoutMode::Notes,
        mbIsHelpViewActive);
    Layout();
    NotifyLayoutModeChange();
}

void PresenterWindowManager::SetPauseState (bool bIsPaused)
{
    if (mbisPaused == bIsPaused)
        return;

    mbisPaused = bIsPaused;

    NotifyLayoutModeChange();
}

void PresenterWindowManager::SetViewMode (const ViewMode eMode)
{
    switch (eMode)
    {
        case VM_Standard:
            SetSlideSorterState(false);
            SetHelpViewState(false);
            SetLayoutMode(LayoutMode::Standard);
            break;

        case VM_Notes:
            SetSlideSorterState(false);
            SetHelpViewState(false);
            SetLayoutMode(LayoutMode::Notes);
            break;

        case VM_SlideOverview:
            SetHelpViewState(false);
            SetSlideSorterState(true);
            break;

        case VM_Help:
            SetHelpViewState(true);
            SetSlideSorterState(false);
            break;
    }

    StoreViewMode(eMode);
}

PresenterWindowManager::ViewMode PresenterWindowManager::GetViewMode() const
{
    if (mbIsHelpViewActive)
        return VM_Help;
    else if (mbIsSlideSorterActive)
        return VM_SlideOverview;
    else if (meLayoutMode == LayoutMode::Notes)
        return VM_Notes;
    else
        return VM_Standard;
}

void PresenterWindowManager::RestoreViewMode()
{
    sal_Int32 nMode (0);
    PresenterConfigurationAccess aConfiguration (
        mxComponentContext,
        u"/org.openoffice.Office.PresenterScreen/"_ustr,
        PresenterConfigurationAccess::READ_ONLY);
    aConfiguration.GetConfigurationNode(u"Presenter/InitialViewMode"_ustr) >>= nMode;
    switch (nMode)
    {
        default:
        case 0:
            SetViewMode(VM_Standard);
            break;

        case 1:
            SetViewMode(VM_Notes);
            break;

        case 2:
            SetViewMode(VM_SlideOverview);
            break;
    }
}

void PresenterWindowManager::StoreViewMode (const ViewMode eViewMode)
{
    try
    {
        PresenterConfigurationAccess aConfiguration (
            mxComponentContext,
            u"/org.openoffice.Office.PresenterScreen/"_ustr,
            PresenterConfigurationAccess::READ_WRITE);
        aConfiguration.GoToChild(u"Presenter"_ustr);
        Any aValue;
        switch (eViewMode)
        {
            default:
            case VM_Standard:
                aValue <<= sal_Int32(0);
                break;

            case VM_Notes:
                aValue <<= sal_Int32(1);
                break;

            case VM_SlideOverview:
                aValue <<= sal_Int32(2);
                break;
        }

        aConfiguration.SetProperty (u"InitialViewMode"_ustr, aValue);
        aConfiguration.CommitChanges();
    }
    catch (Exception&)
    {
    }
}

void PresenterWindowManager::AddLayoutListener (
    const Reference<document::XEventListener>& rxListener)
{
    maLayoutListeners.push_back(rxListener);
}

void PresenterWindowManager::RemoveLayoutListener (
    const Reference<document::XEventListener>& rxListener)
{
    // Assume that there are no multiple entries.
    auto iListener = std::find(maLayoutListeners.begin(), maLayoutListeners.end(), rxListener);
    if (iListener != maLayoutListeners.end())
        maLayoutListeners.erase(iListener);
}

void PresenterWindowManager::Layout()
{
    if (!mxParentWindow.is() || mbIsLayouting)
        return;

    mbIsLayoutPending = false;
    mbIsLayouting = true;
    mxScaledBackgroundBitmap = nullptr;
    mxClipPolygon = nullptr;

    try
    {
        if (mbIsSlideSorterActive)
            LayoutSlideSorterMode();
        else if (mbIsHelpViewActive)
            LayoutHelpMode();
        else
            switch (meLayoutMode)
            {
                case LayoutMode::Standard:
                default:
                    LayoutStandardMode();
                    break;

                case LayoutMode::Notes:
                    LayoutNotesMode();
                    break;
            }
    }
    catch (Exception&)
    {
        OSL_ASSERT(false);
        throw;
    }

    mbIsLayouting = false;
}

void PresenterWindowManager::LayoutStandardMode()
{
    awt::Rectangle aBox = mxParentWindow->getPosSize();

    const double nGoldenRatio ((1 + sqrt(5.0)) / 2);
    const double nGap (20);
    const double nHorizontalSlideDivide (aBox.Width / nGoldenRatio);
    double nSlidePreviewTop (0);


    // For the current slide view calculate the outer height from the outer
    // width.  This takes into account the slide aspect ratio and thus has to
    // go over the inner pane size.
    PresenterPaneContainer::SharedPaneDescriptor pPane (
        mpPaneContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL));
    if (pPane)
    {
        const awt::Size aCurrentSlideOuterBox(CalculatePaneSize(
            nHorizontalSlideDivide - 1.5*nGap,
            PresenterPaneFactory::msCurrentSlidePreviewPaneURL));
        nSlidePreviewTop = (aBox.Height - aCurrentSlideOuterBox.Height) / 2;
        double Temp=nGap;
        /// check whether RTL interface or not
        if(AllSettings::GetLayoutRTL())
            Temp=aBox.Width - aCurrentSlideOuterBox.Width - nGap;
        SetPanePosSizeAbsolute (
            PresenterPaneFactory::msCurrentSlidePreviewPaneURL,
            Temp,
            nSlidePreviewTop,
            aCurrentSlideOuterBox.Width,
            aCurrentSlideOuterBox.Height);
    }

    // For the next slide view calculate the outer height from the outer
    // width.  This takes into account the slide aspect ratio and thus has to
    // go over the inner pane size.
    pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNextSlidePreviewPaneURL);
    if (pPane)
    {
        const awt::Size aNextSlideOuterBox (CalculatePaneSize(
            aBox.Width - nHorizontalSlideDivide - 1.5*nGap,
            PresenterPaneFactory::msNextSlidePreviewPaneURL));
        double Temp=aBox.Width - aNextSlideOuterBox.Width - nGap;
        /// check whether RTL interface or not
        if(AllSettings::GetLayoutRTL())
            Temp=nGap;
        SetPanePosSizeAbsolute (
            PresenterPaneFactory::msNextSlidePreviewPaneURL,
            Temp,
            nSlidePreviewTop,
            aNextSlideOuterBox.Width,
            aNextSlideOuterBox.Height);
    }

    LayoutToolBar();
}

void PresenterWindowManager::LayoutNotesMode()
{
    awt::Rectangle aBox = mxParentWindow->getPosSize();

    const geometry::RealRectangle2D aToolBarBox (LayoutToolBar());

    const double nGoldenRatio ((1 + sqrt(5.0)) / 2);
    const double nGap (20);
    const double nPrimaryWidth (aBox.Width / nGoldenRatio);
    const double nSecondaryWidth (aBox.Width - nPrimaryWidth);
    const double nTertiaryWidth (nSecondaryWidth / nGoldenRatio);
    double nSlidePreviewTop (0);
    double nNotesViewBottom (aToolBarBox.Y1 - nGap);
     /// check whether RTL interface or not


    // The notes view has no fixed aspect ratio.
    PresenterPaneContainer::SharedPaneDescriptor pPane (
        mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNotesPaneURL));
    if (pPane)
    {
        const geometry::RealSize2D aNotesViewOuterSize(
            nPrimaryWidth - 1.5*nGap + 0.5,
            nNotesViewBottom);
        nSlidePreviewTop = (aBox.Height
            - aToolBarBox.Y2 + aToolBarBox.Y1 - aNotesViewOuterSize.Height) / 2;
        /// check whether RTL interface or not
        double Temp=aBox.Width - aNotesViewOuterSize.Width - nGap;
        if(AllSettings::GetLayoutRTL())
            Temp=nGap;
        SetPanePosSizeAbsolute (
            PresenterPaneFactory::msNotesPaneURL,
            Temp,
            nSlidePreviewTop,
            aNotesViewOuterSize.Width,
            aNotesViewOuterSize.Height);
        nNotesViewBottom = nSlidePreviewTop + aNotesViewOuterSize.Height;
    }

    // For the current slide view calculate the outer height from the outer
    // width.  This takes into account the slide aspect ratio and thus has to
    // go over the inner pane size.
    pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL);
    if (pPane)
    {
        const awt::Size aCurrentSlideOuterBox(CalculatePaneSize(
            nSecondaryWidth - 1.5*nGap,
            PresenterPaneFactory::msCurrentSlidePreviewPaneURL));
        /// check whether RTL interface or not
        double Temp=nGap;
        if(AllSettings::GetLayoutRTL())
            Temp=aBox.Width - aCurrentSlideOuterBox.Width - nGap;
        SetPanePosSizeAbsolute (
            PresenterPaneFactory::msCurrentSlidePreviewPaneURL,
            Temp,
            nSlidePreviewTop,
            aCurrentSlideOuterBox.Width,
            aCurrentSlideOuterBox.Height);
    }

    // For the next slide view calculate the outer height from the outer
    // width.  This takes into account the slide aspect ratio and thus has to
    // go over the inner pane size.
    pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNextSlidePreviewPaneURL);
    if (!pPane)
        return;

    const awt::Size aNextSlideOuterBox (CalculatePaneSize(
        nTertiaryWidth,
        PresenterPaneFactory::msNextSlidePreviewPaneURL));
    /// check whether RTL interface or not
    double Temp=nGap;
    if(AllSettings::GetLayoutRTL())
        Temp=aBox.Width - aNextSlideOuterBox.Width - nGap;
    SetPanePosSizeAbsolute (
        PresenterPaneFactory::msNextSlidePreviewPaneURL,
        Temp,
        nNotesViewBottom - aNextSlideOuterBox.Height,
        aNextSlideOuterBox.Width,
        aNextSlideOuterBox.Height);


}

void PresenterWindowManager::LayoutSlideSorterMode()
{
    const geometry::RealRectangle2D aToolBarBox (LayoutToolBar());

    awt::Rectangle aWindowBox = mxParentWindow->getPosSize();
    const double nGap (20);
    SetPanePosSizeAbsolute(
        mpPaneContainer->GetPaneURLForViewURL(PresenterViewFactory::msSlideSorterURL),
        nGap,
        nGap,
        aWindowBox.Width - 2*nGap,
        aToolBarBox.Y1 - 2*nGap);
}

void PresenterWindowManager::LayoutHelpMode()
{
    const geometry::RealRectangle2D aToolBarBox (LayoutToolBar());

    awt::Rectangle aWindowBox = mxParentWindow->getPosSize();
    const double nGap (20);
    const double nGoldenRatio ((1 + sqrt(5.0)) / 2);
    const double nWidth = ::std::min(aWindowBox.Width - 2*nGap, aWindowBox.Width/nGoldenRatio);
    SetPanePosSizeAbsolute(
        mpPaneContainer->GetPaneURLForViewURL(PresenterViewFactory::msHelpViewURL),
        (aWindowBox.Width - nWidth)/2,
        nGap,
        nWidth,
        aToolBarBox.Y1 - 2*nGap);
}

geometry::RealRectangle2D PresenterWindowManager::LayoutToolBar()
{
    double nToolBarWidth (400);
    double nToolBarHeight (80);

    // Get access to the tool bar.
    PresenterPaneContainer::SharedPaneDescriptor pDescriptor(
        mpPaneContainer->FindPaneURL(PresenterPaneFactory::msToolBarPaneURL));
    if (pDescriptor)
    {
        PresenterToolBarView* pToolBarView
            = dynamic_cast<PresenterToolBarView*>(pDescriptor->mxView.get());
        if (pToolBarView != nullptr && pToolBarView->GetPresenterToolBar().is())
        {
            geometry::RealSize2D aSize (pToolBarView->GetPresenterToolBar()->GetMinimalSize());

            if (mpPaneBorderPainter.is())
            {
                const awt::Rectangle aBox (mpPaneBorderPainter->addBorder (
                    PresenterPaneFactory::msToolBarPaneURL,
                    awt::Rectangle(
                        0,
                        0,
                        PresenterGeometryHelper::Round(aSize.Width),
                        PresenterGeometryHelper::Round(aSize.Height)),
                    BorderType::TOTAL));

                nToolBarWidth = aBox.Width;
                nToolBarHeight = aBox.Height;
            }
            else
            {
                nToolBarWidth = aSize.Width + 20;
                nToolBarHeight = aSize.Height + 10;
            }
        }
    }

    const awt::Rectangle aBox = mxParentWindow->getPosSize();
    const double nToolBarX ((aBox.Width - nToolBarWidth) / 2);
    const double nToolBarY (aBox.Height - nToolBarHeight);
    SetPanePosSizeAbsolute(
        PresenterPaneFactory::msToolBarPaneURL,
        nToolBarX,
        nToolBarY,
        nToolBarWidth,
        nToolBarHeight);

    return geometry::RealRectangle2D(
        nToolBarX,
        nToolBarY,
        nToolBarX + nToolBarWidth - 1,
        nToolBarY + nToolBarHeight - 1);
}

awt::Size PresenterWindowManager::CalculatePaneSize (
    const double nOuterWidth,
    const OUString& rsPaneURL)
{
    // Calculate the inner width by removing the pane border.
    awt::Rectangle aInnerBox (mpPaneBorderPainter->RemoveBorder (
        rsPaneURL,
        awt::Rectangle(0,0,
            sal_Int32(nOuterWidth+0.5),sal_Int32(nOuterWidth)),
        BorderType::TOTAL));

    // Calculate the inner height with the help of the slide aspect ratio.
    const double nCurrentSlideInnerHeight (
        aInnerBox.Width / mpPresenterController->GetSlideAspectRatio());

    // Add the pane border to get the outer box.
    awt::Rectangle aOuterBox (mpPaneBorderPainter->AddBorder (
        rsPaneURL,
        awt::Rectangle(0,0,
            aInnerBox.Width,sal_Int32(nCurrentSlideInnerHeight+0.5)),
        BorderType::TOTAL));

    return awt::Size(aOuterBox.Width, aOuterBox.Height);
}

void PresenterWindowManager::NotifyLayoutModeChange()
{
    document::EventObject aEvent;
    aEvent.Source = Reference<XInterface>(static_cast<XWeak*>(this));

    LayoutListenerContainer aContainerCopy (maLayoutListeners);
    for (const auto& rxListener : aContainerCopy)
    {
        if (rxListener.is())
        {
            try
            {
                rxListener->notifyEvent(aEvent);
            }
            catch (lang::DisposedException&)
            {
                RemoveLayoutListener(rxListener);
            }
            catch (RuntimeException&)
            {
            }
        }
    }
}

void PresenterWindowManager::NotifyDisposing()
{
    lang::EventObject aEvent;
    aEvent.Source = static_cast<XWeak*>(this);

    LayoutListenerContainer aContainer;
    aContainer.swap(maLayoutListeners);
    for (auto& rxListener : aContainer)
    {
        if (rxListener.is())
        {
            try
            {
                rxListener->disposing(aEvent);
            }
            catch (lang::DisposedException&)
            {
            }
            catch (RuntimeException&)
            {
            }
        }
    }
}

void PresenterWindowManager::UpdateWindowSize (const Reference<awt::XWindow>& rxBorderWindow)
{
    PresenterPaneContainer::SharedPaneDescriptor pDescriptor (
        mpPaneContainer->FindBorderWindow(rxBorderWindow));
    if (pDescriptor)
    {
        mxClipPolygon = nullptr;

        // ToTop is called last because it may invalidate the iterator.
        if ( ! mbIsLayouting)
            mpPaneContainer->ToTop(pDescriptor);
    }
}

void PresenterWindowManager::PaintBackground (const awt::Rectangle& rUpdateBox)
{
    if ( ! mxParentWindow.is())
        return;

    Reference<rendering::XGraphicDevice> xDevice (mxParentCanvas->getDevice());
    if ( ! xDevice.is())
        return;

    // Create a polygon for the background and for clipping.
    Reference<rendering::XPolyPolygon2D> xBackgroundPolygon (
        PresenterGeometryHelper::CreatePolygon(mxParentWindow->getPosSize(), xDevice));
    if ( ! mxClipPolygon.is())
        mxClipPolygon = CreateClipPolyPolygon();

    // Create View- and RenderState structs.
    const rendering::ViewState aViewState(
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        PresenterGeometryHelper::CreatePolygon(rUpdateBox, xDevice));
    rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        mxClipPolygon,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);

    // Paint the background.
    if (!mpBackgroundBitmap)
        return;

    ProvideBackgroundBitmap();

    if (mxScaledBackgroundBitmap.is())
    {
        const geometry::IntegerSize2D aBitmapSize(mxScaledBackgroundBitmap->getSize());
        Sequence<rendering::Texture> aTextures
        {
            {
                geometry::AffineMatrix2D( aBitmapSize.Width,0,0, 0,aBitmapSize.Height,0),
                1,
                0,
                mxScaledBackgroundBitmap,
                nullptr,
                nullptr,
                rendering::StrokeAttributes(),
                rendering::TexturingMode::REPEAT,
                rendering::TexturingMode::REPEAT
            }
        };

        mxParentCanvas->fillTexturedPolyPolygon(
            xBackgroundPolygon,
            aViewState,
            aRenderState,
            aTextures);
    }
    else
    {
        const util::Color aBackgroundColor (mpBackgroundBitmap->maReplacementColor);
        auto pDeviceColor = aRenderState.DeviceColor.getArray();
        pDeviceColor[0] = ((aBackgroundColor >> 16) & 0x0ff) / 255.0;
        pDeviceColor[1] = ((aBackgroundColor >> 8) & 0x0ff) / 255.0;
        pDeviceColor[2] = ((aBackgroundColor >> 0) & 0x0ff) / 255.0;
        pDeviceColor[3] = ((aBackgroundColor >> 24) & 0x0ff) / 255.0;
        mxParentCanvas->fillPolyPolygon(
            xBackgroundPolygon,
            aViewState,
            aRenderState);
    }
}

void PresenterWindowManager::ProvideBackgroundBitmap()
{
    if (  mxScaledBackgroundBitmap.is())
        return;

    Reference<rendering::XBitmap> xBitmap (mpBackgroundBitmap->GetNormalBitmap());
    if (!xBitmap.is())
        return;

    const bool bStretchVertical (mpBackgroundBitmap->meVerticalTexturingMode
        == PresenterBitmapDescriptor::Stretch);
    const bool bStretchHorizontal (mpBackgroundBitmap->meHorizontalTexturingMode
        == PresenterBitmapDescriptor::Stretch);
    if (bStretchHorizontal || bStretchVertical)
    {
        geometry::RealSize2D aSize;
        if (bStretchVertical)
            aSize.Height = mxParentWindow->getPosSize().Height;
        else
            aSize.Height = xBitmap->getSize().Height;
        if (bStretchHorizontal)
            aSize.Width = mxParentWindow->getPosSize().Width;
        else
            aSize.Width = xBitmap->getSize().Width;
        mxScaledBackgroundBitmap = xBitmap->getScaledBitmap(aSize, false);
    }
    else
    {
        mxScaledBackgroundBitmap = std::move(xBitmap);
    }
}

Reference<rendering::XPolyPolygon2D> PresenterWindowManager::CreateClipPolyPolygon() const
{
    // Create a clip polygon that includes the whole update area but has the
    // content windows as holes.
    const sal_Int32 nPaneCount (mpPaneContainer->maPanes.size());
    ::std::vector<awt::Rectangle> aRectangles;
    aRectangles.reserve(1+nPaneCount);
    aRectangles.push_back(mxParentWindow->getPosSize());
    for (const auto& pDescriptor : mpPaneContainer->maPanes)
    {
        if ( ! pDescriptor->mbIsActive)
            continue;
        if ( ! pDescriptor->mbIsOpaque)
            continue;
        if ( ! pDescriptor->mxBorderWindow.is() || ! pDescriptor->mxContentWindow.is())
            continue;
        Reference<awt::XWindow2> xWindow (pDescriptor->mxBorderWindow, UNO_QUERY);
        if (xWindow.is() && ! xWindow->isVisible())
            continue;

        const awt::Rectangle aOuterBorderBox (pDescriptor->mxBorderWindow->getPosSize());
        awt::Rectangle aInnerBorderBox (pDescriptor->mxContentWindow->getPosSize());
        aInnerBorderBox.X += aOuterBorderBox.X;
        aInnerBorderBox.Y += aOuterBorderBox.Y;
        aRectangles.push_back(aInnerBorderBox);
    }
    Reference<rendering::XPolyPolygon2D> xPolyPolygon (
        PresenterGeometryHelper::CreatePolygon(
            aRectangles,
            mxParentCanvas->getDevice()));
    if (xPolyPolygon.is())
        xPolyPolygon->setFillRule(rendering::FillRule_EVEN_ODD);
    return xPolyPolygon;
}

void PresenterWindowManager::Update()
{
    mxClipPolygon = nullptr;
    mbIsLayoutPending = true;

    mpPresenterController->GetPaintManager()->Invalidate(mxParentWindow);
}

void PresenterWindowManager::ThrowIfDisposed() const
{
    if (rBHelper.bDisposed || rBHelper.bInDispose)
    {
        throw lang::DisposedException (
            u"PresenterWindowManager has already been disposed"_ustr,
            const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this)));
    }
}

} // end of namespace ::sdext::presenter

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