/* -*- 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
 * <http://www.openoffice.org/license.html>
 * for a copy of the LGPLv3 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_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<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

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
