diff options
Diffstat (limited to '')
-rw-r--r-- | slideshow/source/engine/usereventqueue.cxx | 791 |
1 files changed, 791 insertions, 0 deletions
diff --git a/slideshow/source/engine/usereventqueue.cxx b/slideshow/source/engine/usereventqueue.cxx new file mode 100644 index 000000000..bd3e1820e --- /dev/null +++ b/slideshow/source/engine/usereventqueue.cxx @@ -0,0 +1,791 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <tools/diagnose_ex.h> + +#include <com/sun/star/awt/SystemPointer.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/MouseEvent.hpp> + +#include <delayevent.hxx> +#include <usereventqueue.hxx> +#include <cursormanager.hxx> + +#include <vector> +#include <queue> +#include <map> +#include <algorithm> + + +using namespace com::sun::star; + +/* Implementation of UserEventQueue class */ + +namespace slideshow::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*/ ) override { return false;} + virtual bool handleMouseReleased( awt::MouseEvent const& /*e*/) override { return false;} + virtual bool handleMouseDragged( awt::MouseEvent const& /*e*/ ) override { return false;} + virtual bool handleMouseMoved( awt::MouseEvent const& /*e*/ ) override { 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 addEvent( const EventSharedPtr& rEvent ) + { + maEvents.push( rEvent ); + } + +protected: + ImpEventQueue maEvents; +}; + +} // anon namespace + +class AllAnimationEventHandler : public AnimationEventHandler +{ +public: + explicit AllAnimationEventHandler( EventQueue& rEventQueue ) : + mrEventQueue( rEventQueue ), + maAnimationEventMap() + {} + + virtual bool handleAnimationEvent( const AnimationNodeSharedPtr& rNode ) override + { + 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 + for( const auto& pEvent : rVec ) + mrEventQueue.addEvent( pEvent ); + + 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.emplace( xNode, ImpEventVector() ).first; + } + + // add new event to queue + aIter->second.push_back( rEvent ); + } + +private: + EventQueue& mrEventQueue; + ImpAnimationEventMap maAnimationEventMap; +}; + +class ClickEventHandler : public MouseEventHandler_, + public EventHandler, + public EventContainer +{ +public: + explicit ClickEventHandler( EventQueue& rEventQueue ) : + EventContainer(), + mrEventQueue( rEventQueue ), + mbAdvanceOnClick( true ) + {} + + void setAdvanceOnClick( bool bAdvanceOnClick ) + { + mbAdvanceOnClick = bAdvanceOnClick; + } + +private: + + // triggered by API calls, e.g. space bar + virtual bool handleEvent() override + { + return handleEvent_impl(); + } + + // triggered by mouse release: + virtual bool handleMouseReleased( const awt::MouseEvent& evt ) override + { + 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 skipped. + */ + void setSkipTriggersNextEffect (const bool bSkipTriggersNextEffect) + { mbSkipTriggersNextEffect = bSkipTriggersNextEffect; } + + /// Skip the current effect but do not trigger the next effect. + void skipEffect() { handleEvent_impl(false); } + +private: + virtual bool handleEvent_impl() override + { + 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 registered above for next effects + // (multiplexer prio=0) at the user event queue. + return mrEventQueue.addEventWhenQueueIsEmpty( + makeEvent( [this] () { + this->mrEventMultiplexer.notifyNextEffect(); + }, "EventMultiplexer::notifyNextEffect") ); + } + else + return true; + } + return false; + } + +private: + EventQueue & mrEventQueue; + EventMultiplexer & mrEventMultiplexer; + bool mbSkipTriggersNextEffect; +}; + +namespace { + +/** 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: + explicit MouseHandlerBase( EventQueue& rEventQueue ) : + mrEventQueue( rEventQueue ), + maShapeEventMap() + {} + + 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.emplace(rShape, ImpEventQueue()).first; + } + + // add new event to queue + aIter->second.push( rEvent ); + } + +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) + auto aCurrShape = std::find_if(maShapeEventMap.rbegin(), maShapeEventMap.rend(), + [&aPosition](const ImpShapeEventMap::value_type& rShape) { + // TODO(F2): Get proper geometry polygon from the + // shape, to avoid having areas outside the shape + // react on the mouse + return rShape.first->getBounds().isInside( aPosition ) + && rShape.first->isVisible(); + }); + if (aCurrShape != maShapeEventMap.rend()) + { + // shape hit, and shape is visible - report a + // hit + o_rHitShape = aCurrShape; + return true; + } + + return false; // nothing hit + } + + bool sendEvent( ImpShapeEventMap::reverse_iterator const & 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, C++ 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 ) override + { + if(e.Buttons != awt::MouseButton::LEFT) + return false; + return processEvent( e ); + } + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // 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: + explicit MouseEnterHandler( EventQueue& rEventQueue ) + : MouseHandlerBase( rEventQueue ), + mpLastShape() {} + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // 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: + explicit MouseLeaveHandler( EventQueue& rEventQueue ) + : MouseHandlerBase( rEventQueue ), + maLastIter() {} + + virtual bool handleMouseMoved( const awt::MouseEvent& e ) override + { + // 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( + std::shared_ptr< Handler >& rHandler, + const EventSharedPtr& rEvent, + const Functor& rRegistrationFunctor ) +{ + ENSURE_OR_THROW( rEvent, + "UserEventQueue::registerEvent(): Invalid event" ); + + if( !rHandler ) { + // create handler + rHandler = std::make_shared<Handler>( mrEventQueue ); + // register handler on EventMultiplexer + rRegistrationFunctor( rHandler ); + } + + rHandler->addEvent( rEvent ); +} + +template< typename Handler, typename Arg, typename Functor > +void UserEventQueue::registerEvent( + std::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 = std::make_shared<Handler>( mrEventQueue ); + + // register handler on EventMultiplexer + rRegistrationFunctor( rHandler ); + } + + rHandler->addEvent( rEvent, rArg ); +} + + +UserEventQueue::UserEventQueue( EventMultiplexer& rMultiplexer, + EventQueue& rEventQueue, + CursorManager& rCursorManager ) + : mrMultiplexer( rMultiplexer ), + mrEventQueue( rEventQueue ), + mrCursorManager( rCursorManager ), + mpAnimationStartEventHandler(), + mpAnimationEndEventHandler(), + mpAudioStoppedEventHandler(), + mpClickEventHandler(), + mpSkipEffectEventHandler(), + mpMouseEnterHandler(), + mpMouseLeaveHandler(), + mbAdvanceOnClick( true ) +{ +} + +UserEventQueue::~UserEventQueue() +{ + try + { + // unregister all handlers + clear(); + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("slideshow", ""); + } +} + +void UserEventQueue::clear() +{ + // unregister and delete all handlers + 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( mpShapeDoubleClickEventHandler ) { + mrMultiplexer.removeDoubleClickHandler( mpShapeDoubleClickEventHandler ); + mrMultiplexer.removeMouseMoveHandler( mpShapeDoubleClickEventHandler ); + mpShapeDoubleClickEventHandler.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::registerAnimationStartEvent( + const EventSharedPtr& rEvent, + const uno::Reference< animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAnimationStartEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAnimationStartHandler( rHandler ); } ); +} + +void UserEventQueue::registerAnimationEndEvent( + const EventSharedPtr& rEvent, + const uno::Reference<animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAnimationEndEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAnimationEndHandler( rHandler ); } ); +} + +void UserEventQueue::registerAudioStoppedEvent( + const EventSharedPtr& rEvent, + const uno::Reference<animations::XAnimationNode>& xNode ) +{ + registerEvent( mpAudioStoppedEventHandler, + rEvent, + xNode, + [this]( const AnimationEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addAudioStoppedHandler( rHandler ); } ); +} + +void UserEventQueue::registerShapeClickEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + ENSURE_OR_THROW( + rEvent, + "UserEventQueue::registerShapeClickEvent(): Invalid event" ); + + if( !mpShapeClickEventHandler ) + { + // create handler + mpShapeClickEventHandler = + std::make_shared<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 std::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 = + std::make_shared<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::registerShapeDoubleClickEvent( + const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + ENSURE_OR_THROW( + rEvent, + "UserEventQueue::registerShapeDoubleClickEvent(): Invalid event" ); + + if( !mpShapeDoubleClickEventHandler ) + { + // create handler + mpShapeDoubleClickEventHandler = + std::make_shared<ShapeClickEventHandler>(mrCursorManager, + mrEventQueue); + + // register handler on EventMultiplexer + mrMultiplexer.addDoubleClickHandler( mpShapeDoubleClickEventHandler, + 1.0 ); + mrMultiplexer.addMouseMoveHandler( mpShapeDoubleClickEventHandler, + 1.0 ); + } + + mpShapeDoubleClickEventHandler->addEvent( rEvent, rShape ); +} + +void UserEventQueue::registerMouseEnterEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + registerEvent( mpMouseEnterHandler, + rEvent, + rShape, + [this]( const MouseEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addMouseMoveHandler( rHandler, 0.0 ); } ); +} + +void UserEventQueue::registerMouseLeaveEvent( const EventSharedPtr& rEvent, + const ShapeSharedPtr& rShape ) +{ + registerEvent( mpMouseLeaveHandler, + rEvent, + rShape, + [this]( const MouseEventHandlerSharedPtr& rHandler ) + { return this->mrMultiplexer.addMouseMoveHandler( rHandler, 0.0 ); } ); +} + +void UserEventQueue::callSkipEffectEventHandler() +{ + ::std::shared_ptr<SkipEffectEventHandler> pHandler ( + ::std::dynamic_pointer_cast<SkipEffectEventHandler>(mpSkipEffectEventHandler)); + if (pHandler) + pHandler->skipEffect(); +} + +} // namespace presentation + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |