summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/slide
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--slideshow/source/engine/slide/layer.cxx272
-rw-r--r--slideshow/source/engine/slide/layer.hxx263
-rw-r--r--slideshow/source/engine/slide/layermanager.cxx833
-rw-r--r--slideshow/source/engine/slide/layermanager.hxx363
-rw-r--r--slideshow/source/engine/slide/shapemanagerimpl.cxx426
-rw-r--r--slideshow/source/engine/slide/shapemanagerimpl.hxx189
-rw-r--r--slideshow/source/engine/slide/slideanimations.cxx106
-rw-r--r--slideshow/source/engine/slide/slideanimations.hxx107
-rw-r--r--slideshow/source/engine/slide/slideimpl.cxx1130
-rw-r--r--slideshow/source/engine/slide/targetpropertiescreator.cxx370
-rw-r--r--slideshow/source/engine/slide/targetpropertiescreator.hxx39
-rw-r--r--slideshow/source/engine/slide/userpaintoverlay.cxx485
-rw-r--r--slideshow/source/engine/slide/userpaintoverlay.hxx83
-rw-r--r--slideshow/source/engine/slidebitmap.cxx112
-rw-r--r--slideshow/source/engine/slideshowcontext.cxx70
-rw-r--r--slideshow/source/engine/slideshowimpl.cxx2439
-rw-r--r--slideshow/source/engine/slideview.cxx1193
17 files changed, 8480 insertions, 0 deletions
diff --git a/slideshow/source/engine/slide/layer.cxx b/slideshow/source/engine/slide/layer.cxx
new file mode 100644
index 000000000..01ce4efa4
--- /dev/null
+++ b/slideshow/source/engine/slide/layer.cxx
@@ -0,0 +1,272 @@
+/* -*- 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 <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b1drange.hxx>
+#include <basegfx/range/b2dpolyrange.hxx>
+#include <basegfx/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolypolygoncutter.hxx>
+#include <osl/diagnose.h>
+
+#include "layer.hxx"
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+ Layer::Layer( Dummy ) :
+ maViewEntries(),
+ maBounds(),
+ maNewBounds(),
+ mbBoundsDirty(false),
+ mbBackgroundLayer(true),
+ mbClipSet(false)
+ {
+ }
+
+ Layer::Layer() :
+ maViewEntries(),
+ maBounds(),
+ maNewBounds(),
+ mbBoundsDirty(false),
+ mbBackgroundLayer(false),
+ mbClipSet(false)
+ {
+ }
+
+ ViewLayerSharedPtr Layer::addView( const ViewSharedPtr& rNewView )
+ {
+ OSL_ASSERT( rNewView );
+
+ ViewEntryVector::iterator aIter;
+ const ViewEntryVector::iterator aEnd( maViewEntries.end() );
+ if( (aIter=std::find_if( maViewEntries.begin(),
+ aEnd,
+ [&rNewView]( const ViewEntry& rViewEntry )
+ { return rViewEntry.getView() == rNewView; } ) ) != aEnd )
+ {
+ // already added - just return existing layer
+ return aIter->mpViewLayer;
+
+ }
+
+ // not yet added - create new view layer
+ ViewLayerSharedPtr pNewLayer;
+ if( mbBackgroundLayer )
+ pNewLayer = rNewView;
+ else
+ pNewLayer = rNewView->createViewLayer(maBounds);
+
+ // add to local list
+ maViewEntries.emplace_back( rNewView,
+ pNewLayer );
+
+ return maViewEntries.back().mpViewLayer;
+ }
+
+ ViewLayerSharedPtr Layer::removeView( const ViewSharedPtr& rView )
+ {
+ OSL_ASSERT( rView );
+
+ ViewEntryVector::iterator aIter;
+ const ViewEntryVector::iterator aEnd( maViewEntries.end() );
+ if( (aIter=std::find_if( maViewEntries.begin(),
+ aEnd,
+ [&rView]( const ViewEntry& rViewEntry )
+ { return rViewEntry.getView() == rView; } ) ) == aEnd )
+ {
+ // View was not added/is already removed
+ return ViewLayerSharedPtr();
+ }
+
+ OSL_ENSURE( std::count_if( maViewEntries.begin(),
+ aEnd,
+ [&rView]( const ViewEntry& rViewEntry )
+ { return rViewEntry.getView() == rView; } ) == 1,
+ "Layer::removeView(): view added multiple times" );
+
+ ViewLayerSharedPtr pRet( aIter->mpViewLayer );
+ maViewEntries.erase(aIter);
+
+ return pRet;
+ }
+
+ void Layer::setShapeViews( ShapeSharedPtr const& rShape ) const
+ {
+ rShape->clearAllViewLayers();
+
+ for( const auto& rViewEntry : maViewEntries )
+ rShape->addViewLayer( rViewEntry.getViewLayer(), false );
+ }
+
+ void Layer::setPriority( const ::basegfx::B1DRange& rPrioRange )
+ {
+ if( !mbBackgroundLayer )
+ {
+ for( const auto& rViewEntry : maViewEntries )
+ rViewEntry.getViewLayer()->setPriority( rPrioRange );
+ }
+ }
+
+ void Layer::addUpdateRange( ::basegfx::B2DRange const& rUpdateRange )
+ {
+ // TODO(Q1): move this to B2DMultiRange
+ if( !rUpdateRange.isEmpty() )
+ maUpdateAreas.appendElement( rUpdateRange,
+ basegfx::B2VectorOrientation::Positive );
+ }
+
+ void Layer::updateBounds( ShapeSharedPtr const& rShape )
+ {
+ if( !mbBackgroundLayer )
+ {
+ if( !mbBoundsDirty )
+ maNewBounds.reset();
+
+ maNewBounds.expand( rShape->getUpdateArea() );
+ }
+
+ mbBoundsDirty = true;
+ }
+
+ bool Layer::commitBounds()
+ {
+ mbBoundsDirty = false;
+
+ if( mbBackgroundLayer )
+ return false;
+
+ if( maNewBounds == maBounds )
+ return false;
+
+ maBounds = maNewBounds;
+ if( std::count_if( maViewEntries.begin(),
+ maViewEntries.end(),
+ [this]( const ViewEntry& rViewEntry )
+ { return rViewEntry.getViewLayer()->resize( this->maBounds ); }
+ ) == 0 )
+ {
+ return false;
+ }
+
+ // layer content invalid, update areas have wrong
+ // coordinates/not sensible anymore.
+ clearUpdateRanges();
+
+ return true;
+ }
+
+ void Layer::clearUpdateRanges()
+ {
+ maUpdateAreas.clear();
+ }
+
+ void Layer::clearContent()
+ {
+ // clear content on all view layers
+ for( const auto& rViewEntry : maViewEntries )
+ rViewEntry.getViewLayer()->clearAll();
+
+ // layer content cleared, update areas are not sensible
+ // anymore.
+ clearUpdateRanges();
+ }
+
+ class LayerEndUpdate
+ {
+ public:
+ LayerEndUpdate( const LayerEndUpdate& ) = delete;
+ LayerEndUpdate& operator=( const LayerEndUpdate& ) = delete;
+ explicit LayerEndUpdate( LayerSharedPtr const& rLayer ) :
+ mpLayer( rLayer )
+ {}
+
+ ~LayerEndUpdate() { if(mpLayer) mpLayer->endUpdate(); }
+
+ private:
+ LayerSharedPtr mpLayer;
+ };
+
+ Layer::EndUpdater Layer::beginUpdate()
+ {
+ if( maUpdateAreas.count() )
+ {
+ // perform proper layer update. That means, setup proper
+ // clipping, and render each shape that intersects with
+ // the calculated update area
+ ::basegfx::B2DPolyPolygon aClip( maUpdateAreas.solveCrossovers() );
+ aClip = ::basegfx::utils::stripNeutralPolygons(aClip);
+ aClip = ::basegfx::utils::stripDispensablePolygons(aClip);
+
+ // actually, if there happen to be shapes with zero
+ // update area in the maUpdateAreas vector, the
+ // resulting clip polygon will be empty.
+ if( aClip.count() )
+ {
+ for( const auto& rViewEntry : maViewEntries )
+ {
+ const ViewLayerSharedPtr& pViewLayer = rViewEntry.getViewLayer();
+
+ // set clip to all view layers and
+ pViewLayer->setClip( aClip );
+
+ // clear update area on all view layers
+ pViewLayer->clear();
+ }
+
+ mbClipSet = true;
+ }
+ }
+
+ return std::make_shared<LayerEndUpdate>(shared_from_this());
+ }
+
+ void Layer::endUpdate()
+ {
+ if( mbClipSet )
+ {
+ mbClipSet = false;
+
+ basegfx::B2DPolyPolygon aEmptyClip;
+ for( const auto& rViewEntry : maViewEntries )
+ rViewEntry.getViewLayer()->setClip( aEmptyClip );
+ }
+
+ clearUpdateRanges();
+ }
+
+ bool Layer::isInsideUpdateArea( ShapeSharedPtr const& rShape ) const
+ {
+ return maUpdateAreas.overlaps( rShape->getUpdateArea() );
+ }
+
+ LayerSharedPtr Layer::createBackgroundLayer()
+ {
+ return LayerSharedPtr(new Layer( BackgroundLayer ));
+ }
+
+ LayerSharedPtr Layer::createLayer( )
+ {
+ return LayerSharedPtr( new Layer );
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/layer.hxx b/slideshow/source/engine/slide/layer.hxx
new file mode 100644
index 000000000..73f3fcce9
--- /dev/null
+++ b/slideshow/source/engine/slide/layer.hxx
@@ -0,0 +1,263 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYER_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYER_HXX
+
+#include <basegfx/range/b2dpolyrange.hxx>
+
+#include <shape.hxx>
+#include <view.hxx>
+
+#include <vector>
+#include <memory>
+
+
+namespace slideshow::internal
+ {
+ class LayerEndUpdate;
+ class Layer;
+ typedef ::std::shared_ptr< Layer > LayerSharedPtr;
+ typedef ::std::weak_ptr< Layer > LayerWeakPtr;
+
+
+ /* Definition of Layer class */
+
+ /** This class represents one layer of output on a Slide.
+
+ Layers group shapes for a certain depth region of a slide.
+
+ Since slides have a notion of depth, i.e. shapes on it
+ have a certain order in which they lie upon each other,
+ this layering must be modelled. A prime example for this
+ necessity are animations of shapes lying behind other
+ shapes. Then, everything behind the animated shape will be
+ in a background layer, the shape itself will be in an
+ animation layer, and everything before it will be in a
+ foreground layer (these layers are most preferably
+ modelled as XSprite objects internally).
+
+ @attention All methods of this class are only supposed to
+ be called from the LayerManager. Normally, it shouldn't be
+ possible to get hold of an instance of this class at all.
+ */
+ class Layer : public std::enable_shared_from_this<Layer>
+ {
+ public:
+ typedef std::shared_ptr<LayerEndUpdate> EndUpdater;
+
+ /// Forbid copy construction
+ Layer(const Layer&) = delete;
+ /// Forbid copy assignment
+ Layer& operator=(const Layer&) = delete;
+
+ /** Create background layer
+
+ This method will create a layer without a ViewLayer,
+ i.e. one that displays directly on the background.
+ */
+ static LayerSharedPtr createBackgroundLayer();
+
+ /** Create non-background layer
+
+ This method will create a layer in front of the
+ background, to contain shapes that should appear in
+ front of animated objects.
+ */
+ static LayerSharedPtr createLayer();
+
+
+ /** Predicate, whether this layer is the special
+ background layer
+
+ This method is mostly useful for checking invariants.
+ */
+ bool isBackgroundLayer() const { return mbBackgroundLayer; }
+
+ /** Add a view to this layer.
+
+ If the view is already added, this method does not add
+ it a second time, just returning the existing ViewLayer.
+
+ @param rNewView
+ New view to add to this layer.
+
+ @return the newly generated ViewLayer for this View
+ */
+ ViewLayerSharedPtr addView( const ViewSharedPtr& rNewView );
+
+ /** Remove a view
+
+ This method removes the view from this Layer and all
+ shapes included herein.
+
+ @return the ViewLayer of the removed Layer, if
+ any. Otherwise, NULL is returned.
+ */
+ ViewLayerSharedPtr removeView( const ViewSharedPtr& rView );
+
+ /** Init shape with this layer's views
+
+ @param rShape
+ The shape, that will subsequently display on this
+ layer's views
+ */
+ void setShapeViews( ShapeSharedPtr const& rShape ) const;
+
+
+ /** Change layer priority range.
+
+ The layer priority affects the position of the layer
+ in the z direction (i.e. before/behind which other
+ layers this one appears). The higher the prio, the
+ further on top of the layer stack this one appears.
+
+ @param rPrioRange
+ The priority range of differing layers must not
+ intersect
+ */
+ void setPriority( const ::basegfx::B1DRange& rPrioRange );
+
+ /** Add an area that needs update
+
+ @param rUpdateRange
+ Area on this layer that needs update
+ */
+ void addUpdateRange( ::basegfx::B2DRange const& rUpdateRange );
+
+ /** Whether any update ranges have been added
+
+ @return true, if any non-empty addUpdateRange() calls
+ have been made since the last render()/update() call.
+ */
+ bool isUpdatePending() const { return maUpdateAreas.count()!=0; }
+
+ /** Update layer bound rect from shape bounds
+ */
+ void updateBounds( ShapeSharedPtr const& rShape );
+
+ /** Commit collected layer bounds to ViewLayer
+
+ Call this method when you're done adding new shapes to
+ the layer.
+
+ @return true, if layer needed a resize (which
+ invalidates its content - you have to repaint all
+ contained shapes!)
+ */
+ bool commitBounds();
+
+ /** Clear all registered update ranges
+
+ This method clears all update ranges that are
+ registered at this layer.
+ */
+ void clearUpdateRanges();
+
+ /** Clear whole layer content
+
+ This method clears the whole layer content. As a
+ byproduct, all update ranges are cleared as well. It
+ makes no sense to maintain them any further, since
+ they only serve for partial updates.
+ */
+ void clearContent();
+
+ /** Init layer update.
+
+ This method initializes a full layer update of the
+ update area. When the last copy of the returned
+ EndUpdater is destroyed, the Layer leaves update mode
+ again.
+
+ @return an update end RAII object.
+ */
+ EndUpdater beginUpdate();
+
+ /** Finish layer update
+
+ Resets clipping and transformation to normal values
+ */
+ void endUpdate();
+
+ /** Check whether given shape is inside current update area.
+
+ @return true, if the given shape is at least partially
+ inside the current update area.
+ */
+ bool isInsideUpdateArea( ShapeSharedPtr const& rShape ) const;
+
+ private:
+ enum Dummy{ BackgroundLayer };
+
+ /** Create background layer
+
+ This constructor will create a layer without a
+ ViewLayer, i.e. one that displays directly on the
+ background.
+
+ @param eFlag
+ Dummy parameter, to disambiguate from normal layer
+ constructor
+ */
+ explicit Layer( Dummy eFlag );
+
+ /** Create non-background layer
+
+ This constructor will create a layer in front of the
+ background, to contain shapes that should appear in
+ front of animated objects.
+ */
+ explicit Layer();
+
+ struct ViewEntry
+ {
+ ViewEntry( const ViewSharedPtr& rView,
+ const ViewLayerSharedPtr& rViewLayer ) :
+ mpView( rView ),
+ mpViewLayer( rViewLayer )
+ {}
+
+ ViewSharedPtr mpView;
+ ViewLayerSharedPtr mpViewLayer;
+
+ // for generic algo access (which needs actual functions)
+ const ViewSharedPtr& getView() const { return mpView; }
+ const ViewLayerSharedPtr& getViewLayer() const { return mpViewLayer; }
+ };
+
+ typedef ::std::vector< ViewEntry > ViewEntryVector;
+
+ ViewEntryVector maViewEntries;
+ basegfx::B2DPolyRange maUpdateAreas;
+ basegfx::B2DRange maBounds;
+ basegfx::B2DRange maNewBounds;
+ bool mbBoundsDirty; // true, if view layers need resize
+ bool mbBackgroundLayer; // true, if this
+ // layer is the
+ // special
+ // background layer
+ bool mbClipSet; // true, if beginUpdate set a clip
+ };
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/layermanager.cxx b/slideshow/source/engine/slide/layermanager.cxx
new file mode 100644
index 000000000..74a26d1e7
--- /dev/null
+++ b/slideshow/source/engine/slide/layermanager.cxx
@@ -0,0 +1,833 @@
+/* -*- 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 <basegfx/range/b1drange.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <cppcanvas/canvas.hxx>
+
+#include <functional>
+#include <algorithm>
+
+#include "layermanager.hxx"
+
+using namespace ::com::sun::star;
+
+namespace
+{
+ // add operator!= for weak_ptr
+ bool notEqual( slideshow::internal::LayerWeakPtr const& rLHS,
+ slideshow::internal::LayerWeakPtr const& rRHS )
+ {
+ return rLHS.lock().get() != rRHS.lock().get();
+ }
+}
+
+namespace slideshow::internal
+{
+ template<typename LayerFunc,
+ typename ShapeFunc> void LayerManager::manageViews(
+ LayerFunc layerFunc,
+ ShapeFunc shapeFunc )
+ {
+ LayerSharedPtr pCurrLayer;
+ ViewLayerSharedPtr pCurrViewLayer;
+ for( const auto& rShape : maAllShapes )
+ {
+ LayerSharedPtr pLayer = rShape.second.lock();
+ if( pLayer && pLayer != pCurrLayer )
+ {
+ pCurrLayer = pLayer;
+ pCurrViewLayer = layerFunc(pCurrLayer);
+ }
+
+ if( pCurrViewLayer )
+ shapeFunc(rShape.first,pCurrViewLayer);
+ }
+ }
+
+ LayerManager::LayerManager( const UnoViewContainer& rViews,
+ bool bDisableAnimationZOrder ) :
+ mrViews(rViews),
+ maLayers(),
+ maXShapeHash( 101 ),
+ maAllShapes(),
+ maUpdateShapes(),
+ mnActiveSprites(0),
+ mbLayerAssociationDirty(false),
+ mbActive(false),
+ mbDisableAnimationZOrder(bDisableAnimationZOrder)
+ {
+ // prevent frequent resizes (won't have more than 4 layers
+ // for 99.9% of the cases)
+ maLayers.reserve(4);
+
+ // create initial background layer
+ maLayers.push_back( Layer::createBackgroundLayer() );
+
+ // init views
+ for( const auto& rView : mrViews )
+ viewAdded( rView );
+ }
+
+ void LayerManager::activate()
+ {
+ mbActive = true;
+ maUpdateShapes.clear(); // update gets forced via area, or
+ // has happened outside already
+
+ // clear all possibly pending update areas - content
+ // is there, already
+ for( const auto& pLayer : maLayers )
+ pLayer->clearUpdateRanges();
+
+ updateShapeLayers( true/*bSlideBackgroundPainted*/ );
+ }
+
+ void LayerManager::deactivate()
+ {
+ // TODO(F3): This is mostly a hack. Problem is, there's
+ // currently no smart way of telling shapes "remove your
+ // sprites". Others, like MediaShapes, listen to
+ // start/stop animation events, which is too much overhead
+ // for all shapes, though.
+
+ const bool bMoreThanOneLayer(maLayers.size() > 1);
+ if (mnActiveSprites || bMoreThanOneLayer)
+ {
+ // clear all viewlayers, dump everything but the
+ // background layer - this will also remove all shape
+ // sprites
+ for (auto& rShape : maAllShapes)
+ rShape.first->clearAllViewLayers();
+
+ for (auto& rShape : maAllShapes)
+ rShape.second.reset();
+
+ if (bMoreThanOneLayer)
+ maLayers.erase(maLayers.begin() + 1, maLayers.end());
+
+ mbLayerAssociationDirty = true;
+ }
+
+ mbActive = false;
+
+ // only background layer left
+ OSL_ASSERT( maLayers.size() == 1 && maLayers.front()->isBackgroundLayer() );
+ }
+
+ void LayerManager::viewAdded( const UnoViewSharedPtr& rView )
+ {
+ // view must be member of mrViews container
+ OSL_ASSERT( std::find(mrViews.begin(),
+ mrViews.end(),
+ rView) != mrViews.end() );
+
+ // init view content
+ if( mbActive )
+ rView->clearAll();
+
+ // add View to all registered shapes
+ manageViews(
+ [&rView]( const LayerSharedPtr& pLayer )
+ { return pLayer->addView( rView ); },
+ []( const ShapeSharedPtr& pShape, const ViewLayerSharedPtr& pLayer )
+ { return pShape->addViewLayer( pLayer, true ); } );
+
+ // in case we haven't reached all layers from the
+ // maAllShapes, issue addView again for good measure
+ for( const auto& pLayer : maLayers )
+ pLayer->addView( rView );
+ }
+
+ void LayerManager::viewRemoved( const UnoViewSharedPtr& rView )
+ {
+ // view must not be member of mrViews container anymore
+ OSL_ASSERT( std::find(mrViews.begin(),
+ mrViews.end(),
+ rView) == mrViews.end() );
+
+ // remove View from all registered shapes
+ manageViews(
+ [&rView]( const LayerSharedPtr& pLayer )
+ { return pLayer->removeView( rView ); },
+ []( const ShapeSharedPtr& pShape, const ViewLayerSharedPtr& pLayer )
+ { return pShape->removeViewLayer( pLayer ); } );
+
+ // in case we haven't reached all layers from the
+ // maAllShapes, issue removeView again for good measure
+ for( const auto& pLayer : maLayers )
+ pLayer->removeView( rView );
+ }
+
+ void LayerManager::viewChanged( const UnoViewSharedPtr& rView )
+ {
+ // view must be member of mrViews container
+ OSL_ASSERT( std::find(mrViews.begin(),
+ mrViews.end(),
+ rView) != mrViews.end() );
+
+ // TODO(P2): selectively update only changed view
+ viewsChanged();
+ }
+
+ void LayerManager::viewsChanged()
+ {
+ if( !mbActive )
+ return;
+
+ // clear view area
+ for( const auto& pView : mrViews )
+ pView->clearAll();
+
+ // TODO(F3): resize and repaint all layers
+
+ // render all shapes
+ for( const auto& rShape : maAllShapes )
+ rShape.first->render();
+ }
+
+ void LayerManager::addShape( const ShapeSharedPtr& rShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ ENSURE_OR_THROW( rShape, "LayerManager::addShape(): invalid Shape" );
+
+ // add shape to XShape hash map
+ if( !maXShapeHash.emplace(rShape->getXShape(),
+ rShape).second )
+ {
+ // entry already present, nothing to do
+ return;
+ }
+
+ // add shape to appropriate layer
+ implAddShape( rShape );
+ }
+
+ void LayerManager::putShape2BackgroundLayer( LayerShapeMap::value_type& rShapeEntry )
+ {
+ LayerSharedPtr& rBgLayer( maLayers.front() );
+ rBgLayer->setShapeViews(rShapeEntry.first);
+ rShapeEntry.second = rBgLayer;
+ }
+
+ void LayerManager::implAddShape( const ShapeSharedPtr& rShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ ENSURE_OR_THROW( rShape, "LayerManager::implAddShape(): invalid Shape" );
+
+ LayerShapeMap::value_type aValue (rShape, LayerWeakPtr());
+
+ OSL_ASSERT( maAllShapes.find(rShape) == maAllShapes.end() ); // shape must not be added already
+ mbLayerAssociationDirty = true;
+
+ if( mbDisableAnimationZOrder )
+ putShape2BackgroundLayer(
+ *maAllShapes.insert(aValue).first );
+ else
+ maAllShapes.insert(aValue);
+
+ // update shape, it's just added and not yet painted
+ if( rShape->isVisible() )
+ notifyShapeUpdate( rShape );
+ }
+
+ bool LayerManager::removeShape( const ShapeSharedPtr& rShape )
+ {
+ // remove shape from XShape hash map
+ if( maXShapeHash.erase( rShape->getXShape() ) == 0 )
+ return false; // shape not in map
+
+ OSL_ASSERT( maAllShapes.find(rShape) != maAllShapes.end() );
+
+ implRemoveShape( rShape );
+
+ return true;
+ }
+
+ void LayerManager::implRemoveShape( const ShapeSharedPtr& rShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ ENSURE_OR_THROW( rShape, "LayerManager::implRemoveShape(): invalid Shape" );
+
+ const LayerShapeMap::iterator aShapeEntry( maAllShapes.find(rShape) );
+
+ if( aShapeEntry == maAllShapes.end() )
+ return;
+
+ const bool bShapeUpdateNotified = maUpdateShapes.erase( rShape ) != 0;
+
+ // Enter shape area to the update area, but only if shape
+ // is visible and not in sprite mode (otherwise, updating
+ // the area doesn't do actual harm, but costs time)
+ // Actually, also add it if it was listed in
+ // maUpdateShapes (might have just gone invisible).
+ if( bShapeUpdateNotified ||
+ (rShape->isVisible() &&
+ !rShape->isBackgroundDetached()) )
+ {
+ LayerSharedPtr pLayer = aShapeEntry->second.lock();
+ if( pLayer )
+ {
+ // store area early, once the shape is removed from
+ // the layers, it no longer has any view references
+ pLayer->addUpdateRange( rShape->getUpdateArea() );
+ }
+ }
+
+ rShape->clearAllViewLayers();
+ maAllShapes.erase( aShapeEntry );
+
+ mbLayerAssociationDirty = true;
+ }
+
+ ShapeSharedPtr LayerManager::lookupShape( const uno::Reference< drawing::XShape >& xShape ) const
+ {
+ ENSURE_OR_THROW( xShape.is(), "LayerManager::lookupShape(): invalid Shape" );
+
+ const XShapeToShapeMap::const_iterator aIter( maXShapeHash.find( xShape ));
+ if( aIter == maXShapeHash.end() )
+ return ShapeSharedPtr(); // not found
+
+ // found, return data part of entry pair.
+ return aIter->second;
+ }
+
+ AttributableShapeSharedPtr LayerManager::getSubsetShape( const AttributableShapeSharedPtr& rOrigShape,
+ const DocTreeNode& rTreeNode )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+
+ AttributableShapeSharedPtr pSubset;
+
+ // shape already added?
+ if( rOrigShape->createSubset( pSubset,
+ rTreeNode ) )
+ {
+ OSL_ENSURE( pSubset, "LayerManager::getSubsetShape(): failed to create subset" );
+
+ // don't add to shape hash, we're dupes to the
+ // original XShape anyway - all subset shapes return
+ // the same XShape as the original one.
+
+ // add shape to corresponding layer
+ implAddShape( pSubset );
+
+ // update original shape, it now shows less content
+ // (the subset is removed from its displayed
+ // output). Subset shape is updated within
+ // implAddShape().
+ if( rOrigShape->isVisible() )
+ notifyShapeUpdate( rOrigShape );
+ }
+
+ return pSubset;
+ }
+
+ const XShapeToShapeMap& LayerManager::getXShapeToShapeMap() const
+ {
+ return maXShapeHash;
+ }
+
+ void LayerManager::revokeSubset( const AttributableShapeSharedPtr& rOrigShape,
+ const AttributableShapeSharedPtr& rSubsetShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+
+ if( rOrigShape->revokeSubset( rSubsetShape ) )
+ {
+ OSL_ASSERT( maAllShapes.find(rSubsetShape) != maAllShapes.end() );
+
+ implRemoveShape( rSubsetShape );
+
+ // update original shape, it now shows more content
+ // (the subset is added back to its displayed output)
+ if( rOrigShape->isVisible() )
+ notifyShapeUpdate( rOrigShape );
+ }
+ }
+
+ void LayerManager::enterAnimationMode( const AnimatableShapeSharedPtr& rShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ ENSURE_OR_THROW( rShape, "LayerManager::enterAnimationMode(): invalid Shape" );
+
+ const bool bPrevAnimState( rShape->isBackgroundDetached() );
+
+ rShape->enterAnimationMode();
+
+ // if this call _really_ enabled the animation mode at
+ // rShape, insert it to our enter animation queue, to
+ // perform the necessary layer reorg lazily on
+ // LayerManager::update()/render().
+ if( bPrevAnimState != rShape->isBackgroundDetached() )
+ {
+ ++mnActiveSprites;
+ mbLayerAssociationDirty = true;
+
+ // area needs update (shape is removed from normal
+ // slide, and now rendered as an autonomous
+ // sprite). store in update set
+ if( rShape->isVisible() )
+ addUpdateArea( rShape );
+ }
+
+ // TODO(P1): this can lead to potential wasted effort, if
+ // a shape gets toggled animated/unanimated a few times
+ // between two frames, returning to the original state.
+ }
+
+ void LayerManager::leaveAnimationMode( const AnimatableShapeSharedPtr& rShape )
+ {
+ ENSURE_OR_THROW( !maLayers.empty(), "LayerManager::leaveAnimationMode(): no layers" );
+ ENSURE_OR_THROW( rShape, "LayerManager::leaveAnimationMode(): invalid Shape" );
+
+ const bool bPrevAnimState( rShape->isBackgroundDetached() );
+
+ rShape->leaveAnimationMode();
+
+ // if this call _really_ ended the animation mode at
+ // rShape, insert it to our leave animation queue, to
+ // perform the necessary layer reorg lazily on
+ // LayerManager::update()/render().
+ if( bPrevAnimState != rShape->isBackgroundDetached() )
+ {
+ --mnActiveSprites;
+ mbLayerAssociationDirty = true;
+
+ // shape needs update, no previous rendering, fast
+ // update possible.
+ if( rShape->isVisible() )
+ notifyShapeUpdate( rShape );
+ }
+
+ // TODO(P1): this can lead to potential wasted effort, if
+ // a shape gets toggled animated/unanimated a few times
+ // between two frames, returning to the original state.
+ }
+
+ void LayerManager::notifyShapeUpdate( const ShapeSharedPtr& rShape )
+ {
+ if( !mbActive || mrViews.empty() )
+ return;
+
+ // hidden sprite-shape needs render() call still, to hide sprite
+ if( rShape->isVisible() || rShape->isBackgroundDetached() )
+ maUpdateShapes.insert( rShape );
+ else
+ addUpdateArea( rShape );
+ }
+
+ bool LayerManager::isUpdatePending() const
+ {
+ if( !mbActive )
+ return false;
+
+ if( mbLayerAssociationDirty || !maUpdateShapes.empty() )
+ return true;
+
+ return std::any_of( maLayers.begin(),
+ maLayers.end(),
+ std::mem_fn(&Layer::isUpdatePending) );
+ }
+
+ bool LayerManager::updateSprites()
+ {
+ bool bRet(true);
+
+ // send update() calls to every shape in the
+ // maUpdateShapes set, which is _animated_ (i.e. a
+ // sprite).
+ for( const auto& pShape : maUpdateShapes )
+ {
+ if( pShape->isBackgroundDetached() )
+ {
+ // can update shape directly, without
+ // affecting layer content (shape is
+ // currently displayed in a sprite)
+ if( !pShape->update() )
+ bRet = false; // delay error exit
+ }
+ else
+ {
+ // TODO(P2): addUpdateArea() involves log(n)
+ // search for shape layer. Have a frequent
+ // shape/layer association cache, or ptr back to
+ // layer at the shape?
+
+ // cannot update shape directly, it's not
+ // animated and update() calls will prolly
+ // overwrite other page content.
+ addUpdateArea( pShape );
+ }
+ }
+
+ maUpdateShapes.clear();
+
+ return bRet;
+ }
+
+ bool LayerManager::update()
+ {
+ bool bRet = true;
+
+ if( !mbActive )
+ return bRet;
+
+ // going to render - better flush any pending layer reorg
+ // now
+ updateShapeLayers(false);
+
+ // all sprites
+ bRet = updateSprites();
+
+ // any non-sprite update areas left?
+ if( std::none_of( maLayers.begin(),
+ maLayers.end(),
+ std::mem_fn( &Layer::isUpdatePending ) ) )
+ return bRet; // nope, done.
+
+ // update each shape on each layer, that has
+ // isUpdatePending()
+ bool bIsCurrLayerUpdating(false);
+ Layer::EndUpdater aEndUpdater;
+ LayerSharedPtr pCurrLayer;
+ for( const auto& rShape : maAllShapes )
+ {
+ LayerSharedPtr pLayer = rShape.second.lock();
+ if( pLayer != pCurrLayer )
+ {
+ pCurrLayer = pLayer;
+ bIsCurrLayerUpdating = pCurrLayer->isUpdatePending();
+
+ if( bIsCurrLayerUpdating )
+ aEndUpdater = pCurrLayer->beginUpdate();
+ }
+
+ if( bIsCurrLayerUpdating &&
+ !rShape.first->isBackgroundDetached() &&
+ pCurrLayer->isInsideUpdateArea(rShape.first) )
+ {
+ if( !rShape.first->render() )
+ bRet = false;
+ }
+ }
+
+ return bRet;
+ }
+
+ namespace
+ {
+ /** Little wrapper around a Canvas, to render one-shot
+ into a canvas
+ */
+ class DummyLayer : public ViewLayer
+ {
+ public:
+ explicit DummyLayer( const ::cppcanvas::CanvasSharedPtr& rCanvas ) :
+ mpCanvas( rCanvas )
+ {
+ }
+
+ virtual bool isOnView(ViewSharedPtr const& /*rView*/) const override
+ {
+ return true; // visible on all views
+ }
+
+ virtual ::cppcanvas::CanvasSharedPtr getCanvas() const override
+ {
+ return mpCanvas;
+ }
+
+ virtual void clear() const override
+ {
+ // NOOP
+ }
+
+ virtual void clearAll() const override
+ {
+ // NOOP
+ }
+
+ virtual ::cppcanvas::CustomSpriteSharedPtr createSprite( const ::basegfx::B2DSize& /*rSpriteSizePixel*/,
+ double /*nSpritePrio*/ ) const override
+ {
+ ENSURE_OR_THROW( false,
+ "DummyLayer::createSprite(): This method is not supposed to be called!" );
+ return ::cppcanvas::CustomSpriteSharedPtr();
+ }
+
+ virtual void setPriority( const basegfx::B1DRange& /*rRange*/ ) override
+ {
+ OSL_FAIL( "BitmapView::setPriority(): This method is not supposed to be called!" );
+ }
+
+ virtual css::geometry::IntegerSize2D getTranslationOffset() const override
+ {
+ return geometry::IntegerSize2D(0,0);
+ }
+
+ virtual ::basegfx::B2DHomMatrix getTransformation() const override
+ {
+ return mpCanvas->getTransformation();
+ }
+
+ virtual ::basegfx::B2DHomMatrix getSpriteTransformation() const override
+ {
+ OSL_FAIL( "BitmapView::getSpriteTransformation(): This method is not supposed to be called!" );
+ return ::basegfx::B2DHomMatrix();
+ }
+
+ virtual void setClip( const ::basegfx::B2DPolyPolygon& /*rClip*/ ) override
+ {
+ OSL_FAIL( "BitmapView::setClip(): This method is not supposed to be called!" );
+ }
+
+ virtual bool resize( const ::basegfx::B2DRange& /*rArea*/ ) override
+ {
+ OSL_FAIL( "BitmapView::resize(): This method is not supposed to be called!" );
+ return false;
+ }
+
+ private:
+ ::cppcanvas::CanvasSharedPtr mpCanvas;
+ };
+ }
+
+ bool LayerManager::renderTo( const ::cppcanvas::CanvasSharedPtr& rTargetCanvas ) const
+ {
+ bool bRet( true );
+ ViewLayerSharedPtr pTmpLayer = std::make_shared<DummyLayer>( rTargetCanvas );
+
+ for( const auto& rShape : maAllShapes )
+ {
+ try
+ {
+ // forward to all shape's addViewLayer method (which
+ // we request to render the Shape on the new
+ // ViewLayer. Since we add the shapes in the
+ // maShapeSet order (which is also the render order),
+ // this is equivalent to a subsequent render() call)
+ rShape.first->addViewLayer( pTmpLayer,
+ true );
+
+ // and remove again, this is only temporary
+ rShape.first->removeViewLayer( pTmpLayer );
+ }
+ catch( uno::Exception& )
+ {
+ // TODO(E1): Might be superfluous. Nowadays,
+ // addViewLayer swallows all errors, anyway.
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ // at least one shape could not be rendered
+ bRet = false;
+ }
+ }
+
+ return bRet;
+ }
+
+ void LayerManager::addUpdateArea( ShapeSharedPtr const& rShape )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ ENSURE_OR_THROW( rShape, "LayerManager::addUpdateArea(): invalid Shape" );
+
+ const LayerShapeMap::const_iterator aShapeEntry( maAllShapes.find(rShape) );
+
+ if( aShapeEntry == maAllShapes.end() )
+ return;
+
+ LayerSharedPtr pLayer = aShapeEntry->second.lock();
+ if( pLayer )
+ pLayer->addUpdateRange( rShape->getUpdateArea() );
+ }
+
+ void LayerManager::commitLayerChanges( std::size_t nCurrLayerIndex,
+ LayerShapeMap::const_iterator aFirstLayerShape,
+ const LayerShapeMap::const_iterator& aEndLayerShapes )
+ {
+ const bool bLayerExists( maLayers.size() > nCurrLayerIndex );
+ if( !bLayerExists )
+ return;
+
+ const LayerSharedPtr& rLayer( maLayers.at(nCurrLayerIndex) );
+ const bool bLayerResized( rLayer->commitBounds() );
+ rLayer->setPriority( basegfx::B1DRange(nCurrLayerIndex,
+ nCurrLayerIndex+1) );
+
+ if( !bLayerResized )
+ return;
+
+ // need to re-render whole layer - start from
+ // clean state
+ rLayer->clearContent();
+
+ // render and remove from update set
+ while( aFirstLayerShape != aEndLayerShapes )
+ {
+ maUpdateShapes.erase(aFirstLayerShape->first);
+ aFirstLayerShape->first->render();
+ ++aFirstLayerShape;
+ }
+ }
+
+ LayerSharedPtr LayerManager::createForegroundLayer() const
+ {
+ OSL_ASSERT( mbActive );
+
+ LayerSharedPtr pLayer( Layer::createLayer() );
+
+ // create ViewLayers for all registered views, and add to
+ // newly created layer.
+ for( const auto& rView : mrViews )
+ pLayer->addView( rView );
+
+ return pLayer;
+ }
+
+ void LayerManager::updateShapeLayers( bool bBackgroundLayerPainted )
+ {
+ OSL_ASSERT( !maLayers.empty() ); // always at least background layer
+ OSL_ASSERT( mbActive );
+
+ // do we need to process shapes?
+ if( !mbLayerAssociationDirty )
+ return;
+
+ if( mbDisableAnimationZOrder )
+ {
+ // layer setup happened elsewhere, is only bg layer
+ // anyway.
+ mbLayerAssociationDirty = false;
+ return;
+ }
+
+ // scan through maAllShapes, and determine shape animation
+ // discontinuities: when a shape that has
+ // isBackgroundDetached() return false follows a shape
+ // with isBackgroundDetached() true, the former and all
+ // following ones must be moved into an own layer.
+
+ // to avoid tons of temporaries, create weak_ptr to Layers
+ // beforehand
+ std::vector< LayerWeakPtr > aWeakLayers(maLayers.begin(),maLayers.end());
+
+ std::size_t nCurrLayerIndex(0);
+ bool bIsBackgroundLayer(true);
+ bool bLastWasBackgroundDetached(false); // last shape sprite state
+ LayerShapeMap::iterator aCurrShapeEntry( maAllShapes.begin() );
+ LayerShapeMap::iterator aCurrLayerFirstShapeEntry( maAllShapes.begin() );
+ const LayerShapeMap::iterator aEndShapeEntry ( maAllShapes.end() );
+ while( aCurrShapeEntry != aEndShapeEntry )
+ {
+ const ShapeSharedPtr pCurrShape( aCurrShapeEntry->first );
+ const bool bThisIsBackgroundDetached(
+ pCurrShape->isBackgroundDetached() );
+
+ if( bLastWasBackgroundDetached &&
+ !bThisIsBackgroundDetached )
+ {
+ // discontinuity found - current shape needs to
+ // get into a new layer
+
+
+ // commit changes to previous layer
+ commitLayerChanges(nCurrLayerIndex,
+ aCurrLayerFirstShapeEntry,
+ aCurrShapeEntry);
+ aCurrLayerFirstShapeEntry=aCurrShapeEntry;
+ ++nCurrLayerIndex;
+ bIsBackgroundLayer = false;
+
+ if( aWeakLayers.size() <= nCurrLayerIndex ||
+ notEqual(aWeakLayers.at(nCurrLayerIndex), aCurrShapeEntry->second) )
+ {
+ // no more layers left, or shape was not
+ // member of this layer - create a new one
+ maLayers.insert( maLayers.begin()+nCurrLayerIndex,
+ createForegroundLayer() );
+ aWeakLayers.insert( aWeakLayers.begin()+nCurrLayerIndex,
+ maLayers[nCurrLayerIndex] );
+ }
+ }
+
+ OSL_ASSERT( maLayers.size() == aWeakLayers.size() );
+
+ // note: using indices here, since vector::insert
+ // above invalidates iterators
+ LayerSharedPtr& rCurrLayer( maLayers.at(nCurrLayerIndex) );
+ LayerWeakPtr& rCurrWeakLayer( aWeakLayers.at(nCurrLayerIndex) );
+ if( notEqual(rCurrWeakLayer, aCurrShapeEntry->second) )
+ {
+ // mismatch: shape is not contained in current
+ // layer - move shape to that layer, then.
+ maLayers.at(nCurrLayerIndex)->setShapeViews(
+ pCurrShape );
+
+ // layer got new shape(s), need full repaint, if
+ // non-sprite shape
+ if( !bThisIsBackgroundDetached && pCurrShape->isVisible() )
+ {
+ LayerSharedPtr pOldLayer( aCurrShapeEntry->second.lock() );
+ if( pOldLayer )
+ {
+ // old layer still valid? then we need to
+ // repaint former shape area
+ pOldLayer->addUpdateRange(
+ pCurrShape->getUpdateArea() );
+ }
+
+ // render on new layer (only if not
+ // explicitly disabled)
+ if( !(bBackgroundLayerPainted && bIsBackgroundLayer) )
+ maUpdateShapes.insert( pCurrShape );
+ }
+
+ aCurrShapeEntry->second = rCurrWeakLayer;
+ }
+
+ // update layerbounds regardless of the fact that the
+ // shape might be contained in said layer
+ // already. updateBounds() is dumb and needs to
+ // collect all shape bounds.
+ // of course, no need to expand layer bounds for
+ // shapes that reside in sprites themselves.
+ if( !bThisIsBackgroundDetached && !bIsBackgroundLayer )
+ rCurrLayer->updateBounds( pCurrShape );
+
+ bLastWasBackgroundDetached = bThisIsBackgroundDetached;
+ ++aCurrShapeEntry;
+ }
+
+ // commit very last layer data
+ commitLayerChanges(nCurrLayerIndex,
+ aCurrLayerFirstShapeEntry,
+ aCurrShapeEntry);
+
+ // any layers left? Bin them!
+ if( maLayers.size() > nCurrLayerIndex+1 )
+ maLayers.erase(maLayers.begin()+nCurrLayerIndex+1,
+ maLayers.end());
+
+ mbLayerAssociationDirty = false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/layermanager.hxx b/slideshow/source/engine/slide/layermanager.hxx
new file mode 100644
index 000000000..1969f0ccc
--- /dev/null
+++ b/slideshow/source/engine/slide/layermanager.hxx
@@ -0,0 +1,363 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYERMANAGER_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYERMANAGER_HXX
+
+#include <unoviewcontainer.hxx>
+#include <attributableshape.hxx>
+#include "layer.hxx"
+#include <tools.hxx>
+
+#include <memory>
+#include <map>
+#include <unordered_map>
+#include <vector>
+
+namespace basegfx {
+ class B2DRange;
+}
+
+namespace slideshow::internal
+ {
+ /** A hash map which maps the XShape to the corresponding Shape object.
+
+ Provides quicker lookup than ShapeSet for simple mappings
+ */
+ typedef std::unordered_map<
+ css::uno::Reference< css::drawing::XShape >,
+ ShapeSharedPtr,
+ hash< css::uno::Reference< css::drawing::XShape > >
+ > XShapeToShapeMap;
+
+ /* Definition of Layermanager class */
+
+ /** This class manages all of a slide's layers (and shapes)
+
+ Since layer content changes when animations start or end,
+ the layer manager keeps track of this and also handles
+ starting/stopping of Shape animations. Note that none of
+ the methods actually perform a screen update, this is
+ always delayed until the ActivitiesQueue explicitly
+ performs it.
+
+ @see Layer
+ @see Shape
+ */
+ class LayerManager
+ {
+ public:
+ /** Create a new layer manager for the given page bounds
+
+ @param rViews
+ Views currently registered
+
+ @param bDisableAnimationZOrder
+ When true, all sprite animations run in the
+ foreground. That is, no extra layers are created, and
+ the slideshow runs potentially faster.
+ */
+ LayerManager( const UnoViewContainer& rViews,
+ bool bDisableAnimationZOrder );
+
+ /// Forbid copy construction
+ LayerManager(const LayerManager&) = delete;
+
+ /// Forbid copy assignment
+ LayerManager& operator=(const LayerManager&) = delete;
+
+ /** Activate the LayerManager
+
+ This method activates the LayerManager. Prior to
+ activation, this instance will be passive, i.e. won't
+ render anything to any view.
+ */
+ void activate();
+
+ /** Deactivate the LayerManager
+
+ This method deactivates the LayerManager. After
+ deactivation, this instance will be passive,
+ i.e. don't render anything to any view. Furthermore,
+ if there's currently more than one Layer active, this
+ method also removes all but one.
+ */
+ void deactivate();
+
+ // From ViewEventHandler, forwarded by SlideImpl
+ /// Notify new view added to UnoViewContainer
+ void viewAdded( const UnoViewSharedPtr& rView );
+ /// Notify view removed from UnoViewContainer
+ void viewRemoved( const UnoViewSharedPtr& rView );
+ void viewChanged( const UnoViewSharedPtr& rView );
+ void viewsChanged();
+
+ /** Add the shape to this object
+
+ This method adds a shape to the page.
+ */
+ void addShape( const ShapeSharedPtr& rShape );
+
+ /** Remove shape from this object
+
+ This method removes a shape from the shape.
+ */
+ bool removeShape( const ShapeSharedPtr& rShape );
+
+ /** Lookup a Shape from an XShape model object
+
+ This method looks up the internal shape map for one
+ representing the given XShape.
+
+ @param xShape
+ The XShape object, for which the representing Shape
+ should be looked up.
+ */
+ ShapeSharedPtr lookupShape( const css::uno::Reference< css::drawing::XShape >& xShape ) const;
+
+ /** Query a subset of the given original shape
+
+ This method queries a new (but not necessarily unique)
+ shape, which displays only the given subset of the
+ original one.
+ */
+ AttributableShapeSharedPtr getSubsetShape( const AttributableShapeSharedPtr& rOrigShape,
+ const DocTreeNode& rTreeNode );
+
+ /** Get a map that maps all Shapes with their XShape reference as the key
+ *
+ * @return an unordered map that contains all shapes in the
+ * current page with their XShape reference as the key
+ */
+ const XShapeToShapeMap& getXShapeToShapeMap() const;
+
+ /** Revoke a previously queried subset shape.
+
+ With this method, a previously requested subset shape
+ is revoked again. If the last client revokes a given
+ subset, it will cease to be displayed, and the
+ original shape will again show the subset data.
+
+ @param rOrigShape
+ The shape the subset was created from
+
+ @param rSubsetShape
+ The subset created from rOrigShape
+ */
+ void revokeSubset( const AttributableShapeSharedPtr& rOrigShape,
+ const AttributableShapeSharedPtr& rSubsetShape );
+
+ /** Notify the LayerManager that the given Shape starts an
+ animation now.
+
+ This method enters animation mode for the Shape on all
+ registered views.
+ */
+ void enterAnimationMode( const AnimatableShapeSharedPtr& rShape );
+
+ /** Notify the LayerManager that the given Shape is no
+ longer animated.
+
+ This methods ends animation mode for the given Shape
+ on all registered views.
+ */
+ void leaveAnimationMode( const AnimatableShapeSharedPtr& rShape );
+
+ /** Notify that a shape needs an update
+
+ This method notifies the layer manager that a shape
+ update is necessary. This is useful if, during
+ animation playback, changes occur to shapes which make
+ an update necessary on an update() call. Otherwise,
+ update() will not render anything, which is not
+ triggered by calling one of the other LayerManager
+ methods.
+
+ @param rShape
+ Shape which needs an update
+ */
+ void notifyShapeUpdate( const ShapeSharedPtr& rShape);
+
+ /** Check whether any update operations are pending.
+
+ @return true, if this LayerManager has any updates
+ pending, i.e. needs to repaint something for the next
+ frame.
+ */
+ bool isUpdatePending() const;
+
+ /** Update the content
+
+ This method updates the content on all layers on all
+ registered views. It does not issues a
+ View::updateScreen() call on registered views. Please
+ note that this method only takes into account changes
+ to shapes induced directly by calling methods of the
+ LayerManager. If a shape needs an update, because of
+ some external event unknown to the LayerManager (most
+ notably running animations), you have to notify the
+ LayerManager via notifyShapeUpdate().
+
+ @see LayerManager::updateScreen()
+
+ @return whether the update finished successfully.
+ */
+ bool update();
+
+ /** Render the content to given canvas
+
+ This is a one-shot operation, which simply draws all
+ shapes onto the given canvas, without any caching or
+ other fuzz. Don't use that for repeated output onto
+ the same canvas, the View concept is more optimal
+ then.
+
+ @param rTargetCanvas
+ Target canvas to output onto.
+ */
+ bool renderTo( const ::cppcanvas::CanvasSharedPtr& rTargetCanvas ) const;
+
+ private:
+
+ class ShapeComparator
+ {
+ public:
+ bool operator() (const ShapeSharedPtr& rpS1, const ShapeSharedPtr& rpS2 ) const
+ {
+ return Shape::lessThanShape::compare(rpS1.get(), rpS2.get());
+ }
+ };
+ /** Set of all shapes
+ */
+ private:
+ typedef ::std::map< ShapeSharedPtr, LayerWeakPtr, ShapeComparator > LayerShapeMap;
+
+
+ /// Adds shape area to containing layer's damage area
+ void addUpdateArea( ShapeSharedPtr const& rShape );
+
+ LayerSharedPtr createForegroundLayer() const;
+
+ /** Push changes from updateShapeLayerAssociations() to current layer
+
+ Factored-out method that resizes layer, if necessary,
+ assigns correct layer priority, and repaints contained shapes.
+
+ @param nCurrLayerIndex
+ Index of current layer in maLayers
+
+ @param aFirstLayerShape
+ Valid iterator out of maAllShapes, denoting the first
+ shape from nCurrLayerIndex
+
+ @param aEndLayerShapes
+ Valid iterator or end iterator out of maAllShapes,
+ denoting one-behind-the-last shape of nCurrLayerIndex
+ */
+ void commitLayerChanges( std::size_t nCurrLayerIndex,
+ LayerShapeMap::const_iterator aFirstLayerShape,
+ const LayerShapeMap::const_iterator& aEndLayerShapes );
+
+ /** Init Shape layers with background layer.
+ */
+ void putShape2BackgroundLayer( LayerShapeMap::value_type& rShapeEntry );
+
+ /** Commits any pending layer reorg, due to shapes either
+ entering or leaving animation mode
+
+ @param bBackgroundLayerPainted
+ When true, LayerManager does not render anything on
+ the background layer. Use this, if background has been
+ updated by other means (e.g. slide transition)
+ */
+ void updateShapeLayers( bool bBackgroundLayerPainted );
+
+ /** Common stuff when adding a shape
+ */
+ void implAddShape( const ShapeSharedPtr& rShape );
+
+ /** Common stuff when removing a shape
+ */
+ void implRemoveShape( const ShapeSharedPtr& rShape );
+
+ /** Add or remove views
+
+ Sharing duplicate code from viewAdded and viewRemoved
+ method. The only point of variation at those places
+ are removal vs. adding.
+ */
+ template<typename LayerFunc,
+ typename ShapeFunc> void manageViews( LayerFunc layerFunc,
+ ShapeFunc shapeFunc );
+
+ bool updateSprites();
+
+ /// Registered views
+ const UnoViewContainer& mrViews;
+
+ /// All layers of this object. Vector owns the layers
+ ::std::vector< LayerSharedPtr >
+ maLayers;
+
+ /** Contains all shapes with their XShape reference as the key
+ */
+ XShapeToShapeMap maXShapeHash;
+
+ /** Set of shapes this LayerManager own
+
+ Contains the same set of shapes as XShapeHash, but is
+ sorted in z order, for painting and layer
+ association. Set entries are enriched with two flags
+ for buffering animation enable/disable changes, and
+ shape update requests.
+ */
+ LayerShapeMap maAllShapes;
+
+ /** Set of shapes that have requested an update
+
+ When a shape is member of this set, its maShapes entry
+ has bNeedsUpdate set to true. We maintain this
+ redundant information for faster update processing.
+ */
+ ::std::set< ShapeSharedPtr >
+ maUpdateShapes;
+
+ /// Number of shape sprites currently active on this LayerManager
+ sal_Int32 mnActiveSprites;
+
+ /// sal_True, if shapes might need to move to different layer
+ bool mbLayerAssociationDirty;
+
+ /// sal_False when deactivated
+ bool mbActive;
+
+ /** When true, all sprite animations run in the foreground. That
+ is, no extra layers are created, and the slideshow runs
+ potentially faster.
+ */
+ bool mbDisableAnimationZOrder;
+ };
+
+ typedef ::std::shared_ptr< LayerManager > LayerManagerSharedPtr;
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_LAYERMANAGER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/shapemanagerimpl.cxx b/slideshow/source/engine/slide/shapemanagerimpl.cxx
new file mode 100644
index 000000000..1c730970e
--- /dev/null
+++ b/slideshow/source/engine/slide/shapemanagerimpl.cxx
@@ -0,0 +1,426 @@
+/* -*- 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/processfactory.hxx>
+#include <tools/diagnose_ex.h>
+#include <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/awt/SystemPointer.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <svx/ImageMapInfo.hxx>
+
+#include "shapemanagerimpl.hxx"
+
+#include <functional>
+
+using namespace css;
+using namespace css::uno;
+using namespace css::drawing;
+using namespace css::system;
+
+namespace slideshow::internal {
+
+ShapeManagerImpl::ShapeManagerImpl( EventMultiplexer& rMultiplexer,
+ LayerManagerSharedPtr const& rLayerManager,
+ CursorManager& rCursorManager,
+ const ShapeEventListenerMap& rGlobalListenersMap,
+ const ShapeCursorMap& rGlobalCursorMap,
+ const Reference<XDrawPage>& xDrawPage ):
+ mrMultiplexer(rMultiplexer),
+ mpLayerManager(rLayerManager),
+ mrCursorManager(rCursorManager),
+ mrGlobalListenersMap(rGlobalListenersMap),
+ mrGlobalCursorMap(rGlobalCursorMap),
+ maShapeListenerMap(),
+ maShapeCursorMap(),
+ maHyperlinkShapes(),
+ mbEnabled(false),
+ mxDrawPage(xDrawPage)
+{
+}
+
+void ShapeManagerImpl::activate()
+{
+ if( mbEnabled )
+ return;
+
+ mbEnabled = true;
+
+ // register this handler on EventMultiplexer.
+ // Higher prio (overrides other engine handlers)
+ mrMultiplexer.addMouseMoveHandler( shared_from_this(), 2.0 );
+ mrMultiplexer.addClickHandler( shared_from_this(), 2.0 );
+ mrMultiplexer.addShapeListenerHandler( shared_from_this() );
+
+ // clone listener map
+ for( const auto& rListener : mrGlobalListenersMap )
+ listenerAdded( rListener.first );
+
+ // clone cursor map
+ for( const auto& rListener : mrGlobalCursorMap )
+ cursorChanged( rListener.first, rListener.second );
+
+ if( mpLayerManager )
+ mpLayerManager->activate();
+}
+
+void ShapeManagerImpl::deactivate()
+{
+ if( !mbEnabled )
+ return;
+
+ mbEnabled = false;
+
+ if( mpLayerManager )
+ mpLayerManager->deactivate();
+
+ maShapeListenerMap.clear();
+ maShapeCursorMap.clear();
+
+ mrMultiplexer.removeShapeListenerHandler( shared_from_this() );
+ mrMultiplexer.removeMouseMoveHandler( shared_from_this() );
+ mrMultiplexer.removeClickHandler( shared_from_this() );
+}
+
+void ShapeManagerImpl::dispose()
+{
+ // remove listeners (EventMultiplexer holds shared_ptr on us)
+ deactivate();
+
+ maHyperlinkShapes.clear();
+ maShapeCursorMap.clear();
+ maShapeListenerMap.clear();
+ mpLayerManager.reset();
+}
+
+bool ShapeManagerImpl::handleMousePressed( awt::MouseEvent const& )
+{
+ // not used here
+ return false; // did not handle the event
+}
+
+bool ShapeManagerImpl::handleMouseReleased( awt::MouseEvent const& e )
+{
+ if( !mbEnabled || e.Buttons != awt::MouseButton::LEFT)
+ return false;
+
+ basegfx::B2DPoint const aPosition( e.X, e.Y );
+
+ // first check for hyperlinks, because these have
+ // highest prio:
+ OUString const hyperlink( checkForHyperlink(aPosition) );
+ if( !hyperlink.isEmpty() )
+ {
+ mrMultiplexer.notifyHyperlinkClicked(hyperlink);
+ return true; // event consumed
+ }
+
+ // tdf#74045 Handle ImageMaps
+ OUString const imageMapLink(checkForImageMap(e));
+ if (!imageMapLink.isEmpty())
+ {
+ Reference<XSystemShellExecute> exec(
+ SystemShellExecute::create(comphelper::getProcessComponentContext()));
+ exec->execute(imageMapLink, OUString(), SystemShellExecuteFlags::URIS_ONLY);
+
+ return true;
+ }
+
+ // find matching shape (scan reversely, to coarsely match
+ // paint order)
+ auto aCurrBroadcaster = std::find_if(maShapeListenerMap.rbegin(), maShapeListenerMap.rend(),
+ [&aPosition](const ShapeToListenersMap::value_type& rBroadcaster) {
+ // TODO(F2): Get proper geometry polygon from the
+ // shape, to avoid having areas outside the shape
+ // react on the mouse
+ return rBroadcaster.first->getBounds().isInside( aPosition )
+ && rBroadcaster.first->isVisible();
+ });
+ if (aCurrBroadcaster != maShapeListenerMap.rend())
+ {
+ // shape hit, and shape is visible. Raise
+ // event.
+
+ std::shared_ptr<comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener>> const & pCont =
+ aCurrBroadcaster->second;
+ uno::Reference<drawing::XShape> const xShape(
+ aCurrBroadcaster->first->getXShape() );
+
+ // DON'T do anything with /this/ after this point!
+ pCont->forEach(
+ [&xShape, &e]( const uno::Reference< presentation::XShapeEventListener >& rListener )
+ { return rListener->click( xShape, e ); } );
+
+ return true; // handled this event
+ }
+
+ return false; // did not handle this event
+}
+
+bool ShapeManagerImpl::handleMouseDragged( const awt::MouseEvent& )
+{
+ // not used here
+ return false; // did not handle the event
+}
+
+bool ShapeManagerImpl::handleMouseMoved( const awt::MouseEvent& e )
+{
+ if( !mbEnabled )
+ return false;
+
+ // find hit shape in map
+ const ::basegfx::B2DPoint aPosition( e.X, e.Y );
+ sal_Int16 nNewCursor(-1);
+
+ if( !checkForHyperlink(aPosition).isEmpty() || !checkForImageMap(e).isEmpty() )
+ {
+ nNewCursor = awt::SystemPointer::REFHAND;
+ }
+ else
+ {
+ // find matching shape (scan reversely, to coarsely match
+ // paint order)
+ auto aCurrCursor = std::find_if(maShapeCursorMap.rbegin(), maShapeCursorMap.rend(),
+ [&aPosition](const ShapeToCursorMap::value_type& rCursor) {
+ // TODO(F2): Get proper geometry polygon from the
+ // shape, to avoid having areas outside the shape
+ // react on the mouse
+ return rCursor.first->getBounds().isInside( aPosition )
+ && rCursor.first->isVisible();
+ });
+ if (aCurrCursor != maShapeCursorMap.rend())
+ {
+ // shape found, and it's visible. set
+ // requested cursor to shape's
+ nNewCursor = aCurrCursor->second;
+ }
+ }
+
+ if( nNewCursor == -1 )
+ mrCursorManager.resetCursor();
+ else
+ mrCursorManager.requestCursor( nNewCursor );
+
+ return false; // we don't /eat/ this event. Lower prio
+ // handler should see it, too.
+}
+
+bool ShapeManagerImpl::update()
+{
+ if( mbEnabled && mpLayerManager )
+ return mpLayerManager->update();
+
+ return false;
+}
+
+bool ShapeManagerImpl::needsUpdate() const
+{
+ if( mbEnabled && mpLayerManager )
+ return mpLayerManager->isUpdatePending();
+
+ return false;
+}
+
+void ShapeManagerImpl::enterAnimationMode( const AnimatableShapeSharedPtr& rShape )
+{
+ if( mbEnabled && mpLayerManager )
+ mpLayerManager->enterAnimationMode(rShape);
+}
+
+void ShapeManagerImpl::leaveAnimationMode( const AnimatableShapeSharedPtr& rShape )
+{
+ if( mbEnabled && mpLayerManager )
+ mpLayerManager->leaveAnimationMode(rShape);
+}
+
+void ShapeManagerImpl::notifyShapeUpdate( const ShapeSharedPtr& rShape )
+{
+ if( mbEnabled && mpLayerManager )
+ mpLayerManager->notifyShapeUpdate(rShape);
+}
+
+ShapeSharedPtr ShapeManagerImpl::lookupShape( uno::Reference< drawing::XShape > const & xShape ) const
+{
+ if( mpLayerManager )
+ return mpLayerManager->lookupShape(xShape);
+
+ return ShapeSharedPtr();
+}
+
+const XShapeToShapeMap& ShapeManagerImpl::getXShapeToShapeMap() const
+{
+ assert( mpLayerManager );
+ return mpLayerManager->getXShapeToShapeMap();
+}
+
+void ShapeManagerImpl::addHyperlinkArea( const HyperlinkAreaSharedPtr& rArea )
+{
+ maHyperlinkShapes.insert(rArea);
+}
+
+AttributableShapeSharedPtr ShapeManagerImpl::getSubsetShape( const AttributableShapeSharedPtr& rOrigShape,
+ const DocTreeNode& rTreeNode )
+{
+ if( mpLayerManager )
+ return mpLayerManager->getSubsetShape(rOrigShape,rTreeNode);
+
+ return AttributableShapeSharedPtr();
+}
+
+void ShapeManagerImpl::revokeSubset( const AttributableShapeSharedPtr& rOrigShape,
+ const AttributableShapeSharedPtr& rSubsetShape )
+{
+ if( mpLayerManager )
+ mpLayerManager->revokeSubset(rOrigShape,rSubsetShape);
+}
+
+bool ShapeManagerImpl::listenerAdded(
+ const uno::Reference<drawing::XShape>& xShape )
+{
+ ShapeEventListenerMap::const_iterator aIter = mrGlobalListenersMap.find( xShape );
+ if( aIter == mrGlobalListenersMap.end() )
+ {
+ ENSURE_OR_RETURN_FALSE(false,
+ "ShapeManagerImpl::listenerAdded(): global "
+ "shape listener map inconsistency!");
+ }
+
+ // is this one of our shapes? other shapes are ignored.
+ ShapeSharedPtr pShape( lookupShape(xShape) );
+ if( pShape )
+ {
+ maShapeListenerMap.emplace(pShape, aIter->second);
+ }
+
+ return true;
+}
+
+bool ShapeManagerImpl::listenerRemoved( const uno::Reference<drawing::XShape>& xShape )
+{
+ // shape really erased from map? maybe there are other listeners
+ // for the same shape pending...
+ if( mrGlobalListenersMap.find(xShape) == mrGlobalListenersMap.end() )
+ {
+ // is this one of our shapes? other shapes are ignored.
+ ShapeSharedPtr pShape( lookupShape(xShape) );
+ if( pShape )
+ maShapeListenerMap.erase(pShape);
+ }
+
+ return true;
+}
+
+void ShapeManagerImpl::cursorChanged( const uno::Reference<drawing::XShape>& xShape,
+ sal_Int16 nCursor )
+{
+ ShapeSharedPtr pShape( lookupShape(xShape) );
+
+ // is this one of our shapes? other shapes are ignored.
+ if( !pShape )
+ return;
+
+ if( mrGlobalCursorMap.find(xShape) == mrGlobalCursorMap.end() )
+ {
+ // erased from global map - erase locally, too
+ maShapeCursorMap.erase(pShape);
+ }
+ else
+ {
+ // included in global map - update local one
+ ShapeToCursorMap::iterator aIter;
+ if( (aIter = maShapeCursorMap.find(pShape))
+ == maShapeCursorMap.end() )
+ {
+ maShapeCursorMap.emplace(pShape, nCursor);
+ }
+ else
+ {
+ aIter->second = nCursor;
+ }
+ }
+}
+
+OUString ShapeManagerImpl::checkForHyperlink( basegfx::B2DPoint const& hitPos ) const
+{
+ // find matching region (scan reversely, to coarsely match
+ // paint order): set is ordered by priority
+ AreaSet::const_reverse_iterator iPos( maHyperlinkShapes.rbegin() );
+ AreaSet::const_reverse_iterator const iEnd( maHyperlinkShapes.rend() );
+ for( ; iPos != iEnd; ++iPos )
+ {
+ HyperlinkAreaSharedPtr const& pArea = *iPos;
+
+ HyperlinkArea::HyperlinkRegions const linkRegions(
+ pArea->getHyperlinkRegions() );
+
+ for( std::size_t i = linkRegions.size(); i--; )
+ {
+ basegfx::B2DRange const& region = linkRegions[i].first;
+ if( region.isInside(hitPos) )
+ return linkRegions[i].second;
+ }
+ }
+
+ return OUString();
+}
+
+OUString ShapeManagerImpl::checkForImageMap( awt::MouseEvent const& evt ) const
+{
+ for (sal_Int32 i = 0; i < mxDrawPage->getCount(); i++)
+ {
+ Reference<XShape> xShape(mxDrawPage->getByIndex(i), UNO_QUERY_THROW);
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape);
+ if (!pObj)
+ continue;
+ const IMapObject* pIMapObj = SvxIMapInfo::GetHitIMapObject(pObj, Point(evt.X, evt.Y));
+ if (pIMapObj && !pIMapObj->GetURL().isEmpty())
+ {
+ return pIMapObj->GetURL();
+ }
+ }
+ return OUString();
+}
+
+void ShapeManagerImpl::addIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler )
+{
+ maIntrinsicAnimationEventHandlers.add( rHandler );
+}
+
+void ShapeManagerImpl::removeIntrinsicAnimationHandler( const IntrinsicAnimationEventHandlerSharedPtr& rHandler )
+{
+ maIntrinsicAnimationEventHandlers.remove( rHandler );
+}
+
+void ShapeManagerImpl::notifyIntrinsicAnimationsEnabled()
+{
+ maIntrinsicAnimationEventHandlers.applyAll(
+ std::mem_fn(&IntrinsicAnimationEventHandler::enableAnimations));
+}
+
+void ShapeManagerImpl::notifyIntrinsicAnimationsDisabled()
+{
+ maIntrinsicAnimationEventHandlers.applyAll(
+ std::mem_fn(&IntrinsicAnimationEventHandler::disableAnimations));
+}
+
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/shapemanagerimpl.hxx b/slideshow/source/engine/slide/shapemanagerimpl.hxx
new file mode 100644
index 000000000..20bbe0340
--- /dev/null
+++ b/slideshow/source/engine/slide/shapemanagerimpl.hxx
@@ -0,0 +1,189 @@
+/* -*- 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 .
+ */
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SHAPEMANAGERIMPL_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SHAPEMANAGERIMPL_HXX
+
+#include <com/sun/star/drawing/XDrawPage.hpp>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <comphelper/interfacecontainer3.hxx>
+#include <shape.hxx>
+#include <subsettableshapemanager.hxx>
+#include <eventmultiplexer.hxx>
+#include "layermanager.hxx"
+#include <viewupdate.hxx>
+#include <shapemaps.hxx>
+#include <cursormanager.hxx>
+#include <hyperlinkarea.hxx>
+#include <listenercontainer.hxx>
+#include <shapelistenereventhandler.hxx>
+#include <mouseeventhandler.hxx>
+
+#include <set>
+#include <map>
+#include <memory>
+
+namespace slideshow::internal {
+
+/** Listener class for shape events
+
+ This helper class registers itself on each view, and
+ broadcasts the XShapeEventListener events. The mouse motion
+ events are needed for setting the shape cursor.
+*/
+class ShapeManagerImpl : public SubsettableShapeManager,
+ public ShapeListenerEventHandler,
+ public MouseEventHandler,
+ public ViewUpdate,
+ public std::enable_shared_from_this<ShapeManagerImpl>
+{
+public:
+ /** Create a shape event broadcaster
+
+ @param rEventMultiplexer
+ The slideshow-global event source, where this class
+ registers its event handlers.
+ */
+ ShapeManagerImpl( EventMultiplexer& rMultiplexer,
+ LayerManagerSharedPtr const& rLayerManager,
+ CursorManager& rCursorManager,
+ const ShapeEventListenerMap& rGlobalListenersMap,
+ const ShapeCursorMap& rGlobalCursorMap,
+ const css::uno::Reference<css::drawing::XDrawPage>& xDrawPage);
+
+ /// Forbid copy construction
+ ShapeManagerImpl(const ShapeManagerImpl&) = delete;
+
+ /// Forbid copy assignment
+ ShapeManagerImpl& operator=(const ShapeManagerImpl&) = delete;
+
+ /** Enables event listening.
+
+ The initial slide content on the background layer
+ is already rendered (e.g. from a previous slide
+ transition).
+ */
+ void activate();
+
+ /** Disables event listening.
+ */
+ void deactivate();
+
+ // Disposable interface
+
+
+ virtual void dispose() override;
+
+private:
+
+ // MouseEventHandler interface
+
+
+ virtual bool handleMousePressed(
+ css::awt::MouseEvent const& evt ) override;
+ virtual bool handleMouseReleased(
+ css::awt::MouseEvent const& evt ) override;
+ virtual bool handleMouseDragged(
+ css::awt::MouseEvent const& evt ) override;
+ virtual bool handleMouseMoved(
+ css::awt::MouseEvent const& evt ) override;
+
+
+ // ViewUpdate interface
+
+
+ virtual bool update() override;
+ virtual bool needsUpdate() const override;
+
+
+ // ShapeManager interface
+
+
+ virtual void enterAnimationMode( const AnimatableShapeSharedPtr& rShape ) override;
+ virtual void leaveAnimationMode( const AnimatableShapeSharedPtr& rShape ) override;
+ virtual void notifyShapeUpdate( const ShapeSharedPtr& rShape ) override;
+ virtual ShapeSharedPtr lookupShape(
+ css::uno::Reference< css::drawing::XShape > const & xShape ) const override;
+ virtual const XShapeToShapeMap& getXShapeToShapeMap() const override;
+ virtual void addHyperlinkArea( const HyperlinkAreaSharedPtr& rArea ) override;
+
+
+ // SubsettableShapeManager interface
+
+
+ virtual AttributableShapeSharedPtr getSubsetShape(
+ const AttributableShapeSharedPtr& rOrigShape,
+ const DocTreeNode& rTreeNode ) override;
+ virtual void revokeSubset(
+ const AttributableShapeSharedPtr& rOrigShape,
+ const AttributableShapeSharedPtr& rSubsetShape ) override;
+
+ virtual void addIntrinsicAnimationHandler(
+ const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) override;
+ virtual void removeIntrinsicAnimationHandler(
+ const IntrinsicAnimationEventHandlerSharedPtr& rHandler ) override;
+ virtual void notifyIntrinsicAnimationsEnabled() override;
+ virtual void notifyIntrinsicAnimationsDisabled() override;
+
+
+ // ShapeListenerEventHandler
+
+
+ virtual bool listenerAdded( const css::uno::Reference< css::drawing::XShape>& xShape ) override;
+
+ virtual bool listenerRemoved( const css::uno::Reference< css::drawing::XShape>& xShape ) override;
+
+ void cursorChanged( const css::uno::Reference< css::drawing::XShape>& xShape,
+ sal_Int16 nCursor );
+
+
+ OUString checkForHyperlink( ::basegfx::B2DPoint const& hitPos )const;
+ OUString checkForImageMap( css::awt::MouseEvent const& evt ) const;
+
+
+ typedef std::map<ShapeSharedPtr,
+ std::shared_ptr< ::comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener> >,
+ Shape::lessThanShape> ShapeToListenersMap;
+ typedef std::map<ShapeSharedPtr, sal_Int16,
+ Shape::lessThanShape> ShapeToCursorMap;
+ typedef std::set<HyperlinkAreaSharedPtr,
+ HyperlinkArea::lessThanArea> AreaSet;
+
+ typedef ThreadUnsafeListenerContainer<
+ IntrinsicAnimationEventHandlerSharedPtr,
+ std::vector<IntrinsicAnimationEventHandlerSharedPtr> > ImplIntrinsicAnimationEventHandlers;
+
+ EventMultiplexer& mrMultiplexer;
+ LayerManagerSharedPtr mpLayerManager;
+ CursorManager& mrCursorManager;
+ const ShapeEventListenerMap& mrGlobalListenersMap;
+ const ShapeCursorMap& mrGlobalCursorMap;
+ ShapeToListenersMap maShapeListenerMap;
+ ShapeToCursorMap maShapeCursorMap;
+ AreaSet maHyperlinkShapes;
+ ImplIntrinsicAnimationEventHandlers maIntrinsicAnimationEventHandlers;
+ bool mbEnabled;
+ const css::uno::Reference<css::drawing::XDrawPage> mxDrawPage;
+};
+
+} // namespace presentation::internal
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SHAPEMANAGERIMPL_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/slideanimations.cxx b/slideshow/source/engine/slide/slideanimations.cxx
new file mode 100644
index 000000000..d433b7af1
--- /dev/null
+++ b/slideshow/source/engine/slide/slideanimations.cxx
@@ -0,0 +1,106 @@
+/* -*- 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 "slideanimations.hxx"
+#include <animationnodefactory.hxx>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+ SlideAnimations::SlideAnimations( const SlideShowContext& rContext,
+ const ::basegfx::B2DVector& rSlideSize ) :
+ maContext( rContext ),
+ maSlideSize( rSlideSize ),
+ mpRootNode()
+ {
+ ENSURE_OR_THROW( maContext.mpSubsettableShapeManager,
+ "SlideAnimations::SlideAnimations(): Invalid SlideShowContext" );
+ }
+
+ SlideAnimations::~SlideAnimations() COVERITY_NOEXCEPT_FALSE
+ {
+ if( !mpRootNode )
+ return;
+
+ SHOW_NODE_TREE( mpRootNode );
+
+ try
+ {
+ mpRootNode->dispose();
+ }
+ catch (uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+ }
+
+ bool SlideAnimations::importAnimations( const uno::Reference< animations::XAnimationNode >& xRootAnimationNode )
+ {
+ mpRootNode = AnimationNodeFactory::createAnimationNode(
+ xRootAnimationNode,
+ maSlideSize,
+ maContext );
+
+ SHOW_NODE_TREE( mpRootNode );
+
+ return static_cast< bool >(mpRootNode);
+ }
+
+ bool SlideAnimations::isAnimated() const
+ {
+ if( !mpRootNode )
+ return false; // no animations there
+
+ // query root node about pending animations
+ return mpRootNode->hasPendingAnimation();
+ }
+
+ bool SlideAnimations::start()
+ {
+ if( !mpRootNode )
+ return false; // no animations there
+
+ // init all nodes
+ if( !mpRootNode->init() )
+ return false;
+
+ // resolve root node
+ if( !mpRootNode->resolve() )
+ return false;
+
+ return true;
+ }
+
+ void SlideAnimations::end()
+ {
+ if( !mpRootNode )
+ return; // no animations there
+
+ // end root node
+ mpRootNode->deactivate();
+ mpRootNode->end();
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/slideanimations.hxx b/slideshow/source/engine/slide/slideanimations.hxx
new file mode 100644
index 000000000..3ebc1b21b
--- /dev/null
+++ b/slideshow/source/engine/slide/slideanimations.hxx
@@ -0,0 +1,107 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SLIDEANIMATIONS_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SLIDEANIMATIONS_HXX
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <basegfx/vector/b2dvector.hxx>
+
+#include <slideshowcontext.hxx>
+#include <animationnode.hxx>
+
+namespace com::sun::star::animations { class XAnimationNode; }
+
+
+/* Definition of SlideAnimations class */
+
+namespace slideshow::internal
+ {
+ /** This class generates and manages all animations of a slide.
+
+ Provided with the root animation node, this class imports
+ the effect information and builds the event tree for all
+ of the slide's animations.
+ */
+ class SlideAnimations
+ {
+ public:
+ /** Create an animation generator.
+
+ @param rContext
+ Slide show context, passing on common parameters
+ */
+ SlideAnimations( const SlideShowContext& rContext,
+ const ::basegfx::B2DVector& rSlideSize );
+ ~SlideAnimations() COVERITY_NOEXCEPT_FALSE;
+
+ /** Import animations from a SMIL root animation node.
+
+ This method might take some time, depending on the
+ complexity of the SMIL animation network to be
+ imported.
+
+ @param xRootAnimationNode
+ Root animation node for the effects to be
+ generated. This is typically obtained from the
+ XDrawPage's XAnimationNodeSupplier.
+
+ */
+ bool importAnimations( const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode );
+
+ /** Check, whether imported animations actually contain
+ any effects.
+
+ @return true, if there are actual animations in the
+ imported node tree.
+ */
+ bool isAnimated() const;
+
+ /** Start the animations.
+
+ This method creates the network of events and
+ activities for all animations. The events and
+ activities are inserted into the constructor-provided
+ queues. These queues are not explicitly cleared, if
+ you rely on this object's effects to run without
+ interference, you should clear the queues by yourself.
+
+ @return true, if all events have been successfully
+ created.
+ */
+ bool start();
+
+ /** End all animations.
+
+ This method force-ends all animations. If a slide end
+ event has been registered, that is fired, too.
+ */
+ void end();
+
+ private:
+ SlideShowContext maContext;
+ const basegfx::B2DVector maSlideSize;
+ AnimationNodeSharedPtr mpRootNode;
+ };
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_SLIDEANIMATIONS_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/slideimpl.cxx b/slideshow/source/engine/slide/slideimpl.cxx
new file mode 100644
index 000000000..033af8756
--- /dev/null
+++ b/slideshow/source/engine/slide/slideimpl.cxx
@@ -0,0 +1,1130 @@
+/* -*- 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 <osl/diagnose.hxx>
+#include <tools/diagnose_ex.h>
+#include <cppcanvas/basegfxfactory.hxx>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+
+#include <com/sun/star/awt/SystemPointer.hpp>
+#include <com/sun/star/drawing/XMasterPageTarget.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <com/sun/star/presentation/EffectNodeType.hpp>
+
+#include <slide.hxx>
+#include <slideshowcontext.hxx>
+#include "slideanimations.hxx"
+#include <doctreenode.hxx>
+#include <screenupdater.hxx>
+#include <cursormanager.hxx>
+#include <shapeimporter.hxx>
+#include <slideshowexceptions.hxx>
+#include <eventqueue.hxx>
+#include <activitiesqueue.hxx>
+#include "layermanager.hxx"
+#include "shapemanagerimpl.hxx"
+#include <usereventqueue.hxx>
+#include "userpaintoverlay.hxx"
+#include "targetpropertiescreator.hxx"
+#include <tools.hxx>
+#include <box2dtools.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <svx/svdograf.hxx>
+
+using namespace ::com::sun::star;
+
+
+namespace slideshow::internal
+{
+namespace
+{
+
+class SlideImpl : public Slide,
+ public CursorManager,
+ public ViewEventHandler,
+ public ::osl::DebugBase<SlideImpl>
+{
+public:
+ SlideImpl( const uno::Reference<drawing::XDrawPage>& xDrawPage,
+ const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages,
+ const uno::Reference<animations::XAnimationNode>& xRootNode,
+ EventQueue& rEventQueue,
+ EventMultiplexer& rEventMultiplexer,
+ ScreenUpdater& rScreenUpdater,
+ ActivitiesQueue& rActivitiesQueue,
+ UserEventQueue& rUserEventQueue,
+ CursorManager& rCursorManager,
+ MediaFileManager& rMediaFileManager,
+ const UnoViewContainer& rViewContainer,
+ const uno::Reference<uno::XComponentContext>& xContext,
+ const ShapeEventListenerMap& rShapeListenerMap,
+ const ShapeCursorMap& rShapeCursorMap,
+ PolyPolygonVector&& rPolyPolygonVector,
+ RGBColor const& rUserPaintColor,
+ double dUserPaintStrokeWidth,
+ bool bUserPaintEnabled,
+ bool bIntrinsicAnimationsAllowed,
+ bool bDisableAnimationZOrder );
+
+ virtual ~SlideImpl() override;
+
+
+ // Slide interface
+
+
+ virtual void prefetch() override;
+ virtual void show( bool ) override;
+ virtual void hide() override;
+
+ virtual basegfx::B2ISize getSlideSize() const override;
+ virtual uno::Reference<drawing::XDrawPage > getXDrawPage() const override;
+ virtual uno::Reference<animations::XAnimationNode> getXAnimationNode() const override;
+ virtual PolyPolygonVector getPolygons() override;
+ virtual void drawPolygons() const override;
+ virtual bool isPaintOverlayActive() const override;
+ virtual void enablePaintOverlay() override;
+ virtual void update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth ) override;
+
+
+ // TODO(F2): Rework SlideBitmap to no longer be based on XBitmap,
+ // but on canvas-independent basegfx bitmaps
+ virtual SlideBitmapSharedPtr getCurrentSlideBitmap( const UnoViewSharedPtr& rView ) const override;
+
+
+private:
+ // ViewEventHandler
+ virtual void viewAdded( const UnoViewSharedPtr& rView ) override;
+ virtual void viewRemoved( const UnoViewSharedPtr& rView ) override;
+ virtual void viewChanged( const UnoViewSharedPtr& rView ) override;
+ virtual void viewsChanged() override;
+
+ // CursorManager
+ virtual bool requestCursor( sal_Int16 nCursorShape ) override;
+ virtual void resetCursor() override;
+
+ void activatePaintOverlay();
+ void deactivatePaintOverlay();
+
+ /** Query whether the slide has animations at all
+
+ If the slide doesn't have animations, show() displays
+ only static content. If an event is registered with
+ registerSlideEndEvent(), this event will be
+ immediately activated at the end of the show() method.
+
+ @return true, if this slide has animations, false
+ otherwise
+ */
+ bool isAnimated();
+
+ /// Set all Shapes to their initial attributes for slideshow
+ bool applyInitialShapeAttributes( const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode );
+
+ /// Set shapes to attributes corresponding to initial or final state of slide
+ void applyShapeAttributes(
+ const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode,
+ bool bInitial) const;
+
+ /// Renders current slide content to bitmap
+ SlideBitmapSharedPtr createCurrentSlideBitmap(
+ const UnoViewSharedPtr& rView,
+ ::basegfx::B2ISize const & rSlideSize ) const;
+
+ /// Prefetch all shapes (not the animations)
+ bool loadShapes();
+
+ /// Retrieve slide size from XDrawPage
+ basegfx::B2ISize getSlideSizeImpl() const;
+
+ /// Prefetch show, but don't call applyInitialShapeAttributes()
+ bool implPrefetchShow();
+
+ /// Add Polygons to the member maPolygons
+ void addPolygons(const PolyPolygonVector& rPolygons);
+
+ // Types
+ // =====
+
+ enum SlideAnimationState
+ {
+ CONSTRUCTING_STATE=0,
+ INITIAL_STATE=1,
+ SHOWING_STATE=2,
+ FINAL_STATE=3,
+ SlideAnimationState_NUM_ENTRIES=4
+ };
+
+ typedef std::vector< SlideBitmapSharedPtr > VectorOfSlideBitmaps;
+ /** Vector of slide bitmaps.
+
+ Since the bitmap content is sensitive to animation
+ effects, we have an inner vector containing a distinct
+ bitmap for each of the SlideAnimationStates.
+ */
+ typedef ::std::vector< std::pair< UnoViewSharedPtr,
+ VectorOfSlideBitmaps > > VectorOfVectorOfSlideBitmaps;
+
+
+ // Member variables
+ // ================
+
+ /// The page model object
+ uno::Reference< drawing::XDrawPage > mxDrawPage;
+ uno::Reference< drawing::XDrawPagesSupplier > mxDrawPagesSupplier;
+ uno::Reference< animations::XAnimationNode > mxRootNode;
+
+ LayerManagerSharedPtr mpLayerManager;
+ std::shared_ptr<ShapeManagerImpl> mpShapeManager;
+ std::shared_ptr<SubsettableShapeManager> mpSubsettableShapeManager;
+ box2d::utils::Box2DWorldSharedPtr mpBox2DWorld;
+
+ /// Contains common objects needed throughout the slideshow
+ SlideShowContext maContext;
+
+ /// parent cursor manager
+ CursorManager& mrCursorManager;
+
+ /// Handles the animation and event generation for us
+ SlideAnimations maAnimations;
+ PolyPolygonVector maPolygons;
+
+ RGBColor maUserPaintColor;
+ double mdUserPaintStrokeWidth;
+ UserPaintOverlaySharedPtr mpPaintOverlay;
+
+ /// Bitmaps with slide content at various states
+ mutable VectorOfVectorOfSlideBitmaps maSlideBitmaps;
+
+ SlideAnimationState meAnimationState;
+
+ const basegfx::B2ISize maSlideSize;
+
+ sal_Int16 mnCurrentCursor;
+
+ /// True, when intrinsic shape animations are allowed
+ bool mbIntrinsicAnimationsAllowed;
+
+ /// True, when user paint overlay is enabled
+ bool mbUserPaintOverlayEnabled;
+
+ /// True, if initial load of all page shapes succeeded
+ bool mbShapesLoaded;
+
+ /// True, if initial load of all animation info succeeded
+ bool mbShowLoaded;
+
+ /** True, if this slide is not static.
+
+ If this slide has animated content, this variable will
+ be true, and false otherwise.
+ */
+ bool mbHaveAnimations;
+
+ /** True, if this slide has a main animation sequence.
+
+ If this slide has animation content, which in turn has
+ a main animation sequence (which must be fully run
+ before EventMultiplexer::notifySlideAnimationsEnd() is
+ called), this member is true.
+ */
+ bool mbMainSequenceFound;
+
+ /// When true, show() was called. Slide hidden otherwise.
+ bool mbActive;
+
+ /// When true, enablePaintOverlay was called and mbUserPaintOverlay = true
+ bool mbPaintOverlayActive;
+
+ /// When true, final state attributes are already applied to shapes
+ bool mbFinalStateApplied;
+};
+
+
+void slideRenderer( SlideImpl const * pSlide, const UnoViewSharedPtr& rView )
+{
+ // fully clear view content to background color
+ rView->clearAll();
+
+ SlideBitmapSharedPtr pBitmap( pSlide->getCurrentSlideBitmap( rView ) );
+ ::cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() );
+
+ const ::basegfx::B2DHomMatrix aViewTransform( rView->getTransformation() );
+ const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
+
+ // setup a canvas with device coordinate space, the slide
+ // bitmap already has the correct dimension.
+ ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
+ pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
+
+ // render at given output position
+ pBitmap->move( aOutPosPixel );
+
+ // clear clip (might have been changed, e.g. from comb
+ // transition)
+ pBitmap->clip( ::basegfx::B2DPolyPolygon() );
+ pBitmap->draw( pDevicePixelCanvas );
+}
+
+
+SlideImpl::SlideImpl( const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages,
+ const uno::Reference< animations::XAnimationNode >& xRootNode,
+ EventQueue& rEventQueue,
+ EventMultiplexer& rEventMultiplexer,
+ ScreenUpdater& rScreenUpdater,
+ ActivitiesQueue& rActivitiesQueue,
+ UserEventQueue& rUserEventQueue,
+ CursorManager& rCursorManager,
+ MediaFileManager& rMediaFileManager,
+ const UnoViewContainer& rViewContainer,
+ const uno::Reference< uno::XComponentContext >& xComponentContext,
+ const ShapeEventListenerMap& rShapeListenerMap,
+ const ShapeCursorMap& rShapeCursorMap,
+ PolyPolygonVector&& rPolyPolygonVector,
+ RGBColor const& aUserPaintColor,
+ double dUserPaintStrokeWidth,
+ bool bUserPaintEnabled,
+ bool bIntrinsicAnimationsAllowed,
+ bool bDisableAnimationZOrder ) :
+ mxDrawPage( xDrawPage ),
+ mxDrawPagesSupplier( xDrawPages ),
+ mxRootNode( xRootNode ),
+ mpLayerManager( std::make_shared<LayerManager>(
+ rViewContainer,
+ bDisableAnimationZOrder) ),
+ mpShapeManager( std::make_shared<ShapeManagerImpl>(
+ rEventMultiplexer,
+ mpLayerManager,
+ rCursorManager,
+ rShapeListenerMap,
+ rShapeCursorMap,
+ xDrawPage)),
+ mpSubsettableShapeManager( mpShapeManager ),
+ mpBox2DWorld( std::make_shared<box2d::utils::box2DWorld>(
+ basegfx::B2DSize( getSlideSizeImpl() ) ) ),
+ maContext( mpSubsettableShapeManager,
+ rEventQueue,
+ rEventMultiplexer,
+ rScreenUpdater,
+ rActivitiesQueue,
+ rUserEventQueue,
+ *this,
+ rMediaFileManager,
+ rViewContainer,
+ xComponentContext,
+ mpBox2DWorld ),
+ mrCursorManager( rCursorManager ),
+ maAnimations( maContext,
+ basegfx::B2DSize( getSlideSizeImpl() ) ),
+ maPolygons(std::move(rPolyPolygonVector)),
+ maUserPaintColor(aUserPaintColor),
+ mdUserPaintStrokeWidth(dUserPaintStrokeWidth),
+ mpPaintOverlay(),
+ maSlideBitmaps(),
+ meAnimationState( CONSTRUCTING_STATE ),
+ maSlideSize(getSlideSizeImpl()),
+ mnCurrentCursor( awt::SystemPointer::ARROW ),
+ mbIntrinsicAnimationsAllowed( bIntrinsicAnimationsAllowed ),
+ mbUserPaintOverlayEnabled(bUserPaintEnabled),
+ mbShapesLoaded( false ),
+ mbShowLoaded( false ),
+ mbHaveAnimations( false ),
+ mbMainSequenceFound( false ),
+ mbActive( false ),
+ mbPaintOverlayActive( false ),
+ mbFinalStateApplied( false )
+{
+ // clone already existing views for slide bitmaps
+ for( const auto& rView : rViewContainer )
+ viewAdded( rView );
+
+ // register screen update (LayerManager needs to signal pending
+ // updates)
+ maContext.mrScreenUpdater.addViewUpdate(mpShapeManager);
+}
+
+void SlideImpl::update_settings( bool bUserPaintEnabled, RGBColor const& aUserPaintColor, double dUserPaintStrokeWidth )
+{
+ maUserPaintColor = aUserPaintColor;
+ mdUserPaintStrokeWidth = dUserPaintStrokeWidth;
+ mbUserPaintOverlayEnabled = bUserPaintEnabled;
+}
+
+SlideImpl::~SlideImpl()
+{
+ if( mpShapeManager )
+ {
+ maContext.mrScreenUpdater.removeViewUpdate(mpShapeManager);
+ mpShapeManager->dispose();
+
+ // TODO(Q3): Make sure LayerManager (and thus Shapes) dies
+ // first, because SlideShowContext has SubsettableShapeManager
+ // as reference member.
+ mpLayerManager.reset();
+ }
+}
+
+void SlideImpl::prefetch()
+{
+ if( !mxRootNode.is() )
+ return;
+
+ // Try to prefetch all graphics from the page. This will be done
+ // in threads to be more efficient than loading them on-demand one by one.
+ std::vector<Graphic*> graphics;
+ for (sal_Int32 i = 0; i < mxDrawPage->getCount(); i++)
+ {
+ com::sun::star::uno::Reference<com::sun::star::drawing::XShape> xShape(mxDrawPage->getByIndex(i), com::sun::star::uno::UNO_QUERY_THROW);
+ SdrObject* pObj = SdrObject::getSdrObjectFromXShape(xShape);
+ if (!pObj)
+ continue;
+ if( SdrGrafObj* grafObj = dynamic_cast<SdrGrafObj*>(pObj))
+ if( !grafObj->GetGraphic().isAvailable())
+ graphics.push_back( const_cast<Graphic*>(&grafObj->GetGraphic()));
+ }
+ if(graphics.size() > 1) // threading does not help with loading just one
+ GraphicFilter::GetGraphicFilter().MakeGraphicsAvailableThreaded( graphics );
+
+ applyInitialShapeAttributes(mxRootNode);
+}
+
+void SlideImpl::show( bool bSlideBackgroundPainted )
+{
+ if( mbActive )
+ return; // already active
+
+ if( !mpShapeManager || !mpLayerManager )
+ return; // disposed
+
+ // set initial shape attributes (e.g. hide shapes that have
+ // 'appear' effect set)
+ if( !applyInitialShapeAttributes(mxRootNode) )
+ return;
+
+ // activate and take over view - clears view, if necessary
+ mbActive = true;
+ requestCursor( mnCurrentCursor );
+
+ // enable shape management & event broadcasting for shapes of this
+ // slide. Also enables LayerManager to record updates. Currently,
+ // never let LayerManager render initial slide content, use
+ // buffered slide bitmaps instead.
+ mpShapeManager->activate();
+
+
+ // render slide to screen, if requested
+ if( !bSlideBackgroundPainted )
+ {
+ for( const auto& rContext : maContext.mrViewContainer )
+ slideRenderer( this, rContext );
+
+ maContext.mrScreenUpdater.notifyUpdate();
+ }
+
+
+ // fire up animations
+ const bool bIsAnimated( isAnimated() );
+ if( bIsAnimated )
+ maAnimations.start(); // feeds initial events into queue
+
+ // NOTE: this looks slightly weird, but is indeed correct:
+ // as isAnimated() might return false, _although_ there is
+ // a main sequence (because the animation nodes don't
+ // contain any executable effects), we gotta check both
+ // conditions here.
+ if( !bIsAnimated || !mbMainSequenceFound )
+ {
+ // manually trigger a slide animation end event (we don't have
+ // animations at all, or we don't have a main animation
+ // sequence, but if we had, it'd end now). Note that having
+ // animations alone does not matter here, as only main
+ // sequence animations prevents showing the next slide on
+ // nextEvent().
+ maContext.mrEventMultiplexer.notifySlideAnimationsEnd();
+ }
+
+ // enable shape-intrinsic animations (drawing layer animations or
+ // GIF animations)
+ if( mbIntrinsicAnimationsAllowed )
+ mpSubsettableShapeManager->notifyIntrinsicAnimationsEnabled();
+
+ // enable paint overlay, if maUserPaintColor is valid
+ activatePaintOverlay();
+
+
+ // from now on, animations might be showing
+ meAnimationState = SHOWING_STATE;
+}
+
+void SlideImpl::hide()
+{
+ if( !mbActive || !mpShapeManager )
+ return; // already hidden/disposed
+
+
+ // from now on, all animations are stopped
+ meAnimationState = FINAL_STATE;
+
+
+ // disable user paint overlay under all circumstances,
+ // this slide now ceases to be active.
+ deactivatePaintOverlay();
+
+
+ // switch off all shape-intrinsic animations.
+ mpSubsettableShapeManager->notifyIntrinsicAnimationsDisabled();
+
+ // force-end all SMIL animations, too
+ maAnimations.end();
+
+
+ // disable shape management & event broadcasting for shapes of this
+ // slide. Also disables LayerManager.
+ mpShapeManager->deactivate();
+
+ // vanish from view
+ resetCursor();
+ mbActive = false;
+}
+
+basegfx::B2ISize SlideImpl::getSlideSize() const
+{
+ return maSlideSize;
+}
+
+uno::Reference<drawing::XDrawPage > SlideImpl::getXDrawPage() const
+{
+ return mxDrawPage;
+}
+
+uno::Reference<animations::XAnimationNode> SlideImpl::getXAnimationNode() const
+{
+ return mxRootNode;
+}
+
+PolyPolygonVector SlideImpl::getPolygons()
+{
+ if(mbPaintOverlayActive)
+ maPolygons = mpPaintOverlay->getPolygons();
+ return maPolygons;
+}
+
+SlideBitmapSharedPtr SlideImpl::getCurrentSlideBitmap( const UnoViewSharedPtr& rView ) const
+{
+ // search corresponding entry in maSlideBitmaps (which
+ // contains the views as the key)
+ VectorOfVectorOfSlideBitmaps::iterator aIter;
+ const VectorOfVectorOfSlideBitmaps::iterator aEnd( maSlideBitmaps.end() );
+ if( (aIter=std::find_if( maSlideBitmaps.begin(),
+ aEnd,
+ [&rView]
+ ( const VectorOfVectorOfSlideBitmaps::value_type& cp )
+ { return rView == cp.first; } ) ) == aEnd )
+ {
+ // corresponding view not found - maybe view was not
+ // added to Slide?
+ ENSURE_OR_THROW( false,
+ "SlideImpl::getInitialSlideBitmap(): view does not "
+ "match any of the added ones" );
+ }
+
+ // ensure that the show is loaded
+ if( !mbShowLoaded )
+ {
+ // only prefetch and init shapes when not done already
+ // (otherwise, at least applyInitialShapeAttributes() will be
+ // called twice for initial slide rendering). Furthermore,
+ // applyInitialShapeAttributes() _always_ performs
+ // initializations, which would be highly unwanted during a
+ // running show. OTOH, a slide whose mbShowLoaded is false is
+ // guaranteed not be running a show.
+
+ // set initial shape attributes (e.g. hide 'appear' effect
+ // shapes)
+ if( !const_cast<SlideImpl*>(this)->applyInitialShapeAttributes( mxRootNode ) )
+ ENSURE_OR_THROW(false,
+ "SlideImpl::getCurrentSlideBitmap(): Cannot "
+ "apply initial attributes");
+ }
+
+ SlideBitmapSharedPtr& rBitmap( aIter->second.at( meAnimationState ));
+ const ::basegfx::B2ISize& rSlideSize(
+ getSlideSizePixel( ::basegfx::B2DSize( getSlideSize() ),
+ rView ));
+
+ // is the bitmap valid (actually existent, and of correct
+ // size)?
+ if( !rBitmap || rBitmap->getSize() != rSlideSize )
+ {
+ // no bitmap there yet, or wrong size - create one
+ rBitmap = createCurrentSlideBitmap(rView, rSlideSize);
+ }
+
+ return rBitmap;
+}
+
+
+// private methods
+
+
+void SlideImpl::viewAdded( const UnoViewSharedPtr& rView )
+{
+ maSlideBitmaps.emplace_back( rView,
+ VectorOfSlideBitmaps(SlideAnimationState_NUM_ENTRIES) );
+
+ if( mpLayerManager )
+ mpLayerManager->viewAdded( rView );
+}
+
+void SlideImpl::viewRemoved( const UnoViewSharedPtr& rView )
+{
+ if( mpLayerManager )
+ mpLayerManager->viewRemoved( rView );
+
+ const VectorOfVectorOfSlideBitmaps::iterator aEnd( maSlideBitmaps.end() );
+ maSlideBitmaps.erase(
+ std::remove_if( maSlideBitmaps.begin(),
+ aEnd,
+ [&rView]
+ ( const VectorOfVectorOfSlideBitmaps::value_type& cp )
+ { return rView == cp.first; } ),
+ aEnd );
+}
+
+void SlideImpl::viewChanged( const UnoViewSharedPtr& rView )
+{
+ // nothing to do for the Slide - getCurrentSlideBitmap() lazily
+ // handles bitmap resizes
+ if( mbActive && mpLayerManager )
+ mpLayerManager->viewChanged(rView);
+}
+
+void SlideImpl::viewsChanged()
+{
+ // nothing to do for the Slide - getCurrentSlideBitmap() lazily
+ // handles bitmap resizes
+ if( mbActive && mpLayerManager )
+ mpLayerManager->viewsChanged();
+}
+
+bool SlideImpl::requestCursor( sal_Int16 nCursorShape )
+{
+ mnCurrentCursor = nCursorShape;
+ return mrCursorManager.requestCursor(mnCurrentCursor);
+}
+
+void SlideImpl::resetCursor()
+{
+ mnCurrentCursor = awt::SystemPointer::ARROW;
+ mrCursorManager.resetCursor();
+}
+
+bool SlideImpl::isAnimated()
+{
+ // prefetch, but don't apply initial shape attributes
+ if( !implPrefetchShow() )
+ return false;
+
+ return mbHaveAnimations && maAnimations.isAnimated();
+}
+
+SlideBitmapSharedPtr SlideImpl::createCurrentSlideBitmap( const UnoViewSharedPtr& rView,
+ const ::basegfx::B2ISize& rBmpSize ) const
+{
+ ENSURE_OR_THROW( rView && rView->getCanvas(),
+ "SlideImpl::createCurrentSlideBitmap(): Invalid view" );
+ ENSURE_OR_THROW( mpLayerManager,
+ "SlideImpl::createCurrentSlideBitmap(): Invalid layer manager" );
+ ENSURE_OR_THROW( mbShowLoaded,
+ "SlideImpl::createCurrentSlideBitmap(): No show loaded" );
+
+ // tdf#96083 ensure end state settings are applied to shapes once when bitmap gets re-rendered
+ // in that state
+ if(!mbFinalStateApplied && FINAL_STATE == meAnimationState && mxRootNode.is())
+ {
+ const_cast< SlideImpl* >(this)->mbFinalStateApplied = true;
+ applyShapeAttributes(mxRootNode, false);
+ }
+
+ ::cppcanvas::CanvasSharedPtr pCanvas( rView->getCanvas() );
+
+ // create a bitmap of appropriate size
+ ::cppcanvas::BitmapSharedPtr pBitmap(
+ ::cppcanvas::BaseGfxFactory::createBitmap(
+ pCanvas,
+ rBmpSize ) );
+
+ ENSURE_OR_THROW( pBitmap,
+ "SlideImpl::createCurrentSlideBitmap(): Cannot create page bitmap" );
+
+ ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( pBitmap->getBitmapCanvas() );
+
+ ENSURE_OR_THROW( pBitmapCanvas,
+ "SlideImpl::createCurrentSlideBitmap(): Cannot create page bitmap canvas" );
+
+ // apply linear part of destination canvas transformation (linear means in this context:
+ // transformation without any translational components)
+ ::basegfx::B2DHomMatrix aLinearTransform( rView->getTransformation() );
+ aLinearTransform.set( 0, 2, 0.0 );
+ aLinearTransform.set( 1, 2, 0.0 );
+ pBitmapCanvas->setTransformation( aLinearTransform );
+
+ // output all shapes to bitmap
+ initSlideBackground( pBitmapCanvas, rBmpSize );
+ mpLayerManager->renderTo( pBitmapCanvas );
+
+ return std::make_shared<SlideBitmap>( pBitmap );
+}
+
+class MainSequenceSearcher
+{
+public:
+ MainSequenceSearcher()
+ {
+ maSearchKey.Name = "node-type";
+ maSearchKey.Value <<= presentation::EffectNodeType::MAIN_SEQUENCE;
+ }
+
+ void operator()( const uno::Reference< animations::XAnimationNode >& xChildNode )
+ {
+ uno::Sequence< beans::NamedValue > aUserData( xChildNode->getUserData() );
+
+ if( findNamedValue( aUserData, maSearchKey ) )
+ {
+ maMainSequence = xChildNode;
+ }
+ }
+
+ const uno::Reference< animations::XAnimationNode >& getMainSequence() const
+ {
+ return maMainSequence;
+ }
+
+private:
+ beans::NamedValue maSearchKey;
+ uno::Reference< animations::XAnimationNode > maMainSequence;
+};
+
+bool SlideImpl::implPrefetchShow()
+{
+ if( mbShowLoaded )
+ return true;
+
+ ENSURE_OR_RETURN_FALSE( mxDrawPage.is(),
+ "SlideImpl::implPrefetchShow(): Invalid draw page" );
+ ENSURE_OR_RETURN_FALSE( mpLayerManager,
+ "SlideImpl::implPrefetchShow(): Invalid layer manager" );
+
+ // fetch desired page content
+ // ==========================
+
+ if( !loadShapes() )
+ return false;
+
+ // New animations framework: import the shape effect info
+ // ======================================================
+
+ try
+ {
+ if( mxRootNode.is() )
+ {
+ if( !maAnimations.importAnimations( mxRootNode ) )
+ {
+ OSL_FAIL( "SlideImpl::implPrefetchShow(): have animation nodes, "
+ "but import animations failed." );
+
+ // could not import animation framework,
+ // _although_ some animation nodes are there -
+ // this is an error (not finding animations at
+ // all is okay - might be a static slide)
+ return false;
+ }
+
+ // now check whether we've got a main sequence (if
+ // not, we must manually call
+ // EventMultiplexer::notifySlideAnimationsEnd()
+ // above, as e.g. interactive sequences alone
+ // don't block nextEvent() from issuing the next
+ // slide)
+ MainSequenceSearcher aSearcher;
+ if( for_each_childNode( mxRootNode, aSearcher ) )
+ mbMainSequenceFound = aSearcher.getMainSequence().is();
+
+ // import successfully done
+ mbHaveAnimations = true;
+ }
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ // TODO(E2): Error handling. For now, bail out
+ }
+
+ mbShowLoaded = true;
+
+ return true;
+}
+
+void SlideImpl::enablePaintOverlay()
+{
+ if( !mbUserPaintOverlayEnabled || !mbPaintOverlayActive )
+ {
+ mbUserPaintOverlayEnabled = true;
+ activatePaintOverlay();
+ }
+}
+
+void SlideImpl::activatePaintOverlay()
+{
+ if( mbUserPaintOverlayEnabled || !maPolygons.empty() )
+ {
+ mpPaintOverlay = UserPaintOverlay::create( maUserPaintColor,
+ mdUserPaintStrokeWidth,
+ maContext,
+ std::vector(maPolygons),
+ mbUserPaintOverlayEnabled );
+ mbPaintOverlayActive = true;
+ }
+}
+
+void SlideImpl::drawPolygons() const
+{
+ if( mpPaintOverlay )
+ mpPaintOverlay->drawPolygons();
+}
+
+void SlideImpl::addPolygons(const PolyPolygonVector& rPolygons)
+{
+ maPolygons.insert(maPolygons.end(), rPolygons.begin(), rPolygons.end());
+}
+
+bool SlideImpl::isPaintOverlayActive() const
+{
+ return mbPaintOverlayActive;
+}
+
+void SlideImpl::deactivatePaintOverlay()
+{
+ if(mbPaintOverlayActive)
+ maPolygons = mpPaintOverlay->getPolygons();
+
+ mpPaintOverlay.reset();
+ mbPaintOverlayActive = false;
+}
+
+void SlideImpl::applyShapeAttributes(
+ const css::uno::Reference< css::animations::XAnimationNode >& xRootAnimationNode,
+ bool bInitial) const
+{
+ const uno::Sequence< animations::TargetProperties > aProps(
+ TargetPropertiesCreator::createTargetProperties( xRootAnimationNode, bInitial ) );
+
+ // apply extracted values to our shapes
+ for( const auto& rProp : aProps )
+ {
+ sal_Int16 nParaIndex( -1 );
+ uno::Reference< drawing::XShape > xShape( rProp.Target,
+ uno::UNO_QUERY );
+
+ if( !xShape.is() )
+ {
+ // not a shape target. Maybe a ParagraphTarget?
+ presentation::ParagraphTarget aParaTarget;
+
+ if( rProp.Target >>= aParaTarget )
+ {
+ // yep, ParagraphTarget found - extract shape
+ // and index
+ xShape = aParaTarget.Shape;
+ nParaIndex = aParaTarget.Paragraph;
+ }
+ }
+
+ if( xShape.is() )
+ {
+ ShapeSharedPtr pShape( mpLayerManager->lookupShape( xShape ) );
+
+ if( !pShape )
+ {
+ OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): no shape found for given target" );
+ continue;
+ }
+
+ AttributableShapeSharedPtr pAttrShape(
+ ::std::dynamic_pointer_cast< AttributableShape >( pShape ) );
+
+ if( !pAttrShape )
+ {
+ OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not "
+ "implement AttributableShape interface" );
+ continue;
+ }
+
+ if( nParaIndex != -1 )
+ {
+ // our target is a paragraph subset, thus look
+ // this up first.
+ const DocTreeNodeSupplier& rNodeSupplier( pAttrShape->getTreeNodeSupplier() );
+
+ if( rNodeSupplier.getNumberOfTreeNodes(
+ DocTreeNode::NodeType::LogicalParagraph ) <= nParaIndex )
+ {
+ OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not "
+ "provide a subset for requested paragraph index" );
+ continue;
+ }
+
+ pAttrShape = pAttrShape->getSubset(
+ rNodeSupplier.getTreeNode(
+ nParaIndex,
+ DocTreeNode::NodeType::LogicalParagraph ) );
+
+ if( !pAttrShape )
+ {
+ OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): shape found does not "
+ "provide a subset for requested paragraph index" );
+ continue;
+ }
+ }
+
+ const uno::Sequence< beans::NamedValue >& rShapeProps( rProp.Properties );
+ for( const auto& rShapeProp : rShapeProps )
+ {
+ bool bVisible=false;
+ if( rShapeProp.Name.equalsIgnoreAsciiCase("visibility") &&
+ extractValue( bVisible,
+ rShapeProp.Value,
+ pShape,
+ ::basegfx::B2DSize( getSlideSize() ) ))
+ {
+ pAttrShape->setVisibility( bVisible );
+ }
+ else
+ {
+ OSL_FAIL( "SlideImpl::applyInitialShapeAttributes(): Unexpected "
+ "(and unimplemented) property encountered" );
+ }
+ }
+ }
+ }
+}
+
+bool SlideImpl::applyInitialShapeAttributes(
+ const uno::Reference< animations::XAnimationNode >& xRootAnimationNode )
+{
+ if( !implPrefetchShow() )
+ return false;
+
+ if( !xRootAnimationNode.is() )
+ {
+ meAnimationState = INITIAL_STATE;
+
+ return true; // no animations - no attributes to apply -
+ // succeeded
+ }
+
+ applyShapeAttributes(xRootAnimationNode, true);
+
+ meAnimationState = INITIAL_STATE;
+
+ return true;
+}
+
+bool SlideImpl::loadShapes()
+{
+ if( mbShapesLoaded )
+ return true;
+
+ ENSURE_OR_RETURN_FALSE( mxDrawPage.is(),
+ "SlideImpl::loadShapes(): Invalid draw page" );
+ ENSURE_OR_RETURN_FALSE( mpLayerManager,
+ "SlideImpl::loadShapes(): Invalid layer manager" );
+
+ // fetch desired page content
+ // ==========================
+
+ // also take master page content
+ uno::Reference< drawing::XDrawPage > xMasterPage;
+ uno::Reference< drawing::XShapes > xMasterPageShapes;
+ sal_Int32 nCurrCount(0);
+
+ uno::Reference< drawing::XMasterPageTarget > xMasterPageTarget( mxDrawPage,
+ uno::UNO_QUERY );
+ if( xMasterPageTarget.is() )
+ {
+ xMasterPage = xMasterPageTarget->getMasterPage();
+ xMasterPageShapes = xMasterPage;
+
+ if( xMasterPage.is() && xMasterPageShapes.is() )
+ {
+ // TODO(P2): maybe cache master pages here (or treat the
+ // masterpage as a single metafile. At least currently,
+ // masterpages do not contain animation effects)
+ try
+ {
+ // load the masterpage shapes
+
+ ShapeImporter aMPShapesFunctor( xMasterPage,
+ mxDrawPage,
+ mxDrawPagesSupplier,
+ maContext,
+ 0, /* shape num starts at 0 */
+ true );
+
+ mpLayerManager->addShape(
+ aMPShapesFunctor.importBackgroundShape() );
+
+ while( !aMPShapesFunctor.isImportDone() )
+ {
+ ShapeSharedPtr const& rShape(
+ aMPShapesFunctor.importShape() );
+ if( rShape )
+ {
+ rShape->setIsForeground(false);
+ mpLayerManager->addShape( rShape );
+ }
+ }
+ addPolygons(aMPShapesFunctor.getPolygons());
+
+ nCurrCount = static_cast<sal_Int32>(aMPShapesFunctor.getImportedShapesCount());
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( ShapeLoadFailedException& )
+ {
+ // TODO(E2): Error handling. For now, bail out
+ TOOLS_WARN_EXCEPTION( "slideshow", "SlideImpl::loadShapes(): caught ShapeLoadFailedException" );
+ return false;
+
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ return false;
+ }
+ }
+ }
+
+ try
+ {
+ // load the normal page shapes
+
+
+ ShapeImporter aShapesFunctor( mxDrawPage,
+ mxDrawPage,
+ mxDrawPagesSupplier,
+ maContext,
+ nCurrCount,
+ false );
+
+ while( !aShapesFunctor.isImportDone() )
+ {
+ ShapeSharedPtr const& rShape(
+ aShapesFunctor.importShape() );
+ if( rShape )
+ mpLayerManager->addShape( rShape );
+ }
+ addPolygons(aShapesFunctor.getPolygons());
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( ShapeLoadFailedException& )
+ {
+ // TODO(E2): Error handling. For now, bail out
+ TOOLS_WARN_EXCEPTION( "slideshow", "SlideImpl::loadShapes(): caught ShapeLoadFailedException" );
+ return false;
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ return false;
+ }
+
+ mbShapesLoaded = true;
+
+ return true;
+}
+
+basegfx::B2ISize SlideImpl::getSlideSizeImpl() const
+{
+ uno::Reference< beans::XPropertySet > xPropSet(
+ mxDrawPage, uno::UNO_QUERY_THROW );
+
+ sal_Int32 nDocWidth = 0;
+ sal_Int32 nDocHeight = 0;
+ xPropSet->getPropertyValue("Width") >>= nDocWidth;
+ xPropSet->getPropertyValue("Height") >>= nDocHeight;
+
+ return basegfx::B2ISize( nDocWidth, nDocHeight );
+}
+
+} // namespace
+
+
+SlideSharedPtr createSlide( const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const uno::Reference<drawing::XDrawPagesSupplier>& xDrawPages,
+ const uno::Reference< animations::XAnimationNode >& xRootNode,
+ EventQueue& rEventQueue,
+ EventMultiplexer& rEventMultiplexer,
+ ScreenUpdater& rScreenUpdater,
+ ActivitiesQueue& rActivitiesQueue,
+ UserEventQueue& rUserEventQueue,
+ CursorManager& rCursorManager,
+ MediaFileManager& rMediaFileManager,
+ const UnoViewContainer& rViewContainer,
+ const uno::Reference< uno::XComponentContext >& xComponentContext,
+ const ShapeEventListenerMap& rShapeListenerMap,
+ const ShapeCursorMap& rShapeCursorMap,
+ PolyPolygonVector&& rPolyPolygonVector,
+ RGBColor const& rUserPaintColor,
+ double dUserPaintStrokeWidth,
+ bool bUserPaintEnabled,
+ bool bIntrinsicAnimationsAllowed,
+ bool bDisableAnimationZOrder )
+{
+ auto pRet = std::make_shared<SlideImpl>( xDrawPage, xDrawPages, xRootNode, rEventQueue,
+ rEventMultiplexer, rScreenUpdater,
+ rActivitiesQueue, rUserEventQueue,
+ rCursorManager, rMediaFileManager, rViewContainer,
+ xComponentContext, rShapeListenerMap,
+ rShapeCursorMap, std::move(rPolyPolygonVector), rUserPaintColor,
+ dUserPaintStrokeWidth, bUserPaintEnabled,
+ bIntrinsicAnimationsAllowed,
+ bDisableAnimationZOrder );
+
+ rEventMultiplexer.addViewHandler( pRet );
+
+ return pRet;
+}
+
+} // namespace slideshow
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/targetpropertiescreator.cxx b/slideshow/source/engine/slide/targetpropertiescreator.cxx
new file mode 100644
index 000000000..fdc45c3da
--- /dev/null
+++ b/slideshow/source/engine/slide/targetpropertiescreator.cxx
@@ -0,0 +1,370 @@
+/* -*- 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 <com/sun/star/animations/XIterateContainer.hpp>
+#include <com/sun/star/presentation/ParagraphTarget.hpp>
+#include <com/sun/star/drawing/XShape.hpp>
+#include <com/sun/star/animations/AnimationNodeType.hpp>
+#include <com/sun/star/animations/XAnimate.hpp>
+#include <comphelper/sequence.hxx>
+
+#include <unordered_map>
+#include <vector>
+
+#include "targetpropertiescreator.hxx"
+#include <tools.hxx>
+
+namespace slideshow::internal
+{
+ namespace
+ {
+ // Vector containing all properties for a given shape
+ typedef ::std::vector< beans::NamedValue > VectorOfNamedValues;
+
+ /** The hash map key
+
+ This key contains both XShape reference and a paragraph
+ index, as we somehow have to handle shape and paragraph
+ targets with the same data structure.
+ */
+ struct ShapeHashKey
+ {
+ /// Shape target
+ uno::Reference< drawing::XShape > mxRef;
+
+ /** Paragraph index.
+
+ If this is a pure shape target, mnParagraphIndex is
+ set to -1.
+ */
+ sal_Int16 mnParagraphIndex;
+
+ /// Comparison needed for unordered_map
+ bool operator==( const ShapeHashKey& rRHS ) const
+ {
+ return mxRef == rRHS.mxRef && mnParagraphIndex == rRHS.mnParagraphIndex;
+ }
+ };
+
+ // A hash functor for ShapeHashKey objects
+ struct ShapeKeyHasher
+ {
+ ::std::size_t operator()( const ShapeHashKey& rKey ) const
+ {
+ // TODO(P2): Maybe a better hash function would be to
+ // spread mnParagraphIndex to 32 bit: a0b0c0d0e0... Hakmem
+ // should have a formula.
+
+ // Yes it has:
+ // x = (x & 0x0000FF00) << 8) | (x >> 8) & 0x0000FF00 | x & 0xFF0000FF;
+ // x = (x & 0x00F000F0) << 4) | (x >> 4) & 0x00F000F0 | x & 0xF00FF00F;
+ // x = (x & 0x0C0C0C0C) << 2) | (x >> 2) & 0x0C0C0C0C | x & 0xC3C3C3C3;
+ // x = (x & 0x22222222) << 1) | (x >> 1) & 0x22222222 | x & 0x99999999;
+
+ // Costs about 17 cycles on a RISC machine with infinite
+ // instruction level parallelism (~42 basic
+ // instructions). Thus I truly doubt this pays off...
+ return reinterpret_cast< ::std::size_t >(rKey.mxRef.get()) ^ (rKey.mnParagraphIndex << 16);
+ }
+ };
+
+ // A hash map which maps a XShape to the corresponding vector of initial properties
+ typedef std::unordered_map< ShapeHashKey, VectorOfNamedValues, ShapeKeyHasher > XShapeToNamedValuesMap;
+
+
+ class NodeFunctor
+ {
+ public:
+ explicit NodeFunctor(
+ XShapeToNamedValuesMap& rShapeHash,
+ bool bInitial )
+ : mrShapeHash( rShapeHash ),
+ mxTargetShape(),
+ mnParagraphIndex( -1 ),
+ mbInitial( bInitial)
+ {
+ }
+
+ NodeFunctor( XShapeToNamedValuesMap& rShapeHash,
+ const uno::Reference< drawing::XShape >& rTargetShape,
+ sal_Int16 nParagraphIndex,
+ bool bInitial) :
+ mrShapeHash( rShapeHash ),
+ mxTargetShape( rTargetShape ),
+ mnParagraphIndex( nParagraphIndex ),
+ mbInitial( bInitial )
+ {
+ }
+
+ void operator()( const uno::Reference< animations::XAnimationNode >& xNode ) const
+ {
+ if( !xNode.is() )
+ {
+ OSL_FAIL( "AnimCore: NodeFunctor::operator(): invalid XAnimationNode" );
+ return;
+ }
+
+ uno::Reference< drawing::XShape > xTargetShape( mxTargetShape );
+ sal_Int16 nParagraphIndex( mnParagraphIndex );
+
+ switch( xNode->getType() )
+ {
+ case animations::AnimationNodeType::ITERATE:
+ {
+ // extract target shape from iterate node
+ // (will override the target for all children)
+
+ uno::Reference< animations::XIterateContainer > xIterNode( xNode,
+ uno::UNO_QUERY );
+
+ // TODO(E1): I'm not too sure what to expect here...
+ if( !xIterNode->getTarget().hasValue() )
+ {
+ OSL_FAIL( "animcore: NodeFunctor::operator(): no target on ITERATE node" );
+ return;
+ }
+
+ xTargetShape.set( xIterNode->getTarget(),
+ uno::UNO_QUERY );
+
+ if( !xTargetShape.is() )
+ {
+ css::presentation::ParagraphTarget aTarget;
+
+ // no shape provided. Maybe a ParagraphTarget?
+ if( !(xIterNode->getTarget() >>= aTarget) )
+ {
+ OSL_FAIL( "animcore: NodeFunctor::operator(): could not extract any "
+ "target information" );
+ return;
+ }
+
+ xTargetShape = aTarget.Shape;
+ nParagraphIndex = aTarget.Paragraph;
+
+ if( !xTargetShape.is() )
+ {
+ OSL_FAIL( "animcore: NodeFunctor::operator(): invalid shape in ParagraphTarget" );
+ return;
+ }
+ }
+ [[fallthrough]];
+ }
+ case animations::AnimationNodeType::PAR:
+ case animations::AnimationNodeType::SEQ:
+ {
+ /// forward bInitial
+ NodeFunctor aFunctor( mrShapeHash,
+ xTargetShape,
+ nParagraphIndex,
+ mbInitial );
+ if( !for_each_childNode( xNode, aFunctor ) )
+ {
+ OSL_FAIL( "AnimCore: NodeFunctor::operator(): child node iteration failed, "
+ "or extraneous container nodes encountered" );
+ }
+ }
+ break;
+
+ case animations::AnimationNodeType::CUSTOM:
+ case animations::AnimationNodeType::ANIMATE:
+ case animations::AnimationNodeType::ANIMATEMOTION:
+ case animations::AnimationNodeType::ANIMATECOLOR:
+ case animations::AnimationNodeType::ANIMATETRANSFORM:
+ case animations::AnimationNodeType::TRANSITIONFILTER:
+ case animations::AnimationNodeType::AUDIO:
+ /*default:
+ // ignore this node, no valuable content for now.
+ break;*/
+
+ case animations::AnimationNodeType::SET:
+ {
+ // evaluate set node content
+ uno::Reference< animations::XAnimate > xAnimateNode( xNode,
+ uno::UNO_QUERY );
+
+ if( !xAnimateNode.is() )
+ break; // invalid node
+
+ // determine target shape (if any)
+ ShapeHashKey aTarget;
+ if( xTargetShape.is() )
+ {
+ // override target shape with parent-supplied
+ aTarget.mxRef = xTargetShape;
+ aTarget.mnParagraphIndex = nParagraphIndex;
+ }
+ else
+ {
+ // no parent-supplied target, retrieve
+ // node target
+ if( xAnimateNode->getTarget() >>= aTarget.mxRef )
+ {
+ // pure shape target - set paragraph
+ // index to magic
+ aTarget.mnParagraphIndex = -1;
+ }
+ else
+ {
+ // not a pure shape target - maybe a
+ // ParagraphTarget?
+ presentation::ParagraphTarget aUnoTarget;
+
+ if( !(xAnimateNode->getTarget() >>= aUnoTarget) )
+ {
+ OSL_FAIL( "AnimCore: NodeFunctor::operator(): unknown target type encountered" );
+ break;
+ }
+
+ aTarget.mxRef = aUnoTarget.Shape;
+ aTarget.mnParagraphIndex = aUnoTarget.Paragraph;
+ }
+ }
+
+ if( !aTarget.mxRef.is() )
+ {
+ OSL_FAIL( "AnimCore: NodeFunctor::operator(): Found target, but XShape is NULL" );
+ break; // invalid target XShape
+ }
+
+ // check whether we already have an entry for
+ // this target (we only take the first set
+ // effect for every shape) - but keep going if
+ // we're requested the final state (which
+ // eventually gets overwritten in the
+ // unordered list, see tdf#96083)
+ if( mbInitial && mrShapeHash.find( aTarget ) != mrShapeHash.end() )
+ break; // already an entry in existence for given XShape
+
+ // if this is an appear effect, hide shape
+ // initially. This is currently the only place
+ // where a shape effect influences shape
+ // attributes outside it's effective duration.
+ bool bVisible( false );
+ if( xAnimateNode->getAttributeName().equalsIgnoreAsciiCase("visibility") )
+ {
+
+ uno::Any aAny( xAnimateNode->getTo() );
+
+ // try to extract bool value
+ if( !(aAny >>= bVisible) )
+ {
+ // try to extract string
+ OUString aString;
+ if( aAny >>= aString )
+ {
+ // we also take the strings "true" and "false",
+ // as well as "on" and "off" here
+ if( aString.equalsIgnoreAsciiCase("true") ||
+ aString.equalsIgnoreAsciiCase("on") )
+ {
+ bVisible = true;
+ }
+ if( aString.equalsIgnoreAsciiCase("false") ||
+ aString.equalsIgnoreAsciiCase("off") )
+ {
+ bVisible = false;
+ }
+ }
+ }
+ }
+
+ // if initial anim sets shape visible, set it
+ // to invisible. If we're asked for the final
+ // state, don't do anything obviously
+ if(mbInitial)
+ bVisible = !bVisible;
+
+ // target is set the 'visible' value,
+ // so we should record the opposite value
+ mrShapeHash.emplace(
+ aTarget,
+ VectorOfNamedValues(
+ 1,
+ beans::NamedValue(
+ //xAnimateNode->getAttributeName(),
+ "visibility",
+ uno::Any( bVisible ) ) ) );
+ break;
+ }
+ }
+ }
+
+ private:
+ XShapeToNamedValuesMap& mrShapeHash;
+ uno::Reference< drawing::XShape > mxTargetShape;
+ sal_Int16 mnParagraphIndex;
+
+ // get initial or final state
+ bool mbInitial;
+ };
+ }
+
+ uno::Sequence< animations::TargetProperties > TargetPropertiesCreator::createTargetProperties
+ (
+ const uno::Reference< animations::XAnimationNode >& xRootNode,
+ bool bInitial
+ ) //throw (uno::RuntimeException, std::exception)
+ {
+ // scan all nodes for visibility changes, and record first
+ // 'visibility=true' for each shape
+ XShapeToNamedValuesMap aShapeHash( 101 );
+
+ NodeFunctor aFunctor(
+ aShapeHash,
+ bInitial );
+
+ // TODO(F1): Maybe limit functor application to main sequence
+ // alone (CL said something that shape visibility is only
+ // affected by effects in the main sequence for PPT).
+
+ // OTOH, client code can pass us only the main sequence (which
+ // it actually does right now, for the slideshow implementation).
+ aFunctor( xRootNode );
+
+ // output to result sequence
+ uno::Sequence< animations::TargetProperties > aRes( aShapeHash.size() );
+ auto aResRange = asNonConstRange(aRes);
+
+ ::std::size_t nCurrIndex(0);
+ for( const auto& rIter : aShapeHash )
+ {
+ animations::TargetProperties& rCurrProps( aResRange[ nCurrIndex++ ] );
+
+ if( rIter.first.mnParagraphIndex == -1 )
+ {
+ rCurrProps.Target <<= rIter.first.mxRef;
+ }
+ else
+ {
+ rCurrProps.Target <<=
+ presentation::ParagraphTarget(
+ rIter.first.mxRef,
+ rIter.first.mnParagraphIndex );
+ }
+
+ rCurrProps.Properties = ::comphelper::containerToSequence( rIter.second );
+ }
+
+ return aRes;
+ }
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/targetpropertiescreator.hxx b/slideshow/source/engine/slide/targetpropertiescreator.hxx
new file mode 100644
index 000000000..79c1e49a5
--- /dev/null
+++ b/slideshow/source/engine/slide/targetpropertiescreator.hxx
@@ -0,0 +1,39 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX
+
+#include <com/sun/star/animations/TargetProperties.hpp>
+#include <com/sun/star/animations/XAnimationNode.hpp>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal::TargetPropertiesCreator
+{
+ /// Generate shape property list - set bInitial to true for initial slide state
+ uno::Sequence< animations::TargetProperties > createTargetProperties(
+ const uno::Reference< animations::XAnimationNode >& rootNode,
+ bool bInitial );
+
+} // namespace slideshow::internal::TargetPropertiesCreator
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_TARGETPROPERTIESCREATOR_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/userpaintoverlay.cxx b/slideshow/source/engine/slide/userpaintoverlay.cxx
new file mode 100644
index 000000000..d635fc60a
--- /dev/null
+++ b/slideshow/source/engine/slide/userpaintoverlay.cxx
@@ -0,0 +1,485 @@
+/* -*- 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 <com/sun/star/awt/MouseButton.hpp>
+#include <com/sun/star/awt/MouseEvent.hpp>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <cppcanvas/basegfxfactory.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <slideshowcontext.hxx>
+#include "userpaintoverlay.hxx"
+#include <mouseeventhandler.hxx>
+#include <eventmultiplexer.hxx>
+#include <screenupdater.hxx>
+#include <vieweventhandler.hxx>
+
+#include <slide.hxx>
+#include <cursormanager.hxx>
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+ class PaintOverlayHandler : public MouseEventHandler,
+ public ViewEventHandler,
+ public UserPaintEventHandler
+ {
+ public:
+ PaintOverlayHandler( const RGBColor& rStrokeColor,
+ double nStrokeWidth,
+ ScreenUpdater& rScreenUpdater,
+ const UnoViewContainer& rViews,
+ Slide& rSlide,
+ PolyPolygonVector&& rPolygons,
+ bool bActive ) :
+ mrScreenUpdater( rScreenUpdater ),
+ maViews(),
+ maPolygons( std::move(rPolygons) ),
+ maStrokeColor( rStrokeColor ),
+ mnStrokeWidth( nStrokeWidth ),
+ maLastPoint(),
+ maLastMouseDownPos(),
+ mbIsLastPointValid( false ),
+ mbIsLastMouseDownPosValid( false ),
+ //handle the "remove all ink from slide" mode of erasing
+ mbIsEraseAllModeActivated( false ),
+ //handle the "remove stroke by stroke" mode of erasing
+ mbIsEraseModeActivated( false ),
+ mrSlide(rSlide),
+ mnSize(100),
+ mbActive( bActive )
+ {
+ for( const auto& rView : rViews )
+ viewAdded( rView );
+
+ drawPolygons();
+ }
+
+ void dispose()
+ {
+ maViews.clear();
+ }
+
+ // ViewEventHandler methods
+ virtual void viewAdded( const UnoViewSharedPtr& rView ) override
+ {
+ maViews.push_back( rView );
+ }
+
+ virtual void viewRemoved( const UnoViewSharedPtr& rView ) override
+ {
+ maViews.erase( ::std::remove( maViews.begin(),
+ maViews.end(),
+ rView ) );
+ }
+
+ virtual void viewChanged( const UnoViewSharedPtr& /*rView*/ ) override
+ {
+ // TODO(F2): for persistent drawings, need to store
+ // polygon and repaint here.
+ }
+
+ virtual void viewsChanged() override
+ {
+ // TODO(F2): for persistent drawings, need to store
+ // polygon and repaint here.
+ }
+
+ bool colorChanged( RGBColor const& rUserColor ) override
+ {
+ mbIsLastPointValid = false;
+ mbActive = true;
+ maStrokeColor = rUserColor;
+ mbIsEraseModeActivated = false;
+ return true;
+ }
+
+ bool widthChanged( double nUserStrokeWidth ) override
+ {
+ mnStrokeWidth = nUserStrokeWidth;
+ mbIsEraseModeActivated = false;
+ return true;
+ }
+
+ void repaintWithoutPolygons()
+ {
+ // must get access to the instance to erase all polygon
+ for( const auto& rxView : maViews )
+ {
+ // fully clear view content to background color
+ //rxView->getCanvas()->clear();
+
+ //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
+ SlideBitmapSharedPtr pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) );
+ ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() );
+
+ const ::basegfx::B2DHomMatrix aViewTransform( rxView->getTransformation() );
+ const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
+
+ // setup a canvas with device coordinate space, the slide
+ // bitmap already has the correct dimension.
+ ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
+
+ pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
+
+ // render at given output position
+ pBitmap->move( aOutPosPixel );
+
+ // clear clip (might have been changed, e.g. from comb
+ // transition)
+ pBitmap->clip( ::basegfx::B2DPolyPolygon() );
+ pBitmap->draw( pDevicePixelCanvas );
+
+ mrScreenUpdater.notifyUpdate(rxView,true);
+ }
+ }
+
+ bool eraseAllInkChanged( bool bEraseAllInk ) override
+ {
+ mbIsEraseAllModeActivated = bEraseAllInk;
+ // if the erase all mode is activated it will remove all ink from slide,
+ // therefore destroy all the polygons stored
+ if(mbIsEraseAllModeActivated)
+ {
+ // The Erase Mode should be deactivated
+ mbIsEraseModeActivated = false;
+ repaintWithoutPolygons();
+ maPolygons.clear();
+ }
+ mbIsEraseAllModeActivated=false;
+ return true;
+ }
+
+ bool eraseInkWidthChanged( sal_Int32 rEraseInkSize ) override
+ {
+ // Change the size
+ mnSize=rEraseInkSize;
+ // Changed to mode Erase
+ mbIsEraseModeActivated = true;
+ return true;
+ }
+
+ bool switchPenMode() override
+ {
+ mbIsLastPointValid = false;
+ mbActive = true;
+ mbIsEraseModeActivated = false;
+ return true;
+ }
+
+ bool switchEraserMode() override
+ {
+ mbIsLastPointValid = false;
+ mbActive = true;
+ mbIsEraseModeActivated = true;
+ return true;
+ }
+
+ bool disable() override
+ {
+ mbIsLastPointValid = false;
+ mbIsLastMouseDownPosValid = false;
+ mbActive = false;
+ return true;
+ }
+
+ //Draw all registered polygons.
+ void drawPolygons()
+ {
+ for( const auto& rxPolygon : maPolygons )
+ {
+ rxPolygon->draw();
+ }
+ // screen update necessary to show painting
+ mrScreenUpdater.notifyUpdate();
+ }
+
+ //Retrieve all registered polygons.
+ const PolyPolygonVector& getPolygons() const
+ {
+ return maPolygons;
+ }
+
+ // MouseEventHandler methods
+ virtual bool handleMousePressed( const awt::MouseEvent& e ) override
+ {
+ if( !mbActive )
+ return false;
+
+ if (e.Buttons == awt::MouseButton::RIGHT)
+ {
+ mbIsLastPointValid = false;
+ return false;
+ }
+
+ if (e.Buttons != awt::MouseButton::LEFT)
+ return false;
+
+ maLastMouseDownPos.setX( e.X );
+ maLastMouseDownPos.setY( e.Y );
+ mbIsLastMouseDownPosValid = true;
+
+ // eat mouse click (though we don't process it
+ // _directly_, it enables the drag mode
+ return true;
+ }
+
+ virtual bool handleMouseReleased( const awt::MouseEvent& e ) override
+ {
+ if( !mbActive )
+ return false;
+
+ if (e.Buttons == awt::MouseButton::RIGHT)
+ {
+ mbIsLastPointValid = false;
+ return false;
+ }
+
+ if (e.Buttons != awt::MouseButton::LEFT)
+ return false;
+
+ // check, whether up- and down press are on exactly
+ // the same pixel. If that's the case, ignore the
+ // click, and pass on the event to low-prio
+ // handlers. This effectively permits effect
+ // advancements via clicks also when user paint is
+ // enabled.
+ if( mbIsLastMouseDownPosValid &&
+ ::basegfx::B2DPoint( e.X,
+ e.Y ) == maLastMouseDownPos )
+ {
+ mbIsLastMouseDownPosValid = false;
+ return false;
+ }
+
+ // invalidate, next downpress will have to start a new
+ // polygon.
+ mbIsLastPointValid = false;
+
+ // eat mouse click (though we don't process it
+ // _directly_, it enables the drag mode
+ return true;
+ }
+
+ virtual bool handleMouseDragged( const awt::MouseEvent& e ) override
+ {
+ if( !mbActive )
+ return false;
+
+ if (e.Buttons == awt::MouseButton::RIGHT)
+ {
+ mbIsLastPointValid = false;
+ return false;
+ }
+
+ if(mbIsEraseModeActivated)
+ {
+ //define the last point as an object
+ //we suppose that there's no way this point could be valid
+ ::basegfx::B2DPolygon aPoly;
+
+ maLastPoint.setX( e.X-mnSize );
+ maLastPoint.setY( e.Y-mnSize );
+
+ aPoly.append( maLastPoint );
+
+ maLastPoint.setX( e.X-mnSize );
+ maLastPoint.setY( e.Y+mnSize );
+
+ aPoly.append( maLastPoint );
+ maLastPoint.setX( e.X+mnSize );
+ maLastPoint.setY( e.Y+mnSize );
+
+ aPoly.append( maLastPoint );
+ maLastPoint.setX( e.X+mnSize );
+ maLastPoint.setY( e.Y-mnSize );
+
+ aPoly.append( maLastPoint );
+ maLastPoint.setX( e.X-mnSize );
+ maLastPoint.setY( e.Y-mnSize );
+
+ aPoly.append( maLastPoint );
+
+ //now we have defined a Polygon that is closed
+
+ //The point is to redraw the LastPoint the way it was originally on the bitmap,
+ //of the slide
+ for (const auto& rxView : maViews)
+ {
+
+ //get via SlideImpl instance the bitmap of the slide unmodified to redraw it
+ SlideBitmapSharedPtr pBitmap( mrSlide.getCurrentSlideBitmap( rxView ) );
+ ::cppcanvas::CanvasSharedPtr pCanvas( rxView->getCanvas() );
+
+ ::basegfx::B2DHomMatrix aViewTransform( rxView->getTransformation() );
+ const ::basegfx::B2DPoint aOutPosPixel( aViewTransform * ::basegfx::B2DPoint() );
+
+ // setup a canvas with device coordinate space, the slide
+ // bitmap already has the correct dimension.
+ ::cppcanvas::CanvasSharedPtr pDevicePixelCanvas( pCanvas->clone() );
+
+ pDevicePixelCanvas->setTransformation( ::basegfx::B2DHomMatrix() );
+
+ // render at given output position
+ pBitmap->move( aOutPosPixel );
+
+ ::basegfx::B2DPolyPolygon aPolyPoly(aPoly);
+ aViewTransform.translate(-aOutPosPixel.getX(), -aOutPosPixel.getY());
+ aPolyPoly.transform(aViewTransform);
+ // set clip so that we just redraw a part of the canvas
+ pBitmap->clip(aPolyPoly);
+ pBitmap->draw( pDevicePixelCanvas );
+
+ mrScreenUpdater.notifyUpdate(rxView,true);
+ }
+
+ }
+ else
+ {
+ if( !mbIsLastPointValid )
+ {
+ mbIsLastPointValid = true;
+ maLastPoint.setX( e.X );
+ maLastPoint.setY( e.Y );
+ }
+ else
+ {
+ ::basegfx::B2DPolygon aPoly;
+ aPoly.append( maLastPoint );
+
+ maLastPoint.setX( e.X );
+ maLastPoint.setY( e.Y );
+
+ aPoly.append( maLastPoint );
+
+ // paint to all views
+ for (const auto& rxView : maViews)
+ {
+ ::cppcanvas::PolyPolygonSharedPtr pPolyPoly(
+ ::cppcanvas::BaseGfxFactory::createPolyPolygon( rxView->getCanvas(),
+ aPoly ) );
+
+ if( pPolyPoly )
+ {
+ pPolyPoly->setStrokeWidth(mnStrokeWidth);
+ pPolyPoly->setRGBALineColor( maStrokeColor.getIntegerColor() );
+ pPolyPoly->draw();
+ maPolygons.push_back(pPolyPoly);
+ }
+ }
+
+ // screen update necessary to show painting
+ mrScreenUpdater.notifyUpdate();
+ }
+ }
+ // mouse events captured
+ return true;
+ }
+
+ virtual bool handleMouseMoved( const awt::MouseEvent& /*e*/ ) override
+ {
+ // not used here
+ return false; // did not handle the event
+ }
+
+ private:
+ ScreenUpdater& mrScreenUpdater;
+ UnoViewVector maViews;
+ PolyPolygonVector maPolygons;
+ RGBColor maStrokeColor;
+ double mnStrokeWidth;
+ basegfx::B2DPoint maLastPoint;
+ basegfx::B2DPoint maLastMouseDownPos;
+ bool mbIsLastPointValid;
+ bool mbIsLastMouseDownPosValid;
+ // added bool for erasing purpose :
+ bool mbIsEraseAllModeActivated;
+ bool mbIsEraseModeActivated;
+ Slide& mrSlide;
+ sal_Int32 mnSize;
+ bool mbActive;
+ };
+
+ UserPaintOverlaySharedPtr UserPaintOverlay::create( const RGBColor& rStrokeColor,
+ double nStrokeWidth,
+ const SlideShowContext& rContext,
+ PolyPolygonVector&& rPolygons,
+ bool bActive )
+ {
+ UserPaintOverlaySharedPtr pRet( new UserPaintOverlay( rStrokeColor,
+ nStrokeWidth,
+ rContext,
+ std::move(rPolygons),
+ bActive));
+
+ return pRet;
+ }
+
+ UserPaintOverlay::UserPaintOverlay( const RGBColor& rStrokeColor,
+ double nStrokeWidth,
+ const SlideShowContext& rContext,
+ PolyPolygonVector&& rPolygons,
+ bool bActive ) :
+ mpHandler( std::make_shared<PaintOverlayHandler>( rStrokeColor,
+ nStrokeWidth,
+ rContext.mrScreenUpdater,
+ rContext.mrViewContainer,
+ //adding a link to Slide
+ dynamic_cast<Slide&>(rContext.mrCursorManager),
+ std::move(rPolygons), bActive )),
+ mrMultiplexer( rContext.mrEventMultiplexer )
+ {
+ mrMultiplexer.addClickHandler( mpHandler, 3.0 );
+ mrMultiplexer.addMouseMoveHandler( mpHandler, 3.0 );
+ mrMultiplexer.addViewHandler( mpHandler );
+ mrMultiplexer.addUserPaintHandler(mpHandler);
+ }
+
+ PolyPolygonVector const & UserPaintOverlay::getPolygons() const
+ {
+ return mpHandler->getPolygons();
+ }
+
+ void UserPaintOverlay::drawPolygons()
+ {
+ mpHandler->drawPolygons();
+ }
+
+ UserPaintOverlay::~UserPaintOverlay()
+ {
+ try
+ {
+ mrMultiplexer.removeMouseMoveHandler( mpHandler );
+ mrMultiplexer.removeClickHandler( mpHandler );
+ mrMultiplexer.removeViewHandler( mpHandler );
+ mpHandler->dispose();
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION("slideshow", "");
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slide/userpaintoverlay.hxx b/slideshow/source/engine/slide/userpaintoverlay.hxx
new file mode 100644
index 000000000..ae443a0fa
--- /dev/null
+++ b/slideshow/source/engine/slide/userpaintoverlay.hxx
@@ -0,0 +1,83 @@
+/* -*- 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 .
+ */
+
+#ifndef INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_USERPAINTOVERLAY_HXX
+#define INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_USERPAINTOVERLAY_HXX
+
+#include <cppcanvas/canvasgraphic.hxx>
+
+#include <rgbcolor.hxx>
+
+#include <memory>
+#include <vector>
+
+/* Definition of UserPaintOverlay class */
+
+namespace slideshow::internal
+ {
+ class EventMultiplexer;
+ struct SlideShowContext;
+
+ class PaintOverlayHandler;
+ typedef ::std::shared_ptr< class UserPaintOverlay > UserPaintOverlaySharedPtr;
+ typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector;
+ /** Slide overlay, which can be painted into by the user.
+
+ This class registers itself at the EventMultiplexer,
+ listening for mouse clicks and moves. When the mouse is
+ dragged, a hand sketching in the selected color is shown.
+ */
+ class UserPaintOverlay
+ {
+ public:
+ /** Create a UserPaintOverlay
+
+ @param rStrokeColor
+ Color to use for drawing
+
+ @param nStrokeWidth
+ Width of the stroked path
+ */
+ static UserPaintOverlaySharedPtr create( const RGBColor& rStrokeColor,
+ double nStrokeWidth,
+ const SlideShowContext& rContext,
+ PolyPolygonVector&& rPolygons,
+ bool bActive);
+ ~UserPaintOverlay();
+ UserPaintOverlay(const UserPaintOverlay&) = delete;
+ UserPaintOverlay& operator=(const UserPaintOverlay&) = delete;
+ PolyPolygonVector const & getPolygons() const;
+ void drawPolygons();
+
+ private:
+ UserPaintOverlay( const RGBColor& rStrokeColor,
+ double nStrokeWidth,
+ const SlideShowContext& rContext,
+ PolyPolygonVector&& rPolygons,
+ bool bActive );
+
+ ::std::shared_ptr<PaintOverlayHandler> mpHandler;
+ EventMultiplexer& mrMultiplexer;
+ };
+
+}
+
+#endif // INCLUDED_SLIDESHOW_SOURCE_ENGINE_SLIDE_USERPAINTOVERLAY_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slidebitmap.cxx b/slideshow/source/engine/slidebitmap.cxx
new file mode 100644
index 000000000..368cb070c
--- /dev/null
+++ b/slideshow/source/engine/slidebitmap.cxx
@@ -0,0 +1,112 @@
+/* -*- 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 <slidebitmap.hxx>
+
+#include <com/sun/star/rendering/XCanvas.hpp>
+#include <com/sun/star/rendering/XBitmap.hpp>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+
+#include <canvas/canvastools.hxx>
+#include <basegfx/utils/canvastools.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+
+ SlideBitmap::SlideBitmap( const ::cppcanvas::BitmapSharedPtr& rBitmap ) :
+ maOutputPos(),
+ maClipPoly(),
+ mxBitmap()
+ {
+ if( rBitmap )
+ mxBitmap = rBitmap->getUNOBitmap();
+
+ ENSURE_OR_THROW( mxBitmap.is(), "SlideBitmap::SlideBitmap(): Invalid bitmap" );
+ }
+
+ bool SlideBitmap::draw( const ::cppcanvas::CanvasSharedPtr& rCanvas ) const
+ {
+ ENSURE_OR_RETURN_FALSE( rCanvas && rCanvas->getUNOCanvas().is(),
+ "SlideBitmap::draw(): Invalid canvas" );
+
+ // selectively only copy the transformation from current viewstate,
+ // don't want no clipping here.
+ rendering::ViewState aViewState;
+ aViewState.AffineTransform = rCanvas->getViewState().AffineTransform;
+
+ rendering::RenderState aRenderState;
+ ::canvas::tools::initRenderState( aRenderState );
+
+ const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix(maOutputPos));
+ ::canvas::tools::setRenderStateTransform( aRenderState, aTranslation );
+
+ try
+ {
+ if( maClipPoly.count() )
+ {
+ // TODO(P1): Buffer the clip polygon
+ aRenderState.Clip =
+ ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon(
+ rCanvas->getUNOCanvas()->getDevice(),
+ maClipPoly );
+ }
+
+ rCanvas->getUNOCanvas()->drawBitmap( mxBitmap,
+ aViewState,
+ aRenderState );
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ return false;
+ }
+
+ return true;
+ }
+
+ ::basegfx::B2ISize SlideBitmap::getSize() const
+ {
+ return ::basegfx::unotools::b2ISizeFromIntegerSize2D( mxBitmap->getSize() );
+ }
+
+ void SlideBitmap::move( const ::basegfx::B2DPoint& rNewPos )
+ {
+ maOutputPos = rNewPos;
+ }
+
+ void SlideBitmap::clip( const ::basegfx::B2DPolyPolygon& rClipPoly )
+ {
+ maClipPoly = rClipPoly;
+ }
+
+ const css::uno::Reference< css::rendering::XBitmap >& SlideBitmap::getXBitmap() const
+ {
+ return mxBitmap;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slideshowcontext.cxx b/slideshow/source/engine/slideshowcontext.cxx
new file mode 100644
index 000000000..f0433b9d8
--- /dev/null
+++ b/slideshow/source/engine/slideshowcontext.cxx
@@ -0,0 +1,70 @@
+/* -*- 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 <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <slideshowcontext.hxx>
+#include <subsettableshapemanager.hxx>
+#include <screenupdater.hxx>
+#include <eventqueue.hxx>
+#include <activitiesqueue.hxx>
+#include <usereventqueue.hxx>
+#include <eventmultiplexer.hxx>
+#include <unoviewcontainer.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace slideshow::internal
+{
+
+SlideShowContext::SlideShowContext( SubsettableShapeManagerSharedPtr& rSubsettableShapeManager,
+ EventQueue& rEventQueue,
+ EventMultiplexer& rEventMultiplexer,
+ ScreenUpdater& rScreenUpdater,
+ ActivitiesQueue& rActivitiesQueue,
+ UserEventQueue& rUserEventQueue,
+ CursorManager& rCursorManager,
+ MediaFileManager& rMediaFileManager,
+ const UnoViewContainer& rViewContainer,
+ const uno::Reference<
+ uno::XComponentContext>& rComponentContext,
+ box2d::utils::Box2DWorldSharedPtr& rBox2DWorldPtr ) :
+ mpSubsettableShapeManager( rSubsettableShapeManager ),
+ mrEventQueue( rEventQueue ),
+ mrEventMultiplexer( rEventMultiplexer ),
+ mrScreenUpdater( rScreenUpdater ),
+ mrActivitiesQueue( rActivitiesQueue ),
+ mrUserEventQueue( rUserEventQueue ),
+ mrCursorManager( rCursorManager ),
+ mrMediaFileManager( rMediaFileManager ),
+ mrViewContainer( rViewContainer ),
+ mxComponentContext( rComponentContext ),
+ mpBox2DWorld( rBox2DWorldPtr )
+ {}
+
+void SlideShowContext::dispose()
+{
+ mxComponentContext.clear();
+}
+
+} // namespace slideshow::internal
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/slideshow/source/engine/slideshowimpl.cxx b/slideshow/source/engine/slideshowimpl.cxx
new file mode 100644
index 000000000..75b15e5c7
--- /dev/null
+++ b/slideshow/source/engine/slideshowimpl.cxx
@@ -0,0 +1,2439 @@
+/* -*- 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 <config_features.h>
+#include <tools/diagnose_ex.h>
+
+#include <cppuhelper/basemutex.hxx>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/interfacecontainer.h>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <comphelper/interfacecontainer3.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <comphelper/storagehelper.hxx>
+#include <cppcanvas/polypolygon.hxx>
+#include <osl/thread.hxx>
+
+#include <tools/debug.hxx>
+
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/utils/canvastools.hxx>
+
+#include <sal/log.hxx>
+
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/util/XUpdatable.hpp>
+#include <com/sun/star/awt/SystemPointer.hpp>
+#include <com/sun/star/presentation/XSlideShow.hpp>
+#include <com/sun/star/presentation/XSlideShowListener.hpp>
+#include <com/sun/star/lang/NoSupportException.hpp>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <com/sun/star/drawing/PointSequenceSequence.hpp>
+#include <com/sun/star/drawing/PointSequence.hpp>
+#include <com/sun/star/drawing/XLayer.hpp>
+#include <com/sun/star/drawing/XLayerSupplier.hpp>
+#include <com/sun/star/drawing/XLayerManager.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/document/XStorageBasedDocument.hpp>
+
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/loader/CannotActivateFactoryException.hpp>
+
+#include <unoviewcontainer.hxx>
+#include <transitionfactory.hxx>
+#include <eventmultiplexer.hxx>
+#include <usereventqueue.hxx>
+#include <eventqueue.hxx>
+#include <cursormanager.hxx>
+#include <mediafilemanager.hxx>
+#include <slideshowcontext.hxx>
+#include <activitiesqueue.hxx>
+#include <activitiesfactory.hxx>
+#include <interruptabledelayevent.hxx>
+#include <slide.hxx>
+#include <shapemaps.hxx>
+#include <slideview.hxx>
+#include <tools.hxx>
+#include <unoview.hxx>
+#include "rehearsetimingsactivity.hxx"
+#include "waitsymbol.hxx"
+#include "effectrewinder.hxx"
+#include <framerate.hxx>
+#include "pointersymbol.hxx"
+
+#include <map>
+#include <vector>
+#include <algorithm>
+
+using namespace com::sun::star;
+using namespace ::slideshow::internal;
+
+namespace box2d::utils { class box2DWorld;
+ typedef ::std::shared_ptr< box2DWorld > Box2DWorldSharedPtr; }
+
+namespace {
+
+/** During animations the update() method tells its caller to call it as
+ soon as possible. This gives us more time to render the next frame and
+ still maintain a steady frame rate. This class is responsible for
+ synchronizing the display of new frames and thus keeping the frame rate
+ steady.
+*/
+class FrameSynchronization
+{
+public:
+ /** Create new object with a predefined duration between two frames.
+ @param nFrameDuration
+ The preferred duration between the display of two frames in
+ seconds.
+ */
+ explicit FrameSynchronization (const double nFrameDuration);
+
+ /** Set the current time as the time at which the current frame is
+ displayed. From this the target time of the next frame is derived.
+ */
+ void MarkCurrentFrame();
+
+ /** When there is time left until the next frame is due then wait.
+ Otherwise return without delay.
+ */
+ void Synchronize();
+
+ /** Activate frame synchronization when an animation is active and
+ frames are to be displayed in a steady rate. While active
+ Synchronize() will wait until the frame duration time has passed.
+ */
+ void Activate();
+
+ /** Deactivate frame synchronization when no animation is active and the
+ time between frames depends on user actions and other external
+ sources. While deactivated Synchronize() will return without delay.
+ */
+ void Deactivate();
+
+private:
+ /** The timer that is used for synchronization is independent from the
+ one used by SlideShowImpl: it is not paused or modified by
+ animations.
+ */
+ canvas::tools::ElapsedTime maTimer;
+ /** Time between the display of frames. Enforced only when mbIsActive
+ is <TRUE/>.
+ */
+ const double mnFrameDuration;
+ /** Time (of maTimer) when the next frame shall be displayed.
+ Synchronize() will wait until this time.
+ */
+ double mnNextFrameTargetTime;
+ /** Synchronize() will wait only when this flag is <TRUE/>. Otherwise
+ it returns immediately.
+ */
+ bool mbIsActive;
+};
+
+/******************************************************************************
+
+ SlideShowImpl
+
+ This class encapsulates the slideshow presentation viewer.
+
+ With an instance of this class, it is possible to statically
+ and dynamically show a presentation, as defined by the
+ constructor-provided draw model (represented by a sequence
+ of css::drawing::XDrawPage objects).
+
+ It is possible to show the presentation on multiple views
+ simultaneously (e.g. for a multi-monitor setup). Since this
+ class also relies on user interaction, the corresponding
+ XSlideShowView interface provides means to register some UI
+ event listeners (mostly borrowed from awt::XWindow interface).
+
+ Since currently (mid 2004), OOo isn't very well suited to
+ multi-threaded rendering, this class relies on <em>very
+ frequent</em> external update() calls, which will render the
+ next frame of animations. This works as follows: after the
+ displaySlide() has been successfully called (which setup and
+ starts an actual slide show), the update() method must be
+ called until it returns false.
+ Effectively, this puts the burden of providing
+ concurrency to the clients of this class, which, as noted
+ above, is currently unavoidable with the current state of
+ affairs (I've actually tried threading here, but failed
+ miserably when using the VCL canvas as the render backend -
+ deadlocked).
+
+ ******************************************************************************/
+
+typedef cppu::WeakComponentImplHelper<css::lang::XServiceInfo, presentation::XSlideShow> SlideShowImplBase;
+
+typedef ::std::vector< ::cppcanvas::PolyPolygonSharedPtr> PolyPolygonVector;
+
+/// Maps XDrawPage for annotations persistence
+typedef ::std::map< css::uno::Reference<
+ css::drawing::XDrawPage>,
+ PolyPolygonVector> PolygonMap;
+
+class SlideShowImpl : private cppu::BaseMutex,
+ public CursorManager,
+ public MediaFileManager,
+ public SlideShowImplBase
+{
+public:
+ explicit SlideShowImpl(
+ uno::Reference<uno::XComponentContext> const& xContext );
+
+ /** Notify that the transition phase of the current slide
+ has ended.
+
+ The life of a slide has three phases: the transition
+ phase, when the previous slide vanishes, and the
+ current slide becomes visible, the shape animation
+ phase, when shape effects are running, and the phase
+ after the last shape animation has ended, but before
+ the next slide transition starts.
+
+ This method notifies the end of the first phase.
+
+ @param bPaintSlide
+ When true, Slide::show() is passed a true as well, denoting
+ explicit paint of slide content. Pass false here, if e.g. a
+ slide transition has already rendered the initial slide image.
+ */
+ void notifySlideTransitionEnded( bool bPaintSlide );
+
+ /** Notify that the shape animation phase of the current slide
+ has ended.
+
+ The life of a slide has three phases: the transition
+ phase, when the previous slide vanishes, and the
+ current slide becomes visible, the shape animation
+ phase, when shape effects are running, and the phase
+ after the last shape animation has ended, but before
+ the next slide transition starts.
+
+ This method notifies the end of the second phase.
+ */
+ void notifySlideAnimationsEnded();
+
+ /** Notify that the slide has ended.
+
+ The life of a slide has three phases: the transition
+ phase, when the previous slide vanishes, and the
+ current slide becomes visible, the shape animation
+ phase, when shape effects are running, and the phase
+ after the last shape animation has ended, but before
+ the next slide transition starts.
+
+ This method notifies the end of the third phase.
+ */
+ void notifySlideEnded (const bool bReverse);
+
+ /** Notification from eventmultiplexer that a hyperlink
+ has been clicked.
+ */
+ bool notifyHyperLinkClicked( OUString const& hyperLink );
+
+ /** Notification from eventmultiplexer that an animation event has occurred.
+ This will be forwarded to all registered XSlideShowListener
+ */
+ bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode );
+
+ /** Obtain a MediaTempFile for the specified url. */
+ virtual std::shared_ptr<avmedia::MediaTempFile> getMediaTempFile(const OUString& aUrl) override;
+
+private:
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override;
+
+ // XSlideShow:
+ virtual sal_Bool SAL_CALL nextEffect() override;
+ virtual sal_Bool SAL_CALL previousEffect() override;
+ virtual sal_Bool SAL_CALL startShapeActivity(
+ uno::Reference<drawing::XShape> const& xShape ) override;
+ virtual sal_Bool SAL_CALL stopShapeActivity(
+ uno::Reference<drawing::XShape> const& xShape ) override;
+ virtual sal_Bool SAL_CALL pause( sal_Bool bPauseShow ) override;
+ virtual uno::Reference<drawing::XDrawPage> SAL_CALL getCurrentSlide() override;
+ virtual void SAL_CALL displaySlide(
+ uno::Reference<drawing::XDrawPage> const& xSlide,
+ uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages,
+ uno::Reference<animations::XAnimationNode> const& xRootNode,
+ uno::Sequence<beans::PropertyValue> const& rProperties ) override;
+ virtual void SAL_CALL registerUserPaintPolygons( const css::uno::Reference< css::lang::XMultiServiceFactory >& xDocFactory ) override;
+ virtual sal_Bool SAL_CALL setProperty(
+ beans::PropertyValue const& rProperty ) override;
+ virtual sal_Bool SAL_CALL addView(
+ uno::Reference<presentation::XSlideShowView> const& xView ) override;
+ virtual sal_Bool SAL_CALL removeView(
+ uno::Reference<presentation::XSlideShowView> const& xView ) override;
+ virtual sal_Bool SAL_CALL update( double & nNextTimeout ) override;
+ virtual void SAL_CALL addSlideShowListener(
+ uno::Reference<presentation::XSlideShowListener> const& xListener ) override;
+ virtual void SAL_CALL removeSlideShowListener(
+ uno::Reference<presentation::XSlideShowListener> const& xListener ) override;
+ virtual void SAL_CALL addShapeEventListener(
+ uno::Reference<presentation::XShapeEventListener> const& xListener,
+ uno::Reference<drawing::XShape> const& xShape ) override;
+ virtual void SAL_CALL removeShapeEventListener(
+ uno::Reference<presentation::XShapeEventListener> const& xListener,
+ uno::Reference<drawing::XShape> const& xShape ) override;
+ virtual void SAL_CALL setShapeCursor(
+ uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape ) override;
+
+ // CursorManager
+
+
+ virtual bool requestCursor( sal_Int16 nCursorShape ) override;
+ virtual void resetCursor() override;
+
+ /** This is somewhat similar to displaySlide when called for the current
+ slide. It has been simplified to take advantage of that no slide
+ change takes place. Furthermore it does not show the slide
+ transition.
+ */
+ void redisplayCurrentSlide();
+
+protected:
+ // WeakComponentImplHelperBase
+ virtual void SAL_CALL disposing() override;
+
+ bool isDisposed() const
+ {
+ return (rBHelper.bDisposed || rBHelper.bInDispose);
+ }
+
+private:
+ struct SeparateListenerImpl; friend struct SeparateListenerImpl;
+ class PrefetchPropertiesFunc; friend class PrefetchPropertiesFunc;
+
+ /// Stop currently running show.
+ void stopShow();
+
+ ///Find a polygons vector in maPolygons (map)
+ PolygonMap::iterator findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage);
+
+ /// Creates a new slide.
+ SlideSharedPtr makeSlide(
+ uno::Reference<drawing::XDrawPage> const& xDrawPage,
+ uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages,
+ uno::Reference<animations::XAnimationNode> const& xRootNode );
+
+ /// Checks whether the given slide/animation node matches mpPrefetchSlide
+ static bool matches(
+ SlideSharedPtr const& pSlide,
+ uno::Reference<drawing::XDrawPage> const& xSlide,
+ uno::Reference<animations::XAnimationNode> const& xNode )
+ {
+ if (pSlide)
+ return (pSlide->getXDrawPage() == xSlide &&
+ pSlide->getXAnimationNode() == xNode);
+ else
+ return (!xSlide.is() && !xNode.is());
+ }
+
+ /// Resets the current slide transition sound object with a new one:
+ SoundPlayerSharedPtr resetSlideTransitionSound(
+ uno::Any const& url, bool bLoopSound );
+
+ /// stops the current slide transition sound
+ void stopSlideTransitionSound();
+
+ /** Prepare a slide transition
+
+ This method registers all necessary events and
+ activities for a slide transition.
+
+ @return the slide change activity, or NULL for no transition effect
+ */
+ ActivitySharedPtr createSlideTransition(
+ const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const SlideSharedPtr& rLeavingSlide,
+ const SlideSharedPtr& rEnteringSlide,
+ const EventSharedPtr& rTransitionEndEvent );
+
+ /** Request/release the wait symbol. The wait symbol is displayed when
+ there are more requests then releases. Locking the wait symbol
+ helps to avoid intermediate repaints.
+
+ Do not call this method directly. Use WaitSymbolLock instead.
+ */
+ void requestWaitSymbol();
+ void releaseWaitSymbol();
+
+ class WaitSymbolLock {public:
+ explicit WaitSymbolLock(SlideShowImpl& rSlideShowImpl) : mrSlideShowImpl(rSlideShowImpl)
+ { mrSlideShowImpl.requestWaitSymbol(); }
+ ~WaitSymbolLock()
+ { mrSlideShowImpl.releaseWaitSymbol(); }
+ private: SlideShowImpl& mrSlideShowImpl;
+ };
+
+ /// Filter requested cursor shape against hard slideshow cursors (wait, etc.)
+ sal_Int16 calcActiveCursor( sal_Int16 nCursorShape ) const;
+
+ /** This method is called asynchronously to finish the rewinding of an
+ effect to the previous slide that was initiated earlier.
+ */
+ void rewindEffectToPreviousSlide();
+
+ /// all registered views
+ UnoViewContainer maViewContainer;
+
+ /// all registered slide show listeners
+ comphelper::OInterfaceContainerHelper3<presentation::XSlideShowListener> maListenerContainer;
+
+ /// map of vectors, containing all registered listeners for a shape
+ ShapeEventListenerMap maShapeEventListeners;
+ /// map of sal_Int16 values, specifying the mouse cursor for every shape
+ ShapeCursorMap maShapeCursors;
+
+ //map of vector of Polygons, containing polygons drawn on each slide.
+ PolygonMap maPolygons;
+
+ std::optional<RGBColor> maUserPaintColor;
+
+ double maUserPaintStrokeWidth;
+
+ //changed for the eraser project
+ std::optional<bool> maEraseAllInk;
+ std::optional<sal_Int32> maEraseInk;
+ //end changed
+
+ std::shared_ptr<canvas::tools::ElapsedTime> mpPresTimer;
+ ScreenUpdater maScreenUpdater;
+ EventQueue maEventQueue;
+ EventMultiplexer maEventMultiplexer;
+ ActivitiesQueue maActivitiesQueue;
+ UserEventQueue maUserEventQueue;
+ SubsettableShapeManagerSharedPtr mpDummyPtr;
+ box2d::utils::Box2DWorldSharedPtr mpBox2DDummyPtr;
+
+ std::shared_ptr<SeparateListenerImpl> mpListener;
+
+ std::shared_ptr<RehearseTimingsActivity> mpRehearseTimingsActivity;
+ std::shared_ptr<WaitSymbol> mpWaitSymbol;
+
+ std::shared_ptr<PointerSymbol> mpPointerSymbol;
+
+ /// the current slide transition sound object:
+ SoundPlayerSharedPtr mpCurrentSlideTransitionSound;
+
+ uno::Reference<uno::XComponentContext> mxComponentContext;
+ uno::Reference<
+ presentation::XTransitionFactory> mxOptionalTransitionFactory;
+
+ /// the previously running slide
+ SlideSharedPtr mpPreviousSlide;
+ /// the currently running slide
+ SlideSharedPtr mpCurrentSlide;
+ /// the already prefetched slide: best candidate for upcoming slide
+ SlideSharedPtr mpPrefetchSlide;
+ /// slide to be prefetched: best candidate for upcoming slide
+ uno::Reference<drawing::XDrawPage> mxPrefetchSlide;
+ /// save the XDrawPagesSupplier to retrieve polygons
+ uno::Reference<drawing::XDrawPagesSupplier> mxDrawPagesSupplier;
+ /// Used by MediaFileManager, for media files with package url.
+ uno::Reference<document::XStorageBasedDocument> mxSBD;
+ /// slide animation to be prefetched:
+ uno::Reference<animations::XAnimationNode> mxPrefetchAnimationNode;
+
+ sal_Int16 mnCurrentCursor;
+
+ sal_Int32 mnWaitSymbolRequestCount;
+ bool mbAutomaticAdvancementMode;
+ bool mbImageAnimationsAllowed;
+ bool mbNoSlideTransitions;
+ bool mbMouseVisible;
+ bool mbForceManualAdvance;
+ bool mbShowPaused;
+ bool mbSlideShowIdle;
+ bool mbDisableAnimationZOrder;
+
+ EffectRewinder maEffectRewinder;
+ FrameSynchronization maFrameSynchronization;
+};
+
+/** Separate event listener for animation, view and hyperlink events.
+
+ This handler is registered for slide animation end, view and
+ hyperlink events at the global EventMultiplexer, and forwards
+ notifications to the SlideShowImpl
+*/
+struct SlideShowImpl::SeparateListenerImpl : public EventHandler,
+ public ViewRepaintHandler,
+ public HyperlinkHandler,
+ public AnimationEventHandler
+{
+ SlideShowImpl& mrShow;
+ ScreenUpdater& mrScreenUpdater;
+ EventQueue& mrEventQueue;
+
+ SeparateListenerImpl( SlideShowImpl& rShow,
+ ScreenUpdater& rScreenUpdater,
+ EventQueue& rEventQueue ) :
+ mrShow( rShow ),
+ mrScreenUpdater( rScreenUpdater ),
+ mrEventQueue( rEventQueue )
+ {}
+
+ SeparateListenerImpl( const SeparateListenerImpl& ) = delete;
+ SeparateListenerImpl& operator=( const SeparateListenerImpl& ) = delete;
+
+ // EventHandler
+ virtual bool handleEvent() override
+ {
+ // DON't call notifySlideAnimationsEnded()
+ // directly, but queue an event. handleEvent()
+ // might be called from e.g.
+ // showNext(), and notifySlideAnimationsEnded() must not be called
+ // in recursion. Note that the event is scheduled for the next
+ // frame so that its expensive execution does not come in between
+ // sprite hiding and shape redraw (at the end of the animation of a
+ // shape), which would cause a flicker.
+ mrEventQueue.addEventForNextRound(
+ makeEvent( [this] () { this->mrShow.notifySlideAnimationsEnded(); },
+ "SlideShowImpl::notifySlideAnimationsEnded"));
+ return true;
+ }
+
+ // ViewRepaintHandler
+ virtual void viewClobbered( const UnoViewSharedPtr& rView ) override
+ {
+ // given view needs repaint, request update
+ mrScreenUpdater.notifyUpdate(rView, true);
+ }
+
+ // HyperlinkHandler
+ virtual bool handleHyperlink( OUString const& rLink ) override
+ {
+ return mrShow.notifyHyperLinkClicked(rLink);
+ }
+
+ // AnimationEventHandler
+ virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override
+ {
+ return mrShow.handleAnimationEvent(rNode);
+ }
+};
+
+SlideShowImpl::SlideShowImpl(
+ uno::Reference<uno::XComponentContext> const& xContext )
+ : SlideShowImplBase(m_aMutex),
+ maViewContainer(),
+ maListenerContainer( m_aMutex ),
+ maShapeEventListeners(),
+ maShapeCursors(),
+ maUserPaintColor(),
+ maUserPaintStrokeWidth(4.0),
+ mpPresTimer( std::make_shared<canvas::tools::ElapsedTime>() ),
+ maScreenUpdater(maViewContainer),
+ maEventQueue( mpPresTimer ),
+ maEventMultiplexer( maEventQueue,
+ maViewContainer ),
+ maActivitiesQueue( mpPresTimer ),
+ maUserEventQueue( maEventMultiplexer,
+ maEventQueue,
+ *this ),
+ mpDummyPtr(),
+ mpBox2DDummyPtr(),
+ mpListener(),
+ mpRehearseTimingsActivity(),
+ mpWaitSymbol(),
+ mpPointerSymbol(),
+ mpCurrentSlideTransitionSound(),
+ mxComponentContext( xContext ),
+ mxOptionalTransitionFactory(),
+ mpCurrentSlide(),
+ mpPrefetchSlide(),
+ mxPrefetchSlide(),
+ mxDrawPagesSupplier(),
+ mxSBD(),
+ mxPrefetchAnimationNode(),
+ mnCurrentCursor(awt::SystemPointer::ARROW),
+ mnWaitSymbolRequestCount(0),
+ mbAutomaticAdvancementMode(false),
+ mbImageAnimationsAllowed( true ),
+ mbNoSlideTransitions( false ),
+ mbMouseVisible( true ),
+ mbForceManualAdvance( false ),
+ mbShowPaused( false ),
+ mbSlideShowIdle( true ),
+ mbDisableAnimationZOrder( false ),
+ maEffectRewinder(maEventMultiplexer, maEventQueue, maUserEventQueue),
+ maFrameSynchronization(1.0 / FrameRate::PreferredFramesPerSecond)
+
+{
+ // keep care not constructing any UNO references to this inside ctor,
+ // shift that code to create()!
+
+ uno::Reference<lang::XMultiComponentFactory> xFactory(
+ mxComponentContext->getServiceManager() );
+
+ if( xFactory.is() )
+ {
+ try
+ {
+ // #i82460# try to retrieve special transition factory
+ mxOptionalTransitionFactory.set(
+ xFactory->createInstanceWithContext(
+ "com.sun.star.presentation.TransitionFactory",
+ mxComponentContext ),
+ uno::UNO_QUERY );
+ }
+ catch (loader::CannotActivateFactoryException const&)
+ {
+ }
+ }
+
+ mpListener = std::make_shared<SeparateListenerImpl>(
+ *this,
+ maScreenUpdater,
+ maEventQueue );
+ maEventMultiplexer.addSlideAnimationsEndHandler( mpListener );
+ maEventMultiplexer.addViewRepaintHandler( mpListener );
+ maEventMultiplexer.addHyperlinkHandler( mpListener, 0.0 );
+ maEventMultiplexer.addAnimationStartHandler( mpListener );
+ maEventMultiplexer.addAnimationEndHandler( mpListener );
+}
+
+// we are about to be disposed (someone call dispose() on us)
+void SlideShowImpl::disposing()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ maEffectRewinder.dispose();
+
+ // stop slide transition sound, if any:
+ stopSlideTransitionSound();
+
+ mxComponentContext.clear();
+
+ if( mpCurrentSlideTransitionSound )
+ {
+ mpCurrentSlideTransitionSound->dispose();
+ mpCurrentSlideTransitionSound.reset();
+ }
+
+ mpWaitSymbol.reset();
+ mpPointerSymbol.reset();
+
+ if( mpRehearseTimingsActivity )
+ {
+ mpRehearseTimingsActivity->dispose();
+ mpRehearseTimingsActivity.reset();
+ }
+
+ if( mpListener )
+ {
+ maEventMultiplexer.removeSlideAnimationsEndHandler(mpListener);
+ maEventMultiplexer.removeViewRepaintHandler(mpListener);
+ maEventMultiplexer.removeHyperlinkHandler(mpListener);
+ maEventMultiplexer.removeAnimationStartHandler( mpListener );
+ maEventMultiplexer.removeAnimationEndHandler( mpListener );
+
+ mpListener.reset();
+ }
+
+ maUserEventQueue.clear();
+ maActivitiesQueue.clear();
+ maEventMultiplexer.clear();
+ maEventQueue.clear();
+ mpPresTimer.reset();
+ maShapeCursors.clear();
+ maShapeEventListeners.clear();
+
+ // send all listeners a disposing() that we are going down:
+ maListenerContainer.disposeAndClear(
+ lang::EventObject( static_cast<cppu::OWeakObject *>(this) ) );
+
+ maViewContainer.dispose();
+
+ // release slides:
+ mxPrefetchAnimationNode.clear();
+ mxPrefetchSlide.clear();
+ mpPrefetchSlide.reset();
+ mpCurrentSlide.reset();
+ mpPreviousSlide.reset();
+}
+
+uno::Sequence< OUString > SAL_CALL SlideShowImpl::getSupportedServiceNames()
+{
+ return { "com.sun.star.presentation.SlideShow" };
+}
+
+OUString SAL_CALL SlideShowImpl::getImplementationName()
+{
+ return "com.sun.star.comp.presentation.SlideShow";
+}
+
+sal_Bool SAL_CALL SlideShowImpl::supportsService(const OUString& aServiceName)
+{
+ return cppu::supportsService(this, aServiceName);
+}
+
+/// stops the current slide transition sound
+void SlideShowImpl::stopSlideTransitionSound()
+{
+ if (mpCurrentSlideTransitionSound)
+ {
+ mpCurrentSlideTransitionSound->stopPlayback();
+ mpCurrentSlideTransitionSound->dispose();
+ mpCurrentSlideTransitionSound.reset();
+ }
+ }
+
+SoundPlayerSharedPtr SlideShowImpl::resetSlideTransitionSound( const uno::Any& rSound, bool bLoopSound )
+{
+ bool bStopSound = false;
+ OUString url;
+
+ if( !(rSound >>= bStopSound) )
+ bStopSound = false;
+ rSound >>= url;
+
+ if( !bStopSound && url.isEmpty() )
+ return SoundPlayerSharedPtr();
+
+ stopSlideTransitionSound();
+
+ if (!url.isEmpty())
+ {
+ try
+ {
+ mpCurrentSlideTransitionSound = SoundPlayer::create(
+ maEventMultiplexer, url, mxComponentContext, *this);
+ mpCurrentSlideTransitionSound->setPlaybackLoop( bLoopSound );
+ }
+ catch (lang::NoSupportException const&)
+ {
+ // catch possible exceptions from SoundPlayer, since
+ // being not able to playback the sound is not a hard
+ // error here (still, the slide transition should be
+ // shown).
+ }
+ }
+ return mpCurrentSlideTransitionSound;
+}
+
+ActivitySharedPtr SlideShowImpl::createSlideTransition(
+ const uno::Reference< drawing::XDrawPage >& xDrawPage,
+ const SlideSharedPtr& rLeavingSlide,
+ const SlideSharedPtr& rEnteringSlide,
+ const EventSharedPtr& rTransitionEndEvent)
+{
+ ENSURE_OR_THROW( !maViewContainer.empty(),
+ "createSlideTransition(): No views" );
+ ENSURE_OR_THROW( rEnteringSlide,
+ "createSlideTransition(): No entering slide" );
+
+ // return empty transition, if slide transitions
+ // are disabled.
+ if (mbNoSlideTransitions)
+ return ActivitySharedPtr();
+
+ // retrieve slide change parameters from XDrawPage
+ uno::Reference< beans::XPropertySet > xPropSet( xDrawPage,
+ uno::UNO_QUERY );
+
+ if( !xPropSet.is() )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Slide has no PropertySet - assuming no transition" );
+ return ActivitySharedPtr();
+ }
+
+ sal_Int16 nTransitionType(0);
+ if( !getPropertyValue( nTransitionType,
+ xPropSet,
+ "TransitionType") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Could not extract slide transition type from XDrawPage - assuming no transition" );
+ return ActivitySharedPtr();
+ }
+
+ sal_Int16 nTransitionSubType(0);
+ if( !getPropertyValue( nTransitionSubType,
+ xPropSet,
+ "TransitionSubtype") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Could not extract slide transition subtype from XDrawPage - assuming no transition" );
+ return ActivitySharedPtr();
+ }
+
+ bool bTransitionDirection(false);
+ if( !getPropertyValue( bTransitionDirection,
+ xPropSet,
+ "TransitionDirection") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Could not extract slide transition direction from XDrawPage - assuming default direction" );
+ }
+
+ sal_Int32 aUnoColor(0);
+ if( !getPropertyValue( aUnoColor,
+ xPropSet,
+ "TransitionFadeColor") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Could not extract slide transition fade color from XDrawPage - assuming black" );
+ }
+
+ const RGBColor aTransitionFadeColor( unoColor2RGBColor( aUnoColor ));
+
+ uno::Any aSound;
+ bool bLoopSound = false;
+
+ if( !getPropertyValue( aSound, xPropSet, "Sound") )
+ SAL_INFO("slideshow", "createSlideTransition(): Could not determine transition sound effect URL from XDrawPage - using no sound" );
+
+ if( !getPropertyValue( bLoopSound, xPropSet, "LoopSound" ) )
+ SAL_INFO("slideshow", "createSlideTransition(): Could not get slide property 'LoopSound' - using no sound" );
+
+ NumberAnimationSharedPtr pTransition(
+ TransitionFactory::createSlideTransition(
+ rLeavingSlide,
+ rEnteringSlide,
+ maViewContainer,
+ maScreenUpdater,
+ maEventMultiplexer,
+ mxOptionalTransitionFactory,
+ nTransitionType,
+ nTransitionSubType,
+ bTransitionDirection,
+ aTransitionFadeColor,
+ resetSlideTransitionSound( aSound, bLoopSound ) ));
+
+ if( !pTransition )
+ return ActivitySharedPtr(); // no transition effect has been
+ // generated. Normally, that means
+ // that simply no transition is
+ // set on this slide.
+
+ double nTransitionDuration(0.0);
+ if( !getPropertyValue( nTransitionDuration,
+ xPropSet,
+ "TransitionDuration") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "Could not extract slide transition duration from XDrawPage - assuming no transition" );
+ return ActivitySharedPtr();
+ }
+
+ sal_Int32 nMinFrames(5);
+ if( !getPropertyValue( nMinFrames,
+ xPropSet,
+ "MinimalFrameNumber") )
+ {
+ SAL_INFO("slideshow", "createSlideTransition(): "
+ "No minimal number of frames given - assuming 5" );
+ }
+
+ // prefetch slide transition bitmaps, but postpone it after
+ // displaySlide() has finished - sometimes, view size has not yet
+ // reached final size
+ maEventQueue.addEvent(
+ makeEvent( [pTransition] () {
+ pTransition->prefetch(); },
+ "Animation::prefetch"));
+
+ return ActivitySharedPtr(
+ ActivitiesFactory::createSimpleActivity(
+ ActivitiesFactory::CommonParameters(
+ rTransitionEndEvent,
+ maEventQueue,
+ maActivitiesQueue,
+ nTransitionDuration,
+ nMinFrames,
+ false,
+ std::optional<double>(1.0),
+ 0.0,
+ 0.0,
+ ShapeSharedPtr(),
+ basegfx::B2DSize( rEnteringSlide->getSlideSize() ) ),
+ pTransition,
+ true ));
+}
+
+PolygonMap::iterator SlideShowImpl::findPolygons( uno::Reference<drawing::XDrawPage> const& xDrawPage)
+{
+ // TODO(P2): optimize research in the map.
+ return maPolygons.find(xDrawPage);
+}
+
+SlideSharedPtr SlideShowImpl::makeSlide(
+ uno::Reference<drawing::XDrawPage> const& xDrawPage,
+ uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages,
+ uno::Reference<animations::XAnimationNode> const& xRootNode )
+{
+ if( !xDrawPage.is() )
+ return SlideSharedPtr();
+
+ //Retrieve polygons for the current slide
+ PolygonMap::iterator aIter = findPolygons(xDrawPage);
+
+ const SlideSharedPtr pSlide( createSlide(xDrawPage,
+ xDrawPages,
+ xRootNode,
+ maEventQueue,
+ maEventMultiplexer,
+ maScreenUpdater,
+ maActivitiesQueue,
+ maUserEventQueue,
+ *this,
+ *this,
+ maViewContainer,
+ mxComponentContext,
+ maShapeEventListeners,
+ maShapeCursors,
+ (aIter != maPolygons.end()) ? aIter->second : PolyPolygonVector(),
+ maUserPaintColor ? *maUserPaintColor : RGBColor(),
+ maUserPaintStrokeWidth,
+ !!maUserPaintColor,
+ mbImageAnimationsAllowed,
+ mbDisableAnimationZOrder) );
+
+ // prefetch show content (reducing latency for slide
+ // bitmap and effect start later on)
+ pSlide->prefetch();
+
+ return pSlide;
+}
+
+void SlideShowImpl::requestWaitSymbol()
+{
+ ++mnWaitSymbolRequestCount;
+ OSL_ASSERT(mnWaitSymbolRequestCount>0);
+
+ if (mnWaitSymbolRequestCount == 1)
+ {
+ if( !mpWaitSymbol )
+ {
+ // fall back to cursor
+ requestCursor(calcActiveCursor(mnCurrentCursor));
+ }
+ else
+ mpWaitSymbol->show();
+ }
+}
+
+void SlideShowImpl::releaseWaitSymbol()
+{
+ --mnWaitSymbolRequestCount;
+ OSL_ASSERT(mnWaitSymbolRequestCount>=0);
+
+ if (mnWaitSymbolRequestCount == 0)
+ {
+ if( !mpWaitSymbol )
+ {
+ // fall back to cursor
+ requestCursor(calcActiveCursor(mnCurrentCursor));
+ }
+ else
+ mpWaitSymbol->hide();
+ }
+}
+
+sal_Int16 SlideShowImpl::calcActiveCursor( sal_Int16 nCursorShape ) const
+{
+ if( mnWaitSymbolRequestCount>0 && !mpWaitSymbol ) // enforce wait cursor
+ nCursorShape = awt::SystemPointer::WAIT;
+ else if( !mbMouseVisible ) // enforce INVISIBLE
+ nCursorShape = awt::SystemPointer::INVISIBLE;
+ else if( maUserPaintColor &&
+ nCursorShape == awt::SystemPointer::ARROW )
+ nCursorShape = awt::SystemPointer::PEN;
+
+ return nCursorShape;
+}
+
+void SlideShowImpl::stopShow()
+{
+ // Force-end running animation
+ // ===========================
+ if (mpCurrentSlide)
+ {
+ mpCurrentSlide->hide();
+ //Register polygons in the map
+ if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end())
+ maPolygons.erase(mpCurrentSlide->getXDrawPage());
+
+ maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons()));
+ }
+
+ // clear all queues
+ maEventQueue.clear();
+ maActivitiesQueue.clear();
+
+ // Attention: we MUST clear the user event queue here,
+ // this is because the current slide might have registered
+ // shape events (click or enter/leave), which might
+ // otherwise dangle forever in the queue (because of the
+ // shared ptr nature). If someone needs to change this:
+ // somehow unregister those shapes at the user event queue
+ // on notifySlideEnded().
+ maUserEventQueue.clear();
+
+ // re-enable automatic effect advancement
+ // (maEventQueue.clear() above might have killed
+ // maEventMultiplexer's tick events)
+ if (mbAutomaticAdvancementMode)
+ {
+ // toggle automatic mode (enabling just again is
+ // ignored by EventMultiplexer)
+ maEventMultiplexer.setAutomaticMode( false );
+ maEventMultiplexer.setAutomaticMode( true );
+ }
+}
+
+class SlideShowImpl::PrefetchPropertiesFunc
+{
+public:
+ PrefetchPropertiesFunc( SlideShowImpl * that_,
+ bool& rbSkipAllMainSequenceEffects,
+ bool& rbSkipSlideTransition)
+ : mpSlideShowImpl(that_),
+ mrbSkipAllMainSequenceEffects(rbSkipAllMainSequenceEffects),
+ mrbSkipSlideTransition(rbSkipSlideTransition)
+ {}
+
+ void operator()( beans::PropertyValue const& rProperty ) const {
+ if (rProperty.Name == "Prefetch" )
+ {
+ uno::Sequence<uno::Any> seq;
+ if ((rProperty.Value >>= seq) && seq.getLength() == 2)
+ {
+ seq[0] >>= mpSlideShowImpl->mxPrefetchSlide;
+ seq[1] >>= mpSlideShowImpl->mxPrefetchAnimationNode;
+ }
+ }
+ else if ( rProperty.Name == "SkipAllMainSequenceEffects" )
+ {
+ rProperty.Value >>= mrbSkipAllMainSequenceEffects;
+ }
+ else if ( rProperty.Name == "SkipSlideTransition" )
+ {
+ rProperty.Value >>= mrbSkipSlideTransition;
+ }
+ else
+ {
+ SAL_WARN( "slideshow", rProperty.Name );
+ }
+ }
+private:
+ SlideShowImpl *const mpSlideShowImpl;
+ bool& mrbSkipAllMainSequenceEffects;
+ bool& mrbSkipSlideTransition;
+};
+
+void SlideShowImpl::displaySlide(
+ uno::Reference<drawing::XDrawPage> const& xSlide,
+ uno::Reference<drawing::XDrawPagesSupplier> const& xDrawPages,
+ uno::Reference<animations::XAnimationNode> const& xRootNode,
+ uno::Sequence<beans::PropertyValue> const& rProperties )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return;
+
+ maEffectRewinder.setRootAnimationNode(xRootNode);
+ maEffectRewinder.setCurrentSlide(xSlide);
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ mxDrawPagesSupplier = xDrawPages;
+ mxSBD = uno::Reference<document::XStorageBasedDocument>(mxDrawPagesSupplier, uno::UNO_QUERY);
+
+ stopShow(); // MUST call that: results in
+ // maUserEventQueue.clear(). What's more,
+ // stopShow()'s currSlide->hide() call is
+ // now also required, notifySlideEnded()
+ // relies on that
+ // unconditionally. Otherwise, genuine
+ // shape animations (drawing layer and
+ // GIF) will not be stopped.
+
+ bool bSkipAllMainSequenceEffects (false);
+ bool bSkipSlideTransition (false);
+ std::for_each( rProperties.begin(),
+ rProperties.end(),
+ PrefetchPropertiesFunc(this, bSkipAllMainSequenceEffects, bSkipSlideTransition) );
+
+ OSL_ENSURE( !maViewContainer.empty(), "### no views!" );
+ if (maViewContainer.empty())
+ return;
+
+ // this here might take some time
+ {
+ WaitSymbolLock aLock (*this);
+
+ mpPreviousSlide = mpCurrentSlide;
+ mpCurrentSlide.reset();
+
+ if (matches( mpPrefetchSlide, xSlide, xRootNode ))
+ {
+ // prefetched slide matches:
+ mpCurrentSlide = mpPrefetchSlide;
+ }
+ else
+ mpCurrentSlide = makeSlide( xSlide, xDrawPages, xRootNode );
+
+ OSL_ASSERT( mpCurrentSlide );
+ if (mpCurrentSlide)
+ {
+ basegfx::B2DSize oldSlideSize;
+ if( mpPreviousSlide )
+ oldSlideSize = basegfx::B2DSize( mpPreviousSlide->getSlideSize() );
+
+ basegfx::B2DSize const slideSize( mpCurrentSlide->getSlideSize() );
+
+ // push new transformation to all views, if size changed
+ if( !mpPreviousSlide || oldSlideSize != slideSize )
+ {
+ for( const auto& pView : maViewContainer )
+ pView->setViewSize( slideSize );
+
+ // explicitly notify view change here,
+ // because transformation might have changed:
+ // optimization, this->notifyViewChange() would
+ // repaint slide which is not necessary.
+ maEventMultiplexer.notifyViewsChanged();
+ }
+
+ // create slide transition, and add proper end event
+ // (which then starts the slide effects
+ // via CURRENT_SLIDE.show())
+ ActivitySharedPtr pSlideChangeActivity (
+ createSlideTransition(
+ mpCurrentSlide->getXDrawPage(),
+ mpPreviousSlide,
+ mpCurrentSlide,
+ makeEvent(
+ [this] () { this->notifySlideTransitionEnded(false); },
+ "SlideShowImpl::notifySlideTransitionEnded")));
+
+ if (bSkipSlideTransition)
+ {
+ // The transition activity was created for the side effects
+ // (like sound transitions). Because we want to skip the
+ // actual transition animation we do not need the activity
+ // anymore.
+ pSlideChangeActivity.reset();
+ }
+
+ if (pSlideChangeActivity)
+ {
+ // factory generated a slide transition - activate it!
+ maActivitiesQueue.addActivity( pSlideChangeActivity );
+ }
+ else
+ {
+ // no transition effect on this slide - schedule slide
+ // effect start event right away.
+ maEventQueue.addEvent(
+ makeEvent(
+ [this] () { this->notifySlideTransitionEnded(true); },
+ "SlideShowImpl::notifySlideTransitionEnded"));
+ }
+ }
+ } // finally
+
+ maListenerContainer.forEach(
+ [](uno::Reference<presentation::XSlideShowListener> const& xListener)
+ {
+ xListener->slideTransitionStarted();
+ });
+
+ // We are currently rewinding an effect. This lead us from the next
+ // slide to this one. To complete this we have to play back all main
+ // sequence effects on this slide.
+ if (bSkipAllMainSequenceEffects)
+ maEffectRewinder.skipAllMainSequenceEffects();
+}
+
+void SlideShowImpl::redisplayCurrentSlide()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+ stopShow();
+
+ OSL_ENSURE( !maViewContainer.empty(), "### no views!" );
+ if (maViewContainer.empty())
+ return;
+
+ // No transition effect on this slide - schedule slide
+ // effect start event right away.
+ maEventQueue.addEvent(
+ makeEvent( [this] () { this->notifySlideTransitionEnded(true); },
+ "SlideShowImpl::notifySlideTransitionEnded"));
+
+ maListenerContainer.forEach(
+ [](uno::Reference<presentation::XSlideShowListener> const& xListener)
+ {
+ xListener->slideTransitionStarted();
+ });
+}
+
+sal_Bool SlideShowImpl::nextEffect()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if (mbShowPaused)
+ return true;
+ else
+ return maEventMultiplexer.notifyNextEffect();
+}
+
+sal_Bool SlideShowImpl::previousEffect()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if (mbShowPaused)
+ return true;
+ else
+ {
+ return maEffectRewinder.rewind(
+ maScreenUpdater.createLock(),
+ [this]() { return this->redisplayCurrentSlide(); },
+ [this]() { return this->rewindEffectToPreviousSlide(); } );
+ }
+}
+
+void SlideShowImpl::rewindEffectToPreviousSlide()
+{
+ // Show the wait symbol now and prevent it from showing temporary slide
+ // content while effects are played back.
+ WaitSymbolLock aLock (*this);
+
+ // A previous call to EffectRewinder::Rewind could not rewind the current
+ // effect because there are no effects on the current slide or none has
+ // yet been displayed. Go to the previous slide.
+ notifySlideEnded(true);
+
+ // Process pending events once more in order to have the following
+ // screen update show the last effect. Not sure whether this should be
+ // necessary.
+ maEventQueue.forceEmpty();
+
+ // We have to call the screen updater before the wait symbol is turned
+ // off. Otherwise the wait symbol would force the display of an
+ // intermediate state of the slide (before the effects are replayed.)
+ maScreenUpdater.commitUpdates();
+}
+
+sal_Bool SlideShowImpl::startShapeActivity(
+ uno::Reference<drawing::XShape> const& /*xShape*/ )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ // TODO(F3): NYI
+ OSL_FAIL( "not yet implemented!" );
+ return false;
+}
+
+sal_Bool SlideShowImpl::stopShapeActivity(
+ uno::Reference<drawing::XShape> const& /*xShape*/ )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ // TODO(F3): NYI
+ OSL_FAIL( "not yet implemented!" );
+ return false;
+}
+
+sal_Bool SlideShowImpl::pause( sal_Bool bPauseShow )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if (bPauseShow)
+ mpPresTimer->pauseTimer();
+ else
+ mpPresTimer->continueTimer();
+
+ maEventMultiplexer.notifyPauseMode(bPauseShow);
+
+ mbShowPaused = bPauseShow;
+ return true;
+}
+
+uno::Reference<drawing::XDrawPage> SlideShowImpl::getCurrentSlide()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return uno::Reference<drawing::XDrawPage>();
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if (mpCurrentSlide)
+ return mpCurrentSlide->getXDrawPage();
+ else
+ return uno::Reference<drawing::XDrawPage>();
+}
+
+sal_Bool SlideShowImpl::addView(
+ uno::Reference<presentation::XSlideShowView> const& xView )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ // first of all, check if view has a valid canvas
+ ENSURE_OR_RETURN_FALSE( xView.is(), "addView(): Invalid view" );
+ ENSURE_OR_RETURN_FALSE( xView->getCanvas().is(),
+ "addView(): View does not provide a valid canvas" );
+
+ UnoViewSharedPtr const pView( createSlideView(
+ xView,
+ maEventQueue,
+ maEventMultiplexer ));
+ if (!maViewContainer.addView( pView ))
+ return false; // view already added
+
+ // initialize view content
+ // =======================
+
+ if (mpCurrentSlide)
+ {
+ // set view transformation
+ const basegfx::B2ISize slideSize = mpCurrentSlide->getSlideSize();
+ pView->setViewSize( basegfx::B2DSize( slideSize.getX(),
+ slideSize.getY() ) );
+ }
+
+ // clear view area (since it's newly added,
+ // we need a clean slate)
+ pView->clearAll();
+
+ // broadcast newly added view
+ maEventMultiplexer.notifyViewAdded( pView );
+
+ // set current mouse ptr
+ pView->setCursorShape( calcActiveCursor(mnCurrentCursor) );
+
+ return true;
+}
+
+sal_Bool SlideShowImpl::removeView(
+ uno::Reference<presentation::XSlideShowView> const& xView )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ ENSURE_OR_RETURN_FALSE( xView.is(), "removeView(): Invalid view" );
+
+ UnoViewSharedPtr const pView( maViewContainer.removeView( xView ) );
+ if( !pView )
+ return false; // view was not added in the first place
+
+ // remove view from EventMultiplexer (mouse events etc.)
+ maEventMultiplexer.notifyViewRemoved( pView );
+
+ pView->_dispose();
+
+ return true;
+}
+
+void SlideShowImpl::registerUserPaintPolygons( const uno::Reference< lang::XMultiServiceFactory >& xDocFactory )
+{
+ //Retrieve Polygons if user ends presentation by context menu
+ if (mpCurrentSlide)
+ {
+ if(findPolygons(mpCurrentSlide->getXDrawPage()) != maPolygons.end())
+ maPolygons.erase(mpCurrentSlide->getXDrawPage());
+
+ maPolygons.insert(make_pair(mpCurrentSlide->getXDrawPage(),mpCurrentSlide->getPolygons()));
+ }
+
+ //Creating the layer for shapes drawn during slideshow
+ // query for the XLayerManager
+ uno::Reference< drawing::XLayerSupplier > xLayerSupplier(xDocFactory, uno::UNO_QUERY);
+ uno::Reference< container::XNameAccess > xNameAccess = xLayerSupplier->getLayerManager();
+ uno::Reference< drawing::XLayerManager > xLayerManager(xNameAccess, uno::UNO_QUERY);
+
+ // create layer
+ uno::Reference< drawing::XLayer > xDrawnInSlideshow;
+ uno::Any aPropLayer;
+ OUString sLayerName = "DrawnInSlideshow";
+ if (xNameAccess->hasByName(sLayerName))
+ {
+ xNameAccess->getByName(sLayerName) >>= xDrawnInSlideshow;
+ }
+ else
+ {
+ xDrawnInSlideshow = xLayerManager->insertNewByIndex(xLayerManager->getCount());
+ aPropLayer <<= sLayerName;
+ xDrawnInSlideshow->setPropertyValue("Name", aPropLayer);
+ }
+
+ // ODF defaults from ctor of SdrLayer are not automatically set on the here
+ // created XLayer. Need to be done explicitly here.
+ aPropLayer <<= true;
+ xDrawnInSlideshow->setPropertyValue("IsVisible", aPropLayer);
+ xDrawnInSlideshow->setPropertyValue("IsPrintable", aPropLayer);
+ aPropLayer <<= false;
+ xDrawnInSlideshow->setPropertyValue("IsLocked", aPropLayer);
+
+ //Register polygons for each slide
+ for( const auto& rPoly : maPolygons )
+ {
+ PolyPolygonVector aPolygons = rPoly.second;
+ //Get shapes for the slide
+ css::uno::Reference< css::drawing::XShapes > Shapes = rPoly.first;
+ //Retrieve polygons for one slide
+ for( const auto& pPolyPoly : aPolygons )
+ {
+ ::basegfx::B2DPolyPolygon b2DPolyPoly = ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(pPolyPoly->getUNOPolyPolygon());
+
+ //Normally there is only one polygon
+ for(sal_uInt32 i=0; i< b2DPolyPoly.count();i++)
+ {
+ const ::basegfx::B2DPolygon& aPoly = b2DPolyPoly.getB2DPolygon(i);
+ sal_uInt32 nPoints = aPoly.count();
+
+ if( nPoints > 1)
+ {
+ //create the PolyLineShape
+ uno::Reference< uno::XInterface > polyshape(xDocFactory->createInstance(
+ "com.sun.star.drawing.PolyLineShape" ) );
+ uno::Reference< drawing::XShape > rPolyShape(polyshape, uno::UNO_QUERY);
+
+ //Add the shape to the slide
+ Shapes->add(rPolyShape);
+
+ //Retrieve shape properties
+ uno::Reference< beans::XPropertySet > aXPropSet( rPolyShape, uno::UNO_QUERY );
+ //Construct a sequence of points sequence
+ drawing::PointSequenceSequence aRetval;
+ //Create only one sequence for one polygon
+ aRetval.realloc( 1 );
+ // Retrieve the sequence of points from aRetval
+ drawing::PointSequence* pOuterSequence = aRetval.getArray();
+ // Create 2 points in this sequence
+ pOuterSequence->realloc(nPoints);
+ // Get these points which are in an array
+ awt::Point* pInnerSequence = pOuterSequence->getArray();
+ for( sal_uInt32 n = 0; n < nPoints; n++ )
+ {
+ //Create a point from the polygon
+ *pInnerSequence++ = awt::Point(
+ basegfx::fround(aPoly.getB2DPoint(n).getX()),
+ basegfx::fround(aPoly.getB2DPoint(n).getY()));
+ }
+
+ //Fill the properties
+ //Give the built PointSequenceSequence.
+ uno::Any aParam;
+ aParam <<= aRetval;
+ aXPropSet->setPropertyValue("PolyPolygon", aParam );
+
+ //LineStyle : SOLID by default
+ drawing::LineStyle eLS;
+ eLS = drawing::LineStyle_SOLID;
+ aXPropSet->setPropertyValue("LineStyle", uno::Any(eLS) );
+
+ //LineColor
+ sal_uInt32 nLineColor;
+ nLineColor = pPolyPoly->getRGBALineColor();
+ //Transform polygon color from RRGGBBAA to AARRGGBB
+ aXPropSet->setPropertyValue("LineColor", uno::Any(RGBAColor2UnoColor(nLineColor)) );
+
+ //LineWidth
+ double fLineWidth;
+ fLineWidth = pPolyPoly->getStrokeWidth();
+ aXPropSet->setPropertyValue("LineWidth", uno::Any(static_cast<sal_Int32>(fLineWidth)) );
+
+ // make polygons special
+ xLayerManager->attachShapeToLayer(rPolyShape, xDrawnInSlideshow);
+ }
+ }
+ }
+ }
+}
+
+sal_Bool SlideShowImpl::setProperty( beans::PropertyValue const& rProperty )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if ( rProperty.Name == "AutomaticAdvancement" )
+ {
+ double nTimeout(0.0);
+ mbAutomaticAdvancementMode = (rProperty.Value >>= nTimeout);
+ if (mbAutomaticAdvancementMode)
+ {
+ maEventMultiplexer.setAutomaticTimeout( nTimeout );
+ }
+ maEventMultiplexer.setAutomaticMode( mbAutomaticAdvancementMode );
+ return true;
+ }
+
+ if ( rProperty.Name == "UserPaintColor" )
+ {
+ sal_Int32 nColor(0);
+ if (rProperty.Value >>= nColor)
+ {
+ OSL_ENSURE( mbMouseVisible,
+ "setProperty(): User paint overrides invisible mouse" );
+
+ // enable user paint
+ maUserPaintColor = unoColor2RGBColor(nColor);
+ if( mpCurrentSlide && !mpCurrentSlide->isPaintOverlayActive() )
+ mpCurrentSlide->enablePaintOverlay();
+
+ maEventMultiplexer.notifyUserPaintColor( *maUserPaintColor );
+ }
+ else
+ {
+ // disable user paint
+ maUserPaintColor.reset();
+ maEventMultiplexer.notifyUserPaintDisabled();
+ }
+
+ resetCursor();
+
+ return true;
+ }
+
+ //adding support for erasing features in UserPaintOverlay
+ if ( rProperty.Name == "EraseAllInk" )
+ {
+ bool bEraseAllInk(false);
+ if (rProperty.Value >>= bEraseAllInk)
+ {
+ OSL_ENSURE( mbMouseVisible,
+ "setProperty(): User paint overrides invisible mouse" );
+
+ // enable user paint
+ maEraseAllInk = bEraseAllInk;
+ maEventMultiplexer.notifyEraseAllInk( *maEraseAllInk );
+ }
+
+ return true;
+ }
+
+ if ( rProperty.Name == "SwitchPenMode" )
+ {
+ bool bSwitchPenMode(false);
+ if (rProperty.Value >>= bSwitchPenMode)
+ {
+ OSL_ENSURE( mbMouseVisible,
+ "setProperty(): User paint overrides invisible mouse" );
+
+ if(bSwitchPenMode){
+ // Switch to Pen Mode
+ maEventMultiplexer.notifySwitchPenMode();
+ }
+ }
+ return true;
+ }
+
+ if ( rProperty.Name == "SwitchEraserMode" )
+ {
+ bool bSwitchEraserMode(false);
+ if (rProperty.Value >>= bSwitchEraserMode)
+ {
+ OSL_ENSURE( mbMouseVisible,
+ "setProperty(): User paint overrides invisible mouse" );
+ if(bSwitchEraserMode){
+ // switch to Eraser mode
+ maEventMultiplexer.notifySwitchEraserMode();
+ }
+ }
+
+ return true;
+ }
+
+ if ( rProperty.Name == "EraseInk" )
+ {
+ sal_Int32 nEraseInk(100);
+ if (rProperty.Value >>= nEraseInk)
+ {
+ OSL_ENSURE( mbMouseVisible,
+ "setProperty(): User paint overrides invisible mouse" );
+
+ // enable user paint
+ maEraseInk = nEraseInk;
+ maEventMultiplexer.notifyEraseInkWidth( *maEraseInk );
+ }
+
+ return true;
+ }
+
+ // new Property for pen's width
+ if ( rProperty.Name == "UserPaintStrokeWidth" )
+ {
+ double nWidth(4.0);
+ if (rProperty.Value >>= nWidth)
+ {
+ OSL_ENSURE( mbMouseVisible,"setProperty(): User paint overrides invisible mouse" );
+ // enable user paint stroke width
+ maUserPaintStrokeWidth = nWidth;
+ maEventMultiplexer.notifyUserPaintStrokeWidth( maUserPaintStrokeWidth );
+ }
+
+ return true;
+ }
+
+ if ( rProperty.Name == "AdvanceOnClick" )
+ {
+ bool bAdvanceOnClick = false;
+ if (! (rProperty.Value >>= bAdvanceOnClick))
+ return false;
+ maUserEventQueue.setAdvanceOnClick( bAdvanceOnClick );
+ return true;
+ }
+
+ if ( rProperty.Name == "DisableAnimationZOrder" )
+ {
+ bool bDisableAnimationZOrder = false;
+ if (! (rProperty.Value >>= bDisableAnimationZOrder))
+ return false;
+ mbDisableAnimationZOrder = bDisableAnimationZOrder;
+ return true;
+ }
+
+ if ( rProperty.Name == "ImageAnimationsAllowed" )
+ {
+ if (! (rProperty.Value >>= mbImageAnimationsAllowed))
+ return false;
+
+ // TODO(F3): Forward to slides!
+ return true;
+ }
+
+ if ( rProperty.Name == "MouseVisible" )
+ {
+ if (! (rProperty.Value >>= mbMouseVisible))
+ return false;
+
+ requestCursor(mnCurrentCursor);
+
+ return true;
+ }
+
+ if ( rProperty.Name == "ForceManualAdvance" )
+ {
+ return (rProperty.Value >>= mbForceManualAdvance);
+ }
+
+ if ( rProperty.Name == "RehearseTimings" )
+ {
+ bool bRehearseTimings = false;
+ if (! (rProperty.Value >>= bRehearseTimings))
+ return false;
+
+ if (bRehearseTimings)
+ {
+ // TODO(Q3): Move to slide
+ mpRehearseTimingsActivity = RehearseTimingsActivity::create(
+ SlideShowContext(
+ mpDummyPtr,
+ maEventQueue,
+ maEventMultiplexer,
+ maScreenUpdater,
+ maActivitiesQueue,
+ maUserEventQueue,
+ *this,
+ *this,
+ maViewContainer,
+ mxComponentContext,
+ mpBox2DDummyPtr ) );
+ }
+ else if (mpRehearseTimingsActivity)
+ {
+ // removes timer from all views:
+ mpRehearseTimingsActivity->dispose();
+ mpRehearseTimingsActivity.reset();
+ }
+ return true;
+ }
+
+ if ( rProperty.Name == "WaitSymbolBitmap" )
+ {
+ uno::Reference<rendering::XBitmap> xBitmap;
+ if (! (rProperty.Value >>= xBitmap))
+ return false;
+
+ mpWaitSymbol = WaitSymbol::create( xBitmap,
+ maScreenUpdater,
+ maEventMultiplexer,
+ maViewContainer );
+
+ return true;
+ }
+
+ if ( rProperty.Name == "PointerSymbolBitmap" )
+ {
+ uno::Reference<rendering::XBitmap> xBitmap;
+ if (! (rProperty.Value >>= xBitmap))
+ return false;
+
+ mpPointerSymbol = PointerSymbol::create( xBitmap,
+ maScreenUpdater,
+ maEventMultiplexer,
+ maViewContainer );
+
+ return true;
+ }
+
+ if ( rProperty.Name == "PointerVisible" )
+ {
+ bool visible;
+ if (!(rProperty.Value >>= visible))
+ return false;
+
+ mpPointerSymbol->setVisible(visible);
+ return true;
+ }
+
+ if ( rProperty.Name == "PointerPosition")
+ {
+ css::geometry::RealPoint2D pos;
+ if (! (rProperty.Value >>= pos))
+ return false;
+
+ mpPointerSymbol->viewsChanged(pos);
+ return true;
+ }
+
+ if (rProperty.Name == "NoSlideTransitions" )
+ {
+ return (rProperty.Value >>= mbNoSlideTransitions);
+ }
+
+ if ( rProperty.Name == "IsSoundEnabled" )
+ {
+ uno::Sequence<uno::Any> aValues;
+ uno::Reference<presentation::XSlideShowView> xView;
+ bool bValue (false);
+ if ((rProperty.Value >>= aValues)
+ && aValues.getLength()==2
+ && (aValues[0] >>= xView)
+ && (aValues[1] >>= bValue))
+ {
+ // Look up the view.
+ auto iView = std::find_if(maViewContainer.begin(), maViewContainer.end(),
+ [&xView](const UnoViewSharedPtr& rxView) { return rxView && rxView->getUnoView() == xView; });
+ if (iView != maViewContainer.end())
+ {
+ // Store the flag at the view so that media shapes have
+ // access to it.
+ (*iView)->setIsSoundEnabled(bValue);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void SlideShowImpl::addSlideShowListener(
+ uno::Reference<presentation::XSlideShowListener> const& xListener )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return;
+
+ // container syncs with passed mutex ref
+ maListenerContainer.addInterface(xListener);
+}
+
+void SlideShowImpl::removeSlideShowListener(
+ uno::Reference<presentation::XSlideShowListener> const& xListener )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ // container syncs with passed mutex ref
+ maListenerContainer.removeInterface(xListener);
+}
+
+void SlideShowImpl::addShapeEventListener(
+ uno::Reference<presentation::XShapeEventListener> const& xListener,
+ uno::Reference<drawing::XShape> const& xShape )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ ShapeEventListenerMap::iterator aIter;
+ if( (aIter=maShapeEventListeners.find( xShape )) ==
+ maShapeEventListeners.end() )
+ {
+ // no entry for this shape -> create one
+ aIter = maShapeEventListeners.emplace(
+ xShape,
+ std::make_shared<comphelper::OInterfaceContainerHelper3<css::presentation::XShapeEventListener>>(
+ m_aMutex)).first;
+ }
+
+ // add new listener to broadcaster
+ if( aIter->second )
+ aIter->second->addInterface( xListener );
+
+ maEventMultiplexer.notifyShapeListenerAdded(xShape);
+}
+
+void SlideShowImpl::removeShapeEventListener(
+ uno::Reference<presentation::XShapeEventListener> const& xListener,
+ uno::Reference<drawing::XShape> const& xShape )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ ShapeEventListenerMap::iterator aIter;
+ if( (aIter = maShapeEventListeners.find( xShape )) !=
+ maShapeEventListeners.end() )
+ {
+ // entry for this shape found -> remove listener from
+ // helper object
+ ENSURE_OR_THROW(
+ aIter->second,
+ "SlideShowImpl::removeShapeEventListener(): "
+ "listener map contains NULL broadcast helper" );
+
+ aIter->second->removeInterface( xListener );
+ }
+
+ maEventMultiplexer.notifyShapeListenerRemoved(xShape);
+}
+
+void SlideShowImpl::setShapeCursor(
+ uno::Reference<drawing::XShape> const& xShape, sal_Int16 nPointerShape )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return;
+
+ // precondition: must only be called from the main thread!
+ DBG_TESTSOLARMUTEX();
+
+ ShapeCursorMap::iterator aIter;
+ if( (aIter=maShapeCursors.find( xShape )) == maShapeCursors.end() )
+ {
+ // no entry for this shape -> create one
+ if( nPointerShape != awt::SystemPointer::ARROW )
+ {
+ // add new entry, unless shape shall display
+ // normal pointer arrow -> no need to handle that
+ // case
+ maShapeCursors.emplace(xShape, nPointerShape);
+ }
+ }
+ else if( nPointerShape == awt::SystemPointer::ARROW )
+ {
+ // shape shall display normal cursor -> can disable
+ // the cursor and clear the entry
+ maShapeCursors.erase( xShape );
+ }
+ else
+ {
+ // existing entry found, update with new cursor ID
+ aIter->second = nPointerShape;
+ }
+}
+
+bool SlideShowImpl::requestCursor( sal_Int16 nCursorShape )
+{
+ mnCurrentCursor = nCursorShape;
+
+ const sal_Int16 nActualCursor = calcActiveCursor(mnCurrentCursor);
+
+ // change all views to the requested cursor ID
+ for( const auto& pView : maViewContainer )
+ pView->setCursorShape( nActualCursor );
+
+ return nActualCursor==nCursorShape;
+}
+
+void SlideShowImpl::resetCursor()
+{
+ mnCurrentCursor = awt::SystemPointer::ARROW;
+
+ const sal_Int16 nActualCursor = calcActiveCursor( mnCurrentCursor );
+ // change all views to the default cursor ID
+ for( const auto& pView : maViewContainer )
+ pView->setCursorShape( nActualCursor );
+}
+
+sal_Bool SlideShowImpl::update( double & nNextTimeout )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ if (isDisposed())
+ return false;
+
+ // precondition: update() must only be called from the
+ // main thread!
+ DBG_TESTSOLARMUTEX();
+
+ if( mbShowPaused )
+ {
+ // commit frame (might be repaints pending)
+ maScreenUpdater.commitUpdates();
+
+ return false;
+ }
+ else
+ {
+ // TODO(F2): re-evaluate whether that timer lagging makes
+ // sense.
+
+ // hold timer, while processing the queues:
+ // 1. when there is more than one active activity this ensures the
+ // same time for all activities and events
+ // 2. processing of events may lead to creation of further events
+ // that have zero delay. While the timer is stopped these events
+ // are processed in the same run.
+ {
+ //Get a shared-ptr that outlives the scope-guard which will
+ //ensure that the pointed-to-item exists in the case of a
+ //::dispose clearing mpPresTimer
+ std::shared_ptr<canvas::tools::ElapsedTime> xTimer(mpPresTimer);
+ comphelper::ScopeGuard scopeGuard(
+ [&xTimer]() { return xTimer->releaseTimer(); } );
+ xTimer->holdTimer();
+
+ // process queues
+ maEventQueue.process();
+
+ // #i118671# the call above may execute a macro bound to an object. In
+ // that case this macro may have destroyed this local slideshow so that it
+ // is disposed (see bugdoc at task). In that case, detect this and exit
+ // gently from this slideshow. Do not forget to disable the scoped
+ // call to mpPresTimer, this will be deleted if we are disposed.
+ if (isDisposed())
+ {
+ scopeGuard.dismiss();
+ return false;
+ }
+
+ maActivitiesQueue.process();
+
+ // commit frame to screen
+ maFrameSynchronization.Synchronize();
+ maScreenUpdater.commitUpdates();
+
+ // TODO(Q3): remove need to call dequeued() from
+ // activities. feels like a wart.
+
+ // Rationale for ActivitiesQueue::processDequeued(): when
+ // an activity ends, it usually pushed the end state to
+ // the animated shape in question, and ends the animation
+ // (which, in turn, will usually disable shape sprite
+ // mode). Disabling shape sprite mode causes shape
+ // repaint, which, depending on slide content, takes
+ // considerably more time than sprite updates. Thus, the
+ // last animation step tends to look delayed. To
+ // camouflage this, reaching end position and disabling
+ // sprite mode is split into two (normal Activity::end(),
+ // and Activity::dequeued()). Now, the reason to call
+ // commitUpdates() twice here is caused by the unrelated
+ // fact that during wait cursor display/hide, the screen
+ // is updated, and shows hidden sprites, but, in case of
+ // leaving the second commitUpdates() call out and punting
+ // that to the next round, no updated static slide
+ // content. In short, the last shape animation of a slide
+ // tends to blink at its end.
+
+ // process dequeued activities _after_ commit to screen
+ maActivitiesQueue.processDequeued();
+
+ // commit frame to screen
+ maScreenUpdater.commitUpdates();
+ }
+ // Time held until here
+
+ const bool bActivitiesLeft = ! maActivitiesQueue.isEmpty();
+ const bool bTimerEventsLeft = ! maEventQueue.isEmpty();
+ const bool bRet = (bActivitiesLeft || bTimerEventsLeft);
+
+ if (bRet)
+ {
+ // calc nNextTimeout value:
+ if (bActivitiesLeft)
+ {
+ // Activity queue is not empty. Tell caller that we would
+ // like to render another frame.
+
+ // Return a zero time-out to signal our caller to call us
+ // back as soon as possible. The actual timing, waiting the
+ // appropriate amount of time between frames, is then done
+ // by the maFrameSynchronization object.
+ nNextTimeout = 0;
+ maFrameSynchronization.Activate();
+ }
+ else
+ {
+ // timer events left:
+ // difference from current time (nota bene:
+ // time no longer held here!) to the next event in
+ // the event queue.
+
+ // #i61190# Retrieve next timeout only _after_
+ // processing activity queue
+
+ // ensure positive value:
+ nNextTimeout = std::max( 0.0, maEventQueue.nextTimeout() );
+
+ // There is no active animation so the frame rate does not
+ // need to be synchronized.
+ maFrameSynchronization.Deactivate();
+ }
+
+ mbSlideShowIdle = false;
+ }
+
+#if defined(DBG_UTIL)
+ // when slideshow is idle, issue an XUpdatable::update() call
+ // exactly once after a previous animation sequence finished -
+ // this might trigger screen dumps on some canvas
+ // implementations
+ if( !mbSlideShowIdle &&
+ (!bRet ||
+ nNextTimeout > 1.0) )
+ {
+ for( const auto& pView : maViewContainer )
+ {
+ try
+ {
+ uno::Reference< presentation::XSlideShowView > xView( pView->getUnoView(),
+ uno::UNO_SET_THROW );
+ uno::Reference<util::XUpdatable> const xUpdatable(
+ xView->getCanvas(), uno::UNO_QUERY);
+ if (xUpdatable.is()) // not supported in PresenterCanvas
+ {
+ xUpdatable->update();
+ }
+ }
+ catch( uno::RuntimeException& )
+ {
+ throw;
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "slideshow", "" );
+ }
+ }
+
+ mbSlideShowIdle = true;
+ }
+#endif
+
+ return bRet;
+ }
+}
+
+void SlideShowImpl::notifySlideTransitionEnded( bool bPaintSlide )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ OSL_ENSURE( !isDisposed(), "### already disposed!" );
+ OSL_ENSURE( mpCurrentSlide,
+ "notifySlideTransitionEnded(): Invalid current slide" );
+ if (mpCurrentSlide)
+ {
+ mpCurrentSlide->update_settings( !!maUserPaintColor, maUserPaintColor ? *maUserPaintColor : RGBColor(), maUserPaintStrokeWidth );
+
+ // first init show, to give the animations
+ // the chance to register SlideStartEvents
+ const bool bBackgroundLayerRendered( !bPaintSlide );
+ mpCurrentSlide->show( bBackgroundLayerRendered );
+ maEventMultiplexer.notifySlideStartEvent();
+ }
+}
+
+void queryAutomaticSlideTransition( uno::Reference<drawing::XDrawPage> const& xDrawPage,
+ double& nAutomaticNextSlideTimeout,
+ bool& bHasAutomaticNextSlide )
+{
+ // retrieve slide change parameters from XDrawPage
+ // ===============================================
+
+ uno::Reference< beans::XPropertySet > xPropSet( xDrawPage,
+ uno::UNO_QUERY );
+
+ sal_Int32 nChange(0);
+ if( !xPropSet.is() ||
+ !getPropertyValue( nChange,
+ xPropSet,
+ "Change") )
+ {
+ SAL_INFO("slideshow",
+ "queryAutomaticSlideTransition(): "
+ "Could not extract slide change mode from XDrawPage - assuming <none>" );
+ }
+
+ bHasAutomaticNextSlide = nChange == 1;
+
+ if( !xPropSet.is() ||
+ !getPropertyValue( nAutomaticNextSlideTimeout,
+ xPropSet,
+ "HighResDuration") )
+ {
+ SAL_INFO("slideshow",
+ "queryAutomaticSlideTransition(): "
+ "Could not extract slide transition timeout from "
+ "XDrawPage - assuming 1 sec" );
+ }
+}
+
+void SlideShowImpl::notifySlideAnimationsEnded()
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ //Draw polygons above animations
+ mpCurrentSlide->drawPolygons();
+
+ OSL_ENSURE( !isDisposed(), "### already disposed!" );
+
+ // This struct will receive the (interruptable) event,
+ // that triggers the notifySlideEnded() method.
+ InterruptableEventPair aNotificationEvents;
+
+ if( maEventMultiplexer.getAutomaticMode() )
+ {
+ OSL_ENSURE( ! mpRehearseTimingsActivity,
+ "unexpected: RehearseTimings mode!" );
+
+ // schedule a slide end event, with automatic mode's
+ // delay
+ aNotificationEvents = makeInterruptableDelay(
+ [this]() { return this->notifySlideEnded( false ); },
+ maEventMultiplexer.getAutomaticTimeout() );
+ }
+ else
+ {
+ OSL_ENSURE( mpCurrentSlide,
+ "notifySlideAnimationsEnded(): Invalid current slide!" );
+
+ bool bHasAutomaticNextSlide=false;
+ double nAutomaticNextSlideTimeout=0.0;
+ queryAutomaticSlideTransition(mpCurrentSlide->getXDrawPage(),
+ nAutomaticNextSlideTimeout,
+ bHasAutomaticNextSlide);
+
+ // check whether slide transition should happen
+ // 'automatically'. If yes, simply schedule the
+ // specified timeout.
+ // NOTE: mbForceManualAdvance and mpRehearseTimingsActivity
+ // override any individual slide setting, to always
+ // step slides manually.
+ if( !mbForceManualAdvance &&
+ !mpRehearseTimingsActivity &&
+ bHasAutomaticNextSlide )
+ {
+ aNotificationEvents = makeInterruptableDelay(
+ [this]() { return this->notifySlideEnded( false ); },
+ nAutomaticNextSlideTimeout);
+
+ // TODO(F2): Provide a mechanism to let the user override
+ // this automatic timeout via next()
+ }
+ else
+ {
+ if (mpRehearseTimingsActivity)
+ mpRehearseTimingsActivity->start();
+
+ // generate click event. Thus, the user must
+ // trigger the actual end of a slide. No need to
+ // generate interruptable event here, there's no
+ // timeout involved.
+ aNotificationEvents.mpImmediateEvent =
+ makeEvent( [this] () { this->notifySlideEnded(false); },
+ "SlideShowImpl::notifySlideEnded");
+ }
+ }
+
+ // register events on the queues. To make automatic slide
+ // changes interruptable, register the interruption event
+ // as a nextEffectEvent target. Note that the timeout
+ // event is optional (e.g. manual slide changes don't
+ // generate a timeout)
+ maUserEventQueue.registerNextEffectEvent(
+ aNotificationEvents.mpImmediateEvent );
+
+ if( aNotificationEvents.mpTimeoutEvent )
+ maEventQueue.addEvent( aNotificationEvents.mpTimeoutEvent );
+
+ // current slide's main sequence is over. Now should be
+ // the time to prefetch the next slide (if any), and
+ // prepare the initial slide bitmap (speeds up slide
+ // change setup time a lot). Show the wait cursor, this
+ // indeed might take some seconds.
+ {
+ WaitSymbolLock aLock (*this);
+
+ if (! matches( mpPrefetchSlide,
+ mxPrefetchSlide, mxPrefetchAnimationNode ))
+ {
+ mpPrefetchSlide = makeSlide( mxPrefetchSlide, mxDrawPagesSupplier,
+ mxPrefetchAnimationNode );
+ }
+ if (mpPrefetchSlide)
+ {
+ // ignore return value, this is just to populate
+ // Slide's internal bitmap buffer, such that the time
+ // needed to generate the slide bitmap is not spent
+ // when the slide change is requested.
+ mpPrefetchSlide->getCurrentSlideBitmap( *maViewContainer.begin() );
+ }
+ } // finally
+
+ maListenerContainer.forEach(
+ [](uno::Reference<presentation::XSlideShowListener> const& xListener)
+ {
+ xListener->slideAnimationsEnded();
+ });
+}
+
+void SlideShowImpl::notifySlideEnded (const bool bReverse)
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ OSL_ENSURE( !isDisposed(), "### already disposed!" );
+
+ if (mpRehearseTimingsActivity && !bReverse)
+ {
+ const double time = mpRehearseTimingsActivity->stop();
+ if (mpRehearseTimingsActivity->hasBeenClicked())
+ {
+ // save time at current drawpage:
+ uno::Reference<beans::XPropertySet> xPropSet(
+ mpCurrentSlide->getXDrawPage(), uno::UNO_QUERY );
+ OSL_ASSERT( xPropSet.is() );
+ if (xPropSet.is())
+ {
+ xPropSet->setPropertyValue(
+ "Change",
+ uno::Any( static_cast<sal_Int32>(1) ) );
+ xPropSet->setPropertyValue(
+ "Duration",
+ uno::Any( static_cast<sal_Int32>(time) ) );
+ }
+ }
+ }
+
+ if (bReverse)
+ maEventMultiplexer.notifySlideEndEvent();
+
+ stopShow(); // MUST call that: results in
+ // maUserEventQueue.clear(). What's more,
+ // stopShow()'s currSlide->hide() call is
+ // now also required, notifySlideEnded()
+ // relies on that
+ // unconditionally. Otherwise, genuine
+ // shape animations (drawing layer and
+ // GIF) will not be stopped.
+
+ maListenerContainer.forEach(
+ [&bReverse]( const uno::Reference< presentation::XSlideShowListener >& xListener )
+ { return xListener->slideEnded( bReverse ); } );
+}
+
+bool SlideShowImpl::notifyHyperLinkClicked( OUString const& hyperLink )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ maListenerContainer.forEach(
+ [&hyperLink]( const uno::Reference< presentation::XSlideShowListener >& xListener )
+ { return xListener->hyperLinkClicked( hyperLink ); } );
+ return true;
+}
+
+/** Notification from eventmultiplexer that an animation event has occurred.
+ This will be forwarded to all registered XSlideShoeListener
+ */
+bool SlideShowImpl::handleAnimationEvent( const AnimationNodeSharedPtr& rNode )
+{
+ osl::MutexGuard const guard( m_aMutex );
+
+ uno::Reference<animations::XAnimationNode> xNode( rNode->getXAnimationNode() );
+
+ switch( rNode->getState() )
+ {
+ case AnimationNode::ACTIVE:
+ maListenerContainer.forEach(
+ [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener )
+ { return xListener->beginEvent( xNode ); } );
+ break;
+
+ case AnimationNode::FROZEN:
+ case AnimationNode::ENDED:
+ maListenerContainer.forEach(
+ [&xNode]( const uno::Reference< animations::XAnimationListener >& xListener )
+ { return xListener->endEvent( xNode ); } );
+ if(mpCurrentSlide->isPaintOverlayActive())
+ mpCurrentSlide->drawPolygons();
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+std::shared_ptr<avmedia::MediaTempFile> SlideShowImpl::getMediaTempFile(const OUString& aUrl)
+{
+ std::shared_ptr<avmedia::MediaTempFile> aRet;
+
+#if !HAVE_FEATURE_AVMEDIA
+ (void)aUrl;
+#else
+ if (!mxSBD.is())
+ return aRet;
+
+ comphelper::LifecycleProxy aProxy;
+ uno::Reference<io::XStream> xStream =
+ comphelper::OStorageHelper::GetStreamAtPackageURL(mxSBD->getDocumentStorage(), aUrl,
+ css::embed::ElementModes::READ, aProxy);
+
+ uno::Reference<io::XInputStream> xInStream = xStream->getInputStream();
+ if (xInStream.is())
+ {
+ sal_Int32 nLastDot = aUrl.lastIndexOf('.');
+ sal_Int32 nLastSlash = aUrl.lastIndexOf('/');
+ OUString sDesiredExtension;
+ if (nLastDot > nLastSlash && nLastDot+1 < aUrl.getLength())
+ sDesiredExtension = aUrl.copy(nLastDot);
+
+ OUString sTempUrl;
+ if (::avmedia::CreateMediaTempFile(xInStream, sTempUrl, sDesiredExtension))
+ aRet = std::make_shared<avmedia::MediaTempFile>(sTempUrl);
+
+ xInStream->closeInput();
+ }
+#endif
+
+ return aRet;
+}
+
+//===== FrameSynchronization ==================================================
+
+FrameSynchronization::FrameSynchronization (const double nFrameDuration)
+ : maTimer(),
+ mnFrameDuration(nFrameDuration),
+ mnNextFrameTargetTime(0),
+ mbIsActive(false)
+{
+ MarkCurrentFrame();
+}
+
+void FrameSynchronization::MarkCurrentFrame()
+{
+ mnNextFrameTargetTime = maTimer.getElapsedTime() + mnFrameDuration;
+}
+
+void FrameSynchronization::Synchronize()
+{
+ if (mbIsActive)
+ {
+ // Do busy waiting for now.
+ for(;;)
+ {
+ double remainingTime = mnNextFrameTargetTime - maTimer.getElapsedTime();
+ if(remainingTime <= 0)
+ break;
+ // Try to sleep most of it.
+ int remainingMilliseconds = remainingTime * 1000;
+ if(remainingMilliseconds > 2)
+ osl::Thread::wait(std::chrono::milliseconds(remainingMilliseconds - 2));
+ }
+ }
+
+ MarkCurrentFrame();
+}
+
+void FrameSynchronization::Activate()
+{
+ mbIsActive = true;
+}
+
+void FrameSynchronization::Deactivate()
+{
+ mbIsActive = false;
+}
+
+} // anon namespace
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+slideshow_SlideShowImpl_get_implementation(
+ css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
+{
+ return cppu::acquire(new SlideShowImpl(context));
+}
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */