blob: d15280e1944972a8e0cb8b42d0244a2523a60a51 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_slideshow.hxx"
// must be first
#include <canvas/debug.hxx>
#include <canvas/verbosetrace.hxx>
#include <com/sun/star/animations/XAnimate.hpp>
#include <com/sun/star/presentation/ParagraphTarget.hpp>
#include <com/sun/star/animations/AnimationFill.hpp>
#include <com/sun/star/animations/AnimationRestart.hpp>
#include <com/sun/star/presentation/EffectNodeType.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include "basenode.hxx"
#include "eventmultiplexer.hxx"
#include "basecontainernode.hxx"
#include "eventqueue.hxx"
#include "delayevent.hxx"
#include "tools.hxx"
#include "nodetools.hxx"
#include "generateevent.hxx"
#include "debug.hxx"
#include <boost/bind.hpp>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace ::com::sun::star;
namespace slideshow {
namespace internal {
namespace {
typedef int StateTransitionTable[17];
// State transition tables
// =========================================================================
const int* getStateTransitionTable( sal_Int16 nRestartMode,
sal_Int16 nFillMode )
{
// TODO(F2): restart issues in below tables
// transition table for restart=NEVER, fill=REMOVE
static const StateTransitionTable stateTransitionTable_Never_Remove = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::ENDED, // active successors for ACTIVE: no freeze here
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID, // active successors for FROZEN: this state is unreachable here
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED // active successors for ENDED: this state is a sink here (cannot restart)
};
// transition table for restart=WHEN_NOT_ACTIVE, fill=REMOVE
static const StateTransitionTable stateTransitionTable_NotActive_Remove = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::ENDED, // active successors for ACTIVE: no freeze here
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID, // active successors for FROZEN:
// this state is unreachable here
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED:
// restart possible when ended
};
// transition table for restart=ALWAYS, fill=REMOVE
static const StateTransitionTable stateTransitionTable_Always_Remove = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE: restart
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID, // active successors for FROZEN:
// this state is unreachable here
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart
};
// transition table for restart=NEVER, fill=FREEZE
static const StateTransitionTable stateTransitionTable_Never_Freeze = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED, // active successors for FROZEN: end
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED, // active successors for ENDED: this state is a sink here (cannot restart)
};
// transition table for restart=WHEN_NOT_ACTIVE, fill=FREEZE
static const StateTransitionTable stateTransitionTable_NotActive_Freeze = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::FROZEN|AnimationNode::ENDED, // active successors for ACTIVE: freeze object
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN:
// restart possible when ended
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE // active successors for ENDED:
// restart possible when ended
};
// transition table for restart=ALWAYS, fill=FREEZE
static const StateTransitionTable stateTransitionTable_Always_Freeze = {
AnimationNode::INVALID,
AnimationNode::RESOLVED|AnimationNode::ENDED, // active successors for UNRESOLVED
AnimationNode::ACTIVE|AnimationNode::ENDED, // active successors for RESOLVED
AnimationNode::INVALID,
AnimationNode::FROZEN|AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED, // active successors for ACTIVE:
// end object, restart
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::RESOLVED|AnimationNode::ACTIVE, // active successors for FROZEN: restart possible
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::INVALID,
AnimationNode::ENDED|AnimationNode::ACTIVE|AnimationNode::RESOLVED // active successors for ENDED: restart
};
static const StateTransitionTable* tableGuide[] = {
&stateTransitionTable_Never_Remove,
&stateTransitionTable_NotActive_Remove,
&stateTransitionTable_Always_Remove,
&stateTransitionTable_Never_Freeze,
&stateTransitionTable_NotActive_Freeze,
&stateTransitionTable_Always_Freeze
};
int nRestartValue;
switch( nRestartMode ) {
default:
case animations::AnimationRestart::DEFAULT:
// same value: animations::AnimationRestart::INHERIT:
OSL_ENSURE(
false, "getStateTransitionTable(): unexpected case for restart" );
// FALLTHROUGH intended
case animations::AnimationRestart::NEVER:
nRestartValue = 0;
break;
case animations::AnimationRestart::WHEN_NOT_ACTIVE:
nRestartValue = 1;
break;
case animations::AnimationRestart::ALWAYS:
nRestartValue = 2;
break;
}
int nFillValue;
switch( nFillMode ) {
default:
case animations::AnimationFill::AUTO:
case animations::AnimationFill::DEFAULT:
// same value: animations::AnimationFill::INHERIT:
OSL_ENSURE(
false, "getStateTransitionTable(): unexpected case for fill" );
// FALLTHROUGH intended
case animations::AnimationFill::REMOVE:
nFillValue = 0;
break;
case animations::AnimationFill::FREEZE:
case animations::AnimationFill::HOLD:
case animations::AnimationFill::TRANSITION:
nFillValue = 1;
break;
}
return *tableGuide[ 3*nFillValue + nRestartValue ];
}
/// Little helper predicate, to detect main sequence root node
bool isMainSequenceRootNode_(
const uno::Reference< animations::XAnimationNode >& xNode )
{
// detect main sequence root node (need that for
// end-of-mainsequence signalling below)
beans::NamedValue const aSearchKey(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "node-type" ) ),
uno::makeAny( presentation::EffectNodeType::MAIN_SEQUENCE ) );
uno::Sequence<beans::NamedValue> const userData(xNode->getUserData());
return findNamedValue( userData, aSearchKey );
}
} // anon namespace
// BaseNode implementation
//=========================================================================
/** state transition handling
*/
class BaseNode::StateTransition : private boost::noncopyable
{
public:
enum Options { NONE, FORCE };
explicit StateTransition( BaseNode * pNode )
: mpNode(pNode), meToState(INVALID) {}
~StateTransition() {
clear();
}
bool enter( NodeState eToState, int options = NONE )
{
OSL_ENSURE( meToState == INVALID,
"### commit() before enter()ing again!" );
if (meToState != INVALID)
return false;
bool const bForce = ((options & FORCE) != 0);
if (!bForce && !mpNode->isTransition( mpNode->meCurrState, eToState ))
return false;
// recursion detection:
if ((mpNode->meCurrentStateTransition & eToState) != 0)
return false; // already in wanted transition
// mark transition:
mpNode->meCurrentStateTransition |= eToState;
meToState = eToState;
return true; // in transition
}
void commit() {
OSL_ENSURE( meToState != INVALID, "### nothing to commit!" );
if (meToState != INVALID) {
mpNode->meCurrState = meToState;
clear();
}
// Uncomment the following line to write the node tree to file on
// every state change of one of its nodes.
// Debug_ShowNodeTree(mpNode->mpSelf);
}
void clear() {
if (meToState != INVALID) {
OSL_ASSERT( (mpNode->meCurrentStateTransition & meToState) != 0 );
mpNode->meCurrentStateTransition &= ~meToState;
meToState = INVALID;
}
}
private:
BaseNode *const mpNode;
NodeState meToState;
};
BaseNode::BaseNode( const uno::Reference< animations::XAnimationNode >& xNode,
const BaseContainerNodeSharedPtr& rParent,
const NodeContext& rContext ) :
maContext( rContext.maContext ),
maDeactivatingListeners(),
mxAnimationNode( xNode ),
mpParent( rParent ),
mpSelf(),
mpStateTransitionTable( NULL ),
mnStartDelay( rContext.mnStartDelay ),
meCurrState( UNRESOLVED ),
meCurrentStateTransition( 0 ),
mpCurrentEvent(),
mbIsMainSequenceRootNode( isMainSequenceRootNode_( xNode ) )
{
ENSURE_OR_THROW( mxAnimationNode.is(),
"BaseNode::BaseNode(): Invalid XAnimationNode" );
// setup state transition table
mpStateTransitionTable = getStateTransitionTable( getRestartMode(),
getFillMode() );
}
void BaseNode::dispose()
{
meCurrState = INVALID;
// discharge a loaded event, if any:
if (mpCurrentEvent) {
mpCurrentEvent->dispose();
mpCurrentEvent.reset();
}
maDeactivatingListeners.clear();
mxAnimationNode.clear();
mpParent.reset();
mpSelf.reset();
maContext.dispose();
}
sal_Int16 BaseNode::getRestartMode()
{
const sal_Int16 nTmp( mxAnimationNode->getRestart() );
return (nTmp != animations::AnimationRestart::DEFAULT &&
nTmp != animations::AnimationRestart::INHERIT)
? nTmp : getRestartDefaultMode();
}
sal_Int16 BaseNode::getFillMode()
{
const sal_Int16 nTmp( mxAnimationNode->getFill() );
const sal_Int16 nFill((nTmp != animations::AnimationFill::DEFAULT &&
nTmp != animations::AnimationFill::INHERIT)
? nTmp : getFillDefaultMode());
// For AUTO fill mode, SMIL specifies that fill mode is FREEZE,
// if no explicit active duration is given
// (no duration, end, repeatCount or repeatDuration given),
// and REMOVE otherwise
if( nFill == animations::AnimationFill::AUTO ) {
return (isIndefiniteTiming( mxAnimationNode->getDuration() ) &&
isIndefiniteTiming( mxAnimationNode->getEnd() ) &&
!mxAnimationNode->getRepeatCount().hasValue() &&
isIndefiniteTiming( mxAnimationNode->getRepeatDuration() ))
? animations::AnimationFill::FREEZE
: animations::AnimationFill::REMOVE;
}
else {
return nFill;
}
}
sal_Int16 BaseNode::getFillDefaultMode() const
{
sal_Int16 nFillDefault = mxAnimationNode->getFillDefault();
if (nFillDefault == animations::AnimationFill::DEFAULT) {
nFillDefault = (mpParent != 0
? mpParent->getFillDefaultMode()
: animations::AnimationFill::AUTO);
}
return nFillDefault;
}
sal_Int16 BaseNode::getRestartDefaultMode() const
{
sal_Int16 nRestartDefaultMode = mxAnimationNode->getRestartDefault();
if (nRestartDefaultMode == animations::AnimationRestart::DEFAULT) {
nRestartDefaultMode = (mpParent != 0
? mpParent->getRestartDefaultMode()
: animations::AnimationRestart::ALWAYS);
}
return nRestartDefaultMode;
}
uno::Reference<animations::XAnimationNode> BaseNode::getXAnimationNode() const
{
return mxAnimationNode;
}
bool BaseNode::init()
{
if (! checkValidNode())
return false;
meCurrState = UNRESOLVED;
// discharge a loaded event, if any:
if (mpCurrentEvent) {
mpCurrentEvent->dispose();
mpCurrentEvent.reset();
}
return init_st(); // may call derived class
}
bool BaseNode::init_st()
{
return true;
}
bool BaseNode::resolve()
{
if (! checkValidNode())
return false;
OSL_ASSERT( meCurrState != RESOLVED );
if (inStateOrTransition( RESOLVED ))
return true;
StateTransition st(this);
if (st.enter( RESOLVED ) &&
isTransition( RESOLVED, ACTIVE ) &&
resolve_st() /* may call derived class */)
{
st.commit(); // changing state
// discharge a loaded event, if any:
if (mpCurrentEvent)
mpCurrentEvent->dispose();
// schedule activation event:
// This method takes the NodeContext::mnStartDelay value into account,
// to cater for iterate container time shifts. We cannot put different
// iterations of the iterate container's children into different
// subcontainer (such as a 'DelayContainer', which delays resolving its
// children by a fixed amount), since all iterations' nodes must be
// resolved at the same time (otherwise, the delayed subset creation
// will not work, i.e. deactivate the subsets too late in the master
// shape).
uno::Any const aBegin( mxAnimationNode->getBegin() );
if (aBegin.hasValue()) {
mpCurrentEvent = generateEvent(
aBegin, boost::bind( &AnimationNode::activate, mpSelf ),
maContext, mnStartDelay );
}
else {
// For some leaf nodes, PPT import yields empty begin time,
// although semantically, it should be 0.0
// TODO(F3): That should really be provided by the PPT import
// schedule delayed activation event. Take iterate node
// timeout into account
mpCurrentEvent = makeDelay(
boost::bind( &AnimationNode::activate, mpSelf ),
mnStartDelay,
"AnimationNode::activate with delay");
maContext.mrEventQueue.addEvent( mpCurrentEvent );
}
return true;
}
return false;
}
bool BaseNode::resolve_st()
{
return true;
}
bool BaseNode::activate()
{
if (! checkValidNode())
return false;
OSL_ASSERT( meCurrState != ACTIVE );
if (inStateOrTransition( ACTIVE ))
return true;
StateTransition st(this);
if (st.enter( ACTIVE )) {
activate_st(); // calling derived class
st.commit(); // changing state
maContext.mrEventMultiplexer.notifyAnimationStart( mpSelf );
return true;
}
return false;
}
void BaseNode::activate_st()
{
scheduleDeactivationEvent();
}
void BaseNode::scheduleDeactivationEvent( EventSharedPtr const& pEvent )
{
if (mpCurrentEvent) {
mpCurrentEvent->dispose();
mpCurrentEvent.reset();
}
if (pEvent) {
if (maContext.mrEventQueue.addEvent( pEvent ))
mpCurrentEvent = pEvent;
}
else {
// This method need not take the
// NodeContext::mnStartDelay value into account,
// because the deactivation event is only scheduled
// when the effect is started: the timeout is then
// already respected.
// xxx todo:
// think about set node, anim base node!
// if anim base node has no activity, this is called to schedule deactivatiion,
// but what if it does not schedule anything?
// TODO(F2): Handle end time attribute, too
mpCurrentEvent = generateEvent(
mxAnimationNode->getDuration(),
boost::bind( &AnimationNode::deactivate, mpSelf ),
maContext, 0.0 );
}
}
void BaseNode::deactivate()
{
if (inStateOrTransition( ENDED | FROZEN ) || !checkValidNode())
return;
if (isTransition( meCurrState, FROZEN, false /* no OSL_ASSERT */ )) {
// do transition to FROZEN:
StateTransition st(this);
if (st.enter( FROZEN, StateTransition::FORCE )) {
deactivate_st( FROZEN );
st.commit();
notifyEndListeners();
// discharge a loaded event, before going on:
if (mpCurrentEvent) {
mpCurrentEvent->dispose();
mpCurrentEvent.reset();
}
}
}
else {
// use end instead:
end();
}
// state has changed either to FROZEN or ENDED
}
void BaseNode::deactivate_st( NodeState )
{
}
void BaseNode::end()
{
bool const bIsFrozenOrInTransitionToFrozen = inStateOrTransition( FROZEN );
if (inStateOrTransition( ENDED ) || !checkValidNode())
return;
// END must always be reachable. If not, that's an error in the
// transition tables
OSL_ENSURE( isTransition( meCurrState, ENDED ),
"end state not reachable in transition table" );
StateTransition st(this);
if (st.enter( ENDED, StateTransition::FORCE )) {
deactivate_st( ENDED );
st.commit(); // changing state
// if is FROZEN or is to be FROZEN, then
// will/already notified deactivating listeners
if (!bIsFrozenOrInTransitionToFrozen)
notifyEndListeners();
// discharge a loaded event, before going on:
if (mpCurrentEvent) {
mpCurrentEvent->dispose();
mpCurrentEvent.reset();
}
}
}
void BaseNode::notifyDeactivating( const AnimationNodeSharedPtr& rNotifier )
{
(void) rNotifier; // avoid warning
OSL_ASSERT( rNotifier->getState() == FROZEN ||
rNotifier->getState() == ENDED );
// TODO(F1): for end sync functionality, this might indeed be used some day
}
void BaseNode::notifyEndListeners() const
{
// notify all listeners
std::for_each( maDeactivatingListeners.begin(),
maDeactivatingListeners.end(),
boost::bind( &AnimationNode::notifyDeactivating, _1,
boost::cref(mpSelf) ) );
// notify state change
maContext.mrEventMultiplexer.notifyAnimationEnd( mpSelf );
// notify main sequence end (iff we're the main
// sequence root node). This is because the main
// sequence determines the active duration of the
// slide. All other sequences are secondary, in that
// they don't prevent a slide change from happening,
// even if they have not been completed. In other
// words, all sequences except the main sequence are
// optional for the slide lifetime.
if (isMainSequenceRootNode())
maContext.mrEventMultiplexer.notifySlideAnimationsEnd();
}
AnimationNode::NodeState BaseNode::getState() const
{
return meCurrState;
}
bool BaseNode::registerDeactivatingListener(
const AnimationNodeSharedPtr& rNotifee )
{
if (! checkValidNode())
return false;
ENSURE_OR_RETURN_FALSE(
rNotifee,
"BaseNode::registerDeactivatingListener(): invalid notifee" );
maDeactivatingListeners.push_back( rNotifee );
return true;
}
void BaseNode::setSelf( const BaseNodeSharedPtr& rSelf )
{
ENSURE_OR_THROW( rSelf.get() == this,
"BaseNode::setSelf(): got ptr to different object" );
ENSURE_OR_THROW( !mpSelf,
"BaseNode::setSelf(): called multiple times" );
mpSelf = rSelf;
}
// Debug
//=========================================================================
#if defined(VERBOSE) && defined(DBG_UTIL)
void BaseNode::showState() const
{
const AnimationNode::NodeState eNodeState( getState() );
if( eNodeState == AnimationNode::INVALID )
VERBOSE_TRACE( "Node state: n0x%X [label=\"%s\",style=filled,"
"fillcolor=\"0.5,0.2,0.5\"]",
(const char*)this+debugGetCurrentOffset(),
getDescription() );
else
VERBOSE_TRACE( "Node state: n0x%X [label=\"%s\",style=filled,"
"fillcolor=\"%f,1.0,1.0\"]",
(const char*)this+debugGetCurrentOffset(),
getDescription(),
log(double(getState()))/4.0 );
// determine additional node information
uno::Reference<animations::XAnimate> const xAnimate( mxAnimationNode,
uno::UNO_QUERY );
if( xAnimate.is() )
{
uno::Reference< drawing::XShape > xTargetShape( xAnimate->getTarget(),
uno::UNO_QUERY );
if( !xTargetShape.is() )
{
::com::sun::star::presentation::ParagraphTarget aTarget;
// no shape provided. Maybe a ParagraphTarget?
if( (xAnimate->getTarget() >>= aTarget) )
xTargetShape = aTarget.Shape;
}
if( xTargetShape.is() )
{
uno::Reference< beans::XPropertySet > xPropSet( xTargetShape,
uno::UNO_QUERY );
// read shape name
::rtl::OUString aName;
if( (xPropSet->getPropertyValue(
::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("Name") ) )
>>= aName) )
{
const ::rtl::OString& rAsciiName(
::rtl::OUStringToOString( aName,
RTL_TEXTENCODING_ASCII_US ) );
VERBOSE_TRACE( "Node info: n0x%X, name \"%s\"",
(const char*)this+debugGetCurrentOffset(),
rAsciiName.getStr() );
}
}
}
}
const char* BaseNode::getDescription() const
{
return "BaseNode";
}
void BaseNode::showTreeFromWithin() const
{
// find root node
BaseNodeSharedPtr pCurrNode( mpSelf );
while( pCurrNode->mpParent ) pCurrNode = pCurrNode->mpParent;
pCurrNode->showState();
}
#endif
} // namespace internal
} // namespace slideshow