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