summaryrefslogtreecommitdiffstats
path: root/slideshow/source/engine/slide
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /slideshow/source/engine/slide
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'slideshow/source/engine/slide')
-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
13 files changed, 4666 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: */