Avoid potential negative array index access to cached text.
[LibreOffice.git] / framework / source / fwe / helper / undomanagerhelper.cxx
blob3a2fdd6c066b68aa6568543d85f0dc49eaf7a9dd
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
41 #include <functional>
42 #include <mutex>
43 #include <stack>
44 #include <queue>
45 #include <utility>
47 namespace framework
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;
71 //= UndoActionWrapper
73 namespace {
75 class UndoActionWrapper : public SfxUndoAction
77 public:
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;
88 private:
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
116 OUString sComment;
119 sComment = m_xUndoAction->getTitle();
121 catch( const Exception& )
123 DBG_UNHANDLED_EXCEPTION("fwk");
125 return sComment;
128 void UndoActionWrapper::Undo()
130 m_xUndoAction->undo();
133 void UndoActionWrapper::Redo()
135 m_xUndoAction->redo();
138 bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const
140 return false;
143 //= UndoManagerRequest
145 namespace {
147 class UndoManagerRequest : public ::comphelper::AnyEvent
149 public:
150 explicit UndoManagerRequest( ::std::function<void ()> i_request )
151 :m_request(std::move( i_request ))
153 m_finishCondition.reset();
156 void execute()
160 m_request();
162 catch( const Exception& )
164 m_caughtException = ::cppu::getCaughtException();
166 m_finishCondition.set();
169 void wait()
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.",
180 i_context
182 m_finishCondition.set();
185 protected:
186 virtual ~UndoManagerRequest() override
190 private:
191 ::std::function<void ()> m_request;
192 Any m_caughtException;
193 ::osl::Condition m_finishCondition;
198 //= UndoManagerHelper_Impl
200 class UndoManagerHelper_Impl : public SfxUndoListener
202 private:
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;
217 #endif
218 ::std::queue< ::rtl::Reference< UndoManagerRequest > >
219 m_aEventQueue;
221 public:
222 ::osl::Mutex& getMutex() { return m_aMutex; }
224 public:
225 explicit UndoManagerHelper_Impl( IUndoManagerImplementation& i_undoManagerImpl )
226 :m_bAPIActionRunning( false )
227 ,m_bProcessingEvents( false )
228 ,m_nLockCount( 0 )
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();
248 // SfxUndoListener
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;
259 // public operations
260 void disposing();
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 );
271 void lock();
272 void unlock();
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 );
298 UndoManagerEvent
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& ) );
307 private:
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 );
316 void impl_clear();
317 void impl_clearRedo();
318 void impl_reset();
321 void UndoManagerHelper_Impl::disposing()
323 EventObject aEvent;
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();
341 return aEvent;
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 )
383 impl_processRequest(
384 [this, &i_title, i_hidden] () { return this->impl_enterUndoContext(i_title, i_hidden); },
385 i_instanceLock
389 void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock )
391 impl_processRequest(
392 [this] () { return this->impl_leaveUndoContext(); },
393 i_instanceLock
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",
402 getXUndoManager(),
406 impl_processRequest(
407 [this, &i_action] () { return this->impl_addUndoAction(i_action); },
408 i_instanceLock
412 void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock )
414 impl_processRequest(
415 [this] () { return this->impl_clear(); },
416 i_instanceLock
420 void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock )
422 impl_processRequest(
423 [this] () { return this->impl_clearRedo(); },
424 i_instanceLock
428 void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock )
430 impl_processRequest(
431 [this] () { return this->impl_reset(); },
432 i_instanceLock
436 void UndoManagerHelper_Impl::lock()
438 // SYNCHRONIZED --->
439 ::osl::MutexGuard aGuard( getMutex() );
441 if ( ++m_nLockCount == 1 )
443 SfxUndoManager& rUndoManager = getUndoManager();
444 rUndoManager.EnableUndo( false );
446 // <--- SYNCHRONIZED
449 void UndoManagerHelper_Impl::unlock()
451 // SYNCHRONIZED --->
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 );
462 // <--- SYNCHRONIZED
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
479 pRequest->wait();
480 return;
483 m_bProcessingEvents = true;
486 pRequest.clear();
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;
495 return;
497 pRequest = m_aEventQueue.front();
498 m_aEventQueue.pop();
502 pRequest->execute();
503 pRequest->wait();
505 catch( ... )
508 // no chance to process further requests, if the current one failed
509 // => discard them
510 std::unique_lock aQueueGuard( m_aQueueMutex );
511 while ( !m_aEventQueue.empty() )
513 pRequest = m_aEventQueue.front();
514 m_aEventQueue.pop();
515 pRequest->cancel( getXUndoManager() );
517 m_bProcessingEvents = false;
519 // re-throw the error
520 throw;
523 while ( true );
526 void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString& i_title, const bool i_hidden )
528 // SYNCHRONIZED --->
529 ::osl::ClearableMutexGuard aGuard( m_aMutex );
531 SfxUndoManager& rUndoManager = getUndoManager();
532 if ( !rUndoManager.IsUndoEnabled() )
533 // ignore this request if the manager is locked
534 return;
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 ) );
550 aGuard.clear();
551 // <--- SYNCHRONIZED
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()
562 // SYNCHRONIZED --->
563 ::osl::ClearableMutexGuard aGuard( m_aMutex );
565 SfxUndoManager& rUndoManager = getUndoManager();
566 if ( !rUndoManager.IsUndoEnabled() )
567 // ignore this request if the manager is locked
568 return;
570 if ( !rUndoManager.IsInListAction() )
571 throw InvalidStateException(
572 "no active undo context",
573 getXUndoManager()
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();
586 else
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;
604 else
606 aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment();
607 notificationMethod = &XUndoManagerListener::leftContext;
610 aGuard.clear();
611 // <--- SYNCHRONIZED
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
628 // SYNCHRONIZED --->
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() );
641 aGuard.clear();
642 // <--- SYNCHRONIZED
646 if ( i_undo )
647 rUndoManager.Undo();
648 else
649 rUndoManager.Redo();
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 )
671 // SYNCHRONIZED --->
672 ::osl::ClearableMutexGuard aGuard( m_aMutex );
674 SfxUndoManager& rUndoManager = getUndoManager();
675 if ( !rUndoManager.IsUndoEnabled() )
676 // ignore the request if the manager is locked
677 return;
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 );
689 aGuard.clear();
690 // <--- SYNCHRONIZED
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()
703 EventObject aEvent;
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()
729 // SYNCHRONIZED --->
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() );
742 aGuard.clear();
743 // <--- SYNCHRONIZED
746 std::unique_lock g(m_aListenerMutex);
747 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::redoActionsCleared, aEvent );
749 impl_notifyModified();
752 void UndoManagerHelper_Impl::impl_reset()
754 // SYNCHRONIZED --->
755 ::osl::ClearableMutexGuard aGuard( m_aMutex );
757 SfxUndoManager& rUndoManager = getUndoManager();
759 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
760 rUndoManager.Reset();
763 const EventObject aEvent( getXUndoManager() );
764 aGuard.clear();
765 // <--- SYNCHRONIZED
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 )
803 return;
805 notify( i_actionComment, &XUndoManagerListener::undoActionAdded );
808 void UndoManagerHelper_Impl::cleared()
810 if ( m_bAPIActionRunning )
811 return;
813 notify( &XUndoManagerListener::allActionsCleared );
816 void UndoManagerHelper_Impl::clearedRedo()
818 if ( m_bAPIActionRunning )
819 return;
821 notify( &XUndoManagerListener::redoActionsCleared );
824 void UndoManagerHelper_Impl::resetAll()
826 if ( m_bAPIActionRunning )
827 return;
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;
837 #endif
839 if ( m_bAPIActionRunning )
840 return;
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!" );
860 #endif
862 if ( m_bAPIActionRunning )
863 return;
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!" );
874 #endif
876 if ( m_bAPIActionRunning )
877 return;
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 )
915 impl_processRequest(
916 [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, true); },
917 i_instanceLock
921 void UndoManagerHelper_Impl::redo( IMutexGuard& i_instanceLock )
923 impl_processRequest(
924 [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, false); },
925 i_instanceLock
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
946 // SYNCHRONIZED --->
947 ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
948 SfxUndoManager& rUndoManager = m_xImpl->getUndoManager();
949 if ( rUndoManager.IsInListAction() )
950 return false;
951 return rUndoManager.GetUndoActionCount( SfxUndoManager::TopLevel ) > 0;
952 // <--- SYNCHRONIZED
955 bool UndoManagerHelper::isRedoPossible() const
957 // SYNCHRONIZED --->
958 ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
959 const SfxUndoManager& rUndoManager = m_xImpl->getUndoManager();
960 if ( rUndoManager.IsInListAction() )
961 return false;
962 return rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0;
963 // <--- SYNCHRONIZED
966 namespace
969 OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo )
971 // SYNCHRONIZED --->
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()
984 return i_undo
985 ? rUndoManager.GetUndoActionComment( 0, SfxUndoManager::TopLevel )
986 : rUndoManager.GetRedoActionComment( 0, SfxUndoManager::TopLevel );
987 // <--- SYNCHRONIZED
990 Sequence< OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo )
992 // SYNCHRONIZED --->
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 );
1008 return aTitles;
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()
1050 m_xImpl->lock();
1053 void UndoManagerHelper::unlock()
1055 m_xImpl->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: */