/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



#include "precompiled_framework.hxx"

#include "framework/undomanagerhelper.hxx"

/** === begin UNO includes === **/
#include <com/sun/star/lang/XComponent.hpp>
/** === end UNO includes === **/

#include <cppuhelper/interfacecontainer.hxx>
#include <cppuhelper/exc_hlp.hxx>
#include <comphelper/flagguard.hxx>
#include <comphelper/asyncnotification.hxx>
#include <svl/undo.hxx>
#include <tools/diagnose_ex.h>
#include <osl/conditn.hxx>

#include <stack>
#include <queue>
#include <boost/function.hpp>

//......................................................................................................................
namespace framework
{
//......................................................................................................................

	/** === begin UNO using === **/
	using ::com::sun::star::uno::Reference;
	using ::com::sun::star::uno::XInterface;
	using ::com::sun::star::uno::UNO_QUERY;
	using ::com::sun::star::uno::UNO_QUERY_THROW;
	using ::com::sun::star::uno::UNO_SET_THROW;
	using ::com::sun::star::uno::Exception;
	using ::com::sun::star::uno::RuntimeException;
	using ::com::sun::star::uno::Any;
	using ::com::sun::star::uno::makeAny;
	using ::com::sun::star::uno::Sequence;
	using ::com::sun::star::uno::Type;
    using ::com::sun::star::document::XUndoManagerListener;
    using ::com::sun::star::document::UndoManagerEvent;
    using ::com::sun::star::document::EmptyUndoStackException;
    using ::com::sun::star::document::UndoContextNotClosedException;
    using ::com::sun::star::document::UndoFailedException;
    using ::com::sun::star::util::NotLockedException;
    using ::com::sun::star::lang::EventObject;
    using ::com::sun::star::document::XUndoAction;
    using ::com::sun::star::lang::XComponent;
    using ::com::sun::star::document::XUndoManager;
    using ::com::sun::star::util::InvalidStateException;
    using ::com::sun::star::lang::IllegalArgumentException;
    using ::com::sun::star::util::XModifyListener;
	/** === end UNO using === **/
    using ::svl::IUndoManager;

	//==================================================================================================================
	//= UndoActionWrapper
	//==================================================================================================================
    class UndoActionWrapper : public SfxUndoAction
    {
    public:
                            UndoActionWrapper(
                                Reference< XUndoAction > const& i_undoAction
                            );
        virtual             ~UndoActionWrapper();

	    virtual String      GetComment() const;
        virtual void        Undo();
        virtual void        Redo();
        virtual sal_Bool    CanRepeat(SfxRepeatTarget&) const;

    private:
        const Reference< XUndoAction >  m_xUndoAction;
    };

	//------------------------------------------------------------------------------------------------------------------
    UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction )
        :SfxUndoAction()
        ,m_xUndoAction( i_undoAction )
    {
        ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" );
    }

	//------------------------------------------------------------------------------------------------------------------
    UndoActionWrapper::~UndoActionWrapper()
    {
        try
        {
            Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY );
            if ( xComponent.is() )
                xComponent->dispose();
        }
        catch( const Exception& )
        {
        	DBG_UNHANDLED_EXCEPTION();
        }
    }

	//------------------------------------------------------------------------------------------------------------------
    String UndoActionWrapper::GetComment() const
    {
        String sComment;
        try
        {
            sComment = m_xUndoAction->getTitle();
        }
        catch( const Exception& )
        {
        	DBG_UNHANDLED_EXCEPTION();
        }
        return sComment;
    }

	//------------------------------------------------------------------------------------------------------------------
    void UndoActionWrapper::Undo()
    {
        m_xUndoAction->undo();
    }

	//------------------------------------------------------------------------------------------------------------------
    void UndoActionWrapper::Redo()
    {
        m_xUndoAction->redo();
    }

	//------------------------------------------------------------------------------------------------------------------
    sal_Bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const
    {
        return sal_False;
    }

	//==================================================================================================================
	//= UndoManagerRequest
	//==================================================================================================================
    class UndoManagerRequest : public ::comphelper::AnyEvent
    {
    public:
        UndoManagerRequest( ::boost::function0< void > const& i_request )
            :m_request( i_request )
            ,m_caughtException()
            ,m_finishCondition()
        {
            m_finishCondition.reset();
        }

        void execute()
        {
            try
            {
                m_request();
            }
            catch( const Exception& )
            {
                m_caughtException = ::cppu::getCaughtException();
            }
            m_finishCondition.set();
        }

        void wait()
        {
            m_finishCondition.wait();
            if ( m_caughtException.hasValue() )
                ::cppu::throwException( m_caughtException );
        }

        void cancel( const Reference< XInterface >& i_context )
        {
            m_caughtException <<= RuntimeException(
                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Concurrency error: an ealier operation on the stack failed." ) ),
                i_context
            );
            m_finishCondition.set();
        }

    protected:
        ~UndoManagerRequest()
        {
        }

    private:
        ::boost::function0< void >  m_request;
        Any                         m_caughtException;
        ::osl::Condition            m_finishCondition;
    };

	//------------------------------------------------------------------------------------------------------------------

	//==================================================================================================================
	//= UndoManagerHelper_Impl
	//==================================================================================================================
    class UndoManagerHelper_Impl : public SfxUndoListener
    {
    private:
        ::osl::Mutex                        m_aMutex;
        ::osl::Mutex                        m_aQueueMutex;
        bool                                m_disposed;
        bool                                m_bAPIActionRunning;
        bool                                m_bProcessingEvents;
        sal_Int32                           m_nLockCount;
        ::cppu::OInterfaceContainerHelper   m_aUndoListeners;
        ::cppu::OInterfaceContainerHelper   m_aModifyListeners;
        IUndoManagerImplementation&         m_rUndoManagerImplementation;
        UndoManagerHelper&                  m_rAntiImpl;
        ::std::stack< bool >                m_aContextVisibilities;
#if OSL_DEBUG_LEVEL > 0
        ::std::stack< bool >                m_aContextAPIFlags;
#endif
        ::std::queue< ::rtl::Reference< UndoManagerRequest > >
                                            m_aEventQueue;

    public:
        ::osl::Mutex&   getMutex() { return m_aMutex; }

    public:
        UndoManagerHelper_Impl( UndoManagerHelper& i_antiImpl, IUndoManagerImplementation& i_undoManagerImpl )
            :m_aMutex()
            ,m_aQueueMutex()
            ,m_disposed( false )
            ,m_bAPIActionRunning( false )
            ,m_bProcessingEvents( false )
            ,m_nLockCount( 0 )
            ,m_aUndoListeners( m_aMutex )
            ,m_aModifyListeners( m_aMutex )
            ,m_rUndoManagerImplementation( i_undoManagerImpl )
            ,m_rAntiImpl( i_antiImpl )
        {
            getUndoManager().AddUndoListener( *this );
        }

        virtual ~UndoManagerHelper_Impl()
        {
        }

        //..............................................................................................................
        IUndoManager& getUndoManager() const
        {
            return m_rUndoManagerImplementation.getImplUndoManager();
        }

        //..............................................................................................................
        Reference< XUndoManager > getXUndoManager() const
        {
            return m_rUndoManagerImplementation.getThis();
        }

        // SfxUndoListener
        virtual void actionUndone( const String& i_actionComment );
        virtual void actionRedone( const String& i_actionComment );
        virtual void undoActionAdded( const String& i_actionComment );
        virtual void cleared();
        virtual void clearedRedo();
        virtual void resetAll();
        virtual void listActionEntered( const String& i_comment );
        virtual void listActionLeft( const String& i_comment );
        virtual void listActionLeftAndMerged();
        virtual void listActionCancelled();
        virtual void undoManagerDying();

        // public operations
        void disposing();

        void enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock );
        void leaveUndoContext( IMutexGuard& i_instanceLock );
        void addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock );
        void undo( IMutexGuard& i_instanceLock );
        void redo( IMutexGuard& i_instanceLock );
        void clear( IMutexGuard& i_instanceLock );
        void clearRedo( IMutexGuard& i_instanceLock );
        void reset( IMutexGuard& i_instanceLock );

        void lock();
        void unlock();

        void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
        {
            m_aUndoListeners.addInterface( i_listener );
        }

        void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
        {
            m_aUndoListeners.removeInterface( i_listener );
        }

        void addModifyListener( const Reference< XModifyListener >& i_listener )
        {
            m_aModifyListeners.addInterface( i_listener );
        }

        void removeModifyListener( const Reference< XModifyListener >& i_listener )
        {
            m_aModifyListeners.removeInterface( i_listener );
        }

        UndoManagerEvent
            buildEvent( ::rtl::OUString const& i_title ) const;

        void impl_notifyModified();
        void notify(    ::rtl::OUString const& i_title,
                        void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& )
                    );
        void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
        {
            notify( ::rtl::OUString(), i_notificationMethod );
        }

        void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) );

    private:
        /// adds a function to be called to the request processor's queue
        void impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock );

        /// impl-versions of the XUndoManager API.
        void impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden );
        void impl_leaveUndoContext();
        void impl_addUndoAction( const Reference< XUndoAction >& i_action );
        void impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo );
        void impl_clear();
        void impl_clearRedo();
        void impl_reset();
    };

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::disposing()
    {
        EventObject aEvent;
        aEvent.Source = getXUndoManager();
        m_aUndoListeners.disposeAndClear( aEvent );
        m_aModifyListeners.disposeAndClear( aEvent );

        ::osl::MutexGuard aGuard( m_aMutex );

        getUndoManager().RemoveUndoListener( *this );

        m_disposed = true;
    }

    //------------------------------------------------------------------------------------------------------------------
    UndoManagerEvent UndoManagerHelper_Impl::buildEvent( ::rtl::OUString const& i_title ) const
    {
        UndoManagerEvent aEvent;
        aEvent.Source = getXUndoManager();
        aEvent.UndoActionTitle = i_title;
        aEvent.UndoContextDepth = getUndoManager().GetListActionDepth();
        return aEvent;
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_notifyModified()
    {
        const EventObject aEvent( getXUndoManager() );
        m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::notify( ::rtl::OUString const& i_title,
        void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
    {
        const UndoManagerEvent aEvent( buildEvent( i_title ) );

        // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we
        // receive from the IUndoManager. Those notitications are sent with a locked SolarMutex, which means
        // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM).
        // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead
        // to problems of its own, since clients might expect synchronous notifications.

        m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) )
    {
        const EventObject aEvent( getXUndoManager() );

        // TODO: the same comment as in the other notify, regarding SM locking applies here ...

        m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_enterUndoContext,
                this,
                ::boost::cref( i_title ),
                i_hidden
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_leaveUndoContext,
                this
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
    {
        if ( !i_action.is() )
            throw IllegalArgumentException(
                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "illegal undo action object" ) ),
                getXUndoManager(),
                1
            );

        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_addUndoAction,
                this,
                ::boost::ref( i_action )
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_clear,
                this
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_clearRedo,
                this
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_reset,
                this
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::lock()
    {
        // SYNCHRONIZED --->
        ::osl::MutexGuard aGuard( getMutex() );

        if ( ++m_nLockCount == 1 )
        {
            IUndoManager& rUndoManager = getUndoManager();
            rUndoManager.EnableUndo( false );
        }
        // <--- SYNCHRONIZED
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::unlock()
    {
        // SYNCHRONIZED --->
        ::osl::MutexGuard aGuard( getMutex() );

        if ( m_nLockCount == 0 )
            throw NotLockedException( ::rtl::OUString::createFromAscii( "Undo manager is not locked" ), getXUndoManager() );

        if ( --m_nLockCount == 0 )
        {
            IUndoManager& rUndoManager = getUndoManager();
            rUndoManager.EnableUndo( true );
        }
        // <--- SYNCHRONIZED
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock )
    {
        // create the request, and add it to our queue
        ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) );
        {
            ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
            m_aEventQueue.push( pRequest );
        }

        i_instanceLock.clear();

        if ( m_bProcessingEvents )
        {
            // another thread is processing the event queue currently => it will also process the event which we just added
            pRequest->wait();
            return;
        }

        m_bProcessingEvents = true;
        do
        {
            pRequest.clear();
            {
                ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
                if ( m_aEventQueue.empty() )
                {
                    // reset the flag before releasing the queue mutex, otherwise it's possible that another thread
                    // could add an event after we release the mutex, but before we reset the flag. If then this other
                    // thread checks the flag before be reset it, this thread's event would starve.
                    m_bProcessingEvents = false;
                    return;
                }
                pRequest = m_aEventQueue.front();
                m_aEventQueue.pop();
            }
            try
            {
                pRequest->execute();
                pRequest->wait();
            }
            catch( ... )
            {
                {
                    // no chance to process further requests, if the current one failed
                    // => discard them
                    ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
                    while ( !m_aEventQueue.empty() )
                    {
                        pRequest = m_aEventQueue.front();
                        m_aEventQueue.pop();
                        pRequest->cancel( getXUndoManager() );
                    }
                    m_bProcessingEvents = false;
                }
                // re-throw the error
                throw;
            }
        }
        while ( true );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden )
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( !rUndoManager.IsUndoEnabled() )
            // ignore this request if the manager is locked
            return;

        if ( i_hidden && ( rUndoManager.GetUndoActionCount( IUndoManager::CurrentLevel ) == 0 ) )
            throw EmptyUndoStackException(
                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "can't enter a hidden context without a previous Undo action" ) ),
                m_rUndoManagerImplementation.getThis()
            );

        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            rUndoManager.EnterListAction( i_title, ::rtl::OUString() );
        }

        m_aContextVisibilities.push( i_hidden );

        const UndoManagerEvent aEvent( buildEvent( i_title ) );
        aGuard.clear();
        // <--- SYNCHRONIZED

        m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_leaveUndoContext()
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( !rUndoManager.IsUndoEnabled() )
            // ignore this request if the manager is locked
            return;

        if ( !rUndoManager.IsInListAction() )
            throw InvalidStateException(
                ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no active undo context" ) ),
                getXUndoManager()
            );

        size_t nContextElements = 0;

        const bool isHiddenContext = m_aContextVisibilities.top();;
        m_aContextVisibilities.pop();

        const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );
        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            if ( isHiddenContext )
                nContextElements = rUndoManager.LeaveAndMergeListAction();
            else
                nContextElements = rUndoManager.LeaveListAction();
        }
        const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );

        // prepare notification
        void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = NULL;

        UndoManagerEvent aContextEvent( buildEvent( ::rtl::OUString() ) );
        const EventObject aClearedEvent( getXUndoManager() );
        if ( nContextElements == 0 )
        {
            notificationMethod = &XUndoManagerListener::cancelledContext;
        }
        else if ( isHiddenContext )
        {
            notificationMethod = &XUndoManagerListener::leftHiddenContext;
        }
        else
        {
            aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment( 0, IUndoManager::CurrentLevel );
            notificationMethod = &XUndoManagerListener::leftContext;
        }

        aGuard.clear();
        // <--- SYNCHRONIZED

        if ( bHadRedoActions && !bHasRedoActions )
            m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent );
        m_aUndoListeners.notifyEach( notificationMethod, aContextEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo )
    {
        ::osl::Guard< ::framework::IMutex > aExternalGuard( i_externalLock.getGuardedMutex() );
            // note that this assumes that the mutex has been released in the thread which added the
            // Undo/Redo request, so we can successfully acquire it

        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( rUndoManager.IsInListAction() )
            throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );

        const size_t nElements  =   i_undo
                                ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
                                :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
        if ( nElements == 0 )
            throw EmptyUndoStackException( ::rtl::OUString::createFromAscii( "stack is empty" ), getXUndoManager() );

        aGuard.clear();
        // <--- SYNCHRONIZED

        try
        {
            if ( i_undo )
                rUndoManager.Undo();
            else
                rUndoManager.Redo();
        }
        catch( const RuntimeException& ) { /* allowed to leave here */ throw; }
        catch( const UndoFailedException& ) { /* allowed to leave here */ throw; }
        catch( const Exception& )
        {
            // not allowed to leave
            const Any aError( ::cppu::getCaughtException() );
            throw UndoFailedException( ::rtl::OUString(), getXUndoManager(), aError );
        }

        // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling
        // into the IUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also
        // called without our mutex being locked.
        // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods
        // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This
        // again is different from all other SfxUndoListener methods).
        // So, we do not need to do this notification here ourself.
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action )
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( !rUndoManager.IsUndoEnabled() )
            // ignore the request if the manager is locked
            return;

        const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) );
        const EventObject aEventClear( getXUndoManager() );

        const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );
        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            rUndoManager.AddUndoAction( new UndoActionWrapper( i_action ) );
        }
        const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );

        aGuard.clear();
        // <--- SYNCHRONIZED

        m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd );
        if ( bHadRedoActions && !bHasRedoActions )
            m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_clear()
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( rUndoManager.IsInListAction() )
            throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );

        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            rUndoManager.Clear();
        }

        const EventObject aEvent( getXUndoManager() );
        aGuard.clear();
        // <--- SYNCHRONIZED

        m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_clearRedo()
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        if ( rUndoManager.IsInListAction() )
            throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );

        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            rUndoManager.ClearRedo();
        }

        const EventObject aEvent( getXUndoManager() );
        aGuard.clear();
        // <--- SYNCHRONIZED

        m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::impl_reset()
    {
        // SYNCHRONIZED --->
        ::osl::ClearableMutexGuard aGuard( m_aMutex );

        IUndoManager& rUndoManager = getUndoManager();
        {
            ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
            rUndoManager.Reset();
        }

        const EventObject aEvent( getXUndoManager() );
        aGuard.clear();
        // <--- SYNCHRONIZED

        m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent );
        impl_notifyModified();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::actionUndone( const String& i_actionComment )
    {
        UndoManagerEvent aEvent;
        aEvent.Source = getXUndoManager();
        aEvent.UndoActionTitle = i_actionComment;
        aEvent.UndoContextDepth = 0;    // Undo can happen on level 0 only
        m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent );
        impl_notifyModified();
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::actionRedone( const String& i_actionComment )
    {
        UndoManagerEvent aEvent;
        aEvent.Source = getXUndoManager();
        aEvent.UndoActionTitle = i_actionComment;
        aEvent.UndoContextDepth = 0;    // Redo can happen on level 0 only
        m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent );
        impl_notifyModified();
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::undoActionAdded( const String& i_actionComment )
    {
        if ( m_bAPIActionRunning )
            return;

        notify( i_actionComment, &XUndoManagerListener::undoActionAdded );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::cleared()
    {
        if ( m_bAPIActionRunning )
            return;

        notify( &XUndoManagerListener::allActionsCleared );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::clearedRedo()
    {
        if ( m_bAPIActionRunning )
            return;

        notify( &XUndoManagerListener::redoActionsCleared );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::resetAll()
    {
        if ( m_bAPIActionRunning )
            return;

        notify( &XUndoManagerListener::resetAll );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::listActionEntered( const String& i_comment )
    {
#if OSL_DEBUG_LEVEL > 0
        m_aContextAPIFlags.push( m_bAPIActionRunning );
#endif

        if ( m_bAPIActionRunning )
            return;

        notify( i_comment, &XUndoManagerListener::enteredContext );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::listActionLeft( const String& i_comment )
    {
#if OSL_DEBUG_LEVEL > 0
        const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
        m_aContextAPIFlags.pop();
        OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" );
#endif

        if ( m_bAPIActionRunning )
            return;

        notify( i_comment, &XUndoManagerListener::leftContext );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::listActionLeftAndMerged()
    {
#if OSL_DEBUG_LEVEL > 0
        const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
        m_aContextAPIFlags.pop();
        OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeftAndMerged: API and non-API contexts interwoven!" );
#endif

        if ( m_bAPIActionRunning )
            return;

        notify( &XUndoManagerListener::leftHiddenContext );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::listActionCancelled()
    {
#if OSL_DEBUG_LEVEL > 0
        const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
        m_aContextAPIFlags.pop();
        OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" );
#endif

        if ( m_bAPIActionRunning )
            return;

        notify( &XUndoManagerListener::cancelledContext );
    }

 	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::undoManagerDying()
    {
        // TODO: do we need to care? Or is this the responsibility of our owner?
    }

	//==================================================================================================================
	//= UndoManagerHelper
	//==================================================================================================================
	//------------------------------------------------------------------------------------------------------------------
    UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl )
        :m_pImpl( new UndoManagerHelper_Impl( *this, i_undoManagerImpl ) )
    {
    }

	//------------------------------------------------------------------------------------------------------------------
    UndoManagerHelper::~UndoManagerHelper()
    {
    }

	//------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::disposing()
    {
        m_pImpl->disposing();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::enterUndoContext( const ::rtl::OUString& i_title, IMutexGuard& i_instanceLock )
    {
        m_pImpl->enterUndoContext( i_title, false, i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock )
    {
        m_pImpl->enterUndoContext( ::rtl::OUString(), true, i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock )
    {
        m_pImpl->leaveUndoContext( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::undo( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_doUndoRedo,
                this,
                ::boost::ref( i_instanceLock ),
                true
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock )
    {
        impl_processRequest(
            ::boost::bind(
                &UndoManagerHelper_Impl::impl_doUndoRedo,
                this,
                ::boost::ref( i_instanceLock ),
                false
            ),
            i_instanceLock
        );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
    {
        m_pImpl->addUndoAction( i_action, i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::undo( IMutexGuard& i_instanceLock )
    {
        m_pImpl->undo( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::redo( IMutexGuard& i_instanceLock )
    {
        m_pImpl->redo( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    ::sal_Bool UndoManagerHelper::isUndoPossible() const
    {
        // SYNCHRONIZED --->
        ::osl::MutexGuard aGuard( m_pImpl->getMutex() );
        IUndoManager& rUndoManager = m_pImpl->getUndoManager();
        if ( rUndoManager.IsInListAction() )
            return sal_False;
        return rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) > 0;
        // <--- SYNCHRONIZED
    }
    
    //------------------------------------------------------------------------------------------------------------------
    ::sal_Bool UndoManagerHelper::isRedoPossible() const
    {
        // SYNCHRONIZED --->
        ::osl::MutexGuard aGuard( m_pImpl->getMutex() );
        const IUndoManager& rUndoManager = m_pImpl->getUndoManager();
        if ( rUndoManager.IsInListAction() )
            return sal_False;
        return rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0;
        // <--- SYNCHRONIZED
    }
    
    //------------------------------------------------------------------------------------------------------------------
    namespace
    {
        //..............................................................................................................
        ::rtl::OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo )
        {
            // SYNCHRONIZED --->
            ::osl::MutexGuard aGuard( i_impl.getMutex() );

            const IUndoManager& rUndoManager = i_impl.getUndoManager();
            const size_t nActionCount = i_undo
                                    ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
                                    :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
            if ( nActionCount == 0 )
                throw EmptyUndoStackException(
                    i_undo ? ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the undo stack" ) )
                           : ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the redo stack" ) ),
                    i_impl.getXUndoManager()
                );
            return  i_undo
                ?   rUndoManager.GetUndoActionComment( 0, IUndoManager::TopLevel )
                :   rUndoManager.GetRedoActionComment( 0, IUndoManager::TopLevel );
            // <--- SYNCHRONIZED
        }

        //..............................................................................................................
        Sequence< ::rtl::OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo )
        {
            // SYNCHRONIZED --->
            ::osl::MutexGuard aGuard( i_impl.getMutex() );

            const IUndoManager& rUndoManager = i_impl.getUndoManager();
            const size_t nCount =   i_undo
                                ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
                                :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );

            Sequence< ::rtl::OUString > aTitles( nCount );
            for ( size_t i=0; i<nCount; ++i )
            {
                aTitles[i] =    i_undo
                            ?   rUndoManager.GetUndoActionComment( i, IUndoManager::TopLevel )
                            :   rUndoManager.GetRedoActionComment( i, IUndoManager::TopLevel );
            }
            return aTitles;
            // <--- SYNCHRONIZED
        }
    }

    //------------------------------------------------------------------------------------------------------------------
    ::rtl::OUString UndoManagerHelper::getCurrentUndoActionTitle() const
    {
        return lcl_getCurrentActionTitle( *m_pImpl, true );
    }
    
    //------------------------------------------------------------------------------------------------------------------
    ::rtl::OUString UndoManagerHelper::getCurrentRedoActionTitle() const
    {
        return lcl_getCurrentActionTitle( *m_pImpl, false );
    }
    
    //------------------------------------------------------------------------------------------------------------------
    Sequence< ::rtl::OUString > UndoManagerHelper::getAllUndoActionTitles() const
    {
        return lcl_getAllActionTitles( *m_pImpl, true );
    }
    
    //------------------------------------------------------------------------------------------------------------------
    Sequence< ::rtl::OUString > UndoManagerHelper::getAllRedoActionTitles() const
    {
        return lcl_getAllActionTitles( *m_pImpl, false );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::clear( IMutexGuard& i_instanceLock )
    {
        m_pImpl->clear( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock )
    {
        m_pImpl->clearRedo( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::reset( IMutexGuard& i_instanceLock )
    {
        m_pImpl->reset( i_instanceLock );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::lock()
    {
        m_pImpl->lock();
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::unlock()
    {
        m_pImpl->unlock();
    }

    //------------------------------------------------------------------------------------------------------------------
    ::sal_Bool UndoManagerHelper::isLocked()
    {
        // SYNCHRONIZED --->
        ::osl::MutexGuard aGuard( m_pImpl->getMutex() );

        IUndoManager& rUndoManager = m_pImpl->getUndoManager();
        return !rUndoManager.IsUndoEnabled();
        // <--- SYNCHRONIZED
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
    {
        if ( i_listener.is() )
            m_pImpl->addUndoManagerListener( i_listener );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
    {
        if ( i_listener.is() )
            m_pImpl->removeUndoManagerListener( i_listener );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener )
    {
        if ( i_listener.is() )
            m_pImpl->addModifyListener( i_listener );
    }

    //------------------------------------------------------------------------------------------------------------------
    void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener )
    {
        if ( i_listener.is() )
            m_pImpl->removeModifyListener( i_listener );
    }

//......................................................................................................................
} // namespace framework
//......................................................................................................................
