1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 #include "precompiled_framework.hxx"
25 
26 #include "framework/undomanagerhelper.hxx"
27 
28 /** === begin UNO includes === **/
29 #include <com/sun/star/lang/XComponent.hpp>
30 /** === end UNO includes === **/
31 
32 #include <cppuhelper/interfacecontainer.hxx>
33 #include <cppuhelper/exc_hlp.hxx>
34 #include <comphelper/flagguard.hxx>
35 #include <comphelper/asyncnotification.hxx>
36 #include <svl/undo.hxx>
37 #include <tools/diagnose_ex.h>
38 #include <osl/conditn.hxx>
39 
40 #include <stack>
41 #include <queue>
42 #include <boost/function.hpp>
43 
44 //......................................................................................................................
45 namespace framework
46 {
47 //......................................................................................................................
48 
49 	/** === begin UNO using === **/
50 	using ::com::sun::star::uno::Reference;
51 	using ::com::sun::star::uno::XInterface;
52 	using ::com::sun::star::uno::UNO_QUERY;
53 	using ::com::sun::star::uno::UNO_QUERY_THROW;
54 	using ::com::sun::star::uno::UNO_SET_THROW;
55 	using ::com::sun::star::uno::Exception;
56 	using ::com::sun::star::uno::RuntimeException;
57 	using ::com::sun::star::uno::Any;
58 	using ::com::sun::star::uno::makeAny;
59 	using ::com::sun::star::uno::Sequence;
60 	using ::com::sun::star::uno::Type;
61     using ::com::sun::star::document::XUndoManagerListener;
62     using ::com::sun::star::document::UndoManagerEvent;
63     using ::com::sun::star::document::EmptyUndoStackException;
64     using ::com::sun::star::document::UndoContextNotClosedException;
65     using ::com::sun::star::document::UndoFailedException;
66     using ::com::sun::star::util::NotLockedException;
67     using ::com::sun::star::lang::EventObject;
68     using ::com::sun::star::document::XUndoAction;
69     using ::com::sun::star::lang::XComponent;
70     using ::com::sun::star::document::XUndoManager;
71     using ::com::sun::star::util::InvalidStateException;
72     using ::com::sun::star::lang::IllegalArgumentException;
73     using ::com::sun::star::util::XModifyListener;
74 	/** === end UNO using === **/
75     using ::svl::IUndoManager;
76 
77 	//==================================================================================================================
78 	//= UndoActionWrapper
79 	//==================================================================================================================
80     class UndoActionWrapper : public SfxUndoAction
81     {
82     public:
83                             UndoActionWrapper(
84                                 Reference< XUndoAction > const& i_undoAction
85                             );
86         virtual             ~UndoActionWrapper();
87 
88 	    virtual String      GetComment() const;
89         virtual void        Undo();
90         virtual void        Redo();
91         virtual sal_Bool    CanRepeat(SfxRepeatTarget&) const;
92 
93     private:
94         const Reference< XUndoAction >  m_xUndoAction;
95     };
96 
97 	//------------------------------------------------------------------------------------------------------------------
98     UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction )
99         :SfxUndoAction()
100         ,m_xUndoAction( i_undoAction )
101     {
102         ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" );
103     }
104 
105 	//------------------------------------------------------------------------------------------------------------------
106     UndoActionWrapper::~UndoActionWrapper()
107     {
108         try
109         {
110             Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY );
111             if ( xComponent.is() )
112                 xComponent->dispose();
113         }
114         catch( const Exception& )
115         {
116         	DBG_UNHANDLED_EXCEPTION();
117         }
118     }
119 
120 	//------------------------------------------------------------------------------------------------------------------
121     String UndoActionWrapper::GetComment() const
122     {
123         String sComment;
124         try
125         {
126             sComment = m_xUndoAction->getTitle();
127         }
128         catch( const Exception& )
129         {
130         	DBG_UNHANDLED_EXCEPTION();
131         }
132         return sComment;
133     }
134 
135 	//------------------------------------------------------------------------------------------------------------------
136     void UndoActionWrapper::Undo()
137     {
138         m_xUndoAction->undo();
139     }
140 
141 	//------------------------------------------------------------------------------------------------------------------
142     void UndoActionWrapper::Redo()
143     {
144         m_xUndoAction->redo();
145     }
146 
147 	//------------------------------------------------------------------------------------------------------------------
148     sal_Bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const
149     {
150         return sal_False;
151     }
152 
153 	//==================================================================================================================
154 	//= UndoManagerRequest
155 	//==================================================================================================================
156     class UndoManagerRequest : public ::comphelper::AnyEvent
157     {
158     public:
159         UndoManagerRequest( ::boost::function0< void > const& i_request )
160             :m_request( i_request )
161             ,m_caughtException()
162             ,m_finishCondition()
163         {
164             m_finishCondition.reset();
165         }
166 
167         void execute()
168         {
169             try
170             {
171                 m_request();
172             }
173             catch( const Exception& )
174             {
175                 m_caughtException = ::cppu::getCaughtException();
176             }
177             m_finishCondition.set();
178         }
179 
180         void wait()
181         {
182             m_finishCondition.wait();
183             if ( m_caughtException.hasValue() )
184                 ::cppu::throwException( m_caughtException );
185         }
186 
187         void cancel( const Reference< XInterface >& i_context )
188         {
189             m_caughtException <<= RuntimeException(
190                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Concurrency error: an ealier operation on the stack failed." ) ),
191                 i_context
192             );
193             m_finishCondition.set();
194         }
195 
196     protected:
197         ~UndoManagerRequest()
198         {
199         }
200 
201     private:
202         ::boost::function0< void >  m_request;
203         Any                         m_caughtException;
204         ::osl::Condition            m_finishCondition;
205     };
206 
207 	//------------------------------------------------------------------------------------------------------------------
208 
209 	//==================================================================================================================
210 	//= UndoManagerHelper_Impl
211 	//==================================================================================================================
212     class UndoManagerHelper_Impl : public SfxUndoListener
213     {
214     private:
215         ::osl::Mutex                        m_aMutex;
216         ::osl::Mutex                        m_aQueueMutex;
217         bool                                m_disposed;
218         bool                                m_bAPIActionRunning;
219         bool                                m_bProcessingEvents;
220         sal_Int32                           m_nLockCount;
221         ::cppu::OInterfaceContainerHelper   m_aUndoListeners;
222         ::cppu::OInterfaceContainerHelper   m_aModifyListeners;
223         IUndoManagerImplementation&         m_rUndoManagerImplementation;
224         UndoManagerHelper&                  m_rAntiImpl;
225         ::std::stack< bool >                m_aContextVisibilities;
226 #if OSL_DEBUG_LEVEL > 0
227         ::std::stack< bool >                m_aContextAPIFlags;
228 #endif
229         ::std::queue< ::rtl::Reference< UndoManagerRequest > >
230                                             m_aEventQueue;
231 
232     public:
233         ::osl::Mutex&   getMutex() { return m_aMutex; }
234 
235     public:
236         UndoManagerHelper_Impl( UndoManagerHelper& i_antiImpl, IUndoManagerImplementation& i_undoManagerImpl )
237             :m_aMutex()
238             ,m_aQueueMutex()
239             ,m_disposed( false )
240             ,m_bAPIActionRunning( false )
241             ,m_bProcessingEvents( false )
242             ,m_nLockCount( 0 )
243             ,m_aUndoListeners( m_aMutex )
244             ,m_aModifyListeners( m_aMutex )
245             ,m_rUndoManagerImplementation( i_undoManagerImpl )
246             ,m_rAntiImpl( i_antiImpl )
247         {
248             getUndoManager().AddUndoListener( *this );
249         }
250 
251         virtual ~UndoManagerHelper_Impl()
252         {
253         }
254 
255         //..............................................................................................................
256         IUndoManager& getUndoManager() const
257         {
258             return m_rUndoManagerImplementation.getImplUndoManager();
259         }
260 
261         //..............................................................................................................
262         Reference< XUndoManager > getXUndoManager() const
263         {
264             return m_rUndoManagerImplementation.getThis();
265         }
266 
267         // SfxUndoListener
268         virtual void actionUndone( const String& i_actionComment );
269         virtual void actionRedone( const String& i_actionComment );
270         virtual void undoActionAdded( const String& i_actionComment );
271         virtual void cleared();
272         virtual void clearedRedo();
273         virtual void resetAll();
274         virtual void listActionEntered( const String& i_comment );
275         virtual void listActionLeft( const String& i_comment );
276         virtual void listActionLeftAndMerged();
277         virtual void listActionCancelled();
278         virtual void undoManagerDying();
279 
280         // public operations
281         void disposing();
282 
283         void enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock );
284         void leaveUndoContext( IMutexGuard& i_instanceLock );
285         void addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock );
286         void undo( IMutexGuard& i_instanceLock );
287         void redo( IMutexGuard& i_instanceLock );
288         void clear( IMutexGuard& i_instanceLock );
289         void clearRedo( IMutexGuard& i_instanceLock );
290         void reset( IMutexGuard& i_instanceLock );
291 
292         void lock();
293         void unlock();
294 
295         void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
296         {
297             m_aUndoListeners.addInterface( i_listener );
298         }
299 
300         void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
301         {
302             m_aUndoListeners.removeInterface( i_listener );
303         }
304 
305         void addModifyListener( const Reference< XModifyListener >& i_listener )
306         {
307             m_aModifyListeners.addInterface( i_listener );
308         }
309 
310         void removeModifyListener( const Reference< XModifyListener >& i_listener )
311         {
312             m_aModifyListeners.removeInterface( i_listener );
313         }
314 
315         UndoManagerEvent
316             buildEvent( ::rtl::OUString const& i_title ) const;
317 
318         void impl_notifyModified();
319         void notify(    ::rtl::OUString const& i_title,
320                         void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& )
321                     );
322         void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
323         {
324             notify( ::rtl::OUString(), i_notificationMethod );
325         }
326 
327         void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) );
328 
329     private:
330         /// adds a function to be called to the request processor's queue
331         void impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock );
332 
333         /// impl-versions of the XUndoManager API.
334         void impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden );
335         void impl_leaveUndoContext();
336         void impl_addUndoAction( const Reference< XUndoAction >& i_action );
337         void impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo );
338         void impl_clear();
339         void impl_clearRedo();
340         void impl_reset();
341     };
342 
343     //------------------------------------------------------------------------------------------------------------------
344     void UndoManagerHelper_Impl::disposing()
345     {
346         EventObject aEvent;
347         aEvent.Source = getXUndoManager();
348         m_aUndoListeners.disposeAndClear( aEvent );
349         m_aModifyListeners.disposeAndClear( aEvent );
350 
351         ::osl::MutexGuard aGuard( m_aMutex );
352 
353         getUndoManager().RemoveUndoListener( *this );
354 
355         m_disposed = true;
356     }
357 
358     //------------------------------------------------------------------------------------------------------------------
359     UndoManagerEvent UndoManagerHelper_Impl::buildEvent( ::rtl::OUString const& i_title ) const
360     {
361         UndoManagerEvent aEvent;
362         aEvent.Source = getXUndoManager();
363         aEvent.UndoActionTitle = i_title;
364         aEvent.UndoContextDepth = getUndoManager().GetListActionDepth();
365         return aEvent;
366     }
367 
368     //------------------------------------------------------------------------------------------------------------------
369     void UndoManagerHelper_Impl::impl_notifyModified()
370     {
371         const EventObject aEvent( getXUndoManager() );
372         m_aModifyListeners.notifyEach( &XModifyListener::modified, aEvent );
373     }
374 
375     //------------------------------------------------------------------------------------------------------------------
376     void UndoManagerHelper_Impl::notify( ::rtl::OUString const& i_title,
377         void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
378     {
379         const UndoManagerEvent aEvent( buildEvent( i_title ) );
380 
381         // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we
382         // receive from the IUndoManager. Those notitications are sent with a locked SolarMutex, which means
383         // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM).
384         // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead
385         // to problems of its own, since clients might expect synchronous notifications.
386 
387         m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
388         impl_notifyModified();
389     }
390 
391     //------------------------------------------------------------------------------------------------------------------
392     void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) )
393     {
394         const EventObject aEvent( getXUndoManager() );
395 
396         // TODO: the same comment as in the other notify, regarding SM locking applies here ...
397 
398         m_aUndoListeners.notifyEach( i_notificationMethod, aEvent );
399         impl_notifyModified();
400     }
401 
402     //------------------------------------------------------------------------------------------------------------------
403     void UndoManagerHelper_Impl::enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock )
404     {
405         impl_processRequest(
406             ::boost::bind(
407                 &UndoManagerHelper_Impl::impl_enterUndoContext,
408                 this,
409                 ::boost::cref( i_title ),
410                 i_hidden
411             ),
412             i_instanceLock
413         );
414     }
415 
416     //------------------------------------------------------------------------------------------------------------------
417     void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock )
418     {
419         impl_processRequest(
420             ::boost::bind(
421                 &UndoManagerHelper_Impl::impl_leaveUndoContext,
422                 this
423             ),
424             i_instanceLock
425         );
426     }
427 
428     //------------------------------------------------------------------------------------------------------------------
429     void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
430     {
431         if ( !i_action.is() )
432             throw IllegalArgumentException(
433                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "illegal undo action object" ) ),
434                 getXUndoManager(),
435                 1
436             );
437 
438         impl_processRequest(
439             ::boost::bind(
440                 &UndoManagerHelper_Impl::impl_addUndoAction,
441                 this,
442                 ::boost::ref( i_action )
443             ),
444             i_instanceLock
445         );
446     }
447 
448     //------------------------------------------------------------------------------------------------------------------
449     void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock )
450     {
451         impl_processRequest(
452             ::boost::bind(
453                 &UndoManagerHelper_Impl::impl_clear,
454                 this
455             ),
456             i_instanceLock
457         );
458     }
459 
460     //------------------------------------------------------------------------------------------------------------------
461     void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock )
462     {
463         impl_processRequest(
464             ::boost::bind(
465                 &UndoManagerHelper_Impl::impl_clearRedo,
466                 this
467             ),
468             i_instanceLock
469         );
470     }
471 
472     //------------------------------------------------------------------------------------------------------------------
473     void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock )
474     {
475         impl_processRequest(
476             ::boost::bind(
477                 &UndoManagerHelper_Impl::impl_reset,
478                 this
479             ),
480             i_instanceLock
481         );
482     }
483 
484     //------------------------------------------------------------------------------------------------------------------
485     void UndoManagerHelper_Impl::lock()
486     {
487         // SYNCHRONIZED --->
488         ::osl::MutexGuard aGuard( getMutex() );
489 
490         if ( ++m_nLockCount == 1 )
491         {
492             IUndoManager& rUndoManager = getUndoManager();
493             rUndoManager.EnableUndo( false );
494         }
495         // <--- SYNCHRONIZED
496     }
497 
498     //------------------------------------------------------------------------------------------------------------------
499     void UndoManagerHelper_Impl::unlock()
500     {
501         // SYNCHRONIZED --->
502         ::osl::MutexGuard aGuard( getMutex() );
503 
504         if ( m_nLockCount == 0 )
505             throw NotLockedException( ::rtl::OUString::createFromAscii( "Undo manager is not locked" ), getXUndoManager() );
506 
507         if ( --m_nLockCount == 0 )
508         {
509             IUndoManager& rUndoManager = getUndoManager();
510             rUndoManager.EnableUndo( true );
511         }
512         // <--- SYNCHRONIZED
513     }
514 
515     //------------------------------------------------------------------------------------------------------------------
516     void UndoManagerHelper_Impl::impl_processRequest( ::boost::function0< void > const& i_request, IMutexGuard& i_instanceLock )
517     {
518         // create the request, and add it to our queue
519         ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) );
520         {
521             ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
522             m_aEventQueue.push( pRequest );
523         }
524 
525         i_instanceLock.clear();
526 
527         if ( m_bProcessingEvents )
528         {
529             // another thread is processing the event queue currently => it will also process the event which we just added
530             pRequest->wait();
531             return;
532         }
533 
534         m_bProcessingEvents = true;
535         do
536         {
537             pRequest.clear();
538             {
539                 ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
540                 if ( m_aEventQueue.empty() )
541                 {
542                     // reset the flag before releasing the queue mutex, otherwise it's possible that another thread
543                     // could add an event after we release the mutex, but before we reset the flag. If then this other
544                     // thread checks the flag before be reset it, this thread's event would starve.
545                     m_bProcessingEvents = false;
546                     return;
547                 }
548                 pRequest = m_aEventQueue.front();
549                 m_aEventQueue.pop();
550             }
551             try
552             {
553                 pRequest->execute();
554                 pRequest->wait();
555             }
556             catch( ... )
557             {
558                 {
559                     // no chance to process further requests, if the current one failed
560                     // => discard them
561                     ::osl::MutexGuard aQueueGuard( m_aQueueMutex );
562                     while ( !m_aEventQueue.empty() )
563                     {
564                         pRequest = m_aEventQueue.front();
565                         m_aEventQueue.pop();
566                         pRequest->cancel( getXUndoManager() );
567                     }
568                     m_bProcessingEvents = false;
569                 }
570                 // re-throw the error
571                 throw;
572             }
573         }
574         while ( true );
575     }
576 
577     //------------------------------------------------------------------------------------------------------------------
578     void UndoManagerHelper_Impl::impl_enterUndoContext( const ::rtl::OUString& i_title, const bool i_hidden )
579     {
580         // SYNCHRONIZED --->
581         ::osl::ClearableMutexGuard aGuard( m_aMutex );
582 
583         IUndoManager& rUndoManager = getUndoManager();
584         if ( !rUndoManager.IsUndoEnabled() )
585             // ignore this request if the manager is locked
586             return;
587 
588         if ( i_hidden && ( rUndoManager.GetUndoActionCount( IUndoManager::CurrentLevel ) == 0 ) )
589             throw EmptyUndoStackException(
590                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "can't enter a hidden context without a previous Undo action" ) ),
591                 m_rUndoManagerImplementation.getThis()
592             );
593 
594         {
595             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
596             rUndoManager.EnterListAction( i_title, ::rtl::OUString() );
597         }
598 
599         m_aContextVisibilities.push( i_hidden );
600 
601         const UndoManagerEvent aEvent( buildEvent( i_title ) );
602         aGuard.clear();
603         // <--- SYNCHRONIZED
604 
605         m_aUndoListeners.notifyEach( i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent );
606         impl_notifyModified();
607     }
608 
609     //------------------------------------------------------------------------------------------------------------------
610     void UndoManagerHelper_Impl::impl_leaveUndoContext()
611     {
612         // SYNCHRONIZED --->
613         ::osl::ClearableMutexGuard aGuard( m_aMutex );
614 
615         IUndoManager& rUndoManager = getUndoManager();
616         if ( !rUndoManager.IsUndoEnabled() )
617             // ignore this request if the manager is locked
618             return;
619 
620         if ( !rUndoManager.IsInListAction() )
621             throw InvalidStateException(
622                 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no active undo context" ) ),
623                 getXUndoManager()
624             );
625 
626         size_t nContextElements = 0;
627 
628         const bool isHiddenContext = m_aContextVisibilities.top();;
629         m_aContextVisibilities.pop();
630 
631         const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );
632         {
633             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
634             if ( isHiddenContext )
635                 nContextElements = rUndoManager.LeaveAndMergeListAction();
636             else
637                 nContextElements = rUndoManager.LeaveListAction();
638         }
639         const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0 );
640 
641         // prepare notification
642         void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = NULL;
643 
644         UndoManagerEvent aContextEvent( buildEvent( ::rtl::OUString() ) );
645         const EventObject aClearedEvent( getXUndoManager() );
646         if ( nContextElements == 0 )
647         {
648             notificationMethod = &XUndoManagerListener::cancelledContext;
649         }
650         else if ( isHiddenContext )
651         {
652             notificationMethod = &XUndoManagerListener::leftHiddenContext;
653         }
654         else
655         {
656             aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment( 0, IUndoManager::CurrentLevel );
657             notificationMethod = &XUndoManagerListener::leftContext;
658         }
659 
660         aGuard.clear();
661         // <--- SYNCHRONIZED
662 
663         if ( bHadRedoActions && !bHasRedoActions )
664             m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aClearedEvent );
665         m_aUndoListeners.notifyEach( notificationMethod, aContextEvent );
666         impl_notifyModified();
667     }
668 
669     //------------------------------------------------------------------------------------------------------------------
670     void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo )
671     {
672         ::osl::Guard< ::framework::IMutex > aExternalGuard( i_externalLock.getGuardedMutex() );
673             // note that this assumes that the mutex has been released in the thread which added the
674             // Undo/Redo request, so we can successfully acquire it
675 
676         // SYNCHRONIZED --->
677         ::osl::ClearableMutexGuard aGuard( m_aMutex );
678 
679         IUndoManager& rUndoManager = getUndoManager();
680         if ( rUndoManager.IsInListAction() )
681             throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );
682 
683         const size_t nElements  =   i_undo
684                                 ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
685                                 :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
686         if ( nElements == 0 )
687             throw EmptyUndoStackException( ::rtl::OUString::createFromAscii( "stack is empty" ), getXUndoManager() );
688 
689         aGuard.clear();
690         // <--- SYNCHRONIZED
691 
692         try
693         {
694             if ( i_undo )
695                 rUndoManager.Undo();
696             else
697                 rUndoManager.Redo();
698         }
699         catch( const RuntimeException& ) { /* allowed to leave here */ throw; }
700         catch( const UndoFailedException& ) { /* allowed to leave here */ throw; }
701         catch( const Exception& )
702         {
703             // not allowed to leave
704             const Any aError( ::cppu::getCaughtException() );
705             throw UndoFailedException( ::rtl::OUString(), getXUndoManager(), aError );
706         }
707 
708         // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling
709         // into the IUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also
710         // called without our mutex being locked.
711         // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods
712         // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This
713         // again is different from all other SfxUndoListener methods).
714         // So, we do not need to do this notification here ourself.
715     }
716 
717     //------------------------------------------------------------------------------------------------------------------
718     void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action )
719     {
720         // SYNCHRONIZED --->
721         ::osl::ClearableMutexGuard aGuard( m_aMutex );
722 
723         IUndoManager& rUndoManager = getUndoManager();
724         if ( !rUndoManager.IsUndoEnabled() )
725             // ignore the request if the manager is locked
726             return;
727 
728         const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) );
729         const EventObject aEventClear( getXUndoManager() );
730 
731         const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );
732         {
733             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
734             rUndoManager.AddUndoAction( new UndoActionWrapper( i_action ) );
735         }
736         const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( IUndoManager::CurrentLevel ) > 0 );
737 
738         aGuard.clear();
739         // <--- SYNCHRONIZED
740 
741         m_aUndoListeners.notifyEach( &XUndoManagerListener::undoActionAdded, aEventAdd );
742         if ( bHadRedoActions && !bHasRedoActions )
743             m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEventClear );
744         impl_notifyModified();
745     }
746 
747     //------------------------------------------------------------------------------------------------------------------
748     void UndoManagerHelper_Impl::impl_clear()
749     {
750         // SYNCHRONIZED --->
751         ::osl::ClearableMutexGuard aGuard( m_aMutex );
752 
753         IUndoManager& rUndoManager = getUndoManager();
754         if ( rUndoManager.IsInListAction() )
755             throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );
756 
757         {
758             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
759             rUndoManager.Clear();
760         }
761 
762         const EventObject aEvent( getXUndoManager() );
763         aGuard.clear();
764         // <--- SYNCHRONIZED
765 
766         m_aUndoListeners.notifyEach( &XUndoManagerListener::allActionsCleared, aEvent );
767         impl_notifyModified();
768     }
769 
770     //------------------------------------------------------------------------------------------------------------------
771     void UndoManagerHelper_Impl::impl_clearRedo()
772     {
773         // SYNCHRONIZED --->
774         ::osl::ClearableMutexGuard aGuard( m_aMutex );
775 
776         IUndoManager& rUndoManager = getUndoManager();
777         if ( rUndoManager.IsInListAction() )
778             throw UndoContextNotClosedException( ::rtl::OUString(), getXUndoManager() );
779 
780         {
781             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
782             rUndoManager.ClearRedo();
783         }
784 
785         const EventObject aEvent( getXUndoManager() );
786         aGuard.clear();
787         // <--- SYNCHRONIZED
788 
789         m_aUndoListeners.notifyEach( &XUndoManagerListener::redoActionsCleared, aEvent );
790         impl_notifyModified();
791     }
792 
793     //------------------------------------------------------------------------------------------------------------------
794     void UndoManagerHelper_Impl::impl_reset()
795     {
796         // SYNCHRONIZED --->
797         ::osl::ClearableMutexGuard aGuard( m_aMutex );
798 
799         IUndoManager& rUndoManager = getUndoManager();
800         {
801             ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
802             rUndoManager.Reset();
803         }
804 
805         const EventObject aEvent( getXUndoManager() );
806         aGuard.clear();
807         // <--- SYNCHRONIZED
808 
809         m_aUndoListeners.notifyEach( &XUndoManagerListener::resetAll, aEvent );
810         impl_notifyModified();
811     }
812 
813     //------------------------------------------------------------------------------------------------------------------
814     void UndoManagerHelper_Impl::actionUndone( const String& i_actionComment )
815     {
816         UndoManagerEvent aEvent;
817         aEvent.Source = getXUndoManager();
818         aEvent.UndoActionTitle = i_actionComment;
819         aEvent.UndoContextDepth = 0;    // Undo can happen on level 0 only
820         m_aUndoListeners.notifyEach( &XUndoManagerListener::actionUndone, aEvent );
821         impl_notifyModified();
822     }
823 
824  	//------------------------------------------------------------------------------------------------------------------
825     void UndoManagerHelper_Impl::actionRedone( const String& i_actionComment )
826     {
827         UndoManagerEvent aEvent;
828         aEvent.Source = getXUndoManager();
829         aEvent.UndoActionTitle = i_actionComment;
830         aEvent.UndoContextDepth = 0;    // Redo can happen on level 0 only
831         m_aUndoListeners.notifyEach( &XUndoManagerListener::actionRedone, aEvent );
832         impl_notifyModified();
833     }
834 
835  	//------------------------------------------------------------------------------------------------------------------
836     void UndoManagerHelper_Impl::undoActionAdded( const String& i_actionComment )
837     {
838         if ( m_bAPIActionRunning )
839             return;
840 
841         notify( i_actionComment, &XUndoManagerListener::undoActionAdded );
842     }
843 
844  	//------------------------------------------------------------------------------------------------------------------
845     void UndoManagerHelper_Impl::cleared()
846     {
847         if ( m_bAPIActionRunning )
848             return;
849 
850         notify( &XUndoManagerListener::allActionsCleared );
851     }
852 
853  	//------------------------------------------------------------------------------------------------------------------
854     void UndoManagerHelper_Impl::clearedRedo()
855     {
856         if ( m_bAPIActionRunning )
857             return;
858 
859         notify( &XUndoManagerListener::redoActionsCleared );
860     }
861 
862  	//------------------------------------------------------------------------------------------------------------------
863     void UndoManagerHelper_Impl::resetAll()
864     {
865         if ( m_bAPIActionRunning )
866             return;
867 
868         notify( &XUndoManagerListener::resetAll );
869     }
870 
871  	//------------------------------------------------------------------------------------------------------------------
872     void UndoManagerHelper_Impl::listActionEntered( const String& i_comment )
873     {
874 #if OSL_DEBUG_LEVEL > 0
875         m_aContextAPIFlags.push( m_bAPIActionRunning );
876 #endif
877 
878         if ( m_bAPIActionRunning )
879             return;
880 
881         notify( i_comment, &XUndoManagerListener::enteredContext );
882     }
883 
884  	//------------------------------------------------------------------------------------------------------------------
885     void UndoManagerHelper_Impl::listActionLeft( const String& i_comment )
886     {
887 #if OSL_DEBUG_LEVEL > 0
888         const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
889         m_aContextAPIFlags.pop();
890         OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" );
891 #endif
892 
893         if ( m_bAPIActionRunning )
894             return;
895 
896         notify( i_comment, &XUndoManagerListener::leftContext );
897     }
898 
899  	//------------------------------------------------------------------------------------------------------------------
900     void UndoManagerHelper_Impl::listActionLeftAndMerged()
901     {
902 #if OSL_DEBUG_LEVEL > 0
903         const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
904         m_aContextAPIFlags.pop();
905         OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionLeftAndMerged: API and non-API contexts interwoven!" );
906 #endif
907 
908         if ( m_bAPIActionRunning )
909             return;
910 
911         notify( &XUndoManagerListener::leftHiddenContext );
912     }
913 
914  	//------------------------------------------------------------------------------------------------------------------
915     void UndoManagerHelper_Impl::listActionCancelled()
916     {
917 #if OSL_DEBUG_LEVEL > 0
918         const bool bCurrentContextIsAPIContext = m_aContextAPIFlags.top();
919         m_aContextAPIFlags.pop();
920         OSL_ENSURE( bCurrentContextIsAPIContext == m_bAPIActionRunning, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" );
921 #endif
922 
923         if ( m_bAPIActionRunning )
924             return;
925 
926         notify( &XUndoManagerListener::cancelledContext );
927     }
928 
929  	//------------------------------------------------------------------------------------------------------------------
930     void UndoManagerHelper_Impl::undoManagerDying()
931     {
932         // TODO: do we need to care? Or is this the responsibility of our owner?
933     }
934 
935 	//==================================================================================================================
936 	//= UndoManagerHelper
937 	//==================================================================================================================
938 	//------------------------------------------------------------------------------------------------------------------
939     UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl )
940         :m_pImpl( new UndoManagerHelper_Impl( *this, i_undoManagerImpl ) )
941     {
942     }
943 
944 	//------------------------------------------------------------------------------------------------------------------
945     UndoManagerHelper::~UndoManagerHelper()
946     {
947     }
948 
949 	//------------------------------------------------------------------------------------------------------------------
950     void UndoManagerHelper::disposing()
951     {
952         m_pImpl->disposing();
953     }
954 
955     //------------------------------------------------------------------------------------------------------------------
956     void UndoManagerHelper::enterUndoContext( const ::rtl::OUString& i_title, IMutexGuard& i_instanceLock )
957     {
958         m_pImpl->enterUndoContext( i_title, false, i_instanceLock );
959     }
960 
961     //------------------------------------------------------------------------------------------------------------------
962     void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard& i_instanceLock )
963     {
964         m_pImpl->enterUndoContext( ::rtl::OUString(), true, i_instanceLock );
965     }
966 
967     //------------------------------------------------------------------------------------------------------------------
968     void UndoManagerHelper::leaveUndoContext( IMutexGuard& i_instanceLock )
969     {
970         m_pImpl->leaveUndoContext( i_instanceLock );
971     }
972 
973     //------------------------------------------------------------------------------------------------------------------
974     void UndoManagerHelper_Impl::undo( IMutexGuard& i_instanceLock )
975     {
976         impl_processRequest(
977             ::boost::bind(
978                 &UndoManagerHelper_Impl::impl_doUndoRedo,
979                 this,
980                 ::boost::ref( i_instanceLock ),
981                 true
982             ),
983             i_instanceLock
984         );
985     }
986 
987     //------------------------------------------------------------------------------------------------------------------
988     void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock )
989     {
990         impl_processRequest(
991             ::boost::bind(
992                 &UndoManagerHelper_Impl::impl_doUndoRedo,
993                 this,
994                 ::boost::ref( i_instanceLock ),
995                 false
996             ),
997             i_instanceLock
998         );
999     }
1000 
1001     //------------------------------------------------------------------------------------------------------------------
1002     void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
1003     {
1004         m_pImpl->addUndoAction( i_action, i_instanceLock );
1005     }
1006 
1007     //------------------------------------------------------------------------------------------------------------------
1008     void UndoManagerHelper::undo( IMutexGuard& i_instanceLock )
1009     {
1010         m_pImpl->undo( i_instanceLock );
1011     }
1012 
1013     //------------------------------------------------------------------------------------------------------------------
1014     void UndoManagerHelper::redo( IMutexGuard& i_instanceLock )
1015     {
1016         m_pImpl->redo( i_instanceLock );
1017     }
1018 
1019     //------------------------------------------------------------------------------------------------------------------
1020     ::sal_Bool UndoManagerHelper::isUndoPossible() const
1021     {
1022         // SYNCHRONIZED --->
1023         ::osl::MutexGuard aGuard( m_pImpl->getMutex() );
1024         IUndoManager& rUndoManager = m_pImpl->getUndoManager();
1025         if ( rUndoManager.IsInListAction() )
1026             return sal_False;
1027         return rUndoManager.GetUndoActionCount( IUndoManager::TopLevel ) > 0;
1028         // <--- SYNCHRONIZED
1029     }
1030 
1031     //------------------------------------------------------------------------------------------------------------------
1032     ::sal_Bool UndoManagerHelper::isRedoPossible() const
1033     {
1034         // SYNCHRONIZED --->
1035         ::osl::MutexGuard aGuard( m_pImpl->getMutex() );
1036         const IUndoManager& rUndoManager = m_pImpl->getUndoManager();
1037         if ( rUndoManager.IsInListAction() )
1038             return sal_False;
1039         return rUndoManager.GetRedoActionCount( IUndoManager::TopLevel ) > 0;
1040         // <--- SYNCHRONIZED
1041     }
1042 
1043     //------------------------------------------------------------------------------------------------------------------
1044     namespace
1045     {
1046         //..............................................................................................................
1047         ::rtl::OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo )
1048         {
1049             // SYNCHRONIZED --->
1050             ::osl::MutexGuard aGuard( i_impl.getMutex() );
1051 
1052             const IUndoManager& rUndoManager = i_impl.getUndoManager();
1053             const size_t nActionCount = i_undo
1054                                     ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
1055                                     :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
1056             if ( nActionCount == 0 )
1057                 throw EmptyUndoStackException(
1058                     i_undo ? ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the undo stack" ) )
1059                            : ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "no action on the redo stack" ) ),
1060                     i_impl.getXUndoManager()
1061                 );
1062             return  i_undo
1063                 ?   rUndoManager.GetUndoActionComment( 0, IUndoManager::TopLevel )
1064                 :   rUndoManager.GetRedoActionComment( 0, IUndoManager::TopLevel );
1065             // <--- SYNCHRONIZED
1066         }
1067 
1068         //..............................................................................................................
1069         Sequence< ::rtl::OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo )
1070         {
1071             // SYNCHRONIZED --->
1072             ::osl::MutexGuard aGuard( i_impl.getMutex() );
1073 
1074             const IUndoManager& rUndoManager = i_impl.getUndoManager();
1075             const size_t nCount =   i_undo
1076                                 ?   rUndoManager.GetUndoActionCount( IUndoManager::TopLevel )
1077                                 :   rUndoManager.GetRedoActionCount( IUndoManager::TopLevel );
1078 
1079             Sequence< ::rtl::OUString > aTitles( nCount );
1080             for ( size_t i=0; i<nCount; ++i )
1081             {
1082                 aTitles[i] =    i_undo
1083                             ?   rUndoManager.GetUndoActionComment( i, IUndoManager::TopLevel )
1084                             :   rUndoManager.GetRedoActionComment( i, IUndoManager::TopLevel );
1085             }
1086             return aTitles;
1087             // <--- SYNCHRONIZED
1088         }
1089     }
1090 
1091     //------------------------------------------------------------------------------------------------------------------
1092     ::rtl::OUString UndoManagerHelper::getCurrentUndoActionTitle() const
1093     {
1094         return lcl_getCurrentActionTitle( *m_pImpl, true );
1095     }
1096 
1097     //------------------------------------------------------------------------------------------------------------------
1098     ::rtl::OUString UndoManagerHelper::getCurrentRedoActionTitle() const
1099     {
1100         return lcl_getCurrentActionTitle( *m_pImpl, false );
1101     }
1102 
1103     //------------------------------------------------------------------------------------------------------------------
1104     Sequence< ::rtl::OUString > UndoManagerHelper::getAllUndoActionTitles() const
1105     {
1106         return lcl_getAllActionTitles( *m_pImpl, true );
1107     }
1108 
1109     //------------------------------------------------------------------------------------------------------------------
1110     Sequence< ::rtl::OUString > UndoManagerHelper::getAllRedoActionTitles() const
1111     {
1112         return lcl_getAllActionTitles( *m_pImpl, false );
1113     }
1114 
1115     //------------------------------------------------------------------------------------------------------------------
1116     void UndoManagerHelper::clear( IMutexGuard& i_instanceLock )
1117     {
1118         m_pImpl->clear( i_instanceLock );
1119     }
1120 
1121     //------------------------------------------------------------------------------------------------------------------
1122     void UndoManagerHelper::clearRedo( IMutexGuard& i_instanceLock )
1123     {
1124         m_pImpl->clearRedo( i_instanceLock );
1125     }
1126 
1127     //------------------------------------------------------------------------------------------------------------------
1128     void UndoManagerHelper::reset( IMutexGuard& i_instanceLock )
1129     {
1130         m_pImpl->reset( i_instanceLock );
1131     }
1132 
1133     //------------------------------------------------------------------------------------------------------------------
1134     void UndoManagerHelper::lock()
1135     {
1136         m_pImpl->lock();
1137     }
1138 
1139     //------------------------------------------------------------------------------------------------------------------
1140     void UndoManagerHelper::unlock()
1141     {
1142         m_pImpl->unlock();
1143     }
1144 
1145     //------------------------------------------------------------------------------------------------------------------
1146     ::sal_Bool UndoManagerHelper::isLocked()
1147     {
1148         // SYNCHRONIZED --->
1149         ::osl::MutexGuard aGuard( m_pImpl->getMutex() );
1150 
1151         IUndoManager& rUndoManager = m_pImpl->getUndoManager();
1152         return !rUndoManager.IsUndoEnabled();
1153         // <--- SYNCHRONIZED
1154     }
1155 
1156     //------------------------------------------------------------------------------------------------------------------
1157     void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1158     {
1159         if ( i_listener.is() )
1160             m_pImpl->addUndoManagerListener( i_listener );
1161     }
1162 
1163     //------------------------------------------------------------------------------------------------------------------
1164     void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1165     {
1166         if ( i_listener.is() )
1167             m_pImpl->removeUndoManagerListener( i_listener );
1168     }
1169 
1170     //------------------------------------------------------------------------------------------------------------------
1171     void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener )
1172     {
1173         if ( i_listener.is() )
1174             m_pImpl->addModifyListener( i_listener );
1175     }
1176 
1177     //------------------------------------------------------------------------------------------------------------------
1178     void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener )
1179     {
1180         if ( i_listener.is() )
1181             m_pImpl->removeModifyListener( i_listener );
1182     }
1183 
1184 //......................................................................................................................
1185 } // namespace framework
1186 //......................................................................................................................
1187