1185 lines
37 KiB
C++
1185 lines
37 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
#include <canvas/canvastools.hxx>
|
|
|
|
#include <eventqueue.hxx>
|
|
#include <eventmultiplexer.hxx>
|
|
#include <slideview.hxx>
|
|
#include <tools.hxx>
|
|
#include <delayevent.hxx>
|
|
#include <unoview.hxx>
|
|
|
|
#include <cppuhelper/basemutex.hxx>
|
|
#include <cppuhelper/compbase.hxx>
|
|
#include <comphelper/make_shared_from_uno.hxx>
|
|
#include <unotools/weakref.hxx>
|
|
|
|
#include <cppcanvas/spritecanvas.hxx>
|
|
#include <cppcanvas/customsprite.hxx>
|
|
#include <cppcanvas/vclfactory.hxx>
|
|
#include <cppcanvas/basegfxfactory.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/utils/canvastools.hxx>
|
|
#include <basegfx/polygon/b2dpolygonclipper.hxx>
|
|
#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
|
|
|
|
#include <com/sun/star/awt/XPaintListener.hpp>
|
|
#include <com/sun/star/presentation/XSlideShowView.hpp>
|
|
#include <com/sun/star/rendering/CompositeOperation.hpp>
|
|
#include <com/sun/star/util/XModifyListener.hpp>
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
using namespace com::sun::star;
|
|
|
|
namespace slideshow::internal {
|
|
|
|
namespace {
|
|
|
|
/** 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;
|
|
}
|
|
|
|
std::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.getWidth(), rUserSize.getHeight());
|
|
|
|
if(rClip.count())
|
|
{
|
|
return basegfx::utils::clipPolyPolygonOnRange(rClip, aClipRange, true, false);
|
|
}
|
|
else
|
|
{
|
|
return basegfx::B2DPolyPolygon(basegfx::utils::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 );
|
|
|
|
// normalize polygon, preparation for clipping
|
|
// in updateCanvas()
|
|
aClip = basegfx::utils::correctOrientations(aClip);
|
|
aClip = basegfx::utils::solveCrossovers(aClip);
|
|
aClip = basegfx::utils::stripNeutralPolygons(aClip);
|
|
aClip = basegfx::utils::stripDispensablePolygons(aClip);
|
|
|
|
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 identity (->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::utils::createPolygonFromRect(
|
|
basegfx::B2DRange(rArea)));
|
|
|
|
::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
|
|
::cppcanvas::BaseGfxFactory::createPolyPolygon( pCanvas, aPoly ) );
|
|
|
|
if( pPolyPoly )
|
|
{
|
|
pPolyPoly->setCompositeOp( css::rendering::CompositeOperation::SOURCE );
|
|
pPolyPoly->setRGBAFillColor( 0xFFFFFF00U );
|
|
pPolyPoly->draw();
|
|
}
|
|
|
|
#if defined(DBG_UTIL)
|
|
::cppcanvas::CanvasSharedPtr pCliplessCanvas( pCanvas->clone() );
|
|
pCliplessCanvas->setClip();
|
|
|
|
if( pCanvas->getClip() )
|
|
{
|
|
::cppcanvas::PolyPolygonSharedPtr pPolyPoly2(
|
|
::cppcanvas::BaseGfxFactory::createPolyPolygon( pCliplessCanvas, aPoly ));
|
|
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(
|
|
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
|
|
for( const auto& rSprite : maSprites )
|
|
{
|
|
cppcanvas::CustomSpriteSharedPtr pCurrSprite( rSprite.mpSprite.lock() );
|
|
|
|
if( pCurrSprite )
|
|
{
|
|
// only copy still valid sprites over to the refreshed
|
|
// sprite vector.
|
|
aValidSprites.push_back( rSprite );
|
|
|
|
pCurrSprite->setPriority(
|
|
getSpritePriority( aValidSprites.size()-1 ));
|
|
}
|
|
}
|
|
|
|
// replace sprite list with pruned one
|
|
maSprites.swap( aValidSprites );
|
|
}
|
|
|
|
public:
|
|
LayerSpriteContainer() :
|
|
maSprites(),
|
|
maLayerPrioRange()
|
|
{
|
|
}
|
|
|
|
const 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
|
|
{
|
|
/// 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( cppcanvas::SpriteCanvasSharedPtr pCanvas,
|
|
basegfx::B2DHomMatrix aTransform,
|
|
const basegfx::B2DRange& rLayerBounds,
|
|
const basegfx::B2DSize& rUserSize,
|
|
View const* const pParentView) :
|
|
maSpriteContainer(),
|
|
maLayerBounds(rLayerBounds),
|
|
maLayerBoundsPixel(),
|
|
maClip(),
|
|
maUserSize(rUserSize),
|
|
maTransformation(std::move(aTransform)),
|
|
mpSpriteCanvas(std::move(pCanvas)),
|
|
mpSprite(),
|
|
mpOutputCanvas(),
|
|
mpParentView(pParentView)
|
|
{
|
|
}
|
|
|
|
SlideViewLayer(const SlideViewLayer&) = delete;
|
|
SlideViewLayer& operator=(const SlideViewLayer&) = delete;
|
|
|
|
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.getWidth(),
|
|
maUserSize.getHeight()) );
|
|
|
|
basegfx::B2IRange const aNewLayerPixel(
|
|
getLayerBoundsPixel(maLayerBounds,
|
|
maTransformation) );
|
|
if( aNewLayerPixel != maLayerBoundsPixel )
|
|
{
|
|
// re-gen sprite with new size
|
|
mpOutputCanvas.reset();
|
|
mpSprite.reset();
|
|
}
|
|
}
|
|
|
|
virtual css::geometry::IntegerSize2D getTranslationOffset() const override
|
|
{
|
|
basegfx::B2DRectangle aTmpRect = canvas::tools::calcTransformedRectBounds(
|
|
maLayerBounds,
|
|
maTransformation );
|
|
geometry::IntegerSize2D offset(0, 0);
|
|
|
|
// Add translation according to the origin of aTmpRect. Ignore the
|
|
// translation when aTmpRect was not properly initialized.
|
|
if ( ! aTmpRect.isEmpty())
|
|
{
|
|
offset.Width = basegfx::fround(aTmpRect.getMinX());
|
|
offset.Height = basegfx::fround(aTmpRect.getMinY());
|
|
}
|
|
return offset;
|
|
}
|
|
|
|
private:
|
|
// ViewLayer interface
|
|
|
|
|
|
virtual cppcanvas::CustomSpriteSharedPtr createSprite(
|
|
const ::basegfx::B2DSize& rSpriteSizePixel,
|
|
double nPriority ) const override
|
|
{
|
|
cppcanvas::CustomSpriteSharedPtr pSprite(
|
|
mpSpriteCanvas->createCustomSprite( rSpriteSizePixel ) );
|
|
|
|
maSpriteContainer.addSprite( pSprite,
|
|
nPriority );
|
|
|
|
return pSprite;
|
|
}
|
|
|
|
virtual void setPriority( const basegfx::B1DRange& rRange ) override
|
|
{
|
|
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 override
|
|
{
|
|
// Offset given transformation by left, top border of given
|
|
// range (after transformation through given transformation)
|
|
basegfx::B2DRectangle aTmpRect = canvas::tools::calcTransformedRectBounds(
|
|
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( -std::round(aTmpRect.getMinX()),
|
|
-std::round(aTmpRect.getMinY()) );
|
|
}
|
|
|
|
return aMatrix;
|
|
}
|
|
|
|
virtual basegfx::B2DHomMatrix getSpriteTransformation() const override
|
|
{
|
|
return maTransformation;
|
|
}
|
|
|
|
virtual void clear() const override
|
|
{
|
|
// grab canvas - that also lazy-initializes maLayerBoundsPixel
|
|
cppcanvas::CanvasSharedPtr pCanvas=getCanvas()->clone();
|
|
|
|
// clear whole canvas
|
|
const basegfx::B2I64Tuple aSpriteSize(maLayerBoundsPixel.getRange());
|
|
clearRect(pCanvas,
|
|
basegfx::B2IRange(0,0,aSpriteSize.getX(),aSpriteSize.getY()));
|
|
}
|
|
|
|
virtual void clearAll() const override
|
|
{
|
|
// grab canvas - that also lazy-initializes maLayerBoundsPixel
|
|
::cppcanvas::CanvasSharedPtr pCanvas( getCanvas()->clone() );
|
|
|
|
// clear layer clip, to clear whole area
|
|
pCanvas->setClip();
|
|
|
|
// clear whole canvas
|
|
const basegfx::B2I64Tuple aSpriteSize(maLayerBoundsPixel.getRange());
|
|
clearRect(pCanvas,
|
|
basegfx::B2IRange(0,0,aSpriteSize.getX(),aSpriteSize.getY()));
|
|
}
|
|
|
|
virtual bool isOnView(ViewSharedPtr const& rView) const override
|
|
{
|
|
return rView.get() == mpParentView;
|
|
}
|
|
|
|
virtual cppcanvas::CanvasSharedPtr getCanvas() const override
|
|
{
|
|
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 aSpriteSize(maLayerBoundsPixel.getRange());
|
|
mpSprite = mpSpriteCanvas->createCustomSprite(
|
|
basegfx::B2DSize(sal::static_int_cast<sal_Int32>(aSpriteSize.getX()),
|
|
sal::static_int_cast<sal_Int32>(aSpriteSize.getY())) );
|
|
|
|
mpSprite->setPriority(
|
|
maSpriteContainer.getLayerPriority().getMinimum() );
|
|
|
|
basegfx::B2DPoint pos(maLayerBoundsPixel.getMinimum());
|
|
double alpha = 1.0;
|
|
#if defined(DBG_UTIL)
|
|
if (isShowingMoreDebugInfo())
|
|
{
|
|
pos += basegfx::B2DPoint(10, 10);
|
|
alpha = 0.5;
|
|
}
|
|
#endif
|
|
mpSprite->movePixel(pos);
|
|
mpSprite->setAlpha(alpha);
|
|
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 ) override
|
|
{
|
|
basegfx::B2DPolyPolygon aNewClip = prepareClip( rClip );
|
|
|
|
if( aNewClip != maClip )
|
|
{
|
|
maClip = std::move(aNewClip);
|
|
|
|
if(mpOutputCanvas )
|
|
mpOutputCanvas->setClip(
|
|
createClipPolygon( maClip,
|
|
mpOutputCanvas,
|
|
maUserSize ));
|
|
}
|
|
}
|
|
|
|
virtual bool resize( const ::basegfx::B2DRange& rArea ) override
|
|
{
|
|
const bool bRet( maLayerBounds != rArea );
|
|
maLayerBounds = rArea;
|
|
updateView( maTransformation,
|
|
maUserSize );
|
|
|
|
return bRet;
|
|
}
|
|
};
|
|
|
|
|
|
typedef cppu::WeakComponentImplHelper<
|
|
css::util::XModifyListener,
|
|
css::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 override;
|
|
virtual bool updateScreen() const override;
|
|
virtual bool paintScreen() const override;
|
|
virtual void setViewSize( const ::basegfx::B2DSize& ) override;
|
|
virtual void setCursorShape( sal_Int16 nPointerShape ) override;
|
|
|
|
// ViewLayer interface
|
|
virtual bool isOnView(ViewSharedPtr const& rView) const override;
|
|
virtual void clear() const override;
|
|
virtual void clearAll() const override;
|
|
virtual cppcanvas::CanvasSharedPtr getCanvas() const override;
|
|
virtual cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& rSpriteSizePixel,
|
|
double nPriority ) const override;
|
|
virtual void setPriority( const basegfx::B1DRange& rRange ) override;
|
|
virtual geometry::IntegerSize2D getTranslationOffset() const override;
|
|
virtual ::basegfx::B2DHomMatrix getTransformation() const override;
|
|
virtual basegfx::B2DHomMatrix getSpriteTransformation() const override;
|
|
virtual void setClip( const ::basegfx::B2DPolyPolygon& rClip ) override;
|
|
virtual bool resize( const ::basegfx::B2DRange& rArea ) override;
|
|
|
|
// UnoView:
|
|
virtual void _dispose() override;
|
|
virtual uno::Reference<presentation::XSlideShowView> getUnoView()const override;
|
|
virtual void setIsSoundEnabled (const bool bValue) override;
|
|
virtual bool isSoundEnabled() const override;
|
|
|
|
// XEventListener:
|
|
virtual void SAL_CALL disposing( lang::EventObject const& evt ) override;
|
|
// XModifyListener:
|
|
virtual void SAL_CALL modified( const lang::EventObject& aEvent ) override;
|
|
// XPaintListener:
|
|
virtual void SAL_CALL windowPaint( const awt::PaintEvent& e ) override;
|
|
|
|
// WeakComponentImplHelperBase:
|
|
virtual void SAL_CALL disposing() override;
|
|
|
|
void updateClip();
|
|
|
|
private:
|
|
typedef std::vector< std::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::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_FAIL( "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();
|
|
|
|
auto xViewLayer = std::make_shared<SlideViewLayer>(mpCanvas,
|
|
getTransformation(),
|
|
rLayerBounds,
|
|
maUserSize,
|
|
this);
|
|
maViewLayers.push_back(xViewLayer);
|
|
|
|
return xViewLayer;
|
|
}
|
|
|
|
bool SlideView::updateScreen() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_RETURN_FALSE( mpCanvas,
|
|
"SlideView::updateScreen(): Disposed" );
|
|
|
|
return mpCanvas->updateScreen( false );
|
|
}
|
|
|
|
bool SlideView::paintScreen() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
ENSURE_OR_RETURN_FALSE( mpCanvas,
|
|
"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.getWidth(),
|
|
maUserSize.getHeight()),
|
|
getTransformation()));
|
|
}
|
|
|
|
void SlideView::clearAll() const
|
|
{
|
|
osl::MutexGuard aGuard( m_aMutex );
|
|
|
|
OSL_ENSURE( mxView.is() && mpCanvas,
|
|
"SlideView::clear(): Disposed" );
|
|
if( !mxView.is() || !mpCanvas )
|
|
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
|
|
|
|
// 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(ViewSharedPtr 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_FAIL( "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.getWidth(), 1.0 / maUserSize.getHeight() );
|
|
|
|
return maViewTransform * aMatrix;
|
|
}
|
|
|
|
geometry::IntegerSize2D SlideView::getTranslationOffset() const
|
|
{
|
|
return mxView->getTranslationOffset();
|
|
}
|
|
|
|
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 = std::move(aNewClip);
|
|
updateClip();
|
|
}
|
|
}
|
|
|
|
bool SlideView::resize( const ::basegfx::B2DRange& /*rArea*/ )
|
|
{
|
|
OSL_FAIL( "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() const
|
|
{
|
|
return mbIsSoundEnabled;
|
|
}
|
|
|
|
void SlideView::_dispose()
|
|
{
|
|
dispose();
|
|
}
|
|
|
|
// XEventListener
|
|
void SlideView::disposing( lang::EventObject const& 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();
|
|
}
|
|
|
|
// silly wrapper to check that event handlers don't touch dead SlideView
|
|
struct WeakRefWrapper
|
|
{
|
|
unotools::WeakReference<SlideView> const m_wObj;
|
|
std::function<void (SlideView&)> const m_func;
|
|
|
|
WeakRefWrapper(SlideView & rObj, std::function<void (SlideView&)> func)
|
|
: m_wObj(&rObj)
|
|
, m_func(std::move(func))
|
|
{
|
|
}
|
|
|
|
void operator()()
|
|
{
|
|
rtl::Reference<SlideView> const xObj(m_wObj);
|
|
if (xObj.is())
|
|
{
|
|
m_func(*xObj);
|
|
}
|
|
}
|
|
};
|
|
|
|
// XModifyListener
|
|
void SlideView::modified( const lang::EventObject& /*aEvent*/ )
|
|
{
|
|
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_FAIL( "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( WeakRefWrapper(*this,
|
|
[] (SlideView & rThis) { rThis.mrEventMultiplexer.notifyViewChanged(rThis.mxView); }),
|
|
u"EventMultiplexer::notifyViewChanged"_ustr));
|
|
}
|
|
|
|
// XPaintListener
|
|
void SlideView::windowPaint( const awt::PaintEvent& /*e*/ )
|
|
{
|
|
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( WeakRefWrapper(*this,
|
|
[] (SlideView & rThis) { rThis.mrEventMultiplexer.notifyViewClobbered(rThis.mxView); }),
|
|
u"EventMultiplexer::notifyViewClobbered"_ustr) );
|
|
}
|
|
|
|
void SlideView::updateCanvas()
|
|
{
|
|
OSL_ENSURE( mpCanvas,
|
|
"SlideView::updateCanvasTransform(): Disposed" );
|
|
|
|
if( !mpCanvas || !mxView.is())
|
|
return;
|
|
|
|
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();
|
|
}
|
|
|
|
void SlideView::pruneLayers( bool bWithViewLayerUpdate ) const
|
|
{
|
|
ViewLayerVector aValidLayers;
|
|
|
|
const basegfx::B2DHomMatrix aCurrTransform(
|
|
getTransformation() );
|
|
|
|
// check all layers for validity, and retain only the live ones
|
|
for( const auto& rView : maViewLayers )
|
|
{
|
|
std::shared_ptr< SlideViewLayer > xCurrLayer( rView.lock() );
|
|
|
|
if ( xCurrLayer )
|
|
{
|
|
aValidLayers.push_back( xCurrLayer );
|
|
|
|
if( bWithViewLayerUpdate )
|
|
xCurrLayer->updateView( aCurrTransform,
|
|
maUserSize );
|
|
}
|
|
}
|
|
|
|
// replace layer list with pruned one
|
|
maViewLayers.swap( aValidLayers );
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
UnoViewSharedPtr createSlideView( uno::Reference< presentation::XSlideShowView> const& xView,
|
|
EventQueue& rEventQueue,
|
|
EventMultiplexer& rEventMultiplexer )
|
|
{
|
|
std::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 slideshow
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|