LibreOffice Module framework (master) 1
undomanagerhelper.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
21#include <framework/imutex.hxx>
22
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>
31
36#include <svl/undo.hxx>
38#include <osl/conditn.hxx>
39#include <vcl/svapp.hxx>
40
41#include <functional>
42#include <mutex>
43#include <stack>
44#include <queue>
45#include <utility>
46
47namespace framework
48{
49
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;
70
71 //= UndoActionWrapper
72
73 namespace {
74
75 class UndoActionWrapper : public SfxUndoAction
76 {
77 public:
78 explicit UndoActionWrapper(
79 Reference< XUndoAction > const& i_undoAction
80 );
81 virtual ~UndoActionWrapper() override;
82
83 virtual OUString GetComment() const override;
84 virtual void Undo() override;
85 virtual void Redo() override;
86 virtual bool CanRepeat(SfxRepeatTarget&) const override;
87
88 private:
89 const Reference< XUndoAction > m_xUndoAction;
90 };
91
92 }
93
94 UndoActionWrapper::UndoActionWrapper( Reference< XUndoAction > const& i_undoAction )
95 : m_xUndoAction( i_undoAction )
96 {
97 ENSURE_OR_THROW( m_xUndoAction.is(), "illegal undo action" );
98 }
99
100 UndoActionWrapper::~UndoActionWrapper()
101 {
102 try
103 {
104 Reference< XComponent > xComponent( m_xUndoAction, UNO_QUERY );
105 if ( xComponent.is() )
106 xComponent->dispose();
107 }
108 catch( const Exception& )
109 {
111 }
112 }
113
114 OUString UndoActionWrapper::GetComment() const
115 {
116 OUString sComment;
117 try
118 {
119 sComment = m_xUndoAction->getTitle();
120 }
121 catch( const Exception& )
122 {
124 }
125 return sComment;
126 }
127
128 void UndoActionWrapper::Undo()
129 {
130 m_xUndoAction->undo();
131 }
132
133 void UndoActionWrapper::Redo()
134 {
135 m_xUndoAction->redo();
136 }
137
138 bool UndoActionWrapper::CanRepeat(SfxRepeatTarget&) const
139 {
140 return false;
141 }
142
143 //= UndoManagerRequest
144
145 namespace {
146
147 class UndoManagerRequest : public ::comphelper::AnyEvent
148 {
149 public:
150 explicit UndoManagerRequest( ::std::function<void ()> i_request )
151 :m_request(std::move( i_request ))
152 {
153 m_finishCondition.reset();
154 }
155
156 void execute()
157 {
158 try
159 {
160 m_request();
161 }
162 catch( const Exception& )
163 {
164 m_caughtException = ::cppu::getCaughtException();
165 }
166 m_finishCondition.set();
167 }
168
169 void wait()
170 {
171 m_finishCondition.wait();
172 if ( m_caughtException.hasValue() )
173 ::cppu::throwException( m_caughtException );
174 }
175
176 void cancel( const Reference< XInterface >& i_context )
177 {
179 "Concurrency error: an earlier operation on the stack failed.",
180 i_context
181 );
182 m_finishCondition.set();
183 }
184
185 protected:
186 virtual ~UndoManagerRequest() override
187 {
188 }
189
190 private:
191 ::std::function<void ()> m_request;
193 ::osl::Condition m_finishCondition;
194 };
195
196 }
197
198 //= UndoManagerHelper_Impl
199
201 {
202 private:
203 ::osl::Mutex m_aMutex;
206 std::mutex m_aQueueMutex;
209 sal_Int32 m_nLockCount;
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 > >
220
221 public:
222 ::osl::Mutex& getMutex() { return m_aMutex; }
223
224 public:
226 :m_bAPIActionRunning( false )
227 ,m_bProcessingEvents( false )
228 ,m_nLockCount( 0 )
229 ,m_rUndoManagerImplementation( i_undoManagerImpl )
230 {
231 getUndoManager().AddUndoListener( *this );
232 }
233
235 {
236 }
237
239 {
240 return m_rUndoManagerImplementation.getImplUndoManager();
241 }
242
243 Reference< XUndoManager > getXUndoManager() const
244 {
245 return m_rUndoManagerImplementation.getThis();
246 }
247
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;
258
259 // public operations
260 void disposing();
261
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 );
270
271 void lock();
272 void unlock();
273
274 void addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
275 {
276 std::unique_lock g(m_aListenerMutex);
277 m_aUndoListeners.addInterface( g, i_listener );
278 }
279
280 void removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
281 {
282 std::unique_lock g(m_aListenerMutex);
283 m_aUndoListeners.removeInterface( g, i_listener );
284 }
285
286 void addModifyListener( const Reference< XModifyListener >& i_listener )
287 {
288 std::unique_lock g(m_aListenerMutex);
289 m_aModifyListeners.addInterface( g, i_listener );
290 }
291
292 void removeModifyListener( const Reference< XModifyListener >& i_listener )
293 {
294 std::unique_lock g(m_aListenerMutex);
295 m_aModifyListeners.removeInterface( g, i_listener );
296 }
297
298 UndoManagerEvent
299 buildEvent( OUString const& i_title ) const;
300
301 void impl_notifyModified();
302 void notify( OUString const& i_title,
303 void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& )
304 );
305 void notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) );
306
307 private:
309 void impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock);
310
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();
319 };
320
321 void UndoManagerHelper_Impl::disposing()
322 {
323 EventObject aEvent;
324 aEvent.Source = getXUndoManager();
325 {
326 std::unique_lock g(m_aListenerMutex);
327 m_aUndoListeners.disposeAndClear( g, aEvent );
328 m_aModifyListeners.disposeAndClear( g, aEvent );
329 }
330 ::osl::MutexGuard aGuard( m_aMutex );
331
332 getUndoManager().RemoveUndoListener( *this );
333 }
334
335 UndoManagerEvent UndoManagerHelper_Impl::buildEvent( OUString const& i_title ) const
336 {
337 UndoManagerEvent aEvent;
338 aEvent.Source = getXUndoManager();
339 aEvent.UndoActionTitle = i_title;
340 aEvent.UndoContextDepth = getUndoManager().GetListActionDepth();
341 return aEvent;
342 }
343
344 void UndoManagerHelper_Impl::impl_notifyModified()
345 {
346 const EventObject aEvent( getXUndoManager() );
347 std::unique_lock g(m_aListenerMutex);
348 m_aModifyListeners.notifyEach( g, &XModifyListener::modified, aEvent );
349 }
350
351 void UndoManagerHelper_Impl::notify( OUString const& i_title,
352 void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const UndoManagerEvent& ) )
353 {
354 const UndoManagerEvent aEvent( buildEvent( i_title ) );
355
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.
361
362 {
363 std::unique_lock g(m_aListenerMutex);
364 m_aUndoListeners.notifyEach( g, i_notificationMethod, aEvent );
365 }
366 impl_notifyModified();
367 }
368
369 void UndoManagerHelper_Impl::notify( void ( SAL_CALL XUndoManagerListener::*i_notificationMethod )( const EventObject& ) )
370 {
371 const EventObject aEvent( getXUndoManager() );
372
373 // TODO: the same comment as in the other notify, regarding SM locking applies here ...
374 {
375 std::unique_lock g(m_aListenerMutex);
376 m_aUndoListeners.notifyEach( g, i_notificationMethod, aEvent );
377 }
378 impl_notifyModified();
379 }
380
381 void UndoManagerHelper_Impl::enterUndoContext( const OUString& i_title, const bool i_hidden, IMutexGuard& i_instanceLock )
382 {
383 impl_processRequest(
384 [this, &i_title, i_hidden] () { return this->impl_enterUndoContext(i_title, i_hidden); },
385 i_instanceLock
386 );
387 }
388
389 void UndoManagerHelper_Impl::leaveUndoContext( IMutexGuard& i_instanceLock )
390 {
391 impl_processRequest(
392 [this] () { return this->impl_leaveUndoContext(); },
393 i_instanceLock
394 );
395 }
396
397 void UndoManagerHelper_Impl::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
398 {
399 if ( !i_action.is() )
400 throw IllegalArgumentException(
401 "illegal undo action object",
402 getXUndoManager(),
403 1
404 );
405
406 impl_processRequest(
407 [this, &i_action] () { return this->impl_addUndoAction(i_action); },
408 i_instanceLock
409 );
410 }
411
412 void UndoManagerHelper_Impl::clear( IMutexGuard& i_instanceLock )
413 {
414 impl_processRequest(
415 [this] () { return this->impl_clear(); },
416 i_instanceLock
417 );
418 }
419
420 void UndoManagerHelper_Impl::clearRedo( IMutexGuard& i_instanceLock )
421 {
422 impl_processRequest(
423 [this] () { return this->impl_clearRedo(); },
424 i_instanceLock
425 );
426 }
427
428 void UndoManagerHelper_Impl::reset( IMutexGuard& i_instanceLock )
429 {
430 impl_processRequest(
431 [this] () { return this->impl_reset(); },
432 i_instanceLock
433 );
434 }
435
436 void UndoManagerHelper_Impl::lock()
437 {
438 // SYNCHRONIZED --->
439 ::osl::MutexGuard aGuard( getMutex() );
440
441 if ( ++m_nLockCount == 1 )
442 {
443 SfxUndoManager& rUndoManager = getUndoManager();
444 rUndoManager.EnableUndo( false );
445 }
446 // <--- SYNCHRONIZED
447 }
448
449 void UndoManagerHelper_Impl::unlock()
450 {
451 // SYNCHRONIZED --->
452 ::osl::MutexGuard aGuard( getMutex() );
453
454 if ( m_nLockCount == 0 )
455 throw NotLockedException( "Undo manager is not locked", getXUndoManager() );
456
457 if ( --m_nLockCount == 0 )
458 {
459 SfxUndoManager& rUndoManager = getUndoManager();
460 rUndoManager.EnableUndo( true );
461 }
462 // <--- SYNCHRONIZED
463 }
464
465 void UndoManagerHelper_Impl::impl_processRequest(::std::function<void ()> const& i_request, IMutexGuard& i_instanceLock)
466 {
467 // create the request, and add it to our queue
468 ::rtl::Reference< UndoManagerRequest > pRequest( new UndoManagerRequest( i_request ) );
469 {
470 std::unique_lock aQueueGuard( m_aQueueMutex );
471 m_aEventQueue.push( pRequest );
472 }
473
474 i_instanceLock.clear();
475
476 if ( m_bProcessingEvents )
477 {
478 // another thread is processing the event queue currently => it will also process the event which we just added
479 pRequest->wait();
480 return;
481 }
482
483 m_bProcessingEvents = true;
484 do
485 {
486 pRequest.clear();
487 {
488 std::unique_lock aQueueGuard( m_aQueueMutex );
489 if ( m_aEventQueue.empty() )
490 {
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;
496 }
497 pRequest = m_aEventQueue.front();
498 m_aEventQueue.pop();
499 }
500 try
501 {
502 pRequest->execute();
503 pRequest->wait();
504 }
505 catch( ... )
506 {
507 {
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() )
512 {
513 pRequest = m_aEventQueue.front();
514 m_aEventQueue.pop();
515 pRequest->cancel( getXUndoManager() );
516 }
517 m_bProcessingEvents = false;
518 }
519 // re-throw the error
520 throw;
521 }
522 }
523 while ( true );
524 }
525
526 void UndoManagerHelper_Impl::impl_enterUndoContext( const OUString& i_title, const bool i_hidden )
527 {
528 // SYNCHRONIZED --->
529 ::osl::ClearableMutexGuard aGuard( m_aMutex );
530
531 SfxUndoManager& rUndoManager = getUndoManager();
532 if ( !rUndoManager.IsUndoEnabled() )
533 // ignore this request if the manager is locked
534 return;
535
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()
540 );
541
542 {
543 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
544 rUndoManager.EnterListAction( i_title, OUString(), 0, ViewShellId(-1) );
545 }
546
547 m_aContextVisibilities.push( i_hidden );
548
549 const UndoManagerEvent aEvent( buildEvent( i_title ) );
550 aGuard.clear();
551 // <--- SYNCHRONIZED
552
553 {
554 std::unique_lock g(m_aListenerMutex);
555 m_aUndoListeners.notifyEach( g, i_hidden ? &XUndoManagerListener::enteredHiddenContext : &XUndoManagerListener::enteredContext, aEvent );
556 }
557 impl_notifyModified();
558 }
559
560 void UndoManagerHelper_Impl::impl_leaveUndoContext()
561 {
562 // SYNCHRONIZED --->
563 ::osl::ClearableMutexGuard aGuard( m_aMutex );
564
565 SfxUndoManager& rUndoManager = getUndoManager();
566 if ( !rUndoManager.IsUndoEnabled() )
567 // ignore this request if the manager is locked
568 return;
569
570 if ( !rUndoManager.IsInListAction() )
571 throw InvalidStateException(
572 "no active undo context",
573 getXUndoManager()
574 );
575
576 size_t nContextElements = 0;
577
578 const bool isHiddenContext = m_aContextVisibilities.top();
579 m_aContextVisibilities.pop();
580
581 const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 );
582 {
583 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
584 if ( isHiddenContext )
585 nContextElements = rUndoManager.LeaveAndMergeListAction();
586 else
587 nContextElements = rUndoManager.LeaveListAction();
588 }
589 const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount( SfxUndoManager::TopLevel ) > 0 );
590
591 // prepare notification
592 void ( SAL_CALL XUndoManagerListener::*notificationMethod )( const UndoManagerEvent& ) = nullptr;
593
594 UndoManagerEvent aContextEvent( buildEvent( OUString() ) );
595 const EventObject aClearedEvent( getXUndoManager() );
596 if ( nContextElements == 0 )
597 {
598 notificationMethod = &XUndoManagerListener::cancelledContext;
599 }
600 else if ( isHiddenContext )
601 {
602 notificationMethod = &XUndoManagerListener::leftHiddenContext;
603 }
604 else
605 {
606 aContextEvent.UndoActionTitle = rUndoManager.GetUndoActionComment();
607 notificationMethod = &XUndoManagerListener::leftContext;
608 }
609
610 aGuard.clear();
611 // <--- SYNCHRONIZED
612
613 {
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 );
618 }
619 impl_notifyModified();
620 }
621
622 void UndoManagerHelper_Impl::impl_doUndoRedo( IMutexGuard& i_externalLock, const bool i_undo )
623 {
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
627
628 // SYNCHRONIZED --->
629 ::osl::ClearableMutexGuard aGuard( m_aMutex );
630
631 SfxUndoManager& rUndoManager = getUndoManager();
632 if ( rUndoManager.IsInListAction() )
633 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
634
635 const size_t nElements = i_undo
638 if ( nElements == 0 )
639 throw EmptyUndoStackException("stack is empty", getXUndoManager() );
640
641 aGuard.clear();
642 // <--- SYNCHRONIZED
643
644 try
645 {
646 if ( i_undo )
647 rUndoManager.Undo();
648 else
649 rUndoManager.Redo();
650 }
651 catch( const RuntimeException& ) { /* allowed to leave here */ throw; }
652 catch( const UndoFailedException& ) { /* allowed to leave here */ throw; }
653 catch( const Exception& )
654 {
655 // not allowed to leave
656 const Any aError( ::cppu::getCaughtException() );
657 throw UndoFailedException( OUString(), getXUndoManager(), aError );
658 }
659
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.
667 }
668
669 void UndoManagerHelper_Impl::impl_addUndoAction( const Reference< XUndoAction >& i_action )
670 {
671 // SYNCHRONIZED --->
672 ::osl::ClearableMutexGuard aGuard( m_aMutex );
673
674 SfxUndoManager& rUndoManager = getUndoManager();
675 if ( !rUndoManager.IsUndoEnabled() )
676 // ignore the request if the manager is locked
677 return;
678
679 const UndoManagerEvent aEventAdd( buildEvent( i_action->getTitle() ) );
680 const EventObject aEventClear( getXUndoManager() );
681
682 const bool bHadRedoActions = ( rUndoManager.GetRedoActionCount() > 0 );
683 {
684 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
685 rUndoManager.AddUndoAction( std::make_unique<UndoActionWrapper>( i_action ) );
686 }
687 const bool bHasRedoActions = ( rUndoManager.GetRedoActionCount() > 0 );
688
689 aGuard.clear();
690 // <--- SYNCHRONIZED
691
692 {
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 );
697 }
698 impl_notifyModified();
699 }
700
701 void UndoManagerHelper_Impl::impl_clear()
702 {
703 EventObject aEvent;
704 {
705 SolarMutexGuard aGuard;
706 ::osl::MutexGuard aGuard2( m_aMutex );
707
708 SfxUndoManager& rUndoManager = getUndoManager();
709 if ( rUndoManager.IsInListAction() )
710 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
711
712 {
713 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
714 rUndoManager.Clear();
715 }
716
717 aEvent = EventObject( getXUndoManager() );
718 }
719
720 {
721 std::unique_lock g(m_aListenerMutex);
722 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::allActionsCleared, aEvent );
723 }
724 impl_notifyModified();
725 }
726
727 void UndoManagerHelper_Impl::impl_clearRedo()
728 {
729 // SYNCHRONIZED --->
730 ::osl::ClearableMutexGuard aGuard( m_aMutex );
731
732 SfxUndoManager& rUndoManager = getUndoManager();
733 if ( rUndoManager.IsInListAction() )
734 throw UndoContextNotClosedException( OUString(), getXUndoManager() );
735
736 {
737 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
738 rUndoManager.ClearRedo();
739 }
740
741 const EventObject aEvent( getXUndoManager() );
742 aGuard.clear();
743 // <--- SYNCHRONIZED
744
745 {
746 std::unique_lock g(m_aListenerMutex);
747 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::redoActionsCleared, aEvent );
748 }
749 impl_notifyModified();
750 }
751
752 void UndoManagerHelper_Impl::impl_reset()
753 {
754 // SYNCHRONIZED --->
755 ::osl::ClearableMutexGuard aGuard( m_aMutex );
756
757 SfxUndoManager& rUndoManager = getUndoManager();
758 {
759 ::comphelper::FlagGuard aNotificationGuard( m_bAPIActionRunning );
760 rUndoManager.Reset();
761 }
762
763 const EventObject aEvent( getXUndoManager() );
764 aGuard.clear();
765 // <--- SYNCHRONIZED
766
767 {
768 std::unique_lock g(m_aListenerMutex);
769 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::resetAll, aEvent );
770 }
771 impl_notifyModified();
772 }
773
774 void UndoManagerHelper_Impl::actionUndone( const OUString& i_actionComment )
775 {
776 UndoManagerEvent aEvent;
777 aEvent.Source = getXUndoManager();
778 aEvent.UndoActionTitle = i_actionComment;
779 aEvent.UndoContextDepth = 0; // Undo can happen on level 0 only
780 {
781 std::unique_lock g(m_aListenerMutex);
782 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::actionUndone, aEvent );
783 }
784 impl_notifyModified();
785 }
786
787 void UndoManagerHelper_Impl::actionRedone( const OUString& i_actionComment )
788 {
789 UndoManagerEvent aEvent;
790 aEvent.Source = getXUndoManager();
791 aEvent.UndoActionTitle = i_actionComment;
792 aEvent.UndoContextDepth = 0; // Redo can happen on level 0 only
793 {
794 std::unique_lock g(m_aListenerMutex);
795 m_aUndoListeners.notifyEach( g, &XUndoManagerListener::actionRedone, aEvent );
796 }
797 impl_notifyModified();
798 }
799
800 void UndoManagerHelper_Impl::undoActionAdded( const OUString& i_actionComment )
801 {
802 if ( m_bAPIActionRunning )
803 return;
804
805 notify( i_actionComment, &XUndoManagerListener::undoActionAdded );
806 }
807
808 void UndoManagerHelper_Impl::cleared()
809 {
810 if ( m_bAPIActionRunning )
811 return;
812
813 notify( &XUndoManagerListener::allActionsCleared );
814 }
815
816 void UndoManagerHelper_Impl::clearedRedo()
817 {
818 if ( m_bAPIActionRunning )
819 return;
820
821 notify( &XUndoManagerListener::redoActionsCleared );
822 }
823
824 void UndoManagerHelper_Impl::resetAll()
825 {
826 if ( m_bAPIActionRunning )
827 return;
828
829 notify( &XUndoManagerListener::resetAll );
830 }
831
832 void UndoManagerHelper_Impl::listActionEntered( const OUString& i_comment )
833 {
834#if OSL_DEBUG_LEVEL > 0
835 m_aContextAPIFlags.push( m_bAPIActionRunning );
836 m_bContextAPIFlagsEverPushed = true;
837#endif
838
839 if ( m_bAPIActionRunning )
840 return;
841
842 notify( i_comment, &XUndoManagerListener::enteredContext );
843 }
844
845 void UndoManagerHelper_Impl::listActionLeft( const OUString& i_comment )
846 {
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)
855 {
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!" );
859 }
860#endif
861
862 if ( m_bAPIActionRunning )
863 return;
864
865 notify( i_comment, &XUndoManagerListener::leftContext );
866 }
867
868 void UndoManagerHelper_Impl::listActionCancelled()
869 {
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
875
876 if ( m_bAPIActionRunning )
877 return;
878
879 notify( OUString(), &XUndoManagerListener::cancelledContext );
880 }
881
882 //= UndoManagerHelper
883
884 UndoManagerHelper::UndoManagerHelper( IUndoManagerImplementation& i_undoManagerImpl )
885 :m_xImpl( new UndoManagerHelper_Impl( i_undoManagerImpl ) )
886 {
887 }
888
890 {
891 }
892
894 {
895 m_xImpl->disposing();
896 }
897
898 void UndoManagerHelper::enterUndoContext( const OUString& i_title, IMutexGuard& i_instanceLock )
899 {
900 m_xImpl->enterUndoContext( i_title, false, i_instanceLock );
901 }
902
904 {
905 m_xImpl->enterUndoContext( OUString(), true, i_instanceLock );
906 }
907
909 {
910 m_xImpl->leaveUndoContext( i_instanceLock );
911 }
912
914 {
916 [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, true); },
917 i_instanceLock
918 );
919 }
920
922 {
924 [this, &i_instanceLock] () { return this->impl_doUndoRedo(i_instanceLock, false); },
925 i_instanceLock
926 );
927 }
928
929 void UndoManagerHelper::addUndoAction( const Reference< XUndoAction >& i_action, IMutexGuard& i_instanceLock )
930 {
931 m_xImpl->addUndoAction( i_action, i_instanceLock );
932 }
933
934 void UndoManagerHelper::undo( IMutexGuard& i_instanceLock )
935 {
936 m_xImpl->undo( i_instanceLock );
937 }
938
939 void UndoManagerHelper::redo( IMutexGuard& i_instanceLock )
940 {
941 m_xImpl->redo( i_instanceLock );
942 }
943
945 {
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
953 }
954
956 {
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
964 }
965
966 namespace
967 {
968
969 OUString lcl_getCurrentActionTitle( UndoManagerHelper_Impl& i_impl, const bool i_undo )
970 {
971 // SYNCHRONIZED --->
972 ::osl::MutexGuard aGuard( i_impl.getMutex() );
973
974 const SfxUndoManager& rUndoManager = i_impl.getUndoManager();
975 const size_t nActionCount = i_undo
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()
983 );
984 return i_undo
987 // <--- SYNCHRONIZED
988 }
989
990 Sequence< OUString > lcl_getAllActionTitles( UndoManagerHelper_Impl& i_impl, const bool i_undo )
991 {
992 // SYNCHRONIZED --->
993 ::osl::MutexGuard aGuard( i_impl.getMutex() );
994
995 const SfxUndoManager& rUndoManager = i_impl.getUndoManager();
996 const size_t nCount = i_undo
999
1000 Sequence< OUString > aTitles( nCount );
1001 auto aTitlesRange = asNonConstRange(aTitles);
1002 for ( size_t i=0; i<nCount; ++i )
1003 {
1004 aTitlesRange[i] = i_undo
1007 }
1008 return aTitles;
1009 // <--- SYNCHRONIZED
1010 }
1011 }
1012
1014 {
1015 return lcl_getCurrentActionTitle( *m_xImpl, true );
1016 }
1017
1019 {
1020 return lcl_getCurrentActionTitle( *m_xImpl, false );
1021 }
1022
1024 {
1025 return lcl_getAllActionTitles( *m_xImpl, true );
1026 }
1027
1029 {
1030 return lcl_getAllActionTitles( *m_xImpl, false );
1031 }
1032
1034 {
1035 m_xImpl->clear( i_instanceLock );
1036 }
1037
1039 {
1040 m_xImpl->clearRedo( i_instanceLock );
1041 }
1042
1044 {
1045 m_xImpl->reset( i_instanceLock );
1046 }
1047
1049 {
1050 m_xImpl->lock();
1051 }
1052
1054 {
1055 m_xImpl->unlock();
1056 }
1057
1059 {
1060 // SYNCHRONIZED --->
1061 ::osl::MutexGuard aGuard( m_xImpl->getMutex() );
1062
1063 SfxUndoManager& rUndoManager = m_xImpl->getUndoManager();
1064 return !rUndoManager.IsUndoEnabled();
1065 // <--- SYNCHRONIZED
1066 }
1067
1068 void UndoManagerHelper::addUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1069 {
1070 if ( i_listener.is() )
1071 m_xImpl->addUndoManagerListener( i_listener );
1072 }
1073
1074 void UndoManagerHelper::removeUndoManagerListener( const Reference< XUndoManagerListener >& i_listener )
1075 {
1076 if ( i_listener.is() )
1077 m_xImpl->removeUndoManagerListener( i_listener );
1078 }
1079
1080 void UndoManagerHelper::addModifyListener( const Reference< XModifyListener >& i_listener )
1081 {
1082 if ( i_listener.is() )
1083 m_xImpl->addModifyListener( i_listener );
1084 }
1085
1086 void UndoManagerHelper::removeModifyListener( const Reference< XModifyListener >& i_listener )
1087 {
1088 if ( i_listener.is() )
1089 m_xImpl->removeModifyListener( i_listener );
1090 }
1091
1092} // namespace framework
1093
1094/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
AnyEventRef aEvent
size_t LeaveListAction()
virtual bool Undo()
void EnableUndo(bool bEnable)
virtual void Clear()
OUString GetRedoActionComment(size_t nNo=0, bool const i_currentLevel=CurrentLevel) const
bool IsInListAction() const
size_t LeaveAndMergeListAction()
OUString GetUndoActionComment(size_t nNo=0, bool const i_currentLevel=CurrentLevel) const
static bool const TopLevel
virtual void EnterListAction(const OUString &rComment, const OUString &rRepeatComment, sal_uInt16 nId, ViewShellId nViewShellId)
virtual bool Redo()
virtual size_t GetRedoActionCount(bool const i_currentLevel=CurrentLevel) const
virtual void ClearRedo()
bool IsUndoEnabled() const
virtual void AddUndoAction(std::unique_ptr< SfxUndoAction > pAction, bool bTryMerg=false)
virtual size_t GetUndoActionCount(bool const i_currentLevel=CurrentLevel) const
sal_Int32 addInterface(std::unique_lock< std::mutex > &rGuard, const css::uno::Reference< ListenerT > &rxIFace)
void notifyEach(std::unique_lock< std::mutex > &rGuard, void(SAL_CALL ListenerT::*NotificationMethod)(const EventT &), const EventT &Event) const
void disposeAndClear(::std::unique_lock<::std::mutex > &rGuard, const css::lang::EventObject &rEvt)
sal_Int32 removeInterface(std::unique_lock< std::mutex > &rGuard, const css::uno::Reference< ListenerT > &rxIFace)
virtual void clear()=0
clears the lock.
virtual IMutex & getGuardedMutex()=0
returns the mutex guarded by the instance.
virtual css::uno::Reference< css::document::XUndoManager > getThis()=0
provides access to a UNO interface for the XUndoManager implementation.
virtual SfxUndoManager & getImplUndoManager()=0
returns the SfxUndoManager interface to the actual Undo stack
std::mutex m_aListenerMutex
Use different mutex for listeners to prevent ABBA deadlocks.
::std::queue< ::rtl::Reference< UndoManagerRequest > > m_aEventQueue
void removeUndoManagerListener(const Reference< XUndoManagerListener > &i_listener)
::std::stack< bool > m_aContextVisibilities
SfxUndoManager & getUndoManager() const
UndoManagerHelper_Impl(IUndoManagerImplementation &i_undoManagerImpl)
Reference< XUndoManager > getXUndoManager() const
::comphelper::OInterfaceContainerHelper4< XModifyListener > m_aModifyListeners
void removeModifyListener(const Reference< XModifyListener > &i_listener)
void addModifyListener(const Reference< XModifyListener > &i_listener)
void impl_doUndoRedo(IMutexGuard &i_externalLock, const bool i_undo)
void redo(IMutexGuard &i_instanceLock)
IUndoManagerImplementation & m_rUndoManagerImplementation
void undo(IMutexGuard &i_instanceLock)
::comphelper::OInterfaceContainerHelper4< XUndoManagerListener > m_aUndoListeners
void impl_processRequest(::std::function< void()> const &i_request, IMutexGuard &i_instanceLock)
adds a function to be called to the request processor's queue
void addUndoManagerListener(const Reference< XUndoManagerListener > &i_listener)
void removeModifyListener(const css::uno::Reference< css::util::XModifyListener > &i_listener)
void leaveUndoContext(IMutexGuard &i_instanceLock)
void reset(IMutexGuard &i_instanceLock)
void enterUndoContext(const OUString &i_title, IMutexGuard &i_instanceLock)
css::uno::Sequence< OUString > getAllRedoActionTitles() const
void clearRedo(IMutexGuard &i_instanceLock)
void removeUndoManagerListener(const css::uno::Reference< css::document::XUndoManagerListener > &i_listener)
void enterHiddenUndoContext(IMutexGuard &i_instanceLock)
css::uno::Sequence< OUString > getAllUndoActionTitles() const
std::unique_ptr< UndoManagerHelper_Impl > m_xImpl
void clear(IMutexGuard &i_instanceLock)
OUString getCurrentRedoActionTitle() const
void redo(IMutexGuard &i_instanceLock)
void addModifyListener(const css::uno::Reference< css::util::XModifyListener > &i_listener)
void undo(IMutexGuard &i_instanceLock)
void addUndoAction(const css::uno::Reference< css::document::XUndoAction > &i_action, IMutexGuard &i_instanceLock)
void addUndoManagerListener(const css::uno::Reference< css::document::XUndoManagerListener > &i_listener)
OUString getCurrentUndoActionTitle() const
sal_Int32 nElements
int nCount
#define ENSURE_OR_THROW(c, m)
#define DBG_UNHANDLED_EXCEPTION(...)
@ Exception
std::shared_ptr< osl::Mutex > const & lock()
::osl::Mutex & getMutex()
int i
std::mutex m_aMutex
::std::function< void()> m_request
const Reference< XUndoAction > m_xUndoAction
::osl::Condition m_finishCondition
Any m_caughtException