/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ // must be first #include #include #include #include #include #include #include #include #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 #include #include #include 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_FAIL( "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_FAIL( "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 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(); } } 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 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 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 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */