/*************************************************************************
 *
 * 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 <tools/diagnose_ex.h>

#include <comphelper/anytostring.hxx>
#include <cppuhelper/exc_hlp.hxx>

#include <com/sun/star/awt/SystemPointer.hpp>
#include <com/sun/star/awt/MouseButton.hpp>
#include <com/sun/star/awt/MouseEvent.hpp>

#include <boost/bind.hpp>

#include "delayevent.hxx"
#include "usereventqueue.hxx"
#include "cursormanager.hxx"
#include "slideshowexceptions.hxx"

#include <vector>
#include <queue>
#include <map>
#include <functional>
#include <algorithm>


using namespace com::sun::star;

/* Implementation of UserEventQueue class */

namespace slideshow {
namespace internal {

namespace {

typedef std::vector<EventSharedPtr> ImpEventVector;
typedef std::queue<EventSharedPtr> ImpEventQueue;
typedef std::map<uno::Reference<animations::XAnimationNode>,
                 ImpEventVector> ImpAnimationEventMap;
typedef std::map<ShapeSharedPtr, ImpEventQueue,
                 Shape::lessThanShape> ImpShapeEventMap;

// MouseEventHandler base class, not consuming any event:
class MouseEventHandler_ : public MouseEventHandler
{
public:
    virtual bool handleMousePressed( awt::MouseEvent const& /*e*/ ) { return false;}
    virtual bool handleMouseReleased( awt::MouseEvent const& /*e*/) { return false;}
    virtual bool handleMouseEntered( awt::MouseEvent const& /*e*/ ) { return false;}
    virtual bool handleMouseExited( awt::MouseEvent const& /*e*/ ) { return false; }
    virtual bool handleMouseDragged( awt::MouseEvent const& /*e*/ ) { return false;}
    virtual bool handleMouseMoved( awt::MouseEvent const& /*e*/ ) { return false; }
};

/** @return one event has been posted
 */
template <typename ContainerT>
bool fireSingleEvent( ContainerT & rQueue, EventQueue & rEventQueue )
{
    // post next event in given queue:
    while (! rQueue.empty())
    {
        EventSharedPtr const pEvent(rQueue.front());
        rQueue.pop();
        
        // skip all inactive events (as the purpose of
        // nextEventFromQueue() is to activate the next
        // event, and events which return false on
        // isCharged() will never be activated by the
        // EventQueue)
        if(pEvent->isCharged())
            return rEventQueue.addEvent( pEvent );
    }
    return false; // no more (active) events in queue
}

/** @return at least one event has been posted
 */
template <typename ContainerT>
bool fireAllEvents( ContainerT & rQueue, EventQueue & rEventQueue )
{
    bool bFiredAny = false;
    while (fireSingleEvent( rQueue, rEventQueue ))
        bFiredAny = true;
    return bFiredAny;
}

class EventContainer
{
public:
    EventContainer() : 
        maEvents() 
    {}
    
    void clearContainer()
    {
        maEvents = ImpEventQueue();
    }
    
    void addEvent( const EventSharedPtr& rEvent )
    {
        maEvents.push( rEvent );
    }
    
    bool isEmpty()
    {
        return maEvents.empty();
    }
    
protected:
    ImpEventQueue maEvents;
};

} // anon namespace

class PlainEventHandler : public EventHandler,
                          public EventContainer
{
public:
    PlainEventHandler( EventQueue & rEventQueue )
        : EventContainer(), mrEventQueue(rEventQueue) {}
    
    virtual void dispose()
    {
        clearContainer();
    }
    
    virtual bool handleEvent()
    {
        return fireAllEvents( maEvents, mrEventQueue );
    }
    
private:
    EventQueue & mrEventQueue;
};

class AllAnimationEventHandler : public AnimationEventHandler
{
public:
    AllAnimationEventHandler( EventQueue& rEventQueue ) :
        mrEventQueue( rEventQueue ),
        maAnimationEventMap() 
    {}
    
    virtual void dispose()
    {
        maAnimationEventMap.clear();
    }
    
    virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode )
    {
        ENSURE_OR_RETURN_FALSE(
            rNode,
            "AllAnimationEventHandler::handleAnimationEvent(): Invalid node" );
        
        bool bRet( false );
        
        ImpAnimationEventMap::iterator aIter;
        if( (aIter=maAnimationEventMap.find( 
                 rNode->getXAnimationNode() )) != maAnimationEventMap.end() )
        {
            ImpEventVector& rVec( aIter->second );
            
            bRet = !rVec.empty();
            
            // registered node found -> fire all events in the vector
            std::for_each( rVec.begin(), rVec.end(),
                           boost::bind( &EventQueue::addEvent,
                                        boost::ref( mrEventQueue ), _1 ) );
            
            rVec.clear();
        }
        
        return bRet;
    }
    
    void addEvent( const EventSharedPtr&                                rEvent,
                   const uno::Reference< animations::XAnimationNode >&  xNode )
    {
        ImpAnimationEventMap::iterator aIter;
        if( (aIter=maAnimationEventMap.find( xNode )) ==
            maAnimationEventMap.end() )
        {
            // no entry for this animation -> create one
            aIter = maAnimationEventMap.insert(
                ImpAnimationEventMap::value_type( xNode,
                                                  ImpEventVector() ) ).first;
        }
        
        // add new event to queue
        aIter->second.push_back( rEvent );
    }
    
    bool isEmpty()
    {
        // find at least one animation with a non-empty vector
        ImpAnimationEventMap::const_iterator aCurr( maAnimationEventMap.begin() );
        const ImpAnimationEventMap::const_iterator aEnd( maAnimationEventMap.end() );
        while( aCurr != aEnd )
        {
            if( !aCurr->second.empty() )
                return false; // at least one non-empty entry found
            
            ++aCurr;
        }
        
        return true; // not a single non-empty entry found
    }
    
private:
    EventQueue&             mrEventQueue;
    ImpAnimationEventMap    maAnimationEventMap;
};

class ClickEventHandler : public MouseEventHandler_,
                          public EventHandler,
                          public EventContainer
{
public:
    ClickEventHandler( EventQueue& rEventQueue ) :
        EventContainer(),
        mrEventQueue( rEventQueue ),
        mbAdvanceOnClick( true ) 
    {}
    
    void setAdvanceOnClick( bool bAdvanceOnClick )
    {
        mbAdvanceOnClick = bAdvanceOnClick;
    }
    
private:
    virtual void dispose()
    {
        clearContainer();
    }

    // triggered by API calls, e.g. space bar
    virtual bool handleEvent()
    {
        return handleEvent_impl();
    }
    
    // triggered by mouse release:
    virtual bool handleMouseReleased( const awt::MouseEvent& evt )
    {
        if(evt.Buttons != awt::MouseButton::LEFT)
            return false;
        
        if( mbAdvanceOnClick ) {
            // fire next event
            return handleEvent_impl();
        }
        else {
            return false; // advance-on-click disabled
        }
    }
    
    // triggered by both:
    virtual bool handleEvent_impl()
    {
        // fire next event:
        return fireSingleEvent( maEvents, mrEventQueue );
    }
    
private:
    EventQueue& mrEventQueue;
    bool        mbAdvanceOnClick;
};

class SkipEffectEventHandler : public ClickEventHandler
{
public:
    SkipEffectEventHandler( EventQueue & rEventQueue,
                            EventMultiplexer & rEventMultiplexer )
        : ClickEventHandler(rEventQueue),
          mrEventQueue(rEventQueue),
          mrEventMultiplexer(rEventMultiplexer),
          mbSkipTriggersNextEffect(true) {}

    /** Remember to trigger (or not to trigger) the next effect after the
        current effect is skiped.
    */
    void setSkipTriggersNextEffect (const bool bSkipTriggersNextEffect)
    { mbSkipTriggersNextEffect = bSkipTriggersNextEffect; }

    ///  Skip the current effect but do not triggere the next effect.
    void skipEffect (void) { handleEvent_impl(false); }
    
private:
    virtual bool handleEvent_impl()
    {
        return handleEvent_impl(true);
    }

    bool handleEvent_impl (bool bNotifyNextEffect)
    {
        // fire all events, so animation nodes can register their
        // next effect listeners:
        if(fireAllEvents( maEvents, mrEventQueue )) 
        {
            if (mbSkipTriggersNextEffect && bNotifyNextEffect)
            {
                // then simulate a next effect event: this skip effect
                // handler is triggered upon next effect events (multiplexer
                // prio=-1)!  Posting a notifyNextEffect() here is only safe
                // (we don't run into busy loop), because we assume that
                // someone has registerered above for next effects
                // (multiplexer prio=0) at the user event queue.
                return mrEventQueue.addEventWhenQueueIsEmpty(
                    makeEvent( boost::bind( &EventMultiplexer::notifyNextEffect,
                                            boost::ref(mrEventMultiplexer) ),
                               "EventMultiplexer::notifyNextEffect") );
            }
            else
                return true;
        }
        return false;
    }
    
private:
    EventQueue & mrEventQueue;
    EventMultiplexer & mrEventMultiplexer;
    bool mbSkipTriggersNextEffect;
};

class RewindEffectEventHandler : public MouseEventHandler_,
                                 public EventContainer
{
public:
    RewindEffectEventHandler( EventQueue & rEventQueue )
        : EventContainer(), mrEventQueue(rEventQueue) {}
    
private:
    virtual void dispose()
    {
        clearContainer();
    }
    
    virtual bool handleMouseReleased( awt::MouseEvent const& evt )
    {
        if(evt.Buttons != awt::MouseButton::RIGHT)
            return false;
        
        return fireAllEvents( maEvents, mrEventQueue );
    }
    
private:
    EventQueue & mrEventQueue;
};

/** Base class to share some common code between
    ShapeClickEventHandler and MouseMoveHandler
    
    @derive override necessary MouseEventHandler interface methods,
    call sendEvent() method to actually process the event.
*/
class MouseHandlerBase : public MouseEventHandler_
{
public:
    MouseHandlerBase( EventQueue& rEventQueue ) :
        mrEventQueue( rEventQueue ),
        maShapeEventMap() 
    {}
    
    virtual void dispose()
    {
        // TODO(Q1): Check whether plain vector with swap idiom is 
        // okay here
        maShapeEventMap = ImpShapeEventMap();
    }
    
    void addEvent( const EventSharedPtr& rEvent,
                   const ShapeSharedPtr& rShape )
    {
        ImpShapeEventMap::iterator aIter;
        if( (aIter=maShapeEventMap.find( rShape )) == maShapeEventMap.end() )
        {
            // no entry for this shape -> create one
            aIter = maShapeEventMap.insert(
                ImpShapeEventMap::value_type( rShape,
                                              ImpEventQueue() ) ).first;
        }
        
        // add new event to queue
        aIter->second.push( rEvent );
    }
    
    bool isEmpty()
    {
        // find at least one shape with a non-empty queue
        ImpShapeEventMap::reverse_iterator aCurrShape( maShapeEventMap.begin());
        ImpShapeEventMap::reverse_iterator aEndShape( maShapeEventMap.end() );
        while( aCurrShape != aEndShape )
        {
            if( !aCurrShape->second.empty() )
                return false; // at least one non-empty entry found
            
            ++aCurrShape;
        }
        
        return true; // not a single non-empty entry found
    }
    
protected:
    bool hitTest( const awt::MouseEvent&                e,
                  ImpShapeEventMap::reverse_iterator&   o_rHitShape )
    {
        // find hit shape in map
        const basegfx::B2DPoint aPosition( e.X, e.Y );
        
        // find matching shape (scan reversely, to coarsely match
        // paint order)
        ImpShapeEventMap::reverse_iterator       aCurrShape(maShapeEventMap.rbegin());
        const ImpShapeEventMap::reverse_iterator aEndShape( maShapeEventMap.rend() );
        while( aCurrShape != aEndShape )
        {
            // TODO(F2): Get proper geometry polygon from the
            // shape, to avoid having areas outside the shape
            // react on the mouse
            if( aCurrShape->first->getBounds().isInside( aPosition ) &&
                aCurrShape->first->isVisible() )
            {
                // shape hit, and shape is visible - report a
                // hit
                o_rHitShape = aCurrShape;
                return true;
            }
            
            ++aCurrShape;
        }
        
        return false; // nothing hit
    }
    
    bool sendEvent( ImpShapeEventMap::reverse_iterator& io_rHitShape )
    {
        // take next event from queue
        const bool bRet( fireSingleEvent( io_rHitShape->second,
                                          mrEventQueue ) );
        
        // clear shape entry, if its queue is
        // empty. This is important, since the shapes
        // are held by shared ptr, and might otherwise
        // not get released, even after their owning
        // slide is long gone.
        if( io_rHitShape->second.empty() )
        {
            // this looks funny, since ::std::map does
            // provide an erase( iterator )
            // method. Unfortunately, stlport does not
            // declare the obvious erase(
            // reverse_iterator ) needed here (missing
            // orthogonality, eh?)
            maShapeEventMap.erase( io_rHitShape->first );
        }
        
        return bRet;
    }
    
    bool processEvent( const awt::MouseEvent& e )
    {
        ImpShapeEventMap::reverse_iterator aCurrShape;
        
        if( hitTest( e, aCurrShape ) )
            return sendEvent( aCurrShape );
        
        return false; // did not handle the event
    }
    
private:
    EventQueue&         mrEventQueue;
    ImpShapeEventMap    maShapeEventMap;
};

class ShapeClickEventHandler : public MouseHandlerBase
{
public:
    ShapeClickEventHandler( CursorManager& rCursorManager,
                            EventQueue&    rEventQueue ) :
        MouseHandlerBase( rEventQueue ),
        mrCursorManager( rCursorManager ) 
    {}
    
    virtual bool handleMouseReleased( const awt::MouseEvent& e )
    {
        if(e.Buttons != awt::MouseButton::LEFT)
            return false;
        return processEvent( e );
    }
    
    virtual bool handleMouseMoved( const awt::MouseEvent& e )
    {
        // TODO(P2): Maybe buffer last shape touched
        
        // if we have a shape click event, and the mouse
        // hovers over this shape, change cursor to hand
        ImpShapeEventMap::reverse_iterator aDummy;
        if( hitTest( e, aDummy ) )
            mrCursorManager.requestCursor( awt::SystemPointer::REFHAND );

        return false; // we don't /eat/ this event. Lower prio
        // handler should see it, too.
    }
    
private:
    CursorManager& mrCursorManager;
};

class MouseEnterHandler : public MouseHandlerBase
{
public:
    MouseEnterHandler( EventQueue& rEventQueue )
        : MouseHandlerBase( rEventQueue ),
          mpLastShape() {}
    
    virtual bool handleMouseMoved( const awt::MouseEvent& e )
    {
        // TODO(P2): Maybe buffer last shape touched, and
        // check against that _first_
        
        ImpShapeEventMap::reverse_iterator aCurr;
        if( hitTest( e, aCurr ) )
        {
            if( aCurr->first != mpLastShape )
            {
                // we actually hit a shape, and it's different
                // from the previous one - thus we just
                // entered it, raise event
                sendEvent( aCurr );
                mpLastShape = aCurr->first;
            }
        }
        else
        {
            // don't hit no shape - thus, last shape is NULL
            mpLastShape.reset();
        }
        
        return false; // we don't /eat/ this event. Lower prio
        // handler should see it, too.
    }
    
private:
    ShapeSharedPtr mpLastShape;
};

class MouseLeaveHandler : public MouseHandlerBase
{
public:
    MouseLeaveHandler( EventQueue& rEventQueue )
        : MouseHandlerBase( rEventQueue ),
          maLastIter() {}
    
    virtual bool handleMouseMoved( const awt::MouseEvent& e )
    {
        // TODO(P2): Maybe buffer last shape touched, and
        // check against that _first_
        
        ImpShapeEventMap::reverse_iterator aCurr;
        if( hitTest( e, aCurr ) )
        {
            maLastIter = aCurr;
        }
        else
        {
            if( maLastIter->first )
            {
                // last time, we were over a shape, now we're
                // not - we thus just left that shape, raise
                // event
                sendEvent( maLastIter );
            }
            
            // in any case, when we hit this else-branch: no
            // shape hit, thus have to clear maLastIter
            maLastIter = ImpShapeEventMap::reverse_iterator();
        }
        
        return false; // we don't /eat/ this event. Lower prio
        // handler should see it, too.
    }
    
private:
    ImpShapeEventMap::reverse_iterator maLastIter;
};

template< typename Handler, typename Functor > 
void UserEventQueue::registerEvent(
    boost::shared_ptr< Handler >& rHandler,
    const EventSharedPtr&         rEvent,
    const Functor&                rRegistrationFunctor )
{
    ENSURE_OR_THROW( rEvent,
                      "UserEventQueue::registerEvent(): Invalid event" );
    
    if( !rHandler ) {
        // create handler
        rHandler.reset( new Handler( mrEventQueue ) );        
        // register handler on EventMultiplexer
        rRegistrationFunctor( rHandler );
    }
    
    rHandler->addEvent( rEvent );
}

template< typename Handler, typename Arg, typename Functor > 
void UserEventQueue::registerEvent(
    boost::shared_ptr< Handler >& rHandler,
    const EventSharedPtr&         rEvent,
    const Arg&                    rArg,
    const Functor&                rRegistrationFunctor )
{
    ENSURE_OR_THROW( rEvent,
                      "UserEventQueue::registerEvent(): Invalid event" );
    
    if( !rHandler ) {
        // create handler
        rHandler.reset( new Handler( mrEventQueue ) );
        
        // register handler on EventMultiplexer
        rRegistrationFunctor( rHandler );
    }
    
    rHandler->addEvent( rEvent, rArg );
}


// Public methods
// =====================================================

UserEventQueue::UserEventQueue( EventMultiplexer&   rMultiplexer,
                                EventQueue&         rEventQueue,
                                CursorManager&      rCursorManager )
    : mrMultiplexer( rMultiplexer ),
      mrEventQueue( rEventQueue ),
      mrCursorManager( rCursorManager ),
      mpStartEventHandler(),
      mpEndEventHandler(),
      mpAnimationStartEventHandler(),
      mpAnimationEndEventHandler(),
      mpAudioStoppedEventHandler(),
      mpClickEventHandler(),
      mpSkipEffectEventHandler(),
      mpRewindEffectEventHandler(),
      mpDoubleClickEventHandler(),
      mpMouseEnterHandler(),
      mpMouseLeaveHandler(),      
      mbAdvanceOnClick( true )
{
}

UserEventQueue::~UserEventQueue()
{
    try
    {
        // unregister all handlers  
        clear();
    }
    catch (uno::Exception &) {
        OSL_ENSURE( false, rtl::OUStringToOString(
                        comphelper::anyToString(
                            cppu::getCaughtException() ),
                        RTL_TEXTENCODING_UTF8 ).getStr() );
    }
}
        
bool UserEventQueue::isEmpty() const
{
    // TODO(T2): This is not thread safe, the handlers are all 
    // only separately synchronized. This poses the danger of 
    // generating false empty status on XSlideShow::update(), such
    // that the last events of a slide are not triggered.
    
    // we're empty iff all handler queues are empty
    return 
        (mpStartEventHandler ? mpStartEventHandler->isEmpty() : true) &&
        (mpEndEventHandler ? mpEndEventHandler->isEmpty() : true) &&
        (mpAnimationStartEventHandler ? mpAnimationStartEventHandler->isEmpty() : true) &&
        (mpAnimationEndEventHandler ? mpAnimationEndEventHandler->isEmpty() : true) &&
        (mpAudioStoppedEventHandler ? mpAudioStoppedEventHandler->isEmpty() : true) &&
        (mpShapeClickEventHandler ? mpShapeClickEventHandler->isEmpty() : true) &&
        (mpClickEventHandler ? mpClickEventHandler->isEmpty() : true) &&
        (mpSkipEffectEventHandler ? mpSkipEffectEventHandler->isEmpty() : true) &&
        (mpRewindEffectEventHandler ? mpRewindEffectEventHandler->isEmpty() : true) &&
        (mpShapeDoubleClickEventHandler ? mpShapeDoubleClickEventHandler->isEmpty() : true) &&
        (mpDoubleClickEventHandler ? mpDoubleClickEventHandler->isEmpty() : true) &&
        (mpMouseEnterHandler ? mpMouseEnterHandler->isEmpty() : true) &&
        (mpMouseLeaveHandler ? mpMouseLeaveHandler->isEmpty() : true);
}

void UserEventQueue::clear()
{
    // unregister and delete all handlers
    if( mpStartEventHandler ) {
        mrMultiplexer.removeSlideStartHandler( mpStartEventHandler );
        mpStartEventHandler.reset();
    }
    if( mpEndEventHandler ) {
        mrMultiplexer.removeSlideEndHandler( mpEndEventHandler );
        mpEndEventHandler.reset();
    }
    if( mpAnimationStartEventHandler ) {
        mrMultiplexer.removeAnimationStartHandler(
            mpAnimationStartEventHandler );
        mpAnimationStartEventHandler.reset();
    }
    if( mpAnimationEndEventHandler ) {
        mrMultiplexer.removeAnimationEndHandler( mpAnimationEndEventHandler );
        mpAnimationEndEventHandler.reset();
    }
    if( mpAudioStoppedEventHandler ) {
        mrMultiplexer.removeAudioStoppedHandler( mpAudioStoppedEventHandler );
        mpAudioStoppedEventHandler.reset();
    }
    if( mpShapeClickEventHandler ) {
        mrMultiplexer.removeClickHandler( mpShapeClickEventHandler );
        mrMultiplexer.removeMouseMoveHandler( mpShapeClickEventHandler );
        mpShapeClickEventHandler.reset();
    }
    if( mpClickEventHandler ) {
        mrMultiplexer.removeClickHandler( mpClickEventHandler );
        mrMultiplexer.removeNextEffectHandler( mpClickEventHandler );
        mpClickEventHandler.reset();
    }
    if(mpSkipEffectEventHandler) {
        mrMultiplexer.removeClickHandler( mpSkipEffectEventHandler );
        mrMultiplexer.removeNextEffectHandler( mpSkipEffectEventHandler );
        mpSkipEffectEventHandler.reset();
    }
    if(mpRewindEffectEventHandler) {
        mrMultiplexer.removeClickHandler( mpRewindEffectEventHandler );
        mpRewindEffectEventHandler.reset();
    }
    if( mpShapeDoubleClickEventHandler ) {
        mrMultiplexer.removeDoubleClickHandler( mpShapeDoubleClickEventHandler );
        mrMultiplexer.removeMouseMoveHandler( mpShapeDoubleClickEventHandler );
        mpShapeDoubleClickEventHandler.reset();
    }
    if( mpDoubleClickEventHandler ) {
        mrMultiplexer.removeDoubleClickHandler( mpDoubleClickEventHandler );
        mpDoubleClickEventHandler.reset();
    }
    if( mpMouseEnterHandler ) {
        mrMultiplexer.removeMouseMoveHandler( mpMouseEnterHandler );
        mpMouseEnterHandler.reset();
    }
    if( mpMouseLeaveHandler ) {
        mrMultiplexer.removeMouseMoveHandler( mpMouseLeaveHandler );
        mpMouseLeaveHandler.reset();
    }
}

void UserEventQueue::setAdvanceOnClick( bool bAdvanceOnClick )
{
    mbAdvanceOnClick = bAdvanceOnClick;
    
    // forward to handler, if existing. Otherwise, the handler
    // creation will do the forwarding.
    if( mpClickEventHandler )
        mpClickEventHandler->setAdvanceOnClick( bAdvanceOnClick );
}


void UserEventQueue::registerSlideStartEvent( const EventSharedPtr& rEvent )
{
    registerEvent( mpStartEventHandler, 
                   rEvent,
                   boost::bind( &EventMultiplexer::addSlideStartHandler,
                                boost::ref( mrMultiplexer ), _1 ) );
}

void UserEventQueue::registerSlideEndEvent( const EventSharedPtr& rEvent )
{
    registerEvent( mpEndEventHandler, 
                   rEvent,
                   boost::bind( &EventMultiplexer::addSlideEndHandler,
                                boost::ref( mrMultiplexer ), _1 ) );
}

void UserEventQueue::registerAnimationStartEvent(
    const EventSharedPtr&                                 rEvent,
    const uno::Reference< animations::XAnimationNode>&    xNode )
{
    registerEvent( mpAnimationStartEventHandler, 
                   rEvent,
                   xNode,
                   boost::bind( &EventMultiplexer::addAnimationStartHandler,
                                boost::ref( mrMultiplexer ), _1 ) );
}

void UserEventQueue::registerAnimationEndEvent(
    const EventSharedPtr&                               rEvent,
    const uno::Reference<animations::XAnimationNode>&   xNode )
{
    registerEvent( mpAnimationEndEventHandler, 
                   rEvent,
                   xNode,
                   boost::bind( &EventMultiplexer::addAnimationEndHandler,
                                boost::ref( mrMultiplexer ), _1 ) );
}

void UserEventQueue::registerAudioStoppedEvent(
    const EventSharedPtr&                               rEvent,
    const uno::Reference<animations::XAnimationNode>&   xNode )
{
    registerEvent( mpAudioStoppedEventHandler, 
                   rEvent,
                   xNode,
                   boost::bind( &EventMultiplexer::addAudioStoppedHandler,
                                boost::ref( mrMultiplexer ), _1 ) );
}

void UserEventQueue::registerShapeClickEvent( const EventSharedPtr& rEvent,
                                              const ShapeSharedPtr& rShape )
{
    ENSURE_OR_THROW(
        rEvent,
        "UserEventQueue::registerShapeClickEvent(): Invalid event" );
    
    if( !mpShapeClickEventHandler )
    {
        // create handler
        mpShapeClickEventHandler.reset(
            new ShapeClickEventHandler(mrCursorManager,
                                       mrEventQueue) );
        
        // register handler on EventMultiplexer
        mrMultiplexer.addClickHandler( mpShapeClickEventHandler, 1.0 );
        mrMultiplexer.addMouseMoveHandler( mpShapeClickEventHandler, 1.0 );
    }
    
    mpShapeClickEventHandler->addEvent( rEvent, rShape );
}

namespace {
class ClickEventRegistrationFunctor
{
public:
    ClickEventRegistrationFunctor( EventMultiplexer& rMultiplexer,
                                   double            nPrio,
                                   bool              bAdvanceOnClick )
        : mrMultiplexer( rMultiplexer ),
          mnPrio(nPrio),
          mbAdvanceOnClick( bAdvanceOnClick ) {}
    
    void operator()( const boost::shared_ptr<ClickEventHandler>& rHandler )const
    {
        // register the handler on _two_ sources: we want the
        // nextEffect events, e.g. space bar, to trigger clicks, as well!
        mrMultiplexer.addClickHandler( rHandler, mnPrio );
        mrMultiplexer.addNextEffectHandler( rHandler, mnPrio );
        
        // forward advance-on-click state to newly
        // generated handler (that's the only reason why
        // we're called here)
        rHandler->setAdvanceOnClick( mbAdvanceOnClick );
    }
    
private:
    EventMultiplexer&   mrMultiplexer;
    double const        mnPrio;
    bool const          mbAdvanceOnClick;
};
} // anon namespace

void UserEventQueue::registerNextEffectEvent( const EventSharedPtr& rEvent )
{
    // TODO: better name may be mpNextEffectEventHandler?  then we have
    //       next effect (=> waiting to be started)
    //       skip effect (skipping the currently running one)
    //       rewind effect (rewinding back running one and waiting (again)
    //                      to be started)
    registerEvent( mpClickEventHandler, 
                   rEvent,
                   ClickEventRegistrationFunctor( mrMultiplexer,
                                                  0.0 /* default prio */,
                                                  mbAdvanceOnClick ) );
}

void UserEventQueue::registerSkipEffectEvent(
    EventSharedPtr const & pEvent,
    const bool bSkipTriggersNextEffect)
{
    if(!mpSkipEffectEventHandler) 
    {
        mpSkipEffectEventHandler.reset(
            new SkipEffectEventHandler( mrEventQueue, mrMultiplexer ) );
        // register the handler on _two_ sources: we want the
        // nextEffect events, e.g. space bar, to trigger clicks, as well!
        mrMultiplexer.addClickHandler( mpSkipEffectEventHandler,
                                       -1.0 /* prio below default */ );
        mrMultiplexer.addNextEffectHandler( mpSkipEffectEventHandler,
                                            -1.0 /* prio below default */ );
        // forward advance-on-click state to newly
        // generated handler (that's the only reason why
        // we're called here)
        mpSkipEffectEventHandler->setAdvanceOnClick( mbAdvanceOnClick );
    }
    mpSkipEffectEventHandler->setSkipTriggersNextEffect(bSkipTriggersNextEffect);
    mpSkipEffectEventHandler->addEvent( pEvent );
}

void UserEventQueue::registerRewindEffectEvent( EventSharedPtr const& pEvent )
{
    registerEvent( mpRewindEffectEventHandler,
                   pEvent,
                   boost::bind( &EventMultiplexer::addClickHandler,
                                boost::ref(mrMultiplexer), _1,
                                -1.0 /* prio below default */ ) );
}

void UserEventQueue::registerShapeDoubleClickEvent(
    const EventSharedPtr& rEvent,
    const ShapeSharedPtr& rShape )
{
    ENSURE_OR_THROW(
        rEvent,
        "UserEventQueue::registerShapeDoubleClickEvent(): Invalid event" );
    
    if( !mpShapeDoubleClickEventHandler )
    {
        // create handler
        mpShapeDoubleClickEventHandler.reset(
            new ShapeClickEventHandler(mrCursorManager,
                                       mrEventQueue) );
        
        // register handler on EventMultiplexer
        mrMultiplexer.addDoubleClickHandler( mpShapeDoubleClickEventHandler,
                                             1.0 );
        mrMultiplexer.addMouseMoveHandler( mpShapeDoubleClickEventHandler,
                                           1.0 );
    }
    
    mpShapeDoubleClickEventHandler->addEvent( rEvent, rShape );
}

void UserEventQueue::registerDoubleClickEvent( const EventSharedPtr& rEvent )
{
    registerEvent( mpDoubleClickEventHandler, 
                   rEvent,
                   boost::bind( &EventMultiplexer::addDoubleClickHandler,
                                boost::ref( mrMultiplexer ), _1,
                                0.0 /* default prio */ ) );
}

void UserEventQueue::registerMouseEnterEvent( const EventSharedPtr& rEvent,
                                              const ShapeSharedPtr& rShape )
{
    registerEvent( mpMouseEnterHandler, 
                   rEvent,
                   rShape,
                   boost::bind( &EventMultiplexer::addMouseMoveHandler,
                                boost::ref( mrMultiplexer ), _1,
                                0.0 /* default prio */ ) );
}

void UserEventQueue::registerMouseLeaveEvent( const EventSharedPtr& rEvent,
                                              const ShapeSharedPtr& rShape )
{
    registerEvent( mpMouseLeaveHandler, 
                   rEvent,
                   rShape,
                   boost::bind( &EventMultiplexer::addMouseMoveHandler,
                                boost::ref( mrMultiplexer ), _1,
                                0.0 /* default prio */ ) );
}

void UserEventQueue::callSkipEffectEventHandler (void)
{
    ::boost::shared_ptr<SkipEffectEventHandler> pHandler (
        ::boost::dynamic_pointer_cast<SkipEffectEventHandler>(mpSkipEffectEventHandler));
    if (pHandler)
        pHandler->skipEffect();
}

} // namespace internal
} // namespace presentation