blob: 862d2ae1e4e7dd6f15693a7f57134a59bc943842 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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 "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.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);
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