diff options
Diffstat (limited to 'framework/source/fwe/helper/undomanagerhelper.cxx')
-rwxr-xr-x | framework/source/fwe/helper/undomanagerhelper.cxx | 1165 |
1 files changed, 1165 insertions, 0 deletions
diff --git a/framework/source/fwe/helper/undomanagerhelper.cxx b/framework/source/fwe/helper/undomanagerhelper.cxx new file mode 100755 index 000000000000..891504adbe71 --- /dev/null +++ b/framework/source/fwe/helper/undomanagerhelper.cxx @@ -0,0 +1,1165 @@ +/************************************************************************* + * 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. + * + ************************************************************************/ + +#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; + ::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_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 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::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() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_pImpl->getMutex() ); + + IUndoManager& rUndoManager = m_pImpl->getUndoManager(); + rUndoManager.EnableUndo( false ); + // <--- SYNCHRONIZED + } + + //------------------------------------------------------------------------------------------------------------------ + void UndoManagerHelper::unlock() + { + // SYNCHRONIZED ---> + ::osl::MutexGuard aGuard( m_pImpl->getMutex() ); + + IUndoManager& rUndoManager = m_pImpl->getUndoManager(); + if ( rUndoManager.IsUndoEnabled() ) + throw NotLockedException( ::rtl::OUString::createFromAscii( "Undo manager is not locked" ), m_pImpl->getXUndoManager() ); + rUndoManager.EnableUndo( true ); + // <--- SYNCHRONIZED + } + + //------------------------------------------------------------------------------------------------------------------ + ::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 +//...................................................................................................................... |