diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source/engine/slideview.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | slideshow/source/engine/slideview.cxx | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/slideshow/source/engine/slideview.cxx b/slideshow/source/engine/slideview.cxx new file mode 100644 index 000000000..013e089a0 --- /dev/null +++ b/slideshow/source/engine/slideview.cxx @@ -0,0 +1,1193 @@ +/* -*- 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 <tools/diagnose_ex.h> +#include <canvas/canvastools.hxx> + +#include <eventqueue.hxx> +#include <eventmultiplexer.hxx> +#include <slideview.hxx> +#include <delayevent.hxx> +#include <unoview.hxx> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <comphelper/make_shared_from_uno.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 <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.getX(), rUserSize.getY()); + + 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( 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 + 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( 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) + { + } + + 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.getX(), + maUserSize.getY()) ); + + basegfx::B2IRange const& rNewLayerPixel( + getLayerBoundsPixel(maLayerBounds, + maTransformation) ); + if( rNewLayerPixel != maLayerBoundsPixel ) + { + // re-gen sprite with new size + mpOutputCanvas.reset(); + mpSprite.reset(); + } + } + + virtual css::geometry::IntegerSize2D getTranslationOffset() const override + { + basegfx::B2DRectangle aTmpRect; + canvas::tools::calcTransformedRectBounds( aTmpRect, + 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( 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 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& rSpriteSize(maLayerBoundsPixel.getRange()); + clearRect(pCanvas, + basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.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& rSpriteSize(maLayerBoundsPixel.getRange()); + clearRect(pCanvas, + basegfx::B2IRange(0,0,rSpriteSize.getX(),rSpriteSize.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& 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(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 ) override + { + 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 ) 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.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; + + 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::MutexGuard aGuard( m_aMutex ); + + 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.getX(), 1.0/maUserSize.getY() ); + + 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 = aNewClip; + + updateClip(); + } +} + +bool SlideView::resize( const ::basegfx::B2DRange& /*rArea*/ ) +{ + osl::MutexGuard aGuard( m_aMutex ); + + 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 +{ + SlideView & m_rObj; + uno::WeakReference<uno::XInterface> const m_wObj; + std::function<void (SlideView&)> const m_func; + + WeakRefWrapper(SlideView & rObj, std::function<void (SlideView&)> const& func) + : m_rObj(rObj) + , m_wObj(static_cast<::cppu::OWeakObject*>(&rObj)) + , m_func(func) + { + } + + void operator()() + { + uno::Reference<uno::XInterface> const xObj(m_wObj); + if (xObj.is()) + { + m_func(m_rObj); + } + } +}; + +// 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); }), + "EventMultiplexer::notifyViewChanged")); +} + +// 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); }), + "EventMultiplexer::notifyViewClobbered") ); +} + +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& rCurrTransform( + 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( rCurrTransform, + 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: */ |