diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /slideshow/source/engine/slide | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
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.cxx | 272 | ||||
-rw-r--r-- | slideshow/source/engine/slide/layer.hxx | 266 | ||||
-rw-r--r-- | slideshow/source/engine/slide/layermanager.cxx | 829 | ||||
-rw-r--r-- | slideshow/source/engine/slide/layermanager.hxx | 357 | ||||
-rw-r--r-- | slideshow/source/engine/slide/shapemanagerimpl.cxx | 425 | ||||
-rw-r--r-- | slideshow/source/engine/slide/shapemanagerimpl.hxx | 189 | ||||
-rw-r--r-- | slideshow/source/engine/slide/slideanimations.cxx | 106 | ||||
-rw-r--r-- | slideshow/source/engine/slide/slideanimations.hxx | 109 | ||||
-rw-r--r-- | slideshow/source/engine/slide/slideimpl.cxx | 1105 | ||||
-rw-r--r-- | slideshow/source/engine/slide/targetpropertiescreator.cxx | 369 | ||||
-rw-r--r-- | slideshow/source/engine/slide/targetpropertiescreator.hxx | 45 | ||||
-rw-r--r-- | slideshow/source/engine/slide/userpaintoverlay.cxx | 485 | ||||
-rw-r--r-- | slideshow/source/engine/slide/userpaintoverlay.hxx | 85 |
13 files changed, 4642 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..2d1b04bb1 --- /dev/null +++ b/slideshow/source/engine/slide/layer.hxx @@ -0,0 +1,266 @@ +/* -*- 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 +{ + namespace 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..2ec37ee2c --- /dev/null +++ b/slideshow/source/engine/slide/layermanager.cxx @@ -0,0 +1,829 @@ +/* -*- 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/*bSlideBackgoundPainted*/ ); + } + + 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 XShapeHash::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; + } + + 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.size()); + std::copy(maLayers.begin(),maLayers.end(),aWeakLayers.begin()); + + 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..0f2844ced --- /dev/null +++ b/slideshow/source/engine/slide/layermanager.hxx @@ -0,0 +1,357 @@ +/* -*- 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 +{ + namespace internal + { + /* 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 ); + + /** 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: + /** 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 > > + > XShapeHash; + + 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 + */ + XShapeHash 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..7863f7e74 --- /dev/null +++ b/slideshow/source/engine/slide/shapemanagerimpl.cxx @@ -0,0 +1,425 @@ +/* -*- 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 <comphelper/servicehelper.hxx> +#include <tools/diagnose_ex.h> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/presentation/XShapeEventListener.hpp> +#include <com/sun/star/system/SystemShellExecute.hpp> +#include <com/sun/star/system/SystemShellExecuteFlags.hpp> +#include <com/sun/star/system/XSystemShellExecute.hpp> +#include <svx/unoshape.hxx> +#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::OInterfaceContainerHelper2> const pCont( + aCurrBroadcaster->second ); + uno::Reference<drawing::XShape> const xShape( + aCurrBroadcaster->first->getXShape() ); + + // DON'T do anything with /this/ after this point! + pCont->forEach<presentation::XShapeEventListener>( + [&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(); +} + +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; + if( (aIter = mrGlobalListenersMap.find( xShape )) == + 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); + SvxShape* pShape = comphelper::getUnoTunnelImplementation<SvxShape>(xShape); + SdrObject* pObj = pShape ? pShape->GetSdrObject() : nullptr; + 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..973097563 --- /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 <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 { +namespace 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 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::OInterfaceContainerHelper2 >, + 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 internal +} // namespace presentation + +#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..67bc6e036 --- /dev/null +++ b/slideshow/source/engine/slide/slideanimations.hxx @@ -0,0 +1,109 @@ +/* -*- 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 +{ + namespace 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..0805d084e --- /dev/null +++ b/slideshow/source/engine/slide/slideimpl.cxx @@ -0,0 +1,1105 @@ +/* -*- 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> + +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, + const 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; + + /// 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, + const 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 ), + maContext( mpSubsettableShapeManager, + rEventQueue, + rEventMultiplexer, + rScreenUpdater, + rActivitiesQueue, + rUserEventQueue, + *this, + rMediaFileManager, + rViewContainer, + xComponentContext ), + mrCursorManager( rCursorManager ), + maAnimations( maContext, + basegfx::B2DSize( getSlideSizeImpl() ) ), + maPolygons(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; + + applyInitialShapeAttributes(mxRootNode); +} + +void SlideImpl::show( bool bSlideBackgoundPainted ) +{ + 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( !bSlideBackgoundPainted ) + { + 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, + maPolygons, + mbUserPaintOverlayEnabled ); + mbPaintOverlayActive = true; + } +} + +void SlideImpl::drawPolygons() const +{ + if( mpPaintOverlay ) + mpPaintOverlay->drawPolygons(); +} + +void SlideImpl::addPolygons(const PolyPolygonVector& rPolygons) +{ + for (const auto& rxPolygon : rPolygons) + maPolygons.push_back(rxPolygon); +} + +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 ) + 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 + OSL_FAIL( "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 + OSL_FAIL( "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, + const 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, 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..7a70b8b63 --- /dev/null +++ b/slideshow/source/engine/slide/targetpropertiescreator.cxx @@ -0,0 +1,369 @@ +/* -*- 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 > XShapeHash; + + + class NodeFunctor + { + public: + explicit NodeFunctor( + XShapeHash& rShapeHash, + bool bInitial ) + : mrShapeHash( rShapeHash ), + mxTargetShape(), + mnParagraphIndex( -1 ), + mbInitial( bInitial) + { + } + + NodeFunctor( XShapeHash& 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::makeAny( bVisible ) ) ) ); + break; + } + } + } + + private: + XShapeHash& 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 + XShapeHash 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() ); + + ::std::size_t nCurrIndex(0); + for( const auto& rIter : aShapeHash ) + { + animations::TargetProperties& rCurrProps( aRes[ 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..04dec8d59 --- /dev/null +++ b/slideshow/source/engine/slide/targetpropertiescreator.hxx @@ -0,0 +1,45 @@ +/* -*- 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 +{ + namespace internal + { + namespace 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 internal +} // namespace slideshow + +#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..045c455e6 --- /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, + const PolyPolygonVector& rPolygons, + bool bActive ) : + mrScreenUpdater( rScreenUpdater ), + maViews(), + maPolygons( 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, + const PolyPolygonVector& rPolygons, + bool bActive ) + { + UserPaintOverlaySharedPtr pRet( new UserPaintOverlay( rStrokeColor, + nStrokeWidth, + rContext, + rPolygons, + bActive)); + + return pRet; + } + + UserPaintOverlay::UserPaintOverlay( const RGBColor& rStrokeColor, + double nStrokeWidth, + const SlideShowContext& rContext, + const 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), + 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..5dab806a9 --- /dev/null +++ b/slideshow/source/engine/slide/userpaintoverlay.hxx @@ -0,0 +1,85 @@ +/* -*- 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 +{ + namespace 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, + const 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, + const 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: */ |