1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <framework/undomanagerhelper.hxx>
21 #include <framework/imutex.hxx>
23 #include <com/sun/star/document/EmptyUndoStackException.hpp>
24 #include <com/sun/star/document/UndoContextNotClosedException.hpp>
25 #include <com/sun/star/document/UndoFailedException.hpp>
26 #include <com/sun/star/document/XUndoManager.hpp>
27 #include <com/sun/star/lang/XComponent.hpp>
28 #include <com/sun/star/util/InvalidStateException.hpp>
29 #include <com/sun/star/util/NotLockedException.hpp>
30 #include <com/sun/star/util/XModifyListener.hpp>
32 #include <comphelper/interfacecontainer4.hxx>
33 #include <cppuhelper/exc_hlp.hxx>
34 #include <comphelper/flagguard.hxx>
35 #include <comphelper/asyncnotification.hxx>
36 #include <svl/undo.hxx>
37 #include <comphelper/diagnose_ex.hxx>
38 #include <osl/conditn.hxx>
39 #include <vcl/svapp.hxx>
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::Exception
;
54 using ::com::sun::star::uno::RuntimeException
;
55 using ::com::sun::star::uno::Any
;
56 using ::com::sun::star::uno::Sequence
;
57 using ::com::sun::star::document::XUndoManagerListener
;
58 using ::com::sun::star::document::UndoManagerEvent
;
59 using ::com::sun::star::document::EmptyUndoStackException
;
60 using ::com::sun::star::document::UndoContextNotClosedException
;
61 using ::com::sun::star::document::UndoFailedException
;
62 using ::com::sun::star::util::NotLockedException
;
63 using ::com::sun::star::lang::EventObject
;
64 using ::com::sun::star::document::XUndoAction
;
65 using ::com::sun::star::lang::XComponent
;
66 using ::com::sun::star::document::XUndoManager
;
67 using ::com::sun::star::util::InvalidStateException
;
68 using ::com::sun::star::lang::IllegalArgumentException
;
69 using ::com::sun::star::util::XModifyListener
;
75 class UndoActionWrapper
: public SfxUndoAction
78 explicit UndoActionWrapper(
79 Reference
< XUndoAction
> const& i_undoAction
81 virtual ~UndoActionWrapper() override
;
83 virtual OUString
GetComment() const override
;
84 virtual void Undo() override
;
85 virtual void Redo() override
;
86 virtual bool CanRepeat(SfxRepeatTarget
&) const override
;
89 const Reference
< XUndoAction
> m_xUndoAction
;
94 UndoActionWrapper::UndoActionWrapper( Reference
< XUndoAction
> const& i_undoAction
)
95 : m_xUndoAction( i_undoAction
)
97 ENSURE_OR_THROW( m_xUndoAction
.is(), "illegal undo action" );
100 UndoActionWrapper::~UndoActionWrapper()
104 Reference
< XComponent
> xComponent( m_xUndoAction
, UNO_QUERY
);
105 if ( xComponent
.is() )
106 xComponent
->dispose();
108 catch( const Exception
& )
110 DBG_UNHANDLED_EXCEPTION("fwk");
114 OUString
UndoActionWrapper::GetComment() const
119 sComment
= m_xUndoAction
->getTitle();
121 catch( const Exception
& )
123 DBG_UNHANDLED_EXCEPTION("fwk");
128 void UndoActionWrapper::Undo()
130 m_xUndoAction
->undo();
133 void UndoActionWrapper::Redo()
135 m_xUndoAction
->redo();
138 bool UndoActionWrapper::CanRepeat(SfxRepeatTarget
&) const
143 //= UndoManagerRequest
147 class UndoManagerRequest
: public ::comphelper::AnyEvent
150 explicit UndoManagerRequest( ::std::function
<void ()> i_request
)
151 :m_request(std::move( i_request
))
153 m_finishCondition
.reset();
162 catch( const Exception
& )
164 m_caughtException
= ::cppu::getCaughtException();
166 m_finishCondition
.set();
171 m_finishCondition
.wait();
172 if ( m_caughtException
.hasValue() )
173 ::cppu::throwException( m_caughtException
);
176 void cancel( const Reference
< XInterface
>& i_context
)
178 m_caughtException
<<= RuntimeException(
179 "Concurrency error: an earlier operation on the stack failed.",
182 m_finishCondition
.set();
186 virtual ~UndoManagerRequest() override
191 ::std::function
<void ()> m_request
;
192 Any m_caughtException
;
193 ::osl::Condition m_finishCondition
;
198 //= UndoManagerHelper_Impl
200 class UndoManagerHelper_Impl
: public SfxUndoListener
203 ::osl::Mutex m_aMutex
;
204 /// Use different mutex for listeners to prevent ABBA deadlocks
205 std::mutex m_aListenerMutex
;
206 std::mutex m_aQueueMutex
;
207 bool m_bAPIActionRunning
;
208 bool m_bProcessingEvents
;
209 sal_Int32 m_nLockCount
;
210 ::comphelper::OInterfaceContainerHelper4
<XUndoManagerListener
> m_aUndoListeners
;
211 ::comphelper::OInterfaceContainerHelper4
<XModifyListener
> m_aModifyListeners
;
212 IUndoManagerImplementation
& m_rUndoManagerImplementation
;
213 ::std::stack
< bool > m_aContextVisibilities
;
214 #if OSL_DEBUG_LEVEL > 0
215 bool m_bContextAPIFlagsEverPushed
= {false};
216 ::std::stack
< bool > m_aContextAPIFlags
;
218 ::std::queue
< ::rtl::Reference
< UndoManagerRequest
> >
222 ::osl::Mutex
& getMutex() { return m_aMutex
; }
225 explicit UndoManagerHelper_Impl( IUndoManagerImplementation
& i_undoManagerImpl
)
226 :m_bAPIActionRunning( false )
227 ,m_bProcessingEvents( false )
229 ,m_rUndoManagerImplementation( i_undoManagerImpl
)
231 getUndoManager().AddUndoListener( *this );
234 virtual ~UndoManagerHelper_Impl()
238 SfxUndoManager
& getUndoManager() const
240 return m_rUndoManagerImplementation
.getImplUndoManager();
243 Reference
< XUndoManager
> getXUndoManager() const
245 return m_rUndoManagerImplementation
.getThis();
249 virtual void actionUndone( const OUString
& i_actionComment
) override
;
250 virtual void actionRedone( const OUString
& i_actionComment
) override
;
251 virtual void undoActionAdded( const OUString
& i_actionComment
) override
;
252 virtual void cleared() override
;
253 virtual void clearedRedo() override
;
254 virtual void resetAll() override
;
255 virtual void listActionEntered( const OUString
& i_comment
) override
;
256 virtual void listActionLeft( const OUString
& i_comment
) override
;
257 virtual void listActionCancelled() override
;
262 void enterUndoContext( const OUString
& i_title
, const bool i_hidden
, IMutexGuard
& i_instanceLock
);
263 void leaveUndoContext( IMutexGuard
& i_instanceLock
);
264 void addUndoAction( const Reference
< XUndoAction
>& i_action
, IMutexGuard
& i_instanceLock
);
265 void undo( IMutexGuard
& i_instanceLock
);
266 void redo( IMutexGuard
& i_instanceLock
);
267 void clear( IMutexGuard
& i_instanceLock
);
268 void clearRedo( IMutexGuard
& i_instanceLock
);
269 void reset( IMutexGuard
& i_instanceLock
);
274 void addUndoManagerListener( const Reference
< XUndoManagerListener
>& i_listener
)
276 std::unique_lock
g(m_aListenerMutex
);
277 m_aUndoListeners
.addInterface( g
, i_listener
);
280 void removeUndoManagerListener( const Reference
< XUndoManagerListener
>& i_listener
)
282 std::unique_lock
g(m_aListenerMutex
);
283 m_aUndoListeners
.removeInterface( g
, i_listener
);
286 void addModifyListener( const Reference
< XModifyListener
>& i_listener
)
288 std::unique_lock
g(m_aListenerMutex
);
289 m_aModifyListeners
.addInterface( g
, i_listener
);
292 void removeModifyListener( const Reference
< XModifyListener
>& i_listener
)
294 std::unique_lock
g(m_aListenerMutex
);
295 m_aModifyListeners
.removeInterface( g
, i_listener
);
299 buildEvent( OUString
const& i_title
) const;
301 void impl_notifyModified();
302 void notify( OUString
const& i_title
,
303 void ( SAL_CALL
XUndoManagerListener::*i_notificationMethod
)( const UndoManagerEvent
& )
305 void notify( void ( SAL_CALL
XUndoManagerListener::*i_notificationMethod
)( const EventObject
& ) );
308 /// adds a function to be called to the request processor's queue
309 void impl_processRequest(::std::function
<void ()> const& i_request
, IMutexGuard
& i_instanceLock
);
311 /// impl-versions of the XUndoManager API.
312 void impl_enterUndoContext( const OUString
& i_title
, const bool i_hidden
);
313 void impl_leaveUndoContext();
314 void impl_addUndoAction( const Reference
< XUndoAction
>& i_action
);
315 void impl_doUndoRedo( IMutexGuard
& i_externalLock
, const bool i_undo
);
317 void impl_clearRedo();
321 void UndoManagerHelper_Impl::disposing()
324 aEvent
.Source
= getXUndoManager();
326 std::unique_lock
g(m_aListenerMutex
);
327 m_aUndoListeners
.disposeAndClear( g
, aEvent
);
328 m_aModifyListeners
.disposeAndClear( g
, aEvent
);
330 ::osl::MutexGuard
aGuard( m_aMutex
);
332 getUndoManager().RemoveUndoListener( *this );
335 UndoManagerEvent
UndoManagerHelper_Impl::buildEvent( OUString
const& i_title
) const
337 UndoManagerEvent aEvent
;
338 aEvent
.Source
= getXUndoManager();
339 aEvent
.UndoActionTitle
= i_title
;
340 aEvent
.UndoContextDepth
= getUndoManager().GetListActionDepth();
344 void UndoManagerHelper_Impl::impl_notifyModified()
346 const EventObject
aEvent( getXUndoManager() );
347 std::unique_lock
g(m_aListenerMutex
);
348 m_aModifyListeners
.notifyEach( g
, &XModifyListener::modified
, aEvent
);
351 void UndoManagerHelper_Impl::notify( OUString
const& i_title
,
352 void ( SAL_CALL
XUndoManagerListener::*i_notificationMethod
)( const UndoManagerEvent
& ) )
354 const UndoManagerEvent
aEvent( buildEvent( i_title
) );
356 // TODO: this notification method here is used by UndoManagerHelper_Impl, to multiplex the notifications we
357 // receive from the SfxUndoManager. Those notifications are sent with a locked SolarMutex, which means
358 // we're doing the multiplexing here with a locked SM, too. Which is Bad (TM).
359 // Fixing this properly would require outsourcing all the notifications into an own thread - which might lead
360 // to problems of its own, since clients might expect synchronous notifications.
363 std::unique_lock
g(m_aListenerMutex
);
364 m_aUndoListeners
.notifyEach( g
, i_notificationMethod
, aEvent
);
366 impl_notifyModified();
369 void UndoManagerHelper_Impl::notify( void ( SAL_CALL
XUndoManagerListener::*i_notificationMethod
)( const EventObject
& ) )
371 const EventObject
aEvent( getXUndoManager() );
373 // TODO: the same comment as in the other notify, regarding SM locking applies here ...
375 std::unique_lock
g(m_aListenerMutex
);
376 m_aUndoListeners
.notifyEach( g
, i_notificationMethod
, aEvent
);
378 impl_notifyModified();
381 void UndoManagerHelper_Impl::enterUndoContext( const OUString
& i_title
, const bool i_hidden
, IMutexGuard
& i_instanceLock
)
384 [this, &i_title
, i_hidden
] () { return this->impl_enterUndoContext(i_title
, i_hidden
); },
389 void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard
& i_instanceLock
)
392 [this] () { return this->impl_leaveUndoContext(); },
397 void UndoManagerHelper_Impl::addUndoAction( const Reference
< XUndoAction
>& i_action
, IMutexGuard
& i_instanceLock
)
399 if ( !i_action
.is() )
400 throw IllegalArgumentException(
401 "illegal undo action object",
407 [this, &i_action
] () { return this->impl_addUndoAction(i_action
); },
412 void UndoManagerHelper_Impl::clear( IMutexGuard
& i_instanceLock
)
415 [this] () { return this->impl_clear(); },
420 void UndoManagerHelper_Impl::clearRedo( IMutexGuard
& i_instanceLock
)
423 [this] () { return this->impl_clearRedo(); },
428 void UndoManagerHelper_Impl::reset( IMutexGuard
& i_instanceLock
)
431 [this] () { return this->impl_reset(); },
436 void UndoManagerHelper_Impl::lock()
439 ::osl::MutexGuard
aGuard( getMutex() );
441 if ( ++m_nLockCount
== 1 )
443 SfxUndoManager
& rUndoManager
= getUndoManager();
444 rUndoManager
.EnableUndo( false );
449 void UndoManagerHelper_Impl::unlock()
452 ::osl::MutexGuard
aGuard( getMutex() );
454 if ( m_nLockCount
== 0 )
455 throw NotLockedException( "Undo manager is not locked", getXUndoManager() );
457 if ( --m_nLockCount
== 0 )
459 SfxUndoManager
& rUndoManager
= getUndoManager();
460 rUndoManager
.EnableUndo( true );
465 void UndoManagerHelper_Impl::impl_processRequest(::std::function
<void ()> const& i_request
, IMutexGuard
& i_instanceLock
)
467 // create the request, and add it to our queue
468 ::rtl::Reference
< UndoManagerRequest
> pRequest( new UndoManagerRequest( i_request
) );
470 std::unique_lock
aQueueGuard( m_aQueueMutex
);
471 m_aEventQueue
.push( pRequest
);
474 i_instanceLock
.clear();
476 if ( m_bProcessingEvents
)
478 // another thread is processing the event queue currently => it will also process the event which we just added
483 m_bProcessingEvents
= true;
488 std::unique_lock
aQueueGuard( m_aQueueMutex
);
489 if ( m_aEventQueue
.empty() )
491 // reset the flag before releasing the queue mutex, otherwise it's possible that another thread
492 // could add an event after we release the mutex, but before we reset the flag. If then this other
493 // thread checks the flag before be reset it, this thread's event would starve.
494 m_bProcessingEvents
= false;
497 pRequest
= m_aEventQueue
.front();
508 // no chance to process further requests, if the current one failed
510 std::unique_lock
aQueueGuard( m_aQueueMutex
);
511 while ( !m_aEventQueue
.empty() )
513 pRequest
= m_aEventQueue
.front();
515 pRequest
->cancel( getXUndoManager() );
517 m_bProcessingEvents
= false;
519 // re-throw the error
526 void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString
& i_title
, const bool i_hidden
)
529 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
531 SfxUndoManager
& rUndoManager
= getUndoManager();
532 if ( !rUndoManager
.IsUndoEnabled() )
533 // ignore this request if the manager is locked
536 if ( i_hidden
&& ( rUndoManager
.GetUndoActionCount() == 0 ) )
537 throw EmptyUndoStackException(
538 "can't enter a hidden context without a previous Undo action",
539 m_rUndoManagerImplementation
.getThis()
543 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
544 rUndoManager
.EnterListAction( i_title
, OUString(), 0, ViewShellId(-1) );
547 m_aContextVisibilities
.push( i_hidden
);
549 const UndoManagerEvent
aEvent( buildEvent( i_title
) );
554 std::unique_lock
g(m_aListenerMutex
);
555 m_aUndoListeners
.notifyEach( g
, i_hidden
? &XUndoManagerListener::enteredHiddenContext
: &XUndoManagerListener::enteredContext
, aEvent
);
557 impl_notifyModified();
560 void UndoManagerHelper_Impl::impl_leaveUndoContext()
563 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
565 SfxUndoManager
& rUndoManager
= getUndoManager();
566 if ( !rUndoManager
.IsUndoEnabled() )
567 // ignore this request if the manager is locked
570 if ( !rUndoManager
.IsInListAction() )
571 throw InvalidStateException(
572 "no active undo context",
576 size_t nContextElements
= 0;
578 const bool isHiddenContext
= m_aContextVisibilities
.top();
579 m_aContextVisibilities
.pop();
581 const bool bHadRedoActions
= ( rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
) > 0 );
583 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
584 if ( isHiddenContext
)
585 nContextElements
= rUndoManager
.LeaveAndMergeListAction();
587 nContextElements
= rUndoManager
.LeaveListAction();
589 const bool bHasRedoActions
= ( rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
) > 0 );
591 // prepare notification
592 void ( SAL_CALL
XUndoManagerListener::*notificationMethod
)( const UndoManagerEvent
& ) = nullptr;
594 UndoManagerEvent
aContextEvent( buildEvent( OUString() ) );
595 const EventObject
aClearedEvent( getXUndoManager() );
596 if ( nContextElements
== 0 )
598 notificationMethod
= &XUndoManagerListener::cancelledContext
;
600 else if ( isHiddenContext
)
602 notificationMethod
= &XUndoManagerListener::leftHiddenContext
;
606 aContextEvent
.UndoActionTitle
= rUndoManager
.GetUndoActionComment();
607 notificationMethod
= &XUndoManagerListener::leftContext
;
614 std::unique_lock
g(m_aListenerMutex
);
615 if ( bHadRedoActions
&& !bHasRedoActions
)
616 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::redoActionsCleared
, aClearedEvent
);
617 m_aUndoListeners
.notifyEach( g
, notificationMethod
, aContextEvent
);
619 impl_notifyModified();
622 void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard
& i_externalLock
, const bool i_undo
)
624 ::osl::Guard
< ::framework::IMutex
> aExternalGuard( i_externalLock
.getGuardedMutex() );
625 // note that this assumes that the mutex has been released in the thread which added the
626 // Undo/Redo request, so we can successfully acquire it
629 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
631 SfxUndoManager
& rUndoManager
= getUndoManager();
632 if ( rUndoManager
.IsInListAction() )
633 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
635 const size_t nElements
= i_undo
636 ? rUndoManager
.GetUndoActionCount( SfxUndoManager::TopLevel
)
637 : rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
);
638 if ( nElements
== 0 )
639 throw EmptyUndoStackException("stack is empty", getXUndoManager() );
651 catch( const RuntimeException
& ) { /* allowed to leave here */ throw; }
652 catch( const UndoFailedException
& ) { /* allowed to leave here */ throw; }
653 catch( const Exception
& )
655 // not allowed to leave
656 const Any
aError( ::cppu::getCaughtException() );
657 throw UndoFailedException( OUString(), getXUndoManager(), aError
);
660 // note that in opposite to all of the other methods, we do *not* have our mutex locked when calling
661 // into the SfxUndoManager implementation. This ensures that an actual XUndoAction::undo/redo is also
662 // called without our mutex being locked.
663 // As a consequence, we do not set m_bAPIActionRunning here. Instead, our actionUndone/actionRedone methods
664 // *always* multiplex the event to our XUndoManagerListeners, not only when m_bAPIActionRunning is FALSE (This
665 // again is different from all other SfxUndoListener methods).
666 // So, we do not need to do this notification here ourself.
669 void UndoManagerHelper_Impl::impl_addUndoAction( const Reference
< XUndoAction
>& i_action
)
672 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
674 SfxUndoManager
& rUndoManager
= getUndoManager();
675 if ( !rUndoManager
.IsUndoEnabled() )
676 // ignore the request if the manager is locked
679 const UndoManagerEvent
aEventAdd( buildEvent( i_action
->getTitle() ) );
680 const EventObject
aEventClear( getXUndoManager() );
682 const bool bHadRedoActions
= ( rUndoManager
.GetRedoActionCount() > 0 );
684 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
685 rUndoManager
.AddUndoAction( std::make_unique
<UndoActionWrapper
>( i_action
) );
687 const bool bHasRedoActions
= ( rUndoManager
.GetRedoActionCount() > 0 );
693 std::unique_lock
g(m_aListenerMutex
);
694 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::undoActionAdded
, aEventAdd
);
695 if ( bHadRedoActions
&& !bHasRedoActions
)
696 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::redoActionsCleared
, aEventClear
);
698 impl_notifyModified();
701 void UndoManagerHelper_Impl::impl_clear()
705 SolarMutexGuard aGuard
;
706 ::osl::MutexGuard
aGuard2( m_aMutex
);
708 SfxUndoManager
& rUndoManager
= getUndoManager();
709 if ( rUndoManager
.IsInListAction() )
710 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
713 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
714 rUndoManager
.Clear();
717 aEvent
= EventObject( getXUndoManager() );
721 std::unique_lock
g(m_aListenerMutex
);
722 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::allActionsCleared
, aEvent
);
724 impl_notifyModified();
727 void UndoManagerHelper_Impl::impl_clearRedo()
730 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
732 SfxUndoManager
& rUndoManager
= getUndoManager();
733 if ( rUndoManager
.IsInListAction() )
734 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
737 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
738 rUndoManager
.ClearRedo();
741 const EventObject
aEvent( getXUndoManager() );
746 std::unique_lock
g(m_aListenerMutex
);
747 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::redoActionsCleared
, aEvent
);
749 impl_notifyModified();
752 void UndoManagerHelper_Impl::impl_reset()
755 ::osl::ClearableMutexGuard
aGuard( m_aMutex
);
757 SfxUndoManager
& rUndoManager
= getUndoManager();
759 ::comphelper::FlagGuard
aNotificationGuard( m_bAPIActionRunning
);
760 rUndoManager
.Reset();
763 const EventObject
aEvent( getXUndoManager() );
768 std::unique_lock
g(m_aListenerMutex
);
769 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::resetAll
, aEvent
);
771 impl_notifyModified();
774 void UndoManagerHelper_Impl::actionUndone( const OUString
& i_actionComment
)
776 UndoManagerEvent aEvent
;
777 aEvent
.Source
= getXUndoManager();
778 aEvent
.UndoActionTitle
= i_actionComment
;
779 aEvent
.UndoContextDepth
= 0; // Undo can happen on level 0 only
781 std::unique_lock
g(m_aListenerMutex
);
782 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::actionUndone
, aEvent
);
784 impl_notifyModified();
787 void UndoManagerHelper_Impl::actionRedone( const OUString
& i_actionComment
)
789 UndoManagerEvent aEvent
;
790 aEvent
.Source
= getXUndoManager();
791 aEvent
.UndoActionTitle
= i_actionComment
;
792 aEvent
.UndoContextDepth
= 0; // Redo can happen on level 0 only
794 std::unique_lock
g(m_aListenerMutex
);
795 m_aUndoListeners
.notifyEach( g
, &XUndoManagerListener::actionRedone
, aEvent
);
797 impl_notifyModified();
800 void UndoManagerHelper_Impl::undoActionAdded( const OUString
& i_actionComment
)
802 if ( m_bAPIActionRunning
)
805 notify( i_actionComment
, &XUndoManagerListener::undoActionAdded
);
808 void UndoManagerHelper_Impl::cleared()
810 if ( m_bAPIActionRunning
)
813 notify( &XUndoManagerListener::allActionsCleared
);
816 void UndoManagerHelper_Impl::clearedRedo()
818 if ( m_bAPIActionRunning
)
821 notify( &XUndoManagerListener::redoActionsCleared
);
824 void UndoManagerHelper_Impl::resetAll()
826 if ( m_bAPIActionRunning
)
829 notify( &XUndoManagerListener::resetAll
);
832 void UndoManagerHelper_Impl::listActionEntered( const OUString
& i_comment
)
834 #if OSL_DEBUG_LEVEL > 0
835 m_aContextAPIFlags
.push( m_bAPIActionRunning
);
836 m_bContextAPIFlagsEverPushed
= true;
839 if ( m_bAPIActionRunning
)
842 notify( i_comment
, &XUndoManagerListener::enteredContext
);
845 void UndoManagerHelper_Impl::listActionLeft( const OUString
& i_comment
)
847 #if OSL_DEBUG_LEVEL > 0
848 // It may happen that the very first event listener is added during a
849 // list action after listActionEntered() was already called, e.g. Calc
850 // formula calculation event listener during the input of the very
851 // first formula. Instead of checking m_aContextAPIFlags for empty,
852 // still assert (on calling top()) other stack mismatches but ignore
853 // this one case. See tdf#142980
854 if (m_bContextAPIFlagsEverPushed
)
856 const bool bCurrentContextIsAPIContext
= m_aContextAPIFlags
.top();
857 m_aContextAPIFlags
.pop();
858 OSL_ENSURE( bCurrentContextIsAPIContext
== m_bAPIActionRunning
, "UndoManagerHelper_Impl::listActionLeft: API and non-API contexts interwoven!" );
862 if ( m_bAPIActionRunning
)
865 notify( i_comment
, &XUndoManagerListener::leftContext
);
868 void UndoManagerHelper_Impl::listActionCancelled()
870 #if OSL_DEBUG_LEVEL > 0
871 const bool bCurrentContextIsAPIContext
= m_aContextAPIFlags
.top();
872 m_aContextAPIFlags
.pop();
873 OSL_ENSURE( bCurrentContextIsAPIContext
== m_bAPIActionRunning
, "UndoManagerHelper_Impl::listActionCancelled: API and non-API contexts interwoven!" );
876 if ( m_bAPIActionRunning
)
879 notify( OUString(), &XUndoManagerListener::cancelledContext
);
882 //= UndoManagerHelper
884 UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation
& i_undoManagerImpl
)
885 :m_xImpl( new UndoManagerHelper_Impl( i_undoManagerImpl
) )
889 UndoManagerHelper::~UndoManagerHelper()
893 void UndoManagerHelper::disposing()
895 m_xImpl
->disposing();
898 void UndoManagerHelper::enterUndoContext( const OUString
& i_title
, IMutexGuard
& i_instanceLock
)
900 m_xImpl
->enterUndoContext( i_title
, false, i_instanceLock
);
903 void UndoManagerHelper::enterHiddenUndoContext( IMutexGuard
& i_instanceLock
)
905 m_xImpl
->enterUndoContext( OUString(), true, i_instanceLock
);
908 void UndoManagerHelper::leaveUndoContext( IMutexGuard
& i_instanceLock
)
910 m_xImpl
->leaveUndoContext( i_instanceLock
);
913 void UndoManagerHelper_Impl::undo( IMutexGuard
& i_instanceLock
)
916 [this, &i_instanceLock
] () { return this->impl_doUndoRedo(i_instanceLock
, true); },
921 void UndoManagerHelper_Impl::redo( IMutexGuard
& i_instanceLock
)
924 [this, &i_instanceLock
] () { return this->impl_doUndoRedo(i_instanceLock
, false); },
929 void UndoManagerHelper::addUndoAction( const Reference
< XUndoAction
>& i_action
, IMutexGuard
& i_instanceLock
)
931 m_xImpl
->addUndoAction( i_action
, i_instanceLock
);
934 void UndoManagerHelper::undo( IMutexGuard
& i_instanceLock
)
936 m_xImpl
->undo( i_instanceLock
);
939 void UndoManagerHelper::redo( IMutexGuard
& i_instanceLock
)
941 m_xImpl
->redo( i_instanceLock
);
944 bool UndoManagerHelper::isUndoPossible() const
947 ::osl::MutexGuard
aGuard( m_xImpl
->getMutex() );
948 SfxUndoManager
& rUndoManager
= m_xImpl
->getUndoManager();
949 if ( rUndoManager
.IsInListAction() )
951 return rUndoManager
.GetUndoActionCount( SfxUndoManager::TopLevel
) > 0;
955 bool UndoManagerHelper::isRedoPossible() const
958 ::osl::MutexGuard
aGuard( m_xImpl
->getMutex() );
959 const SfxUndoManager
& rUndoManager
= m_xImpl
->getUndoManager();
960 if ( rUndoManager
.IsInListAction() )
962 return rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
) > 0;
969 OUString
lcl_getCurrentActionTitle( UndoManagerHelper_Impl
& i_impl
, const bool i_undo
)
972 ::osl::MutexGuard
aGuard( i_impl
.getMutex() );
974 const SfxUndoManager
& rUndoManager
= i_impl
.getUndoManager();
975 const size_t nActionCount
= i_undo
976 ? rUndoManager
.GetUndoActionCount( SfxUndoManager::TopLevel
)
977 : rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
);
978 if ( nActionCount
== 0 )
979 throw EmptyUndoStackException(
980 i_undo
? OUString( "no action on the undo stack" )
981 : OUString( "no action on the redo stack" ),
982 i_impl
.getXUndoManager()
985 ? rUndoManager
.GetUndoActionComment( 0, SfxUndoManager::TopLevel
)
986 : rUndoManager
.GetRedoActionComment( 0, SfxUndoManager::TopLevel
);
990 Sequence
< OUString
> lcl_getAllActionTitles( UndoManagerHelper_Impl
& i_impl
, const bool i_undo
)
993 ::osl::MutexGuard
aGuard( i_impl
.getMutex() );
995 const SfxUndoManager
& rUndoManager
= i_impl
.getUndoManager();
996 const size_t nCount
= i_undo
997 ? rUndoManager
.GetUndoActionCount( SfxUndoManager::TopLevel
)
998 : rUndoManager
.GetRedoActionCount( SfxUndoManager::TopLevel
);
1000 Sequence
< OUString
> aTitles( nCount
);
1001 auto aTitlesRange
= asNonConstRange(aTitles
);
1002 for ( size_t i
=0; i
<nCount
; ++i
)
1004 aTitlesRange
[i
] = i_undo
1005 ? rUndoManager
.GetUndoActionComment( i
, SfxUndoManager::TopLevel
)
1006 : rUndoManager
.GetRedoActionComment( i
, SfxUndoManager::TopLevel
);
1009 // <--- SYNCHRONIZED
1013 OUString
UndoManagerHelper::getCurrentUndoActionTitle() const
1015 return lcl_getCurrentActionTitle( *m_xImpl
, true );
1018 OUString
UndoManagerHelper::getCurrentRedoActionTitle() const
1020 return lcl_getCurrentActionTitle( *m_xImpl
, false );
1023 Sequence
< OUString
> UndoManagerHelper::getAllUndoActionTitles() const
1025 return lcl_getAllActionTitles( *m_xImpl
, true );
1028 Sequence
< OUString
> UndoManagerHelper::getAllRedoActionTitles() const
1030 return lcl_getAllActionTitles( *m_xImpl
, false );
1033 void UndoManagerHelper::clear( IMutexGuard
& i_instanceLock
)
1035 m_xImpl
->clear( i_instanceLock
);
1038 void UndoManagerHelper::clearRedo( IMutexGuard
& i_instanceLock
)
1040 m_xImpl
->clearRedo( i_instanceLock
);
1043 void UndoManagerHelper::reset( IMutexGuard
& i_instanceLock
)
1045 m_xImpl
->reset( i_instanceLock
);
1048 void UndoManagerHelper::lock()
1053 void UndoManagerHelper::unlock()
1058 bool UndoManagerHelper::isLocked()
1060 // SYNCHRONIZED --->
1061 ::osl::MutexGuard
aGuard( m_xImpl
->getMutex() );
1063 SfxUndoManager
& rUndoManager
= m_xImpl
->getUndoManager();
1064 return !rUndoManager
.IsUndoEnabled();
1065 // <--- SYNCHRONIZED
1068 void UndoManagerHelper::addUndoManagerListener( const Reference
< XUndoManagerListener
>& i_listener
)
1070 if ( i_listener
.is() )
1071 m_xImpl
->addUndoManagerListener( i_listener
);
1074 void UndoManagerHelper::removeUndoManagerListener( const Reference
< XUndoManagerListener
>& i_listener
)
1076 if ( i_listener
.is() )
1077 m_xImpl
->removeUndoManagerListener( i_listener
);
1080 void UndoManagerHelper::addModifyListener( const Reference
< XModifyListener
>& i_listener
)
1082 if ( i_listener
.is() )
1083 m_xImpl
->addModifyListener( i_listener
);
1086 void UndoManagerHelper::removeModifyListener( const Reference
< XModifyListener
>& i_listener
)
1088 if ( i_listener
.is() )
1089 m_xImpl
->removeModifyListener( i_listener
);
1092 } // namespace framework
1094 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */