/************************************************************** * * 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 /** === end UNO includes === **/ #include #include #include #include #include #include #include #include #include #include //...................................................................................................................... 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 earlier 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 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 //......................................................................................................................