1201 lines
37 KiB
C++
1201 lines
37 KiB
C++
/*************************************************************************
|
|
*
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* Copyright 2008 by Sun Microsystems, Inc.
|
|
*
|
|
* OpenOffice.org - a multi-platform office productivity suite
|
|
*
|
|
* $RCSfile: slideview.cxx,v $
|
|
* $Revision: 1.9 $
|
|
*
|
|
* 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.
|
|
*
|
|
************************************************************************/
|
|
#include "precompiled_slideshow.hxx"
|
|
|
|
#include <canvas/debug.hxx>
|
|
#include <tools/diagnose_ex.h>
|
|
#include <canvas/canvastools.hxx>
|
|
|
|
#include "eventqueue.hxx"
|
|
#include "eventmultiplexer.hxx"
|
|
#include "slideview.hxx"
|
|
#include "delayevent.hxx"
|
|
#include "unoview.hxx"
|
|
|
|
#include <rtl/instance.hxx>
|
|
#include <cppuhelper/basemutex.hxx>
|
|
#include <cppuhelper/compbase2.hxx>
|
|
#include <cppuhelper/implementationentry.hxx>
|
|
#include <cppuhelper/interfacecontainer.h>
|
|
#include <comphelper/make_shared_from_uno.hxx>
|
|
|
|
#include <cppcanvas/spritecanvas.hxx>
|
|
#include <cppcanvas/customsprite.hxx>
|
|
#include <cppcanvas/vclfactory.hxx>
|
|
#include <cppcanvas/basegfxfactory.hxx>
|
|
|
|
#include <tools/debug.hxx>
|
|
|
|
#include <basegfx/range/b1drange.hxx>
|
|
#include <basegfx/range/b2drange.hxx>
|
|
#include <basegfx/range/b2irange.hxx>
|
|
#include <basegfx/point/b2dpoint.hxx>
|
|
#include <basegfx/polygon/b2dpolygon.hxx>
|
|
#include <basegfx/matrix/b2dhommatrix.hxx>
|
|
#include <basegfx/polygon/b2dpolygontools.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygontools.hxx>
|
|
#include <basegfx/tools/canvastools.hxx>
|
|
#include <basegfx/polygon/b2dpolygonclipper.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
|
|
|
#include <com/sun/star/presentation/XSlideShow.hpp>
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/weak_ptr.hpp>
|
|
|
|
#include <vector>
|
|
#include <iterator>
|
|
#include <algorithm>
|
|
|
|
using namespace com::sun::star;
|
|
|
|
namespace slideshow {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
struct StaticUnitRectPoly : public rtl::StaticWithInit<basegfx::B2DPolygon, StaticUnitRectPoly>
|
|
{
|
|
basegfx::B2DPolygon operator()()
|
|
{
|
|
return basegfx::tools::createPolygonFromRect(
|
|
basegfx::B2DRectangle( 0.0, 0.0,
|
|
1.0, 1.0 ) );
|
|
}
|
|
};
|
|
|
|
/** Sprite entry, to store sprite plus priority
|
|
|
|
The operator<() defines a strict weak ordering of sprites, sort
|
|
key is the sprite priority.
|
|
*/
|
|
struct SpriteEntry
|
|
{
|
|
SpriteEntry( const cppcanvas::CustomSpriteSharedPtr& rSprite,
|
|
double nPrio ) :
|
|
mpSprite( rSprite ),
|
|
mnPriority( nPrio )
|
|
{
|
|
}
|
|
|
|
bool operator<(const SpriteEntry& rRHS) const
|
|
{
|
|
return mnPriority < rRHS.mnPriority;
|
|
}
|
|
|
|
boost::weak_ptr< cppcanvas::CustomSprite > mpSprite;
|
|
double mnPriority;
|
|
};
|
|
|
|
typedef std::vector< SpriteEntry > SpriteVector;
|
|
|
|
|
|
/** Create a clip polygon for slide views
|
|
|
|
@param rClip
|
|
Clip to set (can be empty)
|
|
|
|
@param rCanvas
|
|
Canvas to create the clip polygon for
|
|
|
|
@param rUserSize
|
|
The size of the view. Note that the returned clip will
|
|
<em>always</em> clip to at least the rect defined herein.
|
|
|
|
@return the view clip polygon, in view coordinates, which is
|
|
guaranteed to at least clip to the view size.
|
|
*/
|
|
basegfx::B2DPolyPolygon createClipPolygon( const basegfx::B2DPolyPolygon& rClip,
|
|
const cppcanvas::CanvasSharedPtr& /*rCanvas*/,
|
|
const basegfx::B2DSize& rUserSize )
|
|
{
|
|
// setup canvas clipping
|
|
// =====================
|
|
|
|
// AW: Simplified
|
|
const basegfx::B2DRange aClipRange(0, 0, rUserSize.getX(), rUserSize.getY());
|
|
|
|
if(rClip.count())
|
|
{
|
|
return basegfx::tools::clipPolyPolygonOnRange(rClip, aClipRange, true, false);
|
|
}
|
|
else
|
|
{
|
|
return basegfx::B2DPolyPolygon(basegfx::tools::createPolygonFromRect(aClipRange));
|
|
}
|
|
}
|
|
|
|
/** Prepare given clip polygon to be stored as the current clip
|
|
|
|
Note that this is separate from createClipPolygon(), to allow
|
|
SlideView implementations to store this intermediate result
|
|
(createClipPolygon() has to be called every time the view size
|
|
changes)
|
|
*/
|
|
basegfx::B2DPolyPolygon prepareClip( const basegfx::B2DPolyPolygon& rClip )
|
|
{
|
|
basegfx::B2DPolyPolygon aClip( rClip );
|
|
|
|
// TODO(P2): unnecessary, once XCanvas is correctly handling this
|
|
// AW: Should be no longer necessary; tools are now bezier-safe
|
|
if( aClip.areControlPointsUsed() )
|
|
aClip = basegfx::tools::adaptiveSubdivideByAngle( aClip );
|
|
|
|
// normalize polygon, preparation for clipping
|
|
// in updateCanvas()
|
|
aClip = basegfx::tools::correctOrientations(aClip);
|
|
aClip = basegfx::tools::solveCrossovers(aClip);
|
|
aClip = basegfx::tools::stripNeutralPolygons(aClip);
|
|
aClip = basegfx::tools::stripDispensablePolygons(aClip, false);
|
|
|
|
return aClip;
|
|
}
|
|
|
|
|
|
void clearRect( ::cppcanvas::CanvasSharedPtr const& pCanvas,
|
|
basegfx::B2IRange const& rArea )
|
|
{
|
|
// convert clip polygon to device coordinate system
|
|
::basegfx::B2DPolyPolygon const* pClipPoly( pCanvas->getClip() );
|
|
if( pClipPoly )
|
|
{
|
|
::basegfx::B2DPolyPolygon aClipPoly( *pClipPoly );
|
|
aClipPoly.transform( pCanvas->getTransformation() );
|
|
pCanvas->setClip( aClipPoly );
|
|
}
|
|
|
|
// set transformation to identitiy (->device pixel)
|
|
pCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
|
|
|
|
// #i42440# Fill the _full_ background in
|
|
// black. Since we had to extend the bitmap by one
|
|
// pixel, and the bitmap is initialized white,
|
|
// depending on the slide content a one pixel wide
|
|
// line will show to the bottom and the right.
|
|
const ::basegfx::B2DPolygon aPoly(
|
|
::basegfx::tools::createPolygonFromRect(
|
|
basegfx::B2DRange(rArea)));
|
|
|
|
::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
|
|
::cppcanvas::BaseGfxFactory::getInstance().createPolyPolygon( pCanvas,
|
|
aPoly ) );
|
|
|
|
if( pPolyPoly )
|
|
{
|
|
pPolyPoly->setCompositeOp( cppcanvas::CanvasGraphic::SOURCE );
|
|
pPolyPoly->setRGBAFillColor( 0x00000000U );
|
|
pPolyPoly->draw();
|
|
}
|
|
|
|
#if defined(VERBOSE) && defined(DBG_UTIL)
|
|
::cppcanvas::CanvasSharedPtr pCliplessCanvas( pCanvas->clone() );
|
|
pCliplessCanvas->setClip();
|
|
|
|
if( pCanvas->getClip() )
|
|
{
|
|
::cppcanvas::PolyPolygonSharedPtr pPolyPoly2(
|
|
::cppcanvas::BaseGfxFactory::getInstance().createPolyPolygon( pCliplessCanvas,
|
|
*(pCanvas->getClip()) ));
|
|
if( pPolyPoly2 )
|
|
{
|
|
pPolyPoly2->setRGBALineColor( 0x008000FFU );
|
|
pPolyPoly2->draw();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Get bounds in pixel
|
|
|
|
@param rLayerBounds
|
|
Bound rect, in user space coordinates
|
|
|
|
@param rTransformation
|
|
User space to device pixel transformation
|
|
|
|
@return the layer bounds in pixel, extended by one pixel to the
|
|
right and bottom
|
|
*/
|
|
basegfx::B2IRange getLayerBoundsPixel( basegfx::B2DRange const& rLayerBounds,
|
|
basegfx::B2DHomMatrix const& rTransformation )
|
|
{
|
|
::basegfx::B2DRange aTmpRect;
|
|
::canvas::tools::calcTransformedRectBounds( aTmpRect,
|
|
rLayerBounds,
|
|
rTransformation );
|
|
|
|
if( aTmpRect.isEmpty() )
|
|
return ::basegfx::B2IRange();
|
|
|
|
// #i42440# Returned layer size is one pixel too small, as
|
|
// rendering happens one pixel to the right and below the
|
|
// actual bound rect.
|
|
return ::basegfx::B2IRange( ::basegfx::fround(aTmpRect.getMinX()),
|
|
::basegfx::fround(aTmpRect.getMinY()),
|
|
::basegfx::fround(aTmpRect.getMaxX()) + 1,
|
|
::basegfx::fround(aTmpRect.getMaxY()) + 1 );
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
/** Container class for sprites issued by a ViewLayer
|
|
|
|
This class handles the sprite prioritization issues, that are
|
|
needed for layer sprites (e.g. the need to re-prioritize sprites
|
|
when the layer changes prio).
|
|
*/
|
|
class LayerSpriteContainer
|
|
{
|
|
/** Max fill level of maSprites, before we try to prune it from
|
|
deceased sprites
|
|
*/
|
|
enum{ SPRITE_ULLAGE=256 };
|
|
|
|
/** All sprites that have been issued by this container (pruned
|
|
from time to time, for invalid references). This vector is
|
|
kept sorted with increasing sprite priority.
|
|
*/
|
|
SpriteVector maSprites;
|
|
|
|
/// Priority of this layer, relative to other view layers
|
|
basegfx::B1DRange maLayerPrioRange;
|
|
|
|
double getSpritePriority( std::size_t nSpriteNum ) const
|
|
{
|
|
// divide the available layer range equally between all
|
|
// sprites, assign upper bound of individual sprite range as
|
|
// sprite prio (the layer itself gets assigned the lower bound
|
|
// of sprite 0's individual range):
|
|
//
|
|
// | layer 0 | layer 1 | ...
|
|
// | sprite 0 | sprite 1 | sprite 0 | sprite 1 | ...
|
|
return maLayerPrioRange.getMinimum() + maLayerPrioRange.getRange()*(nSpriteNum+1)/(maSprites.size()+1);
|
|
}
|
|
|
|
/** Rescan sprite vector, and remove deceased sprites (and reset
|
|
sprite prio)
|
|
|
|
@param aBegin
|
|
Iterator to the first entry to rescan
|
|
*/
|
|
void updateSprites()
|
|
{
|
|
SpriteVector aValidSprites;
|
|
|
|
// check all sprites for validity and set new priority
|
|
SpriteVector::iterator aCurrSprite( maSprites.begin() );
|
|
const SpriteVector::iterator aEnd( maSprites.end() );
|
|
while( aCurrSprite != aEnd )
|
|
{
|
|
cppcanvas::CustomSpriteSharedPtr pCurrSprite( aCurrSprite->mpSprite.lock() );
|
|
|
|
if( pCurrSprite )
|
|
{
|
|
// only copy still valid sprites over to the refreshed
|
|
// sprite vector.
|
|
aValidSprites.push_back( *aCurrSprite );
|
|
|
|
pCurrSprite->setPriority(
|
|
getSpritePriority( aValidSprites.size()-1 ));
|
|
}
|
|
|
|
++aCurrSprite;
|
|
}
|
|
|
|
// replace sprite list with pruned one
|
|
maSprites.swap( aValidSprites );
|
|
}
|
|
|
|
public:
|
|
LayerSpriteContainer() :
|
|
maSprites(),
|
|
maLayerPrioRange()
|
|
{
|
|
}
|
|
|
|
basegfx::B1DRange getLayerPriority() const
|
|
{
|
|
return maLayerPrioRange;
|
|
}
|
|
|
|
void setLayerPriority( const basegfx::B1DRange& rRange )
|
|
{
|
|
if( rRange != maLayerPrioRange )
|
|
{
|
|
maLayerPrioRange = rRange;
|
|
|
|
// prune and recalc sprite prios
|
|
updateSprites();
|
|
}
|
|
}
|
|
|
|
void addSprite( const cppcanvas::CustomSpriteSharedPtr& pSprite,
|
|
double nPriority )
|
|
{
|
|
if( !pSprite )
|
|
return;
|
|
|
|
SpriteEntry aEntry( pSprite,nPriority );
|
|
|
|
// insert new sprite, such that vector stays sorted
|
|
SpriteVector::iterator aInsertPos(
|
|
maSprites.insert(
|
|
std::lower_bound( maSprites.begin(),
|
|
maSprites.end(),
|
|
aEntry ),
|
|
aEntry ));
|
|
|
|
const std::size_t nNumSprites( maSprites.size() );
|
|
if( nNumSprites > SPRITE_ULLAGE ||
|
|
maSprites.end() - aInsertPos > 1 )
|
|
{
|
|
// updateSprites() also updates all sprite prios
|
|
updateSprites();
|
|
}
|
|
else
|
|
{
|
|
// added sprite to the end, and not too many sprites in
|
|
// vector - perform optimized update (only need to set
|
|
// prio). This basically caters for the common case of
|
|
// iterated character animations, which generate lots of
|
|
// sprites, all added to the end.
|
|
pSprite->setPriority(
|
|
getSpritePriority( nNumSprites-1 ));
|
|
}
|
|
}
|
|
|
|
void clear()
|
|
{
|
|
maSprites.clear();
|
|
}
|
|
};
|
|
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
|
|
/** This class provides layers for a slide view
|
|
|
|
Layers are used to render animations with the correct z order -
|
|
because sprites are always in front of the static canvas
|
|
background, shapes that must appear <em<before</em> an animation
|
|
must also be displayed as a sprite.
|
|
|
|
Each layer has a priority assigned to it (valid range [0,1]), which
|
|
also affects all sprites created for this specific layer - i.e. if
|
|
the layer priority changes, the sprites change z order together
|
|
with their parent.
|
|
*/
|
|
class SlideViewLayer : public ViewLayer,
|
|
private boost::noncopyable
|
|
{
|
|
/// Smart container for all sprites issued by this layer
|
|
mutable LayerSpriteContainer maSpriteContainer;
|
|
|
|
/// Bounds of this layer in user space coordinates
|
|
basegfx::B2DRange maLayerBounds;
|
|
|
|
/// Bounds of this layer in device pixel
|
|
mutable basegfx::B2IRange maLayerBoundsPixel;
|
|
|
|
/// Current clip polygon in user coordinates
|
|
basegfx::B2DPolyPolygon maClip;
|
|
|
|
/// Current size of the view in user coordinates
|
|
basegfx::B2DSize maUserSize;
|
|
|
|
/// Current overall view transformation
|
|
basegfx::B2DHomMatrix maTransformation;
|
|
|
|
/// 'parent' canvas, this viewlayer is associated with
|
|
const cppcanvas::SpriteCanvasSharedPtr mpSpriteCanvas;
|
|
|
|
/** output surface (necessarily a sprite, won't otherwise be able
|
|
to display anything <em>before</em> other sprites)
|
|
*/
|
|
mutable cppcanvas::CustomSpriteSharedPtr mpSprite;
|
|
|
|
/// actual output canvas retrieved from a sprite
|
|
mutable cppcanvas::CanvasSharedPtr mpOutputCanvas;
|
|
|
|
/// ptr back to owning view. needed for isOnView() method
|
|
View const* const mpParentView;
|
|
|
|
public:
|
|
/** Create a new layer
|
|
|
|
@param pCanvas
|
|
Sprite canvas to create the layer on
|
|
|
|
@param rTransform
|
|
Initial overall canvas transformation
|
|
|
|
@param rLayerBounds
|
|
Initial layer bounds, in view coordinate system
|
|
*/
|
|
SlideViewLayer( const cppcanvas::SpriteCanvasSharedPtr& pCanvas,
|
|
const basegfx::B2DHomMatrix& rTransform,
|
|
const basegfx::B2DRange& rLayerBounds,
|
|
const basegfx::B2DSize& rUserSize,
|
|
View const* const pParentView) :
|
|
maSpriteContainer(),
|
|
maLayerBounds(rLayerBounds),
|
|
maLayerBoundsPixel(),
|
|
maClip(),
|
|
maUserSize(rUserSize),
|
|
maTransformation(rTransform),
|
|
mpSpriteCanvas(pCanvas),
|
|
mpSprite(),
|
|
mpOutputCanvas(),
|
|
mpParentView(pParentView)
|
|
{
|
|
}
|
|
|
|
void updateView( const basegfx::B2DHomMatrix& rMatrix,
|
|
const basegfx::B2DSize& rUserSize )
|
|
{
|
|
maTransformation = rMatrix;
|
|
maUserSize = rUserSize;
|
|
|
|
// limit layer bounds to visible screen
|
|
maLayerBounds.intersect( basegfx::B2DRange(0.0,
|
|
0.0,
|
|
maUserSize.getX(),
|
|
maUserSize.getY()) );
|
|
|
|
basegfx::B2IRange const& rNewLayerPixel(
|
|
getLayerBoundsPixel(maLayerBounds,
|
|
maTransformation) );
|
|
if( rNewLayerPixel != maLayerBoundsPixel )
|
|
{
|
|
// re-gen sprite with new size
|
|
mpOutputCanvas.reset();
|
|
mpSprite.reset();
|
|
}
|
|
}
|
|
|
|
private:
|
|
// ViewLayer interface
|
|
// ----------------------------------------------
|
|
|
|
virtual cppcanvas::CustomSpriteSharedPtr createSprite(
|
|
const ::basegfx::B2DSize& rSpriteSizePixel,
|
|
double nPriority ) const
|
|
{
|
|
cppcanvas::CustomSpriteSharedPtr pSprite(
|
|
mpSpriteCanvas->createCustomSprite( rSpriteSizePixel ) );
|
|
|
|
maSpriteContainer.addSprite( pSprite,
|
|
nPriority );
|
|
|
|
return pSprite;
|
|
}
|
|
|
|
virtual void setPriority( const basegfx::B1DRange& rRange )
|
|
{
|
|
OSL_ENSURE( !rRange.isEmpty() &&
|
|
rRange.getMinimum() >= 1.0,
|
|
"SlideViewLayer::setPriority(): prio MUST be larger than 1.0 (because "
|
|
"the background layer already lies there)" );
|
|
|
|
maSpriteContainer.setLayerPriority( rRange );
|
|
|
|
if( mpSprite )
|
|
mpSprite->setPriority( rRange.getMinimum() );
|
|
}
|
|
|
|
virtual basegfx::B2DHomMatrix getTransformation() const
|
|
{
|
|
// Offset given transformation by left, top border of given
|
|
// range (after transformation through given transformation)
|
|
basegfx::B2DRectangle aTmpRect;
|
|
canvas::tools::calcTransformedRectBounds( aTmpRect,
|
|
maLayerBounds,
|
|
maTransformation );
|
|
|
|
basegfx::B2DHomMatrix aMatrix( maTransformation );
|
|
|
|
// Add translation according to the origin of aTmpRect. Ignore the
|
|
// translation when aTmpRect was not properly initialized.
|
|
if ( ! aTmpRect.isEmpty())
|
|
{
|
|
aMatrix.translate( -basegfx::fround(aTmpRect.getMinX()),
|
|
-basegfx::fround(aTmpRect.getMinY()) );
|
|
}
|
|
|
|
return aMatrix;
|
|
}
|
|
|
|
virtual basegfx::B2DHomMatrix getSpriteTransformation() const
|
|
{
|
|
return maTransformation;
|
|
}
|
|
|
|
virtual void clear() const
|
|
{
|
|
// keep layer clip
|
|
clearRect(getCanvas()->clone(),
|
|
maLayerBoundsPixel);
|
|
}
|
|
|
|
virtual void clearAll() const
|
|
{
|
|
::cppcanvas::CanvasSharedPtr pCanvas( getCanvas()->clone() );
|
|
|
|
// clear layer clip, to clear whole area
|
|
pCanvas->setClip();
|
|
|
|
clearRect(pCanvas,
|
|
maLayerBoundsPixel);
|
|
}
|
|
|
|
virtual bool isOnView(boost::shared_ptr<View> const& rView) const
|
|
{
|
|
return rView.get() == mpParentView;
|
|
}
|
|
|
|
virtual cppcanvas::CanvasSharedPtr getCanvas() const
|
|
{
|
|
if( !mpOutputCanvas )
|
|
{
|
|
if( !mpSprite )
|
|
{
|
|
maLayerBoundsPixel = getLayerBoundsPixel(maLayerBounds,
|
|
maTransformation);
|
|
|
|
// HACK: ensure at least 1x1 pixel size. clients might
|
|
// need an actual canvas (e.g. for bound rect
|
|
// calculations) without rendering anything. Better
|
|
// solution: introduce something like a reference
|
|
// canvas for ViewLayers, which is always available.
|
|
if( maLayerBoundsPixel.isEmpty() )
|
|
maLayerBoundsPixel = basegfx::B2IRange(0,0,1,1);
|
|
|
|
const basegfx::B2I64Tuple& rSpriteSize(maLayerBoundsPixel.getRange());
|
|
mpSprite = mpSpriteCanvas->createCustomSprite(
|
|
basegfx::B2DVector(sal::static_int_cast<sal_Int32>(rSpriteSize.getX()),
|
|
sal::static_int_cast<sal_Int32>(rSpriteSize.getY())) );
|
|
|
|
mpSprite->setPriority(
|
|
maSpriteContainer.getLayerPriority().getMinimum() );
|
|
|
|
#if defined(VERBOSE) && defined(DBG_UTIL)
|
|
mpSprite->movePixel(
|
|
basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) +
|
|
basegfx::B2DPoint(10,10) );
|
|
|
|
mpSprite->setAlpha(0.5);
|
|
#else
|
|
mpSprite->movePixel(
|
|
basegfx::B2DPoint(maLayerBoundsPixel.getMinimum()) );
|
|
|
|
mpSprite->setAlpha(1.0);
|
|
#endif
|
|
mpSprite->show();
|
|
}
|
|
|
|
ENSURE_OR_THROW( mpSprite,
|
|
"SlideViewLayer::getCanvas(): no layer sprite" );
|
|
|
|
mpOutputCanvas = mpSprite->getContentCanvas();
|
|
|
|
ENSURE_OR_THROW( mpOutputCanvas,
|
|
"SlideViewLayer::getCanvas(): sprite doesn't yield a canvas" );
|
|
|
|
// new canvas retrieved - setup transformation and clip
|
|
mpOutputCanvas->setTransformation( getTransformation() );
|
|
mpOutputCanvas->setClip(
|
|
createClipPolygon( maClip,
|
|
mpOutputCanvas,
|
|
maUserSize ));
|
|
}
|
|
|
|
return mpOutputCanvas;
|
|
}
|
|
|
|
virtual void setClip( const basegfx::B2DPolyPolygon& rClip )
|
|
{
|
|
basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip );
|
|
|
|
if( aNewClip != maClip )
|
|
{
|
|
maClip = aNewClip;
|
|
|
|
if(mpOutputCanvas )
|
|
mpOutputCanvas->setClip(
|
|
createClipPolygon( maClip,
|
|
mpOutputCanvas,
|
|
maUserSize ));
|
|
}
|
|
}
|
|
|
|
virtual bool resize( const ::basegfx::B2DRange& rArea )
|
|
{
|
|
const bool bRet( maLayerBounds != rArea );
|
|
maLayerBounds = rArea;
|
|
updateView( maTransformation,
|
|
maUserSize );
|
|
|
|
return bRet;
|
|
}
|
|
};
|
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
typedef cppu::WeakComponentImplHelper2<
|
|
::com::sun::star::util::XModifyListener,
|
|
::com::sun::star::awt::XPaintListener> SlideViewBase;
|
|
|
|
/** SlideView class
|
|
|
|
This class implements the View interface, encapsulating
|
|
<em>one</em> view a slideshow is displayed on.
|
|
*/
|
|
class SlideView : private cppu::BaseMutex,
|
|
public SlideViewBase,
|
|
public UnoView
|
|
{
|
|
public:
|
|
SlideView( const uno::Reference<presentation::XSlideShowView>& xView,
|
|
EventQueue& rEventQueue,
|
|
EventMultiplexer& rEventMultiplexer );
|
|
void updateCanvas();
|
|
|
|
private:
|
|
// View:
|
|
virtual ViewLayerSharedPtr createViewLayer( const basegfx::B2DRange& rLayerBounds ) const;
|
|
virtual bool updateScreen() const;
|
|
virtual bool paintScreen() const;
|
|
virtual void setViewSize( const ::basegfx::B2DSize& );
|
|
virtual void setCursorShape( sal_Int16 nPointerShape );
|
|
|
|
// ViewLayer interface
|
|
virtual bool isOnView(boost::shared_ptr<View> const& rView) const;
|
|
virtual void clear() const;
|
|
virtual void clearAll() const;
|
|
virtual cppcanvas::CanvasSharedPtr getCanvas() const;
|
|
virtual cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& rSpriteSizePixel,
|
|
double nPriority ) const;
|
|
virtual void setPriority( const basegfx::B1DRange& rRange );
|
|
virtual ::basegfx::B2DHomMatrix getTransformation() const;
|
|
virtual basegfx::B2DHomMatrix getSpriteTransformation() const;
|
|
virtual void setClip( const ::basegfx::B2DPolyPolygon& rClip );
|
|
virtual bool resize( const ::basegfx::B2DRange& rArea );
|
|
|
|
// UnoView:
|
|
virtual void _dispose();
|
|
virtual uno::Reference<presentation::XSlideShowView> getUnoView()const;
|
|
virtual void setIsSoundEnabled (const bool bValue);
|
|
virtual bool isSoundEnabled (void) const;
|
|
|
|
// XEventListener:
|
|
virtual void SAL_CALL disposing( lang::EventObject const& evt )
|
|
throw (uno::RuntimeException);
|
|
// XModifyListener:
|
|
virtual void SAL_CALL modified( const lang::EventObject& aEvent )
|
|
throw (uno::RuntimeException);
|
|
// XPaintListener:
|
|
virtual void SAL_CALL windowPaint( const awt::PaintEvent& e )
|
|
throw (uno::RuntimeException);
|
|
|
|
// WeakComponentImplHelperBase:
|
|
virtual void SAL_CALL disposing();
|
|
|
|
void updateClip();
|
|
|
|
private:
|
|
typedef std::vector< boost::weak_ptr<SlideViewLayer> > ViewLayerVector;
|
|
|
|
/// Prune viewlayers from deceased ones, optionally update them
|
|
void pruneLayers( bool bWithViewLayerUpdate=false ) const;
|
|
|
|
/** Max fill level of maViewLayers, before we try to prune it from
|
|
deceased layers
|
|
*/
|
|
enum{ LAYER_ULLAGE=8 };
|
|
|
|
uno::Reference<presentation::XSlideShowView> mxView;
|
|
cppcanvas::SpriteCanvasSharedPtr mpCanvas;
|
|
|
|
EventMultiplexer& mrEventMultiplexer;
|
|
EventQueue& mrEventQueue;
|
|
|
|
mutable LayerSpriteContainer maSprites;
|
|
mutable ViewLayerVector maViewLayers;
|
|
|
|
basegfx::B2DPolyPolygon maClip;
|
|
|
|
basegfx::B2DHomMatrix maViewTransform;
|
|
basegfx::B2DSize maUserSize;
|
|
bool mbIsSoundEnabled;
|
|
};
|
|
|
|
|
|
SlideView::SlideView( const uno::Reference<presentation::XSlideShowView>& xView,
|
|
EventQueue& rEventQueue,
|
|
EventMultiplexer& rEventMultiplexer ) :
|
|
SlideViewBase( m_aMutex ),
|
|
mxView( xView ),
|
|
mpCanvas(),
|
|
mrEventMultiplexer( rEventMultiplexer ),
|
|
mrEventQueue( rEventQueue ),
|
|
maSprites(),
|
|
maViewLayers(),
|
|
maClip(),
|
|
maViewTransform(),
|
|
maUserSize( 1.0, 1.0 ), // default size: one-by-one rectangle
|
|
mbIsSoundEnabled(true)
|
|
{
|
|
// take care not constructing any UNO references to this _inside_
|
|
// ctor, shift that code to createSlideView()!
|
|
ENSURE_OR_THROW( mxView.is(),
|
|
"SlideView::SlideView(): Invalid view" );
|
|
|
|
mpCanvas = cppcanvas::VCLFactory::getInstance().createSpriteCanvas(
|
|
xView->getCanvas() );
|
|
ENSURE_OR_THROW( mpCanvas,
|
|
"Could not create cppcanvas" );
|
|
|
|
geometry::AffineMatrix2D aViewTransform(
|
|
xView->getTransformation() );
|
|
|
|
if( basegfx::fTools::equalZero(
|
|
basegfx::B2DVector(aViewTransform.m00,
|
|
aViewTransform.m10).getLength()) ||
|
|
basegfx::fTools::equalZero(
|
|
basegfx::B2DVector(aViewTransform.m01,
|
|
aViewTransform.m11).getLength()) )
|
|
{
|
|
OSL_ENSURE( false,
|
|
"SlideView::SlideView(): Singular matrix!" );
|
|
|
|
canvas::tools::setIdentityAffineMatrix2D(aViewTransform);
|
|
}
|
|
|
|
basegfx::unotools::homMatrixFromAffineMatrix(
|
|
maViewTransform, aViewTransform );
|
|
|
|
// once and forever: set fixed prio to this 'layer' (we're always
|
|
// the background layer)
|
|
maSprites.setLayerPriority( basegfx::B1DRange(0.0,1.0) );
|
|
}
|
|
|
|
void SlideView::disposing()
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
maViewLayers.clear();
|
|
maSprites.clear();
|
|
mpCanvas.reset();
|
|
|
|
// additionally, also de-register from XSlideShowView
|
|
if (mxView.is())
|
|
{
|
|
mxView->removeTransformationChangedListener( this );
|
|
mxView->removePaintListener( this );
|
|
mxView.clear();
|
|
}
|
|
}
|
|
|
|
ViewLayerSharedPtr SlideView::createViewLayer( const basegfx::B2DRange& rLayerBounds ) const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_THROW( mpCanvas,
|
|
"SlideView::createViewLayer(): Disposed" );
|
|
|
|
const std::size_t nNumLayers( maViewLayers.size() );
|
|
|
|
// avoid filling up layer vector with lots of deceased layer weak
|
|
// ptrs
|
|
if( nNumLayers > LAYER_ULLAGE )
|
|
pruneLayers();
|
|
|
|
boost::shared_ptr<SlideViewLayer> pViewLayer( new SlideViewLayer(mpCanvas,
|
|
getTransformation(),
|
|
rLayerBounds,
|
|
maUserSize,
|
|
this) );
|
|
maViewLayers.push_back( pViewLayer );
|
|
|
|
return pViewLayer;
|
|
}
|
|
|
|
bool SlideView::updateScreen() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_RETURN( mpCanvas.get(),
|
|
"SlideView::updateScreen(): Disposed" );
|
|
|
|
return mpCanvas->updateScreen( false );
|
|
}
|
|
|
|
bool SlideView::paintScreen() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_RETURN( mpCanvas.get(),
|
|
"SlideView::paintScreen(): Disposed" );
|
|
|
|
return mpCanvas->updateScreen( true );
|
|
}
|
|
|
|
void SlideView::clear() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( mxView.is() && mpCanvas,
|
|
"SlideView::clear(): Disposed" );
|
|
if( !mxView.is() || !mpCanvas )
|
|
return;
|
|
|
|
// keep layer clip
|
|
clearRect(getCanvas()->clone(),
|
|
getLayerBoundsPixel(
|
|
basegfx::B2DRange(0,0,
|
|
maUserSize.getX(),
|
|
maUserSize.getY()),
|
|
getTransformation()));
|
|
}
|
|
|
|
void SlideView::clearAll() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( mxView.is() && mpCanvas,
|
|
"SlideView::clear(): Disposed" );
|
|
if( !mxView.is() || !mpCanvas )
|
|
return;
|
|
|
|
// clear whole view
|
|
mxView->clear();
|
|
}
|
|
|
|
void SlideView::setViewSize( const basegfx::B2DSize& rSize )
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
maUserSize = rSize;
|
|
updateCanvas();
|
|
}
|
|
|
|
void SlideView::setCursorShape( sal_Int16 nPointerShape )
|
|
{
|
|
osl::MutexGuard const guard( m_aMutex );
|
|
|
|
if (mxView.is())
|
|
mxView->setMouseCursor( nPointerShape );
|
|
}
|
|
|
|
bool SlideView::isOnView(boost::shared_ptr<View> const& rView) const
|
|
{
|
|
return rView.get() == this;
|
|
}
|
|
|
|
cppcanvas::CanvasSharedPtr SlideView::getCanvas() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_THROW( mpCanvas,
|
|
"SlideView::getCanvas(): Disposed" );
|
|
|
|
return mpCanvas;
|
|
}
|
|
|
|
cppcanvas::CustomSpriteSharedPtr SlideView::createSprite(
|
|
const basegfx::B2DSize& rSpriteSizePixel,
|
|
double nPriority ) const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_THROW( mpCanvas, "SlideView::createSprite(): Disposed" );
|
|
|
|
cppcanvas::CustomSpriteSharedPtr pSprite(
|
|
mpCanvas->createCustomSprite( rSpriteSizePixel ) );
|
|
|
|
maSprites.addSprite( pSprite,
|
|
nPriority );
|
|
|
|
return pSprite;
|
|
}
|
|
|
|
void SlideView::setPriority( const basegfx::B1DRange& /*rRange*/ )
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( false,
|
|
"SlideView::setPriority() is a NOOP for slide view - "
|
|
"content will always be shown in the background" );
|
|
}
|
|
|
|
basegfx::B2DHomMatrix SlideView::getTransformation() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
basegfx::B2DHomMatrix aMatrix;
|
|
aMatrix.scale( 1.0/maUserSize.getX(), 1.0/maUserSize.getY() );
|
|
|
|
return maViewTransform * aMatrix;
|
|
}
|
|
|
|
basegfx::B2DHomMatrix SlideView::getSpriteTransformation() const
|
|
{
|
|
return getTransformation();
|
|
}
|
|
|
|
void SlideView::setClip( const basegfx::B2DPolyPolygon& rClip )
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip );
|
|
|
|
if( aNewClip != maClip )
|
|
{
|
|
maClip = aNewClip;
|
|
|
|
updateClip();
|
|
}
|
|
}
|
|
|
|
bool SlideView::resize( const ::basegfx::B2DRange& /*rArea*/ )
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( false,
|
|
"SlideView::resize(): ignored for the View, can't change size "
|
|
"effectively, anyway" );
|
|
|
|
return false;
|
|
}
|
|
|
|
uno::Reference<presentation::XSlideShowView> SlideView::getUnoView() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
return mxView;
|
|
}
|
|
|
|
void SlideView::setIsSoundEnabled (const bool bValue)
|
|
{
|
|
mbIsSoundEnabled = bValue;
|
|
}
|
|
|
|
bool SlideView::isSoundEnabled (void) const
|
|
{
|
|
return mbIsSoundEnabled;
|
|
}
|
|
|
|
void SlideView::_dispose()
|
|
{
|
|
dispose();
|
|
}
|
|
|
|
// XEventListener
|
|
void SlideView::disposing( lang::EventObject const& evt )
|
|
throw (uno::RuntimeException)
|
|
{
|
|
(void)evt;
|
|
|
|
// no deregistration necessary anymore, XView has left:
|
|
osl::MutexGuard const guard( m_aMutex );
|
|
|
|
if (mxView.is())
|
|
{
|
|
OSL_ASSERT( evt.Source == mxView );
|
|
mxView.clear();
|
|
}
|
|
|
|
dispose();
|
|
}
|
|
|
|
// XModifyListener
|
|
void SlideView::modified( const lang::EventObject& /*aEvent*/ )
|
|
throw (uno::RuntimeException)
|
|
{
|
|
osl::MutexGuard const guard( m_aMutex );
|
|
|
|
OSL_ENSURE( mxView.is(), "SlideView::modified(): "
|
|
"Disposed, but event received from XSlideShowView?!");
|
|
|
|
if( !mxView.is() )
|
|
return;
|
|
|
|
geometry::AffineMatrix2D aViewTransform(
|
|
mxView->getTransformation() );
|
|
|
|
if( basegfx::fTools::equalZero(
|
|
basegfx::B2DVector(aViewTransform.m00,
|
|
aViewTransform.m10).getLength()) ||
|
|
basegfx::fTools::equalZero(
|
|
basegfx::B2DVector(aViewTransform.m01,
|
|
aViewTransform.m11).getLength()) )
|
|
{
|
|
OSL_ENSURE( false,
|
|
"SlideView::modified(): Singular matrix!" );
|
|
|
|
canvas::tools::setIdentityAffineMatrix2D(aViewTransform);
|
|
}
|
|
|
|
// view transformation really changed?
|
|
basegfx::B2DHomMatrix aNewTransform;
|
|
basegfx::unotools::homMatrixFromAffineMatrix(
|
|
aNewTransform,
|
|
aViewTransform );
|
|
|
|
if( aNewTransform == maViewTransform )
|
|
return; // No change, nothing to do
|
|
|
|
maViewTransform = aNewTransform;
|
|
|
|
updateCanvas();
|
|
|
|
// notify view change. Don't call EventMultiplexer directly, this
|
|
// might not be the main thread!
|
|
mrEventQueue.addEvent(
|
|
makeEvent( boost::bind( (bool (EventMultiplexer::*)(
|
|
const uno::Reference<presentation::XSlideShowView>&))
|
|
&EventMultiplexer::notifyViewChanged,
|
|
boost::ref(mrEventMultiplexer), mxView ),
|
|
"EventMultiplexer::notifyViewChanged"));
|
|
}
|
|
|
|
// XPaintListener
|
|
void SlideView::windowPaint( const awt::PaintEvent& /*e*/ )
|
|
throw (uno::RuntimeException)
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( mxView.is() && mpCanvas, "Disposed, but event received?!" );
|
|
|
|
// notify view clobbering. Don't call EventMultiplexer directly,
|
|
// this might not be the main thread!
|
|
mrEventQueue.addEvent(
|
|
makeEvent( boost::bind( &EventMultiplexer::notifyViewClobbered,
|
|
boost::ref(mrEventMultiplexer), mxView ),
|
|
"EventMultiplexer::notifyViewClobbered") );
|
|
}
|
|
|
|
void SlideView::updateCanvas()
|
|
{
|
|
OSL_ENSURE( mpCanvas,
|
|
"SlideView::updateCanvasTransform(): Disposed" );
|
|
|
|
if( !mpCanvas || !mxView.is())
|
|
return;
|
|
|
|
mpCanvas->clear(); // this is unnecessary, strictly speaking. but
|
|
// it makes the SlideView behave exactly like a
|
|
// sprite-based SlideViewLayer, because those
|
|
// are created from scratch after a resize
|
|
clearAll();
|
|
mpCanvas->setTransformation( getTransformation() );
|
|
mpCanvas->setClip(
|
|
createClipPolygon( maClip,
|
|
mpCanvas,
|
|
maUserSize ));
|
|
|
|
// forward update to viewlayers
|
|
pruneLayers( true );
|
|
}
|
|
|
|
void SlideView::updateClip()
|
|
{
|
|
OSL_ENSURE( mpCanvas,
|
|
"SlideView::updateClip(): Disposed" );
|
|
|
|
if( !mpCanvas )
|
|
return;
|
|
|
|
mpCanvas->setClip(
|
|
createClipPolygon( maClip,
|
|
mpCanvas,
|
|
maUserSize ));
|
|
|
|
pruneLayers( false );
|
|
}
|
|
|
|
void SlideView::pruneLayers( bool bWithViewLayerUpdate ) const
|
|
{
|
|
ViewLayerVector aValidLayers;
|
|
|
|
const basegfx::B2DHomMatrix& rCurrTransform(
|
|
getTransformation() );
|
|
|
|
// check all layers for validity, and retain only the live ones
|
|
ViewLayerVector::const_iterator aCurr( maViewLayers.begin() );
|
|
const ViewLayerVector::const_iterator aEnd( maViewLayers.end() );
|
|
while( aCurr != aEnd )
|
|
{
|
|
boost::shared_ptr< SlideViewLayer > pCurrLayer( aCurr->lock() );
|
|
|
|
if( pCurrLayer )
|
|
{
|
|
aValidLayers.push_back( pCurrLayer );
|
|
|
|
if( bWithViewLayerUpdate )
|
|
pCurrLayer->updateView( rCurrTransform,
|
|
maUserSize );
|
|
}
|
|
|
|
++aCurr;
|
|
}
|
|
|
|
// replace layer list with pruned one
|
|
maViewLayers.swap( aValidLayers );
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
UnoViewSharedPtr createSlideView( uno::Reference< presentation::XSlideShowView> const& xView,
|
|
EventQueue& rEventQueue,
|
|
EventMultiplexer& rEventMultiplexer )
|
|
{
|
|
boost::shared_ptr<SlideView> const that(
|
|
comphelper::make_shared_from_UNO(
|
|
new SlideView(xView,
|
|
rEventQueue,
|
|
rEventMultiplexer)));
|
|
|
|
// register listeners with XSlideShowView
|
|
xView->addTransformationChangedListener( that.get() );
|
|
xView->addPaintListener( that.get() );
|
|
|
|
// set new transformation
|
|
that->updateCanvas();
|
|
|
|
return that;
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace slideshow
|
|
|