/* -*- 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 "PresenterSlideSorter.hxx"
#include "PresenterButton.hxx"
#include "PresenterCanvasHelper.hxx"
#include "PresenterGeometryHelper.hxx"
#include "PresenterPaintManager.hxx"
#include "PresenterPaneBase.hxx"
#include "PresenterScrollBar.hxx"
#include "PresenterUIPainter.hxx"
#include "PresenterWindowManager.hxx"
#include <DrawController.hxx>
#include <framework/ConfigurationController.hxx>
#include <com/sun/star/rendering/XBitmapCanvas.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/TextDirection.hpp>
#include <algorithm>
#include <math.h>
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::drawing::framework;
 
namespace {
    const sal_Int32 gnVerticalGap (10);
    const sal_Int32 gnVerticalBorder (10);
    const sal_Int32 gnHorizontalGap (10);
    const sal_Int32 gnHorizontalBorder (10);
 
    const double gnMinimalPreviewWidth (200);
    const double gnPreferredPreviewWidth (300);
    const double gnMaximalPreviewWidth (400);
    const sal_Int32 gnPreferredColumnCount (6);
    const double gnMinimalHorizontalPreviewGap(15);
    const double gnPreferredHorizontalPreviewGap(25);
    const double gnMaximalHorizontalPreviewGap(50);
    const double gnPreferredVerticalPreviewGap(25);
 
    const sal_Int32 gnHorizontalLabelBorder (3);
    const sal_Int32 gnHorizontalLabelPadding (5);
 
    const sal_Int32 gnVerticalButtonPadding (gnVerticalGap);
}
 
namespace sdext::presenter {
 
namespace {
    sal_Int32 round (const double nValue) { return sal::static_int_cast<sal_Int32>(0.5 + nValue); }
    sal_Int32 floor (const double nValue) { return sal::static_int_cast<sal_Int32>(nValue); }
}
 
//===== PresenterSlideSorter::Layout ==========================================
 
class PresenterSlideSorter::Layout
{
public:
    explicit Layout (::rtl::Reference<PresenterScrollBar> xVerticalScrollBar);
 
    void Update (const geometry::RealRectangle2D& rBoundingBox, const double nSlideAspectRatio);
    void SetupVisibleArea();
    void UpdateScrollBars();
    bool IsScrollBarNeeded (const sal_Int32 nSlideCount);
    geometry::RealPoint2D GetLocalPosition (const geometry::RealPoint2D& rWindowPoint) const;
    geometry::RealPoint2D GetWindowPosition(const geometry::RealPoint2D& rLocalPoint) const;
    sal_Int32 GetColumn (const geometry::RealPoint2D& rLocalPoint) const;
    sal_Int32 GetRow (const geometry::RealPoint2D& rLocalPoint,
        const bool bReturnInvalidValue = false) const;
    sal_Int32 GetSlideIndexForPosition (const css::geometry::RealPoint2D& rPoint) const;
    css::geometry::RealPoint2D GetPoint (
        const sal_Int32 nSlideIndex,
        const sal_Int32 nRelativeHorizontalPosition,
        const sal_Int32 nRelativeVerticalPosition) const;
    css::awt::Rectangle GetBoundingBox (const sal_Int32 nSlideIndex) const;
    void ForAllVisibleSlides (const ::std::function<void (sal_Int32)>& rAction);
    sal_Int32 GetFirstVisibleSlideIndex() const;
    sal_Int32 GetLastVisibleSlideIndex() const;
    bool SetHorizontalOffset (const double nOffset);
    bool SetVerticalOffset (const double nOffset);
 
    css::geometry::RealRectangle2D maBoundingBox;
    css::geometry::IntegerSize2D maPreviewSize;
    sal_Int32 mnHorizontalOffset;
    sal_Int32 mnVerticalOffset;
    sal_Int32 mnHorizontalGap;
    sal_Int32 mnVerticalGap;
    sal_Int32 mnHorizontalBorder;
    sal_Int32 mnVerticalBorder;
    sal_Int32 mnRowCount;
    sal_Int32 mnColumnCount;
    sal_Int32 mnSlideCount;
    sal_Int32 mnFirstVisibleColumn;
    sal_Int32 mnLastVisibleColumn;
    sal_Int32 mnFirstVisibleRow;
    sal_Int32 mnLastVisibleRow;
 
private:
    ::rtl::Reference<PresenterScrollBar> mpVerticalScrollBar;
 
    sal_Int32 GetIndex (const sal_Int32 nRow, const sal_Int32 nColumn) const;
    sal_Int32 GetRow (const sal_Int32 nSlideIndex) const;
    sal_Int32 GetColumn (const sal_Int32 nSlideIndex) const;
};
 
//==== PresenterSlideSorter::MouseOverManager =================================
 
class PresenterSlideSorter::MouseOverManager
{
public:
    MouseOverManager (
        const Reference<container::XIndexAccess>& rxSlides,
        const std::shared_ptr<PresenterTheme>& rpTheme,
        const Reference<awt::XWindow>& rxInvalidateTarget,
        std::shared_ptr<PresenterPaintManager> xPaintManager);
    MouseOverManager(const MouseOverManager&) = delete;
    MouseOverManager& operator=(const MouseOverManager&) = delete;
 
    void Paint (
        const sal_Int32 nSlideIndex,
        const Reference<rendering::XCanvas>& rxCanvas,
        const Reference<rendering::XPolyPolygon2D>& rxClip);
 
    void SetSlide (
        const sal_Int32 nSlideIndex,
        const awt::Rectangle& rBox);
 
private:
    Reference<rendering::XCanvas> mxCanvas;
    const Reference<container::XIndexAccess> mxSlides;
    SharedBitmapDescriptor mpLeftLabelBitmap;
    SharedBitmapDescriptor mpCenterLabelBitmap;
    SharedBitmapDescriptor mpRightLabelBitmap;
    PresenterTheme::SharedFontDescriptor mpFont;
    sal_Int32 mnSlideIndex;
    awt::Rectangle maSlideBoundingBox;
    OUString msText;
    Reference<rendering::XBitmap> mxBitmap;
    Reference<awt::XWindow> mxInvalidateTarget;
    std::shared_ptr<PresenterPaintManager> mpPaintManager;
 
    void SetCanvas (
        const Reference<rendering::XCanvas>& rxCanvas);
    /** Create a bitmap that shows the given text and is not wider than the
        given maximal width.
    */
    Reference<rendering::XBitmap> CreateBitmap (
        const OUString& rsText,
        const sal_Int32 nMaximalWidth) const;
    void Invalidate();
    geometry::IntegerSize2D CalculateLabelSize (
        const OUString& rsText) const;
    OUString GetFittingText (const OUString& rsText, const double nMaximalWidth) const;
    void PaintButtonBackground (
        const Reference<rendering::XCanvas>& rxCanvas,
        const geometry::IntegerSize2D& rSize) const;
};
 
//==== PresenterSlideSorter::CurrentSlideFrameRenderer ========================
 
class PresenterSlideSorter::CurrentSlideFrameRenderer
{
public:
    CurrentSlideFrameRenderer (
        const css::uno::Reference<css::uno::XComponentContext>& rxContext,
        const css::uno::Reference<css::rendering::XCanvas>& rxCanvas);
 
    void PaintCurrentSlideFrame (
        const awt::Rectangle& rSlideBoundingBox,
        const Reference<rendering::XCanvas>& rxCanvas,
        const geometry::RealRectangle2D& rClipBox);
 
    /** Enlarge the given rectangle to include the current slide indicator.
    */
    awt::Rectangle GetBoundingBox (
        const awt::Rectangle& rSlideBoundingBox);
 
private:
    SharedBitmapDescriptor mpTopLeft;
    SharedBitmapDescriptor mpTop;
    SharedBitmapDescriptor mpTopRight;
    SharedBitmapDescriptor mpLeft;
    SharedBitmapDescriptor mpRight;
    SharedBitmapDescriptor mpBottomLeft;
    SharedBitmapDescriptor mpBottom;
    SharedBitmapDescriptor mpBottomRight;
    sal_Int32 mnTopFrameSize;
    sal_Int32 mnLeftFrameSize;
    sal_Int32 mnRightFrameSize;
    sal_Int32 mnBottomFrameSize;
 
    static void PaintBitmapOnce(
        const css::uno::Reference<css::rendering::XBitmap>& rxBitmap,
        const css::uno::Reference<css::rendering::XCanvas>& rxCanvas,
        const Reference<rendering::XPolyPolygon2D>& rxClip,
        const double nX,
        const double nY);
    static void PaintBitmapTiled(
        const css::uno::Reference<css::rendering::XBitmap>& rxBitmap,
        const css::uno::Reference<css::rendering::XCanvas>& rxCanvas,
        const geometry::RealRectangle2D& rClipBox,
        const double nX,
        const double nY,
        const double nWidth,
        const double nHeight);
};
 
//===== PresenterSlideSorter ==================================================
 
PresenterSlideSorter::PresenterSlideSorter (
    const Reference<uno::XComponentContext>& rxContext,
    const rtl::Reference<sd::framework::ResourceId>& rxViewId,
    const rtl::Reference<::sd::DrawController>& rxController,
    const ::rtl::Reference<PresenterController>& rpPresenterController)
    : mxComponentContext(rxContext),
      mxViewId(rxViewId),
      mpPresenterController(rpPresenterController),
      mxSlideShowController(mpPresenterController->GetSlideShowController()),
      mbIsLayoutPending(true),
      mnSlideIndexMousePressed(-1),
      mnCurrentSlideIndex(-1),
      mnSeparatorY(0),
      maSeparatorColor(0x00ffffff)
{
    if ( ! rxContext.is()
        || ! rxViewId.is()
        || ! rxController.is()
        || ! rpPresenterController)
    {
        throw lang::IllegalArgumentException();
    }
 
    if ( ! mxSlideShowController.is())
        throw RuntimeException();
 
    try
    {
        // Get pane and window.
        rtl::Reference<sd::framework::ConfigurationController> xCC (rxController->getConfigurationController());
        Reference<lang::XMultiComponentFactory> xFactory (
            mxComponentContext->getServiceManager(), UNO_SET_THROW);
 
        mxPane = dynamic_cast<sd::framework::AbstractPane*>(xCC->getResource(rxViewId->getAnchor()).get());
        mxWindow = mxPane->getWindow();
 
        // Add window listener.
        mxWindow->addWindowListener(this);
        mxWindow->addPaintListener(this);
        mxWindow->addMouseListener(this);
        mxWindow->addMouseMotionListener(this);
        mxWindow->setVisible(true);
 
        // Remember the current slide.
        mnCurrentSlideIndex = mxSlideShowController->getCurrentSlideIndex();
 
        // Create the scroll bar.
        mpVerticalScrollBar.set(
            new PresenterVerticalScrollBar(
                rxContext,
                mxWindow,
                mpPresenterController->GetPaintManager(),
                [this] (double const offset) { return this->SetVerticalOffset(offset); }));
 
        mpCloseButton = PresenterButton::Create(
            rxContext,
            mpPresenterController,
            mpPresenterController->GetTheme(),
            mxWindow,
            mxCanvas,
            u"SlideSorterCloser"_ustr);
 
        if (mpPresenterController->GetTheme() != nullptr)
        {
            PresenterTheme::SharedFontDescriptor pFont (
                mpPresenterController->GetTheme()->GetFont(u"ButtonFont"_ustr));
            if (pFont)
                maSeparatorColor = pFont->mnColor;
        }
 
        // Create the layout.
        mpLayout = std::make_shared<Layout>(mpVerticalScrollBar);
 
        // Create the preview cache.
        mxPreviewCache = new sd::presenter::PresenterPreviewCache();
        Reference<container::XIndexAccess> xSlides (mxSlideShowController, UNO_QUERY);
        mxPreviewCache->setDocumentSlides(xSlides, rxController->getModel());
        mxPreviewCache->addPreviewCreationNotifyListener(this);
        if (xSlides.is())
        {
            mpLayout->mnSlideCount = xSlides->getCount();
        }
 
        // Create the mouse over manager.
        mpMouseOverManager.reset(new MouseOverManager(
            Reference<container::XIndexAccess>(mxSlideShowController, UNO_QUERY),
            mpPresenterController->GetTheme(),
            mxWindow,
            mpPresenterController->GetPaintManager()));
 
        // Listen for changes of the current slide.
        rxController->addPropertyChangeListener(
            u"CurrentPage"_ustr,
            this);
 
        // Move the current slide in the center of the window.
        const awt::Rectangle aCurrentSlideBBox (mpLayout->GetBoundingBox(mnCurrentSlideIndex));
        const awt::Rectangle aWindowBox (mxWindow->getPosSize());
        SetHorizontalOffset(aCurrentSlideBBox.X - aWindowBox.Width/2.0);
    }
    catch (RuntimeException&)
    {
        {
            std::unique_lock l(m_aMutex);
            disposing(l);
        }
        throw;
    }
}
 
PresenterSlideSorter::~PresenterSlideSorter()
{
}
 
void PresenterSlideSorter::disposing(std::unique_lock<std::mutex>&)
{
    mxComponentContext = nullptr;
    mxViewId = nullptr;
    mxPane = nullptr;
 
    if (mpVerticalScrollBar.is())
    {
        rtl::Reference<PresenterScrollBar> xComponent = std::move(mpVerticalScrollBar);
        if (xComponent.is())
            xComponent->dispose();
    }
    if (mpCloseButton.is())
    {
        rtl::Reference<PresenterButton> xComponent = std::move(mpCloseButton);
        if (xComponent.is())
            xComponent->dispose();
    }
 
    if (mxCanvas.is())
    {
        Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY);
        if (xComponent.is())
            xComponent->removeEventListener(static_cast<awt::XWindowListener*>(this));
        mxCanvas = nullptr;
    }
    mpPresenterController = nullptr;
    mxSlideShowController = nullptr;
    mpLayout.reset();
    mpMouseOverManager.reset();
 
    if (mxPreviewCache.is())
    {
        mxPreviewCache->removePreviewCreationNotifyListener(this);
        if (mxPreviewCache.is())
            mxPreviewCache->dispose();
        mxPreviewCache = nullptr;
    }
 
    if (mxWindow.is())
    {
        mxWindow->removeWindowListener(this);
        mxWindow->removePaintListener(this);
        mxWindow->removeMouseListener(this);
        mxWindow->removeMouseMotionListener(this);
    }
}
 
//----- lang::XEventListener --------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::disposing (const lang::EventObject& rEventObject)
{
    if (rEventObject.Source == mxWindow)
    {
        mxWindow = nullptr;
        dispose();
    }
    else if (rEventObject.Source == cppu::getXWeak(mxPreviewCache.get()))
    {
        mxPreviewCache = nullptr;
        dispose();
    }
    else if (rEventObject.Source == mxCanvas)
    {
        mxCanvas = nullptr;
        mbIsLayoutPending = true;
 
        mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
    }
}
 
//----- XWindowListener -------------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::windowResized (const awt::WindowEvent&)
{
    {
        std::unique_lock l(m_aMutex);
        throwIfDisposed(l);
    }
    mbIsLayoutPending = true;
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
}
 
void SAL_CALL PresenterSlideSorter::windowMoved (const awt::WindowEvent&)
{
    std::unique_lock l(m_aMutex);
    throwIfDisposed(l);
}
 
void SAL_CALL PresenterSlideSorter::windowShown (const lang::EventObject&)
{
    {
        std::unique_lock l(m_aMutex);
        throwIfDisposed(l);
    }
    mbIsLayoutPending = true;
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
}
 
void SAL_CALL PresenterSlideSorter::windowHidden (const lang::EventObject&)
{
    std::unique_lock l(m_aMutex);
    throwIfDisposed(l);
}
 
//----- XPaintListener --------------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::windowPaint (const css::awt::PaintEvent& rEvent)
{
    // Deactivated views must not be painted.
    if ( ! mbIsPresenterViewActive)
        return;
 
    Paint(rEvent.UpdateRect);
 
    Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY);
    if (xSpriteCanvas.is())
        xSpriteCanvas->updateScreen(false);
}
 
//----- XMouseListener --------------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::mousePressed (const css::awt::MouseEvent& rEvent)
{
    css::awt::MouseEvent rTemp =rEvent;
    /// check whether RTL interface or not
    if(AllSettings::GetLayoutRTL()){
        awt::Rectangle aBox = mxWindow->getPosSize();
        rTemp.X=aBox.Width-rEvent.X;
    }
    const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y);
    mnSlideIndexMousePressed = mpLayout->GetSlideIndexForPosition(aPosition);
}
 
void SAL_CALL PresenterSlideSorter::mouseReleased (const css::awt::MouseEvent& rEvent)
{
    css::awt::MouseEvent rTemp =rEvent;
    /// check whether RTL interface or not
    if(AllSettings::GetLayoutRTL()){
        awt::Rectangle aBox = mxWindow->getPosSize();
        rTemp.X=aBox.Width-rEvent.X;
    }
    const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y);
    const sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition));
 
    if (nSlideIndex != mnSlideIndexMousePressed || mnSlideIndexMousePressed < 0)
        return;
 
    switch (rEvent.ClickCount)
    {
        case 1:
        default:
            GotoSlide(nSlideIndex);
            break;
 
        case 2:
            OSL_ASSERT(mpPresenterController);
            OSL_ASSERT(mpPresenterController->GetWindowManager());
            mpPresenterController->GetWindowManager()->SetSlideSorterState(false);
            GotoSlide(nSlideIndex);
            break;
    }
}
 
void SAL_CALL PresenterSlideSorter::mouseEntered (const css::awt::MouseEvent&) {}
 
void SAL_CALL PresenterSlideSorter::mouseExited (const css::awt::MouseEvent&)
{
    mnSlideIndexMousePressed = -1;
    if (mpMouseOverManager != nullptr)
        mpMouseOverManager->SetSlide(mnSlideIndexMousePressed, awt::Rectangle(0,0,0,0));
}
 
//----- XMouseMotionListener --------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::mouseMoved (const css::awt::MouseEvent& rEvent)
{
    if (mpMouseOverManager == nullptr)
        return;
 
    css::awt::MouseEvent rTemp =rEvent;
    /// check whether RTL interface or not
    if(AllSettings::GetLayoutRTL()){
        awt::Rectangle aBox = mxWindow->getPosSize();
        rTemp.X=aBox.Width-rEvent.X;
    }
    const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y);
    sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition));
 
    if (nSlideIndex < 0)
    {
        mnSlideIndexMousePressed = -1;
        mpMouseOverManager->SetSlide(nSlideIndex, awt::Rectangle(0,0,0,0));
    }
    else
    {
        mpMouseOverManager->SetSlide(
            nSlideIndex,
            mpLayout->GetBoundingBox(nSlideIndex));
    }
}
 
void SAL_CALL PresenterSlideSorter::mouseDragged (const css::awt::MouseEvent&) {}
 
//----- AbstractResource -----------------------------------------------------------
 
rtl::Reference<sd::framework::ResourceId> PresenterSlideSorter::getResourceId()
{
    {
        std::unique_lock l(m_aMutex);
        throwIfDisposed(l);
    }
    return mxViewId;
}
 
bool PresenterSlideSorter::isAnchorOnly()
{
    return false;
}
 
//----- XPropertyChangeListener -----------------------------------------------
 
void SAL_CALL PresenterSlideSorter::propertyChange (
    const css::beans::PropertyChangeEvent&)
{}
 
//----- XSlidePreviewCacheListener --------------------------------------------
 
void SAL_CALL PresenterSlideSorter::notifyPreviewCreation (
    sal_Int32 nSlideIndex)
{
    OSL_ASSERT(mpLayout != nullptr);
 
    awt::Rectangle aBBox (mpLayout->GetBoundingBox(nSlideIndex));
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow, aBBox, true);
}
 
//----- XDrawView -------------------------------------------------------------
 
void SAL_CALL PresenterSlideSorter::setCurrentPage (const Reference<drawing::XDrawPage>&)
{
    {
        std::unique_lock l(m_aMutex);
        throwIfDisposed(l);
    }
    ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex());
 
    if (!mxSlideShowController.is())
        return;
 
    const sal_Int32 nNewCurrentSlideIndex (mxSlideShowController->getCurrentSlideIndex());
    if (nNewCurrentSlideIndex == mnCurrentSlideIndex)
        return;
 
    mnCurrentSlideIndex = nNewCurrentSlideIndex;
 
    // Request a repaint of the previous current slide to hide its
    // current slide indicator.
    mpPresenterController->GetPaintManager()->Invalidate(
        mxWindow,
        maCurrentSlideFrameBoundingBox);
 
    // Request a repaint of the new current slide to show its
    // current slide indicator.
    maCurrentSlideFrameBoundingBox = mpCurrentSlideFrameRenderer->GetBoundingBox(
        mpLayout->GetBoundingBox(mnCurrentSlideIndex));
    mpPresenterController->GetPaintManager()->Invalidate(
        mxWindow,
        maCurrentSlideFrameBoundingBox);
 
    ScrollSlideIntoView(mnCurrentSlideIndex);
}
 
Reference<drawing::XDrawPage> SAL_CALL PresenterSlideSorter::getCurrentPage()
{
    {
        std::unique_lock l(m_aMutex);
        throwIfDisposed(l);
    }
    return nullptr;
}
 
 
void PresenterSlideSorter::UpdateLayout()
{
    if ( ! mxWindow.is())
        return;
 
    mbIsLayoutPending = false;
 
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    sal_Int32 nLeftBorderWidth (aWindowBox.X);
 
    // Get border width.
    PresenterPaneContainer::SharedPaneDescriptor pPane (
        mpPresenterController->GetPaneContainer()->FindViewURL(
            mxViewId->getResourceURL()));
    do
    {
        if (!pPane)
            break;
        if ( ! pPane->mxPane.is())
            break;
 
        rtl::Reference<PresenterPaneBorderPainter> xBorderPainter (
            pPane->mxPane->GetPaneBorderPainter());
        if ( ! xBorderPainter.is())
            break;
        xBorderPainter->addBorder (
            mxViewId->getAnchor()->getResourceURL(),
            awt::Rectangle(0, 0, aWindowBox.Width, aWindowBox.Height),
            BorderType::INNER);
    }
    while(false);
 
    // Place vertical separator.
    mnSeparatorY = aWindowBox.Height - mpCloseButton->GetSize().Height - gnVerticalButtonPadding;
 
    PlaceCloseButton(pPane, aWindowBox, nLeftBorderWidth);
 
    geometry::RealRectangle2D aUpperBox(
        gnHorizontalBorder,
        gnVerticalBorder,
        aWindowBox.Width - 2*gnHorizontalBorder,
        mnSeparatorY - gnVerticalGap);
 
    // Determine whether the scroll bar has to be displayed.
    aUpperBox = PlaceScrollBars(aUpperBox);
 
    mpLayout->Update(aUpperBox, GetSlideAspectRatio());
    mpLayout->SetupVisibleArea();
    mpLayout->UpdateScrollBars();
 
    // Tell the preview cache about some of the values.
    mxPreviewCache->setPreviewSize(mpLayout->maPreviewSize);
    mxPreviewCache->setVisibleRange(
        mpLayout->GetFirstVisibleSlideIndex(),
        mpLayout->GetLastVisibleSlideIndex());
 
    // Clear the frame polygon so that it is re-created on the next paint.
    mxPreviewFrame = nullptr;
}
 
geometry::RealRectangle2D PresenterSlideSorter::PlaceScrollBars (
    const geometry::RealRectangle2D& rUpperBox)
{
    mpLayout->Update(rUpperBox, GetSlideAspectRatio());
    bool bIsScrollBarNeeded (false);
    Reference<container::XIndexAccess> xSlides (mxSlideShowController, UNO_QUERY_THROW);
    bIsScrollBarNeeded = mpLayout->IsScrollBarNeeded(xSlides->getCount());
    if (mpVerticalScrollBar)
    {
            if (bIsScrollBarNeeded)
            {
                    if(AllSettings::GetLayoutRTL())
                    {
                            mpVerticalScrollBar->SetPosSize(geometry::RealRectangle2D(
                                                                                      rUpperBox.X1,
                                                                                      rUpperBox.Y1,
                                                                                      rUpperBox.X1 +  mpVerticalScrollBar->GetSize(),
                                                                                      rUpperBox.Y2));
                            mpVerticalScrollBar->SetVisible(true);
                            // Reduce area covered by the scroll bar from the available
                            // space.
                            return geometry::RealRectangle2D(
                                                             rUpperBox.X1 + gnHorizontalGap + mpVerticalScrollBar->GetSize(),
                                                             rUpperBox.Y1,
                                                             rUpperBox.X2,
                                                             rUpperBox.Y2);
                    }
                    else
                    {
                            // if it's not RTL place vertical scroll bar at right border.
                            mpVerticalScrollBar->SetPosSize(geometry::RealRectangle2D(
                                                                                      rUpperBox.X2 - mpVerticalScrollBar->GetSize(),
                                                                                      rUpperBox.Y1,
                                                                                      rUpperBox.X2,
                                                                                      rUpperBox.Y2));
                            mpVerticalScrollBar->SetVisible(true);
                            // Reduce area covered by the scroll bar from the available
                            // space.
                            return geometry::RealRectangle2D(
                                                             rUpperBox.X1,
                                                             rUpperBox.Y1,
                                                             rUpperBox.X2 - mpVerticalScrollBar->GetSize() - gnHorizontalGap,
                                                             rUpperBox.Y2);
                    }
            }
            else
                mpVerticalScrollBar->SetVisible(false);
    }
    return rUpperBox;
}
 
void PresenterSlideSorter::PlaceCloseButton (
    const PresenterPaneContainer::SharedPaneDescriptor& rpPane,
    const awt::Rectangle& rCenterBox,
    const sal_Int32 nLeftBorderWidth)
{
    // Place button.  When the callout is near the center then the button is
    // centered over the callout.  Otherwise it is centered with respect to
    // the whole window.
    sal_Int32 nCloseButtonCenter (rCenterBox.Width/2);
    if (rpPane && rpPane->mxPane.is())
    {
        const sal_Int32 nCalloutCenter (-nLeftBorderWidth);
        const sal_Int32 nDistanceFromWindowCenter (abs(nCalloutCenter - rCenterBox.Width/2));
        const sal_Int32 nButtonWidth (mpCloseButton->GetSize().Width);
        const static sal_Int32 nMaxDistanceForCalloutCentering (nButtonWidth * 2);
        if (nDistanceFromWindowCenter < nMaxDistanceForCalloutCentering)
        {
            if (nCalloutCenter < nButtonWidth/2)
                nCloseButtonCenter = nButtonWidth/2;
            else if (nCalloutCenter > rCenterBox.Width-nButtonWidth/2)
                nCloseButtonCenter = rCenterBox.Width-nButtonWidth/2;
            else
                nCloseButtonCenter = nCalloutCenter;
        }
    }
    mpCloseButton->SetCenter(geometry::RealPoint2D(
        nCloseButtonCenter,
        rCenterBox.Height - mpCloseButton->GetSize().Height/ 2));
}
 
void PresenterSlideSorter::ClearBackground (
    const Reference<rendering::XCanvas>& rxCanvas,
    const awt::Rectangle& rUpdateBox)
{
    OSL_ASSERT(rxCanvas.is());
 
    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    mpPresenterController->GetCanvasHelper()->Paint(
        mpPresenterController->GetViewBackground(mxViewId->getResourceURL()),
        rxCanvas,
        rUpdateBox,
        awt::Rectangle(0,0,aWindowBox.Width,aWindowBox.Height),
        awt::Rectangle());
}
 
double PresenterSlideSorter::GetSlideAspectRatio() const
{
    double nSlideAspectRatio (28.0/21.0);
 
    try
    {
        Reference<container::XIndexAccess> xSlides(mxSlideShowController, UNO_QUERY_THROW);
        if (mxSlideShowController.is() && xSlides->getCount()>0)
        {
            Reference<beans::XPropertySet> xProperties(xSlides->getByIndex(0),UNO_QUERY_THROW);
            sal_Int32 nWidth (28000);
            sal_Int32 nHeight (21000);
            if ((xProperties->getPropertyValue(u"Width"_ustr) >>= nWidth)
                && (xProperties->getPropertyValue(u"Height"_ustr) >>= nHeight)
                && nHeight > 0)
            {
                nSlideAspectRatio = double(nWidth) / double(nHeight);
            }
        }
    }
    catch (RuntimeException&)
    {
        OSL_ASSERT(false);
    }
 
    return nSlideAspectRatio;
}
 
Reference<rendering::XBitmap> PresenterSlideSorter::GetPreview (const sal_Int32 nSlideIndex)
{
    if (nSlideIndex < 0 || nSlideIndex>=mpLayout->mnSlideCount)
        return nullptr;
    else if (mxPane.is())
        return mxPreviewCache->getSlidePreview(nSlideIndex, mxPane->getCanvas());
    else
        return nullptr;
}
 
void PresenterSlideSorter::PaintPreview (
    const Reference<rendering::XCanvas>& rxCanvas,
    const css::awt::Rectangle& rUpdateBox,
    const sal_Int32 nSlideIndex)
{
    OSL_ASSERT(rxCanvas.is());
 
    geometry::IntegerSize2D aSize (mpLayout->maPreviewSize);
 
    if (PresenterGeometryHelper::AreRectanglesDisjoint(
        rUpdateBox,
        mpLayout->GetBoundingBox(nSlideIndex)))
    {
        return;
    }
 
    Reference<rendering::XBitmap> xPreview (GetPreview(nSlideIndex));
    bool isRTL = AllSettings::GetLayoutRTL();
 
    const geometry::RealPoint2D aTopLeft (
                                          mpLayout->GetWindowPosition(
                                                                      mpLayout->GetPoint(nSlideIndex, isRTL?1:-1, -1)));
 
    PresenterBitmapContainer aContainer (
        u"PresenterScreenSettings/ScrollBar/Bitmaps"_ustr,
        std::shared_ptr<PresenterBitmapContainer>(),
        mxComponentContext,
        rxCanvas);
    Reference<container::XIndexAccess> xIndexAccess(mxSlideShowController, UNO_QUERY);
    Reference<drawing::XDrawPage> xPage( xIndexAccess->getByIndex(nSlideIndex), UNO_QUERY);
    bool bTransition = PresenterController::HasTransition(xPage);
    bool bCustomAnimation = PresenterController::HasCustomAnimation(xPage);
 
    // Create clip rectangle as intersection of the current update area and
    // the bounding box of all previews.
    geometry::RealRectangle2D aBoundingBox (mpLayout->maBoundingBox);
    aBoundingBox.Y2 += 1;
    const geometry::RealRectangle2D aClipBox (
        PresenterGeometryHelper::Intersection(
            PresenterGeometryHelper::ConvertRectangle(rUpdateBox),
            aBoundingBox));
    Reference<rendering::XPolyPolygon2D> xClip (
        PresenterGeometryHelper::CreatePolygon(aClipBox, rxCanvas->getDevice()));
 
    const rendering::ViewState aViewState (geometry::AffineMatrix2D(1,0,0, 0,1,0), xClip);
 
    rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(
            1, 0, aTopLeft.X,
            0, 1, aTopLeft.Y),
        nullptr,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);
 
    // Emphasize the current slide.
    if (nSlideIndex == mnCurrentSlideIndex)
    {
        if (mpCurrentSlideFrameRenderer != nullptr)
        {
            const awt::Rectangle aSlideBoundingBox(
                sal::static_int_cast<sal_Int32>(0.5 + aTopLeft.X),
                sal::static_int_cast<sal_Int32>(0.5 + aTopLeft.Y),
                aSize.Width,
                aSize.Height);
            maCurrentSlideFrameBoundingBox
                = mpCurrentSlideFrameRenderer->GetBoundingBox(aSlideBoundingBox);
            mpCurrentSlideFrameRenderer->PaintCurrentSlideFrame (
                aSlideBoundingBox,
                mxCanvas,
                aClipBox);
        }
    }
 
    // Paint the preview.
    if (xPreview.is())
    {
        aSize = xPreview->getSize();
        if (aSize.Width > 0 && aSize.Height > 0)
        {
            rxCanvas->drawBitmap(xPreview, aViewState, aRenderState);
            if( bCustomAnimation )
            {
                const awt::Rectangle aAnimationPreviewBox(aTopLeft.X+3, aTopLeft.Y+aSize.Height-40, 0, 0);
                SharedBitmapDescriptor aAnimationDescriptor = aContainer.GetBitmap(u"Animation"_ustr);
                Reference<rendering::XBitmap> xAnimationIcon (aAnimationDescriptor->GetNormalBitmap());
                rendering::RenderState aAnimationRenderState (
                    geometry::AffineMatrix2D(
                    1, 0, aAnimationPreviewBox.X,
                    0, 1, aAnimationPreviewBox.Y),
                    nullptr,
                    Sequence<double>(4),
                    rendering::CompositeOperation::SOURCE);
                rxCanvas->drawBitmap(xAnimationIcon, aViewState, aAnimationRenderState);
            }
            if( bTransition )
            {
                const awt::Rectangle aTransitionPreviewBox(aTopLeft.X+3, aTopLeft.Y+aSize.Height-20, 0, 0);
                SharedBitmapDescriptor aTransitionDescriptor = aContainer.GetBitmap(u"Transition"_ustr);
                Reference<rendering::XBitmap> xTransitionIcon (aTransitionDescriptor->GetNormalBitmap());
                rendering::RenderState aTransitionRenderState (
                    geometry::AffineMatrix2D(
                    1, 0, aTransitionPreviewBox.X,
                    0, 1, aTransitionPreviewBox.Y),
                    nullptr,
                    Sequence<double>(4),
                    rendering::CompositeOperation::SOURCE);
                rxCanvas->drawBitmap(xTransitionIcon, aViewState, aTransitionRenderState);
            }
        }
    }
 
    // Create a polygon that is used to paint a frame around previews.  Its
    // coordinates are chosen in the local coordinate system of a preview.
    if ( ! mxPreviewFrame.is())
        mxPreviewFrame = PresenterGeometryHelper::CreatePolygon(
            awt::Rectangle(-1, -1, aSize.Width+2, aSize.Height+2),
            rxCanvas->getDevice());
 
    // Paint a border around the preview.
    if (mxPreviewFrame.is())
    {
        const util::Color aFrameColor (0x00000000);
        PresenterCanvasHelper::SetDeviceColor(aRenderState, aFrameColor);
        rxCanvas->drawPolyPolygon(mxPreviewFrame, aViewState, aRenderState);
    }
 
    // Paint mouse over effect.
    mpMouseOverManager->Paint(nSlideIndex, mxCanvas, xClip);
}
 
void PresenterSlideSorter::Paint (const awt::Rectangle& rUpdateBox)
{
    const bool bCanvasChanged ( ! mxCanvas.is());
    if ( ! ProvideCanvas())
        return;
 
    if (mpLayout->mnRowCount<=0 || mpLayout->mnColumnCount<=0)
    {
        OSL_ASSERT(mpLayout->mnRowCount>0 || mpLayout->mnColumnCount>0);
        return;
    }
 
    ClearBackground(mxCanvas, rUpdateBox);
 
    // Give the canvas to the controls.
    if (bCanvasChanged)
    {
        if (mpVerticalScrollBar.is())
            mpVerticalScrollBar->SetCanvas(mxCanvas);
        if (mpCloseButton.is())
            mpCloseButton->SetCanvas(mxCanvas, mxWindow);
    }
 
    // Now that the controls have a canvas we can do the layouting.
    if (mbIsLayoutPending)
        UpdateLayout();
 
    // Paint the horizontal separator.
    rendering::RenderState aRenderState (geometry::AffineMatrix2D(1,0,0, 0,1,0),
            nullptr, Sequence<double>(4), rendering::CompositeOperation::SOURCE);
    PresenterCanvasHelper::SetDeviceColor(aRenderState, maSeparatorColor);
    mxCanvas->drawLine(
        geometry::RealPoint2D(0, mnSeparatorY),
        geometry::RealPoint2D(mxWindow->getPosSize().Width, mnSeparatorY),
        rendering::ViewState(geometry::AffineMatrix2D(1,0,0, 0,1,0), nullptr),
        aRenderState);
 
    // Paint the slides.
    if ( ! PresenterGeometryHelper::AreRectanglesDisjoint(
        rUpdateBox,
        PresenterGeometryHelper::ConvertRectangle(mpLayout->maBoundingBox)))
    {
        mpLayout->ForAllVisibleSlides(
            [this, &rUpdateBox] (sal_Int32 const nIndex) {
                return this->PaintPreview(this->mxCanvas, rUpdateBox, nIndex);
            });
    }
 
    Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY);
    if (xSpriteCanvas.is())
        xSpriteCanvas->updateScreen(false);
}
 
void PresenterSlideSorter::SetHorizontalOffset (const double nXOffset)
{
    if (mpLayout->SetHorizontalOffset(nXOffset))
    {
        mxPreviewCache->setVisibleRange(
            mpLayout->GetFirstVisibleSlideIndex(),
            mpLayout->GetLastVisibleSlideIndex());
 
        mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
    }
}
 
void PresenterSlideSorter::SetVerticalOffset (const double nYOffset)
{
    if (mpLayout->SetVerticalOffset(nYOffset))
    {
        mxPreviewCache->setVisibleRange(
            mpLayout->GetFirstVisibleSlideIndex(),
            mpLayout->GetLastVisibleSlideIndex());
 
        mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
    }
}
 
void PresenterSlideSorter::GotoSlide (const sal_Int32 nSlideIndex)
{
    mxSlideShowController->gotoSlideIndex(nSlideIndex);
}
 
void PresenterSlideSorter::ScrollSlideIntoView(sal_Int32 nSlideIndex)
{
    const double fThumbPos = mpVerticalScrollBar->GetThumbPosition();
    const double fThumbSize = mpVerticalScrollBar->GetThumbSize();
    const double fSlidePos = mpLayout->GetPoint(nSlideIndex, -1, -1).Y;
 
    const bool bFullyVisible
        = (fSlidePos - mpLayout->mnVerticalBorder >= fThumbPos)
          && (fSlidePos + mpLayout->maPreviewSize.Height + mpLayout->mnVerticalBorder
              <= fThumbPos + fThumbSize);
    if (!bFullyVisible)
        mpVerticalScrollBar->SetThumbPosition(fSlidePos - mpLayout->mnVerticalBorder, false);
}
 
bool PresenterSlideSorter::ProvideCanvas()
{
    if ( ! mxCanvas.is())
    {
        if (mxPane.is())
            mxCanvas = mxPane->getCanvas();
 
        // Register as event listener so that we are informed when the
        // canvas is disposed (and we have to fetch another one).
        Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY);
        if (xComponent.is())
            xComponent->addEventListener(static_cast<awt::XWindowListener*>(this));
 
        mpCurrentSlideFrameRenderer =
            std::make_shared<CurrentSlideFrameRenderer>(mxComponentContext, mxCanvas);
    }
    return mxCanvas.is();
}
 
//===== PresenterSlideSorter::Layout ==========================================
 
PresenterSlideSorter::Layout::Layout (
    ::rtl::Reference<PresenterScrollBar> xVerticalScrollBar)
    : mnHorizontalOffset(0),
      mnVerticalOffset(0),
      mnHorizontalGap(0),
      mnVerticalGap(0),
      mnHorizontalBorder(0),
      mnVerticalBorder(0),
      mnRowCount(1),
      mnColumnCount(1),
      mnSlideCount(0),
      mnFirstVisibleColumn(-1),
      mnLastVisibleColumn(-1),
      mnFirstVisibleRow(-1),
      mnLastVisibleRow(-1),
      mpVerticalScrollBar(std::move(xVerticalScrollBar))
{
}
 
void PresenterSlideSorter::Layout::Update (
    const geometry::RealRectangle2D& rBoundingBox,
    const double nSlideAspectRatio)
{
    maBoundingBox = rBoundingBox;
 
    mnHorizontalBorder = gnHorizontalBorder;
    mnVerticalBorder = gnVerticalBorder;
 
    const double nWidth (rBoundingBox.X2 - rBoundingBox.X1 - 2*mnHorizontalBorder);
    const double nHeight (rBoundingBox.Y2 - rBoundingBox.Y1 - 2*mnVerticalBorder);
    if (nWidth<=0 || nHeight<=0)
        return;
 
    double nPreviewWidth;
 
    // Determine column count, preview width, and horizontal gap (borders
    // are half the gap).  Try to use the preferred values.  Try more to
    // stay in the valid intervals.  This last constraint may be not
    // fulfilled in some cases.
    const double nElementWidth = nWidth / gnPreferredColumnCount;
    if (nElementWidth < gnMinimalPreviewWidth + gnMinimalHorizontalPreviewGap)
    {
        // The preferred column count is too large.
        // Can we use the preferred preview width?
        if (nWidth - gnMinimalHorizontalPreviewGap >= gnPreferredPreviewWidth)
        {
            // Yes.
            nPreviewWidth = gnPreferredPreviewWidth;
            mnColumnCount = floor((nWidth+gnPreferredHorizontalPreviewGap)
                / (nPreviewWidth+gnPreferredHorizontalPreviewGap));
            mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount);
        }
        else
        {
            // No.  Set the column count to 1 and adapt preview width and
            // gap.
            mnColumnCount = 1;
            mnHorizontalGap = floor(gnMinimalHorizontalPreviewGap);
            if (nWidth - gnMinimalHorizontalPreviewGap >= gnPreferredPreviewWidth)
                nPreviewWidth = nWidth - gnMinimalHorizontalPreviewGap;
            else
                nPreviewWidth = ::std::max(gnMinimalPreviewWidth, nWidth-mnHorizontalGap);
        }
    }
    else if (nElementWidth > gnMaximalPreviewWidth + gnMaximalHorizontalPreviewGap)
    {
        // The preferred column count is too small.
        nPreviewWidth = gnPreferredPreviewWidth;
        mnColumnCount = floor((nWidth+gnPreferredHorizontalPreviewGap)
            / (nPreviewWidth+gnPreferredHorizontalPreviewGap));
        mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount);
    }
    else
    {
        // The preferred column count is possible.  Determine gap and
        // preview width.
        mnColumnCount = gnPreferredColumnCount;
        if (nElementWidth - gnPreferredPreviewWidth < gnMinimalHorizontalPreviewGap)
        {
            // Use the minimal gap and adapt the preview width.
            mnHorizontalGap = floor(gnMinimalHorizontalPreviewGap);
            nPreviewWidth = (nWidth - mnColumnCount*mnHorizontalGap) / mnColumnCount;
        }
        else if (nElementWidth - gnPreferredPreviewWidth <= gnMaximalHorizontalPreviewGap)
        {
            // Use the maximal gap and adapt the preview width.
            mnHorizontalGap = round(gnMaximalHorizontalPreviewGap);
            nPreviewWidth = (nWidth - mnColumnCount*mnHorizontalGap) / mnColumnCount;
        }
        else
        {
            // Use the preferred preview width and adapt the gap.
            nPreviewWidth = gnPreferredPreviewWidth;
            mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount);
        }
    }
 
    // Now determine the row count, preview height, and vertical gap.
    const double nPreviewHeight = nPreviewWidth / nSlideAspectRatio;
    mnRowCount = ::std::max(
        sal_Int32(1),
        sal_Int32(ceil((nHeight+gnPreferredVerticalPreviewGap)
                / (nPreviewHeight + gnPreferredVerticalPreviewGap))));
    mnVerticalGap = round(gnPreferredVerticalPreviewGap);
 
    maPreviewSize = geometry::IntegerSize2D(floor(nPreviewWidth), floor(nPreviewHeight));
 
    // Reset the offset.
    mnVerticalOffset = 0;
    mnHorizontalOffset = round(-(nWidth
        - mnColumnCount*maPreviewSize.Width
        - (mnColumnCount-1)*mnHorizontalGap)
        / 2);
}
 
void PresenterSlideSorter::Layout::SetupVisibleArea()
{
    geometry::RealPoint2D aPoint (GetLocalPosition(
        geometry::RealPoint2D(maBoundingBox.X1, maBoundingBox.Y1)));
    mnFirstVisibleColumn = 0;
    mnFirstVisibleRow = ::std::max(sal_Int32(0), GetRow(aPoint));
 
    aPoint = GetLocalPosition(geometry::RealPoint2D( maBoundingBox.X2, maBoundingBox.Y2));
    mnLastVisibleColumn = mnColumnCount - 1;
    mnLastVisibleRow = GetRow(aPoint, true);
}
 
bool PresenterSlideSorter::Layout::IsScrollBarNeeded (const sal_Int32 nSlideCount)
{
    geometry::RealPoint2D aBottomRight = GetPoint(
        mnColumnCount * (GetRow(nSlideCount)+1) - 1, +1, +1);
    return aBottomRight.X > maBoundingBox.X2-maBoundingBox.X1
        || aBottomRight.Y > maBoundingBox.Y2-maBoundingBox.Y1;
}
 
geometry::RealPoint2D PresenterSlideSorter::Layout::GetLocalPosition(
    const geometry::RealPoint2D& rWindowPoint) const
{
    if(AllSettings::GetLayoutRTL())
    {
            return css::geometry::RealPoint2D(
                                              -rWindowPoint.X  + maBoundingBox.X2 + mnHorizontalOffset,
                                              rWindowPoint.Y - maBoundingBox.Y1 + mnVerticalOffset);
    }
    else
    {
            return css::geometry::RealPoint2D(
                                              rWindowPoint.X - maBoundingBox.X1 + mnHorizontalOffset,
                                              rWindowPoint.Y - maBoundingBox.Y1 + mnVerticalOffset);
    }
}
 
geometry::RealPoint2D PresenterSlideSorter::Layout::GetWindowPosition(
    const geometry::RealPoint2D& rLocalPoint) const
{
    if(AllSettings::GetLayoutRTL())
    {
            return css::geometry::RealPoint2D(
                                              -rLocalPoint.X + mnHorizontalOffset + maBoundingBox.X2,
                                              rLocalPoint.Y - mnVerticalOffset + maBoundingBox.Y1);
    }
    else
    {
            return css::geometry::RealPoint2D(
                                              rLocalPoint.X - mnHorizontalOffset + maBoundingBox.X1,
                                              rLocalPoint.Y - mnVerticalOffset + maBoundingBox.Y1);
    }
}
 
sal_Int32 PresenterSlideSorter::Layout::GetColumn (
    const css::geometry::RealPoint2D& rLocalPoint) const
{
    const sal_Int32 nColumn(floor(
        (rLocalPoint.X + mnHorizontalGap/2.0) / (maPreviewSize.Width+mnHorizontalGap)));
    if (nColumn>=mnFirstVisibleColumn && nColumn<=mnLastVisibleColumn)
    {
        return nColumn;
    }
    else
        return -1;
}
 
sal_Int32 PresenterSlideSorter::Layout::GetRow (
    const css::geometry::RealPoint2D& rLocalPoint,
    const bool bReturnInvalidValue) const
{
    const sal_Int32 nRow (floor(
        (rLocalPoint.Y + mnVerticalGap/2.0) / (maPreviewSize.Height+mnVerticalGap)));
    if (bReturnInvalidValue
        || (nRow>=mnFirstVisibleRow && nRow<=mnLastVisibleRow))
    {
        return nRow;
    }
    else
        return -1;
}
 
sal_Int32 PresenterSlideSorter::Layout::GetSlideIndexForPosition (
    const css::geometry::RealPoint2D& rWindowPoint) const
{
    if ( ! PresenterGeometryHelper::IsInside(maBoundingBox, rWindowPoint))
        return -1;
 
    const css::geometry::RealPoint2D aLocalPosition (GetLocalPosition(rWindowPoint));
    const sal_Int32 nColumn (GetColumn(aLocalPosition));
    const sal_Int32 nRow (GetRow(aLocalPosition));
 
    if (nColumn < 0 || nRow < 0)
        return -1;
    else
    {
        sal_Int32 nIndex (GetIndex(nRow, nColumn));
        if (nIndex >= mnSlideCount)
            return -1;
        else
            return nIndex;
    }
}
 
geometry::RealPoint2D PresenterSlideSorter::Layout::GetPoint (
    const sal_Int32 nSlideIndex,
    const sal_Int32 nRelativeHorizontalPosition,
    const sal_Int32 nRelativeVerticalPosition) const
{
    sal_Int32 nColumn (GetColumn(nSlideIndex));
    sal_Int32 nRow (GetRow(nSlideIndex));
 
    geometry::RealPoint2D aPosition (
        mnHorizontalBorder + nColumn*(maPreviewSize.Width+mnHorizontalGap),
        mnVerticalBorder + nRow*(maPreviewSize.Height+mnVerticalGap));
 
    if (nRelativeHorizontalPosition >= 0)
    {
        if (nRelativeHorizontalPosition > 0)
            aPosition.X += maPreviewSize.Width;
        else
            aPosition.X += maPreviewSize.Width / 2.0;
    }
    if (nRelativeVerticalPosition >= 0)
    {
        if (nRelativeVerticalPosition > 0)
            aPosition.Y += maPreviewSize.Height;
        else
            aPosition.Y += maPreviewSize.Height / 2.0;
    }
 
    return aPosition;
}
 
awt::Rectangle PresenterSlideSorter::Layout::GetBoundingBox (const sal_Int32 nSlideIndex) const
{
    bool isRTL = AllSettings::GetLayoutRTL();
    const geometry::RealPoint2D aWindowPosition(GetWindowPosition(GetPoint(nSlideIndex, isRTL?1:-1, -1)));
    return PresenterGeometryHelper::ConvertRectangle(
                                                     geometry::RealRectangle2D(
                                                                               aWindowPosition.X,
                                                                               aWindowPosition.Y,
                                                                               aWindowPosition.X + maPreviewSize.Width,
                                                                               aWindowPosition.Y + maPreviewSize.Height));
}
 
void PresenterSlideSorter::Layout::ForAllVisibleSlides(
        const ::std::function<void (sal_Int32)>& rAction)
{
    for (sal_Int32 nRow=mnFirstVisibleRow; nRow<=mnLastVisibleRow; ++nRow)
    {
        for (sal_Int32 nColumn=mnFirstVisibleColumn; nColumn<=mnLastVisibleColumn; ++nColumn)
        {
            const sal_Int32 nSlideIndex (GetIndex(nRow, nColumn));
            if (nSlideIndex >= mnSlideCount)
                return;
            rAction(nSlideIndex);
        }
    }
}
 
sal_Int32 PresenterSlideSorter::Layout::GetFirstVisibleSlideIndex() const
{
    return GetIndex(mnFirstVisibleRow, mnFirstVisibleColumn);
}
 
sal_Int32 PresenterSlideSorter::Layout::GetLastVisibleSlideIndex() const
{
    return ::std::min(
        GetIndex(mnLastVisibleRow, mnLastVisibleColumn),
        mnSlideCount);
}
 
bool PresenterSlideSorter::Layout::SetHorizontalOffset (const double nOffset)
{
    if (mnHorizontalOffset != nOffset)
    {
        mnHorizontalOffset = round(nOffset);
        SetupVisibleArea();
        UpdateScrollBars();
        return true;
    }
    else
        return false;
}
 
bool PresenterSlideSorter::Layout::SetVerticalOffset (const double nOffset)
{
    if (mnVerticalOffset != nOffset)
    {
        mnVerticalOffset = round(nOffset);
        SetupVisibleArea();
        UpdateScrollBars();
        return true;
    }
    else
        return false;
}
 
void PresenterSlideSorter::Layout::UpdateScrollBars()
{
    sal_Int32 nTotalRowCount = sal_Int32(ceil(double(mnSlideCount) / double(mnColumnCount)));
 
    if (mpVerticalScrollBar)
    {
        mpVerticalScrollBar->SetTotalSize(
            nTotalRowCount * maPreviewSize.Height
                + (nTotalRowCount-1) * mnVerticalGap
            + 2*mnVerticalGap);
        mpVerticalScrollBar->SetThumbPosition(mnVerticalOffset, false);
        mpVerticalScrollBar->SetThumbSize(maBoundingBox.Y2 - maBoundingBox.Y1 + 1);
        mpVerticalScrollBar->SetLineHeight(maPreviewSize.Height);
    }
 
    // No place yet for the vertical scroll bar.
}
 
sal_Int32 PresenterSlideSorter::Layout::GetIndex (
    const sal_Int32 nRow,
    const sal_Int32 nColumn) const
{
    return nRow * mnColumnCount + nColumn;
}
 
sal_Int32 PresenterSlideSorter::Layout::GetRow (const sal_Int32 nSlideIndex) const
{
    return nSlideIndex / mnColumnCount;
}
 
sal_Int32 PresenterSlideSorter::Layout::GetColumn (const sal_Int32 nSlideIndex) const
{
    return nSlideIndex % mnColumnCount;
}
 
//===== PresenterSlideSorter::MouseOverManager ================================
 
PresenterSlideSorter::MouseOverManager::MouseOverManager (
    const Reference<container::XIndexAccess>& rxSlides,
    const std::shared_ptr<PresenterTheme>& rpTheme,
    const Reference<awt::XWindow>& rxInvalidateTarget,
    std::shared_ptr<PresenterPaintManager> xPaintManager)
    : mxSlides(rxSlides),
      mnSlideIndex(-1),
      mxInvalidateTarget(rxInvalidateTarget),
      mpPaintManager(std::move(xPaintManager))
{
    if (rpTheme != nullptr)
    {
        std::shared_ptr<PresenterBitmapContainer> pBitmaps (rpTheme->GetBitmapContainer());
        if (pBitmaps != nullptr)
        {
            mpLeftLabelBitmap = pBitmaps->GetBitmap(u"LabelLeft"_ustr);
            mpCenterLabelBitmap = pBitmaps->GetBitmap(u"LabelCenter"_ustr);
            mpRightLabelBitmap = pBitmaps->GetBitmap(u"LabelRight"_ustr);
        }
 
        mpFont = rpTheme->GetFont(u"SlideSorterLabelFont"_ustr);
    }
}
 
void PresenterSlideSorter::MouseOverManager::Paint (
    const sal_Int32 nSlideIndex,
    const Reference<rendering::XCanvas>& rxCanvas,
    const Reference<rendering::XPolyPolygon2D>& rxClip)
{
    if (nSlideIndex != mnSlideIndex)
        return;
 
    if (mxCanvas != rxCanvas)
        SetCanvas(rxCanvas);
    if (rxCanvas == nullptr)
        return;
 
    if ( ! mxBitmap.is())
        mxBitmap = CreateBitmap(msText, maSlideBoundingBox.Width);
    if (!mxBitmap.is())
        return;
 
    geometry::IntegerSize2D aSize (mxBitmap->getSize());
    const double nXOffset (maSlideBoundingBox.X
        + (maSlideBoundingBox.Width - aSize.Width) / 2.0);
    const double nYOffset (maSlideBoundingBox.Y
        + (maSlideBoundingBox.Height - aSize.Height) / 2.0);
    rxCanvas->drawBitmap(
        mxBitmap,
        rendering::ViewState(
            geometry::AffineMatrix2D(1,0,0, 0,1,0),
            rxClip),
        rendering::RenderState(
            geometry::AffineMatrix2D(1,0,nXOffset, 0,1,nYOffset),
            nullptr,
            Sequence<double>(4),
            rendering::CompositeOperation::SOURCE));
}
 
void PresenterSlideSorter::MouseOverManager::SetCanvas (
    const Reference<rendering::XCanvas>& rxCanvas)
{
    mxCanvas = rxCanvas;
    if (mpFont)
        mpFont->PrepareFont(mxCanvas);
}
 
void PresenterSlideSorter::MouseOverManager::SetSlide (
    const sal_Int32 nSlideIndex,
    const awt::Rectangle& rBox)
{
    if (mnSlideIndex == nSlideIndex)
        return;
 
    mnSlideIndex = -1;
    Invalidate();
 
    maSlideBoundingBox = rBox;
    mnSlideIndex = nSlideIndex;
 
    if (nSlideIndex >= 0)
    {
        if (mxSlides)
        {
            msText.clear();
 
            Reference<beans::XPropertySet> xSlideProperties(mxSlides->getByIndex(nSlideIndex), UNO_QUERY);
            if (xSlideProperties.is())
                xSlideProperties->getPropertyValue(u"LinkDisplayName"_ustr) >>= msText;
 
            if (msText.isEmpty())
                msText = "Slide " + OUString::number(nSlideIndex + 1);
        }
    }
    else
    {
        msText.clear();
    }
    mxBitmap = nullptr;
 
    Invalidate();
}
 
Reference<rendering::XBitmap> PresenterSlideSorter::MouseOverManager::CreateBitmap (
    const OUString& rsText,
    const sal_Int32 nMaximalWidth) const
{
    if ( ! mxCanvas.is())
        return nullptr;
 
    if (!mpFont || !mpFont->mxFont.is())
        return nullptr;
 
    // Long text has to be shortened.
    const OUString sText (GetFittingText(rsText, nMaximalWidth
            - 2*gnHorizontalLabelBorder
            - 2*gnHorizontalLabelPadding));
 
    // Determine the size of the label.  Its height is defined by the
    // bitmaps that are used to paints its background.  The width is defined
    // by the text.
    geometry::IntegerSize2D aLabelSize (CalculateLabelSize(sText));
 
    // Create a new bitmap that will contain the complete label.
    Reference<rendering::XBitmap> xBitmap (
        mxCanvas->getDevice()->createCompatibleAlphaBitmap(aLabelSize));
 
    if ( ! xBitmap.is())
        return nullptr;
 
    Reference<rendering::XBitmapCanvas> xBitmapCanvas (xBitmap, UNO_QUERY);
    if ( ! xBitmapCanvas.is())
        return nullptr;
 
    // Paint the background.
    PaintButtonBackground(xBitmapCanvas, aLabelSize);
 
    // Paint the text.
    if (!sText.isEmpty())
    {
 
        const rendering::StringContext aContext (sText, 0, sText.getLength());
        const Reference<rendering::XTextLayout> xLayout (mpFont->mxFont->createTextLayout(
            aContext, rendering::TextDirection::WEAK_LEFT_TO_RIGHT,0));
        const geometry::RealRectangle2D aTextBBox (xLayout->queryTextBounds());
 
        const double nXOffset = (aLabelSize.Width - aTextBBox.X2 + aTextBBox.X1) / 2;
        const double nYOffset = aLabelSize.Height
            - (aLabelSize.Height - aTextBBox.Y2 + aTextBBox.Y1)/2 - aTextBBox.Y2;
 
        const rendering::ViewState aViewState(
            geometry::AffineMatrix2D(1,0,0, 0,1,0),
            nullptr);
 
        rendering::RenderState aRenderState (
            geometry::AffineMatrix2D(1,0,nXOffset, 0,1,nYOffset),
            nullptr,
            Sequence<double>(4),
            rendering::CompositeOperation::SOURCE);
        PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
 
        xBitmapCanvas->drawTextLayout (
            xLayout,
            aViewState,
            aRenderState);
    }
 
    return xBitmap;
}
 
OUString PresenterSlideSorter::MouseOverManager::GetFittingText (
    const OUString& rsText,
    const double nMaximalWidth) const
{
    const double nTextWidth (
        PresenterCanvasHelper::GetTextSize(mpFont->mxFont, rsText).Width);
    if (nTextWidth > nMaximalWidth)
    {
        // Text is too wide.  Shorten it by removing characters from the end
        // and replacing them by ellipses.
 
        // Guess a start value of the final string length.
        double nBestWidth (0);
        OUString sBestCandidate;
        sal_Int32 nLength (round(rsText.getLength() * nMaximalWidth / nTextWidth));
        static constexpr OUStringLiteral sEllipses (u"...");
        while (true)
        {
            const OUString sCandidate (rsText.subView(0,nLength) + sEllipses);
            const double nWidth (
                PresenterCanvasHelper::GetTextSize(mpFont->mxFont, sCandidate).Width);
            if (nWidth > nMaximalWidth)
            {
                // Candidate still too wide, shorten it.
                nLength -= 1;
                if (nLength <= 0)
                    break;
            }
            else if (nWidth < nMaximalWidth)
            {
                // Candidate short enough.
                if (nWidth > nBestWidth)
                {
                    // Best length so far.
                    sBestCandidate = sCandidate;
                    nBestWidth = nWidth;
                    nLength += 1;
                    if (nLength >= rsText.getLength())
                        break;
                }
                else
                    break;
            }
            else
            {
                // Candidate is exactly as long as it may be.  Use it
                // without looking any further.
                sBestCandidate = sCandidate;
                break;
            }
        }
        return sBestCandidate;
    }
    else
        return rsText;
}
 
geometry::IntegerSize2D PresenterSlideSorter::MouseOverManager::CalculateLabelSize (
    const OUString& rsText) const
{
    // Height is specified by the label bitmaps.
    sal_Int32 nHeight (32);
    if (mpCenterLabelBitmap)
    {
        Reference<rendering::XBitmap> xBitmap (mpCenterLabelBitmap->GetNormalBitmap());
        if (xBitmap.is())
            nHeight = xBitmap->getSize().Height;
    }
 
    // Width is specified by text width and maximal width.
    const geometry::RealSize2D aTextSize (
        PresenterCanvasHelper::GetTextSize(mpFont->mxFont, rsText));
 
    const sal_Int32 nWidth (round(aTextSize.Width + 2*gnHorizontalLabelPadding));
 
    return geometry::IntegerSize2D(nWidth, nHeight);
}
 
void PresenterSlideSorter::MouseOverManager::PaintButtonBackground (
    const Reference<rendering::XCanvas>& rxCanvas,
    const geometry::IntegerSize2D& rSize) const
{
    // Get the bitmaps for painting the label background.
    Reference<rendering::XBitmap> xLeftLabelBitmap;
    if (mpLeftLabelBitmap)
        xLeftLabelBitmap = mpLeftLabelBitmap->GetNormalBitmap();
 
    Reference<rendering::XBitmap> xCenterLabelBitmap;
    if (mpCenterLabelBitmap)
        xCenterLabelBitmap = mpCenterLabelBitmap->GetNormalBitmap();
 
    Reference<rendering::XBitmap> xRightLabelBitmap;
    if (mpRightLabelBitmap)
        xRightLabelBitmap = mpRightLabelBitmap->GetNormalBitmap();
 
    PresenterUIPainter::PaintHorizontalBitmapComposite (
        rxCanvas,
        awt::Rectangle(0,0, rSize.Width,rSize.Height),
        awt::Rectangle(0,0, rSize.Width,rSize.Height),
        xLeftLabelBitmap,
        xCenterLabelBitmap,
        xRightLabelBitmap);
}
 
void PresenterSlideSorter::MouseOverManager::Invalidate()
{
    if (mpPaintManager != nullptr)
        mpPaintManager->Invalidate(mxInvalidateTarget, maSlideBoundingBox, true);
}
 
//===== PresenterSlideSorter::CurrentSlideFrameRenderer =======================
 
PresenterSlideSorter::CurrentSlideFrameRenderer::CurrentSlideFrameRenderer (
    const css::uno::Reference<css::uno::XComponentContext>& rxContext,
    const css::uno::Reference<css::rendering::XCanvas>& rxCanvas)
    : mnTopFrameSize(0),
      mnLeftFrameSize(0),
      mnRightFrameSize(0),
      mnBottomFrameSize(0)
{
    PresenterConfigurationAccess aConfiguration (
        rxContext,
        u"/org.openoffice.Office.PresenterScreen/"_ustr,
        PresenterConfigurationAccess::READ_ONLY);
    Reference<container::XHierarchicalNameAccess> xBitmaps (
        aConfiguration.GetConfigurationNode(
            u"PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps"_ustr),
        UNO_QUERY);
    if ( ! xBitmaps.is())
        return;
 
    PresenterBitmapContainer aContainer (
        u"PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps"_ustr,
        std::shared_ptr<PresenterBitmapContainer>(),
        rxContext,
        rxCanvas);
 
    mpTopLeft = aContainer.GetBitmap(u"TopLeft"_ustr);
    mpTop = aContainer.GetBitmap(u"Top"_ustr);
    mpTopRight = aContainer.GetBitmap(u"TopRight"_ustr);
    mpLeft = aContainer.GetBitmap(u"Left"_ustr);
    mpRight = aContainer.GetBitmap(u"Right"_ustr);
    mpBottomLeft = aContainer.GetBitmap(u"BottomLeft"_ustr);
    mpBottom = aContainer.GetBitmap(u"Bottom"_ustr);
    mpBottomRight = aContainer.GetBitmap(u"BottomRight"_ustr);
 
    // Determine size of frame.
    if (mpTop)
        mnTopFrameSize = mpTop->mnHeight;
    if (mpLeft)
        mnLeftFrameSize = mpLeft->mnWidth;
    if (mpRight)
        mnRightFrameSize = mpRight->mnWidth;
    if (mpBottom)
        mnBottomFrameSize = mpBottom->mnHeight;
 
    if (mpTopLeft)
    {
        mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopLeft->mnHeight);
        mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpTopLeft->mnWidth);
    }
    if (mpTopRight)
    {
        mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopRight->mnHeight);
        mnRightFrameSize = ::std::max(mnRightFrameSize, mpTopRight->mnWidth);
    }
    if (mpBottomLeft)
    {
        mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpBottomLeft->mnWidth);
        mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomLeft->mnHeight);
    }
    if (mpBottomRight)
    {
        mnRightFrameSize = ::std::max(mnRightFrameSize, mpBottomRight->mnWidth);
        mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomRight->mnHeight);
    }
}
 
void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintCurrentSlideFrame (
    const awt::Rectangle& rSlideBoundingBox,
    const Reference<rendering::XCanvas>& rxCanvas,
    const geometry::RealRectangle2D& rClipBox)
{
    if ( ! rxCanvas.is())
        return;
 
    const Reference<rendering::XPolyPolygon2D> xClip (
        PresenterGeometryHelper::CreatePolygon(rClipBox, rxCanvas->getDevice()));
 
    if (mpTop)
    {
        PaintBitmapTiled(
            mpTop->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X,
            rSlideBoundingBox.Y - mpTop->mnHeight,
            rSlideBoundingBox.Width,
            mpTop->mnHeight);
    }
    if (mpLeft)
    {
        PaintBitmapTiled(
            mpLeft->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X - mpLeft->mnWidth,
            rSlideBoundingBox.Y,
            mpLeft->mnWidth,
            rSlideBoundingBox.Height);
    }
    if (mpRight)
    {
        PaintBitmapTiled(
            mpRight->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X + rSlideBoundingBox.Width,
            rSlideBoundingBox.Y,
            mpRight->mnWidth,
            rSlideBoundingBox.Height);
    }
    if (mpBottom)
    {
        PaintBitmapTiled(
            mpBottom->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X,
            rSlideBoundingBox.Y + rSlideBoundingBox.Height,
            rSlideBoundingBox.Width,
            mpBottom->mnHeight);
    }
    if (mpTopLeft)
    {
        PaintBitmapOnce(
            mpTopLeft->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X - mpTopLeft->mnWidth,
            rSlideBoundingBox.Y - mpTopLeft->mnHeight);
    }
    if (mpTopRight)
    {
        PaintBitmapOnce(
            mpTopRight->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X + rSlideBoundingBox.Width,
            rSlideBoundingBox.Y - mpTopRight->mnHeight);
    }
    if (mpBottomLeft)
    {
        PaintBitmapOnce(
            mpBottomLeft->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X - mpBottomLeft->mnWidth,
            rSlideBoundingBox.Y + rSlideBoundingBox.Height);
    }
    if (mpBottomRight)
    {
        PaintBitmapOnce(
            mpBottomRight->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X + rSlideBoundingBox.Width,
            rSlideBoundingBox.Y + rSlideBoundingBox.Height);
    }
}
 
awt::Rectangle PresenterSlideSorter::CurrentSlideFrameRenderer::GetBoundingBox (
    const awt::Rectangle& rSlideBoundingBox)
{
    return awt::Rectangle(
        rSlideBoundingBox.X - mnLeftFrameSize,
        rSlideBoundingBox.Y - mnTopFrameSize,
        rSlideBoundingBox.Width + mnLeftFrameSize + mnRightFrameSize,
        rSlideBoundingBox.Height + mnTopFrameSize + mnBottomFrameSize);
}
 
void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintBitmapOnce(
    const css::uno::Reference<css::rendering::XBitmap>& rxBitmap,
    const css::uno::Reference<css::rendering::XCanvas>& rxCanvas,
    const Reference<rendering::XPolyPolygon2D>& rxClip,
    const double nX,
    const double nY)
{
    OSL_ASSERT(rxCanvas.is());
    if ( ! rxBitmap.is())
        return;
 
    const rendering::ViewState aViewState(
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        rxClip);
 
    const rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(
            1, 0, nX,
            0, 1, nY),
        nullptr,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);
 
    rxCanvas->drawBitmap(
        rxBitmap,
        aViewState,
        aRenderState);
}
 
void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintBitmapTiled(
    const css::uno::Reference<css::rendering::XBitmap>& rxBitmap,
    const css::uno::Reference<css::rendering::XCanvas>& rxCanvas,
    const geometry::RealRectangle2D& rClipBox,
    const double nX0,
    const double nY0,
    const double nWidth,
    const double nHeight)
{
    OSL_ASSERT(rxCanvas.is());
    if ( ! rxBitmap.is())
        return;
 
    geometry::IntegerSize2D aSize (rxBitmap->getSize());
 
    const rendering::ViewState aViewState(
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        PresenterGeometryHelper::CreatePolygon(
            PresenterGeometryHelper::Intersection(
                rClipBox,
                geometry::RealRectangle2D(nX0,nY0,nX0+nWidth,nY0+nHeight)),
            rxCanvas->getDevice()));
 
    rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(
            1, 0, nX0,
            0, 1, nY0),
        nullptr,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);
 
    const double nX1 = nX0 + nWidth;
    const double nY1 = nY0 + nHeight;
    for (double nY=nY0; nY<nY1; nY+=aSize.Height)
        for (double nX=nX0; nX<nX1; nX+=aSize.Width)
        {
            aRenderState.AffineTransform.m02 = nX;
            aRenderState.AffineTransform.m12 = nY;
            rxCanvas->drawBitmap(
                rxBitmap,
                aViewState,
                aRenderState);
        }
}
 
} // end of namespace ::sdext::presenter
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'addBorder' is required to be utilized.

V1019 Compound assignment expression is used inside condition.

V1051 Consider checking for misprints. It's possible that the 'mnHorizontalGap' should be checked here.