/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * 
 * Copyright 2000, 2010 Oracle and/or its affiliates.
 *
 * OpenOffice.org - a multi-platform office productivity suite
 *
 * This file is part of OpenOffice.org.
 *
 * OpenOffice.org is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * OpenOffice.org is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details
 * (a copy is included in the LICENSE file that accompanied this code).
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with OpenOffice.org.  If not, see
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 License.
 *
 ************************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sdext.hxx"

#include "PresenterSlideSorter.hxx"
#include "PresenterButton.hxx"
#include "PresenterCanvasHelper.hxx"
#include "PresenterComponent.hxx"
#include "PresenterGeometryHelper.hxx"
#include "PresenterHelper.hxx"
#include "PresenterPaintManager.hxx"
#include "PresenterPaneBase.hxx"
#include "PresenterScrollBar.hxx"
#include "PresenterUIPainter.hxx"
#include "PresenterWindowManager.hxx"
#include <com/sun/star/awt/PosSize.hpp>
#include <com/sun/star/awt/XWindowPeer.hpp>
#include <com/sun/star/container/XNameAccess.hpp>
#include <com/sun/star/container/XNamed.hpp>
#include <com/sun/star/drawing/XSlideSorterBase.hpp>
#include <com/sun/star/drawing/framework/XConfigurationController.hpp>
#include <com/sun/star/drawing/framework/XControllerManager.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/TextDirection.hpp>
#include <com/sun/star/rendering/XPolyPolygon2D.hpp>
#include <com/sun/star/util/Color.hpp>
#include <algorithm>
#include <math.h>
#include <boost/bind.hpp>

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

#define A2S(pString) (::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(pString)))

namespace {
    const static sal_Int32 gnVerticalGap (10);
    const static sal_Int32 gnVerticalBorder (10);
    const static sal_Int32 gnHorizontalGap (10);
    const static sal_Int32 gnHorizontalBorder (10);

    const static double gnMinimalPreviewWidth (200);
    const static double gnPreferredPreviewWidth (300);
    const static double gnMaximalPreviewWidth (400);
    const static sal_Int32 gnPreferredColumnCount (6);
    const static double gnMinimalHorizontalPreviewGap(15);
    const static double gnPreferredHorizontalPreviewGap(25);
    const static double gnMaximalHorizontalPreviewGap(50);
    const static double gnMinimalVerticalPreviewGap(15);
    const static double gnPreferredVerticalPreviewGap(25);
    const static double gnMaximalVerticalPreviewGap(50);

    const static sal_Int32 gnHorizontalLabelBorder (3);
    const static sal_Int32 gnHorizontalLabelPadding (5);

    const static sal_Int32 gnVerticalButtonPadding (gnVerticalGap);
}

namespace sdext { namespace 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:
    enum Orientation { Horizontal, Vertical };
    Layout (
        const Orientation eOrientation,
        const ::rtl::Reference<PresenterScrollBar>& rpHorizontalScrollBar,
        const ::rtl::Reference<PresenterScrollBar>& rpVerticalScrollBar);

    void Update (const geometry::RealRectangle2D& rBoundingBox, const double nSlideAspectRatio);
    void SetupVisibleArea (void);
    void UpdateScrollBars (void);
    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 bool bReturnInvalidValue = false) 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 ::boost::function<void(sal_Int32)>& rAction);
    sal_Int32 GetFirstVisibleSlideIndex (void) const;
    sal_Int32 GetLastVisibleSlideIndex (void) const;
    bool SetHorizontalOffset (const double nOffset);
    bool SetVerticalOffset (const double nOffset);
    Orientation GetOrientation (void) const;

    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 mnSlideIndexAtMouse;
    sal_Int32 mnFirstVisibleColumn;
    sal_Int32 mnLastVisibleColumn;
    sal_Int32 mnFirstVisibleRow;
    sal_Int32 mnLastVisibleRow;
    
private:
    Orientation meOrientation;
    ::rtl::Reference<PresenterScrollBar> mpHorizontalScrollBar;
    ::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
    : ::boost::noncopyable
{
public:
    MouseOverManager (
        const Reference<container::XIndexAccess>& rxSlides,
        const ::boost::shared_ptr<PresenterTheme>& rpTheme,
        const Reference<awt::XWindow>& rxInvalidateTarget,
        const ::boost::shared_ptr<PresenterPaintManager>& rpPaintManager);
    ~MouseOverManager (void);

    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;
    ::boost::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 (void);
    geometry::IntegerSize2D CalculateLabelSize (
        const OUString& rsText) const;
    OUString GetFittingText (const OUString& rsText, const double nMaximalWidth) const;
    void PaintButtonBackground (
        const Reference<rendering::XBitmapCanvas>& 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);
    ~CurrentSlideFrameRenderer (void);

    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;

    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);
    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 Reference<XResourceId>& rxViewId,
    const Reference<frame::XController>& rxController,
    const ::rtl::Reference<PresenterController>& rpPresenterController)
    : PresenterSlideSorterInterfaceBase(m_aMutex),
      mxComponentContext(rxContext),
      mxViewId(rxViewId),
      mxPane(),
      mxCanvas(),
      mxWindow(),
      mpPresenterController(rpPresenterController),
      mxSlideShowController(mpPresenterController->GetSlideShowController()),
      mxPreviewCache(),
      mbIsPaintPending(true),
      mbIsLayoutPending(true),
      mpLayout(),
      mpHorizontalScrollBar(),
      mpVerticalScrollBar(),
      mpCloseButton(),
      mpMouseOverManager(),
      mnSlideIndexMousePressed(-1),
      mnCurrentSlideIndex(-1),
      mnSeparatorY(0),
      maSeparatorColor(0x00ffffff),
      maCloseButtonCenter(),
      maCurrentSlideFrameBoundingBox(),
      mpCurrentSlideFrameRenderer(),
      mxPreviewFrame()
{
    if ( ! rxContext.is()
        || ! rxViewId.is()
        || ! rxController.is()
        || rpPresenterController.get()==NULL)
    {
        throw lang::IllegalArgumentException();
    }

    if ( ! mxSlideShowController.is())
        throw RuntimeException();

    try
    {
        // Get pane and window.
        Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW);
        Reference<XConfigurationController> xCC (
            xCM->getConfigurationController(), UNO_QUERY_THROW);
        Reference<lang::XMultiComponentFactory> xFactory (
            mxComponentContext->getServiceManager(), UNO_QUERY_THROW);

        mxPane = Reference<XPane>(xCC->getResource(rxViewId->getAnchor()), UNO_QUERY_THROW);
        mxWindow = mxPane->getWindow();

        // Add window listener.
        mxWindow->addWindowListener(this);
        mxWindow->addPaintListener(this);
        mxWindow->addMouseListener(this);
        mxWindow->addMouseMotionListener(this);
        mxWindow->setVisible(sal_True);

        // Remember the current slide.
        mnCurrentSlideIndex = mxSlideShowController->getCurrentSlideIndex();

        // Set the orientation.
        const bool bIsVertical (true);
        
        // Create the scroll bar.
        if (bIsVertical)
            mpVerticalScrollBar = ::rtl::Reference<PresenterScrollBar>(
                new PresenterVerticalScrollBar(
                    rxContext,
                    mxWindow,
                    mpPresenterController->GetPaintManager(),
                    ::boost::bind(&PresenterSlideSorter::SetVerticalOffset,this,_1)));
        else
            mpHorizontalScrollBar = ::rtl::Reference<PresenterScrollBar>(
                new PresenterHorizontalScrollBar(
                    rxContext,
                    mxWindow,
                    mpPresenterController->GetPaintManager(),
                    ::boost::bind(&PresenterSlideSorter::SetHorizontalOffset,this,_1)));
        mpCloseButton = PresenterButton::Create(
            rxContext,
            mpPresenterController,
            mpPresenterController->GetTheme(),
            mxWindow,
            mxCanvas,
            A2S("SlideSorterCloser"));

        if (mpPresenterController->GetTheme().get() != NULL)
        {
            PresenterTheme::SharedFontDescriptor pFont (
                mpPresenterController->GetTheme()->GetFont(A2S("ButtonFont")));
            if (pFont.get() != NULL)
                maSeparatorColor = pFont->mnColor;
        }

        // Create the layout.
        mpLayout.reset(new Layout(
            Layout::Vertical,
            mpHorizontalScrollBar,
            mpVerticalScrollBar));
        
        // Create the preview cache.
        mxPreviewCache = Reference<drawing::XSlidePreviewCache>(
            xFactory->createInstanceWithContext(
                OUString::createFromAscii("com.sun.star.drawing.PresenterPreviewCache"),
                mxComponentContext),
            UNO_QUERY_THROW);
        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.
        Reference<beans::XPropertySet> xControllerProperties (rxController, UNO_QUERY_THROW);
        xControllerProperties->addPropertyChangeListener(
            OUString::createFromAscii("CurrentPage"),
            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&)
    {
        disposing();
        throw;
    }
}




PresenterSlideSorter::~PresenterSlideSorter (void)
{
}




void SAL_CALL PresenterSlideSorter::disposing (void)
{
    mxComponentContext = NULL;
    mxViewId = NULL;
    mxPane = NULL;

    if (mpVerticalScrollBar.is())
    {
        Reference<lang::XComponent> xComponent (
            static_cast<XWeak*>(mpVerticalScrollBar.get()), UNO_QUERY);
        mpVerticalScrollBar = NULL;
        if (xComponent.is())
            xComponent->dispose();
    }
    if (mpHorizontalScrollBar.is())
    {
        Reference<lang::XComponent> xComponent (
            static_cast<XWeak*>(mpHorizontalScrollBar.get()), UNO_QUERY);
        mpHorizontalScrollBar = NULL;
        if (xComponent.is())
            xComponent->dispose();
    }
    if (mpCloseButton.is())
    {
        Reference<lang::XComponent> xComponent (
            static_cast<XWeak*>(mpCloseButton.get()), UNO_QUERY);
        mpCloseButton = NULL;
        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 = NULL;
    }
    mpPresenterController = NULL;
    mxSlideShowController = NULL;
    mpLayout.reset();
    mpMouseOverManager.reset();

    if (mxPreviewCache.is())
    {
        mxPreviewCache->removePreviewCreationNotifyListener(this);
        
        Reference<XComponent> xComponent (mxPreviewCache, UNO_QUERY);
        mxPreviewCache = NULL;
        if (xComponent.is())
            xComponent->dispose();
    }

    if (mxWindow.is())
    {
        mxWindow->removeWindowListener(this);
        mxWindow->removePaintListener(this);
        mxWindow->removeMouseListener(this);
        mxWindow->removeMouseMotionListener(this);
    }
}




void PresenterSlideSorter::SetActiveState (const bool bIsActive)
{
    (void)bIsActive;
}




//----- lang::XEventListener --------------------------------------------------

void SAL_CALL PresenterSlideSorter::disposing (const lang::EventObject& rEventObject)
    throw (RuntimeException)
{
    if (rEventObject.Source == mxWindow)
    {
        mxWindow = NULL;
        dispose();
    }
    else if (rEventObject.Source == mxPreviewCache)
    {
        mxPreviewCache = NULL;
        dispose();
    }
    else if (rEventObject.Source == mxCanvas)
    {
        mxCanvas = NULL;
        if (mpHorizontalScrollBar.is())
            mpHorizontalScrollBar->SetCanvas(NULL);
        mbIsLayoutPending = true;
        mbIsPaintPending = true;

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




//----- XWindowListener -------------------------------------------------------
    
void SAL_CALL PresenterSlideSorter::windowResized (const awt::WindowEvent& rEvent)
    throw (uno::RuntimeException)
{
    (void)rEvent;
    ThrowIfDisposed();
    mbIsLayoutPending = true;
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
}




void SAL_CALL PresenterSlideSorter::windowMoved (const awt::WindowEvent& rEvent)
    throw (uno::RuntimeException)
{
    (void)rEvent;
    ThrowIfDisposed();
}




void SAL_CALL PresenterSlideSorter::windowShown (const lang::EventObject& rEvent)
    throw (uno::RuntimeException)
{
    (void)rEvent;
    ThrowIfDisposed();
    mbIsLayoutPending = true;
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow);
}




void SAL_CALL PresenterSlideSorter::windowHidden (const lang::EventObject& rEvent)
    throw (uno::RuntimeException)
{
    (void)rEvent;
    ThrowIfDisposed();
}




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

void SAL_CALL PresenterSlideSorter::windowPaint (const css::awt::PaintEvent& rEvent)
    throw (RuntimeException)
{
    (void)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(sal_False);
}




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

void SAL_CALL PresenterSlideSorter::mousePressed (const css::awt::MouseEvent& rEvent)
    throw(css::uno::RuntimeException)
{
    const geometry::RealPoint2D aPosition (rEvent.X, rEvent.Y);
    mnSlideIndexMousePressed = mpLayout->GetSlideIndexForPosition(aPosition);
}




void SAL_CALL PresenterSlideSorter::mouseReleased (const css::awt::MouseEvent& rEvent)
    throw(css::uno::RuntimeException)
{
    const geometry::RealPoint2D aPosition (rEvent.X, rEvent.Y);
    const sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition));

    if (nSlideIndex == mnSlideIndexMousePressed && mnSlideIndexMousePressed >= 0)
    {
        switch (rEvent.ClickCount)
        {
            case 1:
            default:
                GotoSlide(nSlideIndex);
                break;

            case 2:
                OSL_ASSERT(mpPresenterController.get()!=NULL);
                OSL_ASSERT(mpPresenterController->GetWindowManager().get()!=NULL);
                mpPresenterController->GetWindowManager()->SetSlideSorterState(false);
                GotoSlide(nSlideIndex);
                break;
        }
    }
}




void SAL_CALL PresenterSlideSorter::mouseEntered (const css::awt::MouseEvent& rEvent)
    throw(css::uno::RuntimeException)
{
    (void)rEvent;
}




void SAL_CALL PresenterSlideSorter::mouseExited (const css::awt::MouseEvent& rEvent)
    throw(css::uno::RuntimeException)
{
    (void)rEvent;
    mnSlideIndexMousePressed = -1;
    if (mpMouseOverManager.get() != NULL)
        mpMouseOverManager->SetSlide(mnSlideIndexMousePressed, awt::Rectangle(0,0,0,0));
}




//----- XMouseMotionListener --------------------------------------------------
    
void SAL_CALL PresenterSlideSorter::mouseMoved (const css::awt::MouseEvent& rEvent)
    throw (css::uno::RuntimeException)
{
    if (mpMouseOverManager.get() != NULL)
    {
        const geometry::RealPoint2D aPosition (rEvent.X, rEvent.Y);
        sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition));

        if (nSlideIndex < 0)
            mnSlideIndexMousePressed = -1;

        if (nSlideIndex < 0)
        {
            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& rEvent)
    throw (css::uno::RuntimeException)
{
    (void)rEvent;
}




//----- XResourceId -----------------------------------------------------------

Reference<XResourceId> SAL_CALL PresenterSlideSorter::getResourceId (void)
    throw (RuntimeException)
{
    ThrowIfDisposed();
    return mxViewId;
}




sal_Bool SAL_CALL PresenterSlideSorter::isAnchorOnly (void)
    throw (RuntimeException)
{
    return false;
}




//----- XPropertyChangeListener -----------------------------------------------

void SAL_CALL PresenterSlideSorter::propertyChange (
    const css::beans::PropertyChangeEvent& rEvent)
    throw(css::uno::RuntimeException)
{
    (void)rEvent;
}




//----- XSlidePreviewCacheListener --------------------------------------------
    
void SAL_CALL PresenterSlideSorter::notifyPreviewCreation (
    sal_Int32 nSlideIndex)
    throw(css::uno::RuntimeException)
{
    OSL_ASSERT(mpLayout.get()!=NULL);
    
    awt::Rectangle aBBox (mpLayout->GetBoundingBox(nSlideIndex));
    mpPresenterController->GetPaintManager()->Invalidate(mxWindow, aBBox, true);
}




//----- XDrawView -------------------------------------------------------------

void SAL_CALL PresenterSlideSorter::setCurrentPage (const Reference<drawing::XDrawPage>& rxSlide)
    throw (RuntimeException)
{
    (void)rxSlide;
    
    ThrowIfDisposed();
    ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex());

    if (mxSlideShowController.is())
    {
        const sal_Int32 nNewCurrentSlideIndex (mxSlideShowController->getCurrentSlideIndex());
        if (nNewCurrentSlideIndex != mnCurrentSlideIndex)
        {
            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);
        }
    }
}




Reference<drawing::XDrawPage> SAL_CALL PresenterSlideSorter::getCurrentPage (void)
    throw (RuntimeException)
{
    ThrowIfDisposed();
    return NULL;
}




//-----------------------------------------------------------------------------

void PresenterSlideSorter::UpdateLayout (void)
{
    if ( ! mxWindow.is())
        return;

    mbIsLayoutPending = false;
    mbIsPaintPending = true;

    const awt::Rectangle aWindowBox (mxWindow->getPosSize());
    awt::Rectangle aCenterBox (aWindowBox);
    sal_Int32 nLeftBorderWidth (aWindowBox.X);

    // Get border width.
    PresenterPaneContainer::SharedPaneDescriptor pPane (
        mpPresenterController->GetPaneContainer()->FindViewURL(
            mxViewId->getResourceURL()));
    do
    {
        if (pPane.get() == NULL)
            break;
        if ( ! pPane->mxPane.is())
            break;

        Reference<drawing::framework::XPaneBorderPainter> xBorderPainter (
            pPane->mxPane->GetPaneBorderPainter());
        if ( ! xBorderPainter.is())
            break;
        aCenterBox = xBorderPainter->addBorder (
            mxViewId->getAnchor()->getResourceURL(),
            awt::Rectangle(0, 0, aWindowBox.Width, aWindowBox.Height),
            drawing::framework::BorderType_INNER_BORDER);
    }
    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 = NULL;
}




geometry::RealRectangle2D PresenterSlideSorter::PlaceScrollBars (
    const geometry::RealRectangle2D& rUpperBox)
{
    mpLayout->Update(rUpperBox, GetSlideAspectRatio());
    bool bIsScrollBarNeeded (false);
    Reference<container::XIndexAccess> xSlides (mxSlideShowController, UNO_QUERY_THROW);
    if (xSlides.is())
        bIsScrollBarNeeded = mpLayout->IsScrollBarNeeded(xSlides->getCount());
    
    if (mpLayout->GetOrientation() == Layout::Vertical)
    {
        if (mpVerticalScrollBar.get() != NULL)
        {
            if (bIsScrollBarNeeded)
            {
                // 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);
        }
    }
    else
    {
        if (mpHorizontalScrollBar.get() != NULL)
        {
            if (bIsScrollBarNeeded)
            {
                // Place horixontal scroll bar at the bottom.
                mpHorizontalScrollBar->SetPosSize(geometry::RealRectangle2D(
                    rUpperBox.X1,
                    rUpperBox.Y2 - mpHorizontalScrollBar->GetSize(),
                    rUpperBox.X2,
                    rUpperBox.Y2));
                mpHorizontalScrollBar->SetVisible(true);
                
                // Reduce area covered by the scroll bar from the available
                // space.
                return geometry::RealRectangle2D(
                    rUpperBox.X1,
                    rUpperBox.Y1,
                    rUpperBox.X2,
                    rUpperBox.Y2 - mpHorizontalScrollBar->GetSize() - gnVerticalGap);
            }
            else
            mpHorizontalScrollBar->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.get() != NULL && rpPane->mxPane.is())
    {
        const sal_Int32 nCalloutCenter (rpPane->mxPane->GetCalloutAnchor().X - 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 (void) 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(OUString::createFromAscii("Width")) >>= nWidth)
                && (xProperties->getPropertyValue(OUString::createFromAscii("Height")) >>= 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 NULL;
    else if (mxPane.is())
        return mxPreviewCache->getSlidePreview(nSlideIndex, mxPane->getCanvas());
    else
        return NULL;
}




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));

    const geometry::RealPoint2D aTopLeft (
        mpLayout->GetWindowPosition(
            mpLayout->GetPoint(nSlideIndex, -1, -1)));

    // 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),
        NULL,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);


    // Emphasize the current slide.
    if (nSlideIndex == mnCurrentSlideIndex)
    {
        if (mpCurrentSlideFrameRenderer.get() != NULL)
        {
            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);
        }
    }

    // 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 geometry::RealRectangle2D aBox (0, 0, aSize.Width, aSize.Height);
        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;
    }

    mbIsPaintPending = false;

    ClearBackground(mxCanvas, rUpdateBox);

    // Give the canvas to the controls.
    if (bCanvasChanged)
    {
        if (mpHorizontalScrollBar.is())
            mpHorizontalScrollBar->SetCanvas(mxCanvas);
        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),
            NULL, 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), NULL),
        aRenderState);
        
    // Paint the slides.
    if ( ! PresenterGeometryHelper::AreRectanglesDisjoint(
        rUpdateBox,
        PresenterGeometryHelper::ConvertRectangle(mpLayout->maBoundingBox)))
    {
        mpLayout->ForAllVisibleSlides(
            ::boost::bind(&PresenterSlideSorter::PaintPreview, this, mxCanvas, rUpdateBox, _1));
    }

    Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY);
    if (xSpriteCanvas.is())
        xSpriteCanvas->updateScreen(sal_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);
    mpPresenterController->HideSlideSorter();
}




bool PresenterSlideSorter::ProvideCanvas (void)
{
    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));

        // Tell the scrollbar about the canvas.
        if (mpHorizontalScrollBar.is())
            mpHorizontalScrollBar->SetCanvas(mxCanvas);

        mpCurrentSlideFrameRenderer.reset(
            new CurrentSlideFrameRenderer(mxComponentContext, mxCanvas));
    }
    return mxCanvas.is();
}




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




//===== PresenterSlideSorter::Layout ==========================================

PresenterSlideSorter::Layout::Layout (
    const Orientation eOrientation,
    const ::rtl::Reference<PresenterScrollBar>& rpHorizontalScrollBar,
    const ::rtl::Reference<PresenterScrollBar>& rpVerticalScrollBar)
    : maBoundingBox(),
      maPreviewSize(),
      mnHorizontalOffset(0),
      mnVerticalOffset(0),
      mnHorizontalGap(0),
      mnVerticalGap(0),
      mnHorizontalBorder(0),
      mnVerticalBorder(0),
      mnRowCount(1),
      mnColumnCount(1),
      mnSlideCount(0),
      mnSlideIndexAtMouse(-1),
      mnFirstVisibleColumn(-1),
      mnLastVisibleColumn(-1),
      mnFirstVisibleRow(-1),
      mnLastVisibleRow(-1),
      meOrientation(eOrientation),
      mpHorizontalScrollBar(rpHorizontalScrollBar),
      mpVerticalScrollBar(rpVerticalScrollBar)
{
}




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 intervalls.  This last constraint may be not
    // fullfilled 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.
    if (meOrientation == Horizontal)
    {
        mnVerticalOffset = round(-(nHeight
            - mnRowCount*maPreviewSize.Height - (mnRowCount-1)*mnVerticalGap)
            / 2);
        mnHorizontalOffset = 0;
    }
    else
    {
        mnVerticalOffset = 0;
        mnHorizontalOffset = round(-(nWidth
            - mnColumnCount*maPreviewSize.Width
            - (mnColumnCount-1)*mnHorizontalGap)
            / 2);
    }
}




void PresenterSlideSorter::Layout::SetupVisibleArea (void)
{
    geometry::RealPoint2D aPoint (GetLocalPosition(
        geometry::RealPoint2D(maBoundingBox.X1, maBoundingBox.Y1)));
    if (meOrientation == Horizontal)
    {
        mnFirstVisibleColumn = ::std::max(sal_Int32(0), GetColumn(aPoint));
        mnFirstVisibleRow = 0;
    }
    else
    {
        mnFirstVisibleColumn = 0;
        mnFirstVisibleRow = ::std::max(sal_Int32(0), GetRow(aPoint));
    }
    
    aPoint = GetLocalPosition(geometry::RealPoint2D( maBoundingBox.X2, maBoundingBox.Y2));
    if (meOrientation == Horizontal)
    {
        mnLastVisibleColumn = GetColumn(aPoint, true);
        mnLastVisibleRow = mnRowCount - 1;
    }
    else
    {
        mnLastVisibleColumn = mnColumnCount - 1;
        mnLastVisibleRow = GetRow(aPoint, true);
    }
}




bool PresenterSlideSorter::Layout::IsScrollBarNeeded (const sal_Int32 nSlideCount)
{
    geometry::RealPoint2D aBottomRight;
    if (GetOrientation() == Layout::Vertical)
        aBottomRight = GetPoint(
            mnColumnCount * (GetRow(nSlideCount)+1) - 1, +1, +1);
    else
        aBottomRight = GetPoint(
            mnRowCount * (GetColumn(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
{
    return css::geometry::RealPoint2D(
        rWindowPoint.X - maBoundingBox.X1 + mnHorizontalOffset,
        rWindowPoint.Y - maBoundingBox.Y1 + mnVerticalOffset);
}




geometry::RealPoint2D PresenterSlideSorter::Layout::GetWindowPosition(
    const geometry::RealPoint2D& rLocalPoint) const
{
    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 bool bReturnInvalidValue) const
{
    const sal_Int32 nColumn(floor(
        (rLocalPoint.X + mnHorizontalGap/2.0) / (maPreviewSize.Width+mnHorizontalGap)));
    if (bReturnInvalidValue
        || (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
{
    const geometry::RealPoint2D aWindowPosition(GetWindowPosition(GetPoint(nSlideIndex, -1, -1)));
    return PresenterGeometryHelper::ConvertRectangle(
        geometry::RealRectangle2D(
            aWindowPosition.X,
            aWindowPosition.Y,
            aWindowPosition.X + maPreviewSize.Width,
            aWindowPosition.Y + maPreviewSize.Height));
}




void PresenterSlideSorter::Layout::ForAllVisibleSlides (const ::boost::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 (void) const
{
    return GetIndex(mnFirstVisibleRow, mnFirstVisibleColumn);
}




sal_Int32 PresenterSlideSorter::Layout::GetLastVisibleSlideIndex (void) 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;
}




PresenterSlideSorter::Layout::Orientation
    PresenterSlideSorter::Layout::GetOrientation (void) const
{
    return meOrientation;
}




void PresenterSlideSorter::Layout::UpdateScrollBars (void)
{
    sal_Int32 nTotalColumnCount (0);
    sal_Int32 nTotalRowCount (0);
    if (meOrientation == Horizontal)
    {
        nTotalColumnCount = sal_Int32(ceil(double(mnSlideCount) / double(mnRowCount)));
        nTotalRowCount = mnRowCount;
    }
    else
    {
        nTotalColumnCount = mnColumnCount;
        nTotalRowCount = sal_Int32(ceil(double(mnSlideCount) / double(mnColumnCount)));
    }
    
    if (mpHorizontalScrollBar.get() != NULL)
    {
        mpHorizontalScrollBar->SetTotalSize(
            nTotalColumnCount * maPreviewSize.Width
            + (nTotalColumnCount-1) * mnHorizontalGap
            + 2*mnHorizontalBorder);
        mpHorizontalScrollBar->SetThumbPosition(mnHorizontalOffset, false);
        mpHorizontalScrollBar->SetThumbSize(maBoundingBox.X2 - maBoundingBox.X1 + 1);
        mpHorizontalScrollBar->SetLineHeight(maPreviewSize.Width);
    }
    if (mpVerticalScrollBar.get() != NULL)
    {
        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
{
    if (meOrientation == Horizontal)
        return nColumn * mnRowCount + nRow;
    else
        return nRow * mnColumnCount + nColumn;
}




sal_Int32 PresenterSlideSorter::Layout::GetRow (const sal_Int32 nSlideIndex) const
{
    if (meOrientation == Horizontal)
        return nSlideIndex % mnRowCount;
    else
        return nSlideIndex / mnColumnCount;
}




sal_Int32 PresenterSlideSorter::Layout::GetColumn (const sal_Int32 nSlideIndex) const
{
    if (meOrientation == Horizontal)
        return nSlideIndex / mnRowCount;
    else
        return nSlideIndex % mnColumnCount;
}




//===== PresenterSlideSorter::MouseOverManager ================================

PresenterSlideSorter::MouseOverManager::MouseOverManager (
    const Reference<container::XIndexAccess>& rxSlides,
    const ::boost::shared_ptr<PresenterTheme>& rpTheme,
    const Reference<awt::XWindow>& rxInvalidateTarget,
    const ::boost::shared_ptr<PresenterPaintManager>& rpPaintManager)
    : mxCanvas(),
      mxSlides(rxSlides),
      mpLeftLabelBitmap(),
      mpCenterLabelBitmap(),
      mpRightLabelBitmap(),
      mpFont(),
      mnSlideIndex(-1),
      maSlideBoundingBox(),
      mxInvalidateTarget(rxInvalidateTarget),
      mpPaintManager(rpPaintManager)
{
    if (rpTheme.get()!=NULL)
    {
        ::boost::shared_ptr<PresenterBitmapContainer> pBitmaps (rpTheme->GetBitmapContainer());
        if (pBitmaps.get() != NULL)
        {
            mpLeftLabelBitmap = pBitmaps->GetBitmap(A2S("LabelLeft"));
            mpCenterLabelBitmap = pBitmaps->GetBitmap(A2S("LabelCenter"));
            mpRightLabelBitmap = pBitmaps->GetBitmap(A2S("LabelRight"));
        }

        mpFont = rpTheme->GetFont(A2S("SlideSorterLabelFont"));
    }
}




PresenterSlideSorter::MouseOverManager::~MouseOverManager (void)
{
}




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 != NULL)
    {
        if ( ! mxBitmap.is())
            mxBitmap = CreateBitmap(msText, maSlideBoundingBox.Width);
        if (mxBitmap.is())
        {
            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),
                    NULL,
                    Sequence<double>(4),
                    rendering::CompositeOperation::SOURCE));
        }
    }
}




void PresenterSlideSorter::MouseOverManager::SetCanvas (
    const Reference<rendering::XCanvas>& rxCanvas)
{
    mxCanvas = rxCanvas;
    if (mpFont.get() != NULL)
        mpFont->PrepareFont(Reference<rendering::XCanvas>(mxCanvas, UNO_QUERY));
}




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.get() != NULL)
        {
            msText = OUString();

            Reference<beans::XPropertySet> xSlideProperties(mxSlides->getByIndex(nSlideIndex), UNO_QUERY);
            if (xSlideProperties.is())
                xSlideProperties->getPropertyValue(A2S("LinkDisplayName")) >>= msText;

            if (msText.getLength() == 0)
                msText = A2S("Slide ") + OUString::valueOf(nSlideIndex + 1);
        }
    }
    else
    {
        msText = OUString();
    }
    mxBitmap = NULL;

    Invalidate();
}




Reference<rendering::XBitmap> PresenterSlideSorter::MouseOverManager::CreateBitmap (
    const OUString& rsText,
    const sal_Int32 nMaximalWidth) const
{
    if ( ! mxCanvas.is())
        return NULL;

    if (mpFont.get()==NULL || !mpFont->mxFont.is())
        return NULL;

    // 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 NULL;
    
    Reference<rendering::XBitmapCanvas> xBitmapCanvas (xBitmap, UNO_QUERY);
    if ( ! xBitmapCanvas.is())
        return NULL;
    
    // Paint the background.
    PaintButtonBackground(xBitmapCanvas, aLabelSize);

    // Paint the text.
    if (sText.getLength() > 0)
    {

        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),
            NULL);
                
        rendering::RenderState aRenderState (
            geometry::AffineMatrix2D(1,0,nXOffset, 0,1,nYOffset),
            NULL,
            Sequence<double>(4),
            rendering::CompositeOperation::SOURCE);
        PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);

        xBitmapCanvas->drawText(
            aContext,
            mpFont->mxFont,
            aViewState,
            aRenderState,
            rendering::TextDirection::WEAK_LEFT_TO_RIGHT);
    }

    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));
        const OUString sEllipses (A2S("..."));
        while (true)
        {
            const OUString sCandidate (rsText.copy(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.get() != NULL)
    {
        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::XBitmapCanvas>& rxCanvas,
    const geometry::IntegerSize2D& rSize) const
{
    // Get the bitmaps for painting the label background.
    Reference<rendering::XBitmap> xLeftLabelBitmap;
    if (mpLeftLabelBitmap.get() != NULL)
        xLeftLabelBitmap = mpLeftLabelBitmap->GetNormalBitmap();
            
    Reference<rendering::XBitmap> xCenterLabelBitmap;
    if (mpCenterLabelBitmap.get() != NULL)
        xCenterLabelBitmap = mpCenterLabelBitmap->GetNormalBitmap();
            
    Reference<rendering::XBitmap> xRightLabelBitmap;
    if (mpRightLabelBitmap.get() != NULL)
        xRightLabelBitmap = mpRightLabelBitmap->GetNormalBitmap();

    PresenterUIPainter::PaintHorizontalBitmapComposite (
        Reference<rendering::XCanvas>(rxCanvas, UNO_QUERY),
        awt::Rectangle(0,0, rSize.Width,rSize.Height),
        awt::Rectangle(0,0, rSize.Width,rSize.Height),
        xLeftLabelBitmap,
        xCenterLabelBitmap,
        xRightLabelBitmap);
}




void PresenterSlideSorter::MouseOverManager::Invalidate (void)
{
    if (mpPaintManager.get() != NULL)
        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)
    : mpTopLeft(),
      mpTop(),
      mpTopRight(),
      mpLeft(),
      mpRight(),
      mpBottomLeft(),
      mpBottom(),
      mpBottomRight(),
      mnTopFrameSize(0),
      mnLeftFrameSize(0),
      mnRightFrameSize(0),
      mnBottomFrameSize(0)
{
    PresenterConfigurationAccess aConfiguration (
        rxContext,
        OUString::createFromAscii("/org.openoffice.Office.extension.PresenterScreen/"),
        PresenterConfigurationAccess::READ_ONLY);
    Reference<container::XHierarchicalNameAccess> xBitmaps (
        aConfiguration.GetConfigurationNode(
            A2S("PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps")),
        UNO_QUERY);
    if ( ! xBitmaps.is())
        return;

    PresenterBitmapContainer aContainer (
        A2S("PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps"),
        ::boost::shared_ptr<PresenterBitmapContainer>(),
        rxContext,
        rxCanvas,
        PresenterComponent::GetBasePath(rxContext));

    mpTopLeft = aContainer.GetBitmap(A2S("TopLeft"));
    mpTop = aContainer.GetBitmap(A2S("Top"));
    mpTopRight = aContainer.GetBitmap(A2S("TopRight"));
    mpLeft = aContainer.GetBitmap(A2S("Left"));
    mpRight = aContainer.GetBitmap(A2S("Right"));
    mpBottomLeft = aContainer.GetBitmap(A2S("BottomLeft"));
    mpBottom = aContainer.GetBitmap(A2S("Bottom"));
    mpBottomRight = aContainer.GetBitmap(A2S("BottomRight"));

    // Determine size of frame.
    if (mpTop.get() != NULL)
        mnTopFrameSize = mpTop->mnHeight;
    if (mpLeft.get() != NULL)
        mnLeftFrameSize = mpLeft->mnWidth;
    if (mpRight.get() != NULL)
        mnRightFrameSize = mpRight->mnWidth;
    if (mpBottom.get() != NULL)
        mnBottomFrameSize = mpBottom->mnHeight;

    if (mpTopLeft.get() != NULL)
    {
        mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopLeft->mnHeight);
        mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpTopLeft->mnWidth);
    }
    if (mpTopRight.get() != NULL)
    {
        mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopRight->mnHeight);
        mnRightFrameSize = ::std::max(mnRightFrameSize, mpTopRight->mnWidth);
    }
    if (mpBottomLeft.get() != NULL)
    {
        mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpBottomLeft->mnWidth);
        mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomLeft->mnHeight);
    }
    if (mpBottomRight.get() != NULL)
    {
        mnRightFrameSize = ::std::max(mnRightFrameSize, mpBottomRight->mnWidth);
        mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomRight->mnHeight);
    }
}




PresenterSlideSorter::CurrentSlideFrameRenderer::~CurrentSlideFrameRenderer (void)
{
}




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.get() != NULL)
    {
        PaintBitmapTiled(
            mpTop->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X,
            rSlideBoundingBox.Y - mpTop->mnHeight,
            rSlideBoundingBox.Width,
            mpTop->mnHeight);
    }
    if (mpLeft.get() != NULL)
    {
        PaintBitmapTiled(
            mpLeft->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X - mpLeft->mnWidth,
            rSlideBoundingBox.Y,
            mpLeft->mnWidth,
            rSlideBoundingBox.Height);
    }
    if (mpRight.get() != NULL)
    {
        PaintBitmapTiled(
            mpRight->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X + rSlideBoundingBox.Width,
            rSlideBoundingBox.Y,
            mpRight->mnWidth,
            rSlideBoundingBox.Height);
    }
    if (mpBottom.get() != NULL)
    {
        PaintBitmapTiled(
            mpBottom->GetNormalBitmap(),
            rxCanvas,
            rClipBox,
            rSlideBoundingBox.X,
            rSlideBoundingBox.Y + rSlideBoundingBox.Height,
            rSlideBoundingBox.Width,
            mpBottom->mnHeight);
    }
    if (mpTopLeft.get() != NULL)
    {
        PaintBitmapOnce(
            mpTopLeft->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X - mpTopLeft->mnWidth,
            rSlideBoundingBox.Y - mpTopLeft->mnHeight);
    }
    if (mpTopRight.get() != NULL)
    {
        PaintBitmapOnce(
            mpTopRight->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X + rSlideBoundingBox.Width,
            rSlideBoundingBox.Y - mpTopLeft->mnHeight);
    }
    if (mpBottomLeft.get() != NULL)
    {
        PaintBitmapOnce(
            mpBottomLeft->GetNormalBitmap(),
            rxCanvas,
            xClip,
            rSlideBoundingBox.X - mpBottomLeft->mnWidth,
            rSlideBoundingBox.Y + rSlideBoundingBox.Height);
    }
    if (mpBottomRight.get() != NULL)
    {
        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),
        NULL,
        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),
        NULL,
        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
