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 <svl/undo.hxx>
22 #include <com/sun/star/uno/Exception.hpp>
24 #include <osl/mutex.hxx>
25 #include <sal/log.hxx>
26 #include <comphelper/flagguard.hxx>
27 #include <tools/diagnose_ex.h>
28 #include <libxml/xmlwriter.h>
29 #include <boost/property_tree/json_parser.hpp>
30 #include <unotools/datetime.hxx>
37 using ::com::sun::star::uno::Exception
;
40 SfxRepeatTarget::~SfxRepeatTarget()
45 SfxUndoContext::~SfxUndoContext()
50 SfxUndoAction::~SfxUndoAction() COVERITY_NOEXCEPT_FALSE
55 SfxUndoAction::SfxUndoAction()
56 : m_aDateTime(DateTime::SYSTEM
)
58 m_aDateTime
.ConvertToUTC();
62 bool SfxUndoAction::Merge( SfxUndoAction
* )
68 OUString
SfxUndoAction::GetComment() const
74 ViewShellId
SfxUndoAction::GetViewShellId() const
76 return ViewShellId(-1);
79 const DateTime
& SfxUndoAction::GetDateTime() const
84 OUString
SfxUndoAction::GetRepeatComment(SfxRepeatTarget
&) const
90 void SfxUndoAction::Undo()
92 // These are only conceptually pure virtual
93 assert(!"pure virtual function called: SfxUndoAction::Undo()");
97 void SfxUndoAction::UndoWithContext( SfxUndoContext
& )
103 void SfxUndoAction::Redo()
105 // These are only conceptually pure virtual
106 assert(!"pure virtual function called: SfxUndoAction::Redo()");
110 void SfxUndoAction::RedoWithContext( SfxUndoContext
& )
116 void SfxUndoAction::Repeat(SfxRepeatTarget
&)
118 // These are only conceptually pure virtual
119 assert(!"pure virtual function called: SfxUndoAction::Repeat()");
123 bool SfxUndoAction::CanRepeat(SfxRepeatTarget
&) const
128 void SfxUndoAction::dumpAsXml(xmlTextWriterPtr pWriter
) const
130 xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxUndoAction"));
131 xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("ptr"), "%p", this);
132 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
133 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("comment"), BAD_CAST(GetComment().toUtf8().getStr()));
134 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("viewShellId"), BAD_CAST(OString::number(static_cast<sal_Int32
>(GetViewShellId())).getStr()));
135 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("dateTime"), BAD_CAST(utl::toISO8601(m_aDateTime
.GetUNODateTime()).toUtf8().getStr()));
136 xmlTextWriterEndElement(pWriter
);
139 std::unique_ptr
<SfxUndoAction
> SfxUndoArray::Remove(int idx
)
141 auto ret
= std::move(maUndoActions
[idx
].pAction
);
142 maUndoActions
.erase(maUndoActions
.begin() + idx
);
146 void SfxUndoArray::Remove( size_t i_pos
, size_t i_count
)
148 maUndoActions
.erase(maUndoActions
.begin() + i_pos
, maUndoActions
.begin() + i_pos
+ i_count
);
151 void SfxUndoArray::Insert( std::unique_ptr
<SfxUndoAction
> i_action
, size_t i_pos
)
153 maUndoActions
.insert( maUndoActions
.begin() + i_pos
, MarkedUndoAction(std::move(i_action
)) );
156 typedef ::std::vector
< SfxUndoListener
* > UndoListeners
;
158 struct SfxUndoManager_Data
161 std::unique_ptr
<SfxUndoArray
>
163 SfxUndoArray
* pActUndoArray
;
166 sal_Int32 mnEmptyMark
;
169 bool mbClearUntilTopLevel
;
172 UndoListeners aListeners
;
174 explicit SfxUndoManager_Data( size_t i_nMaxUndoActionCount
)
175 :pUndoArray( new SfxUndoArray( i_nMaxUndoActionCount
) )
176 ,pActUndoArray( nullptr )
178 ,mnEmptyMark(MARK_INVALID
)
179 ,mbUndoEnabled( true )
181 ,mbClearUntilTopLevel( false )
182 ,mbEmptyActions( true )
184 pActUndoArray
= pUndoArray
.get();
187 // Copy assignment is forbidden and not implemented.
188 SfxUndoManager_Data (const SfxUndoManager_Data
&) = delete;
189 SfxUndoManager_Data
& operator= (const SfxUndoManager_Data
&) = delete;
192 namespace svl
{ namespace undo
{ namespace impl
197 explicit LockGuard( SfxUndoManager
& i_manager
)
198 :m_manager( i_manager
)
200 m_manager
.ImplEnableUndo_Lock( false );
205 m_manager
.ImplEnableUndo_Lock( true );
209 SfxUndoManager
& m_manager
;
212 typedef void ( SfxUndoListener::*UndoListenerVoidMethod
)();
213 typedef void ( SfxUndoListener::*UndoListenerStringMethod
)( const OUString
& );
215 struct NotifyUndoListener
217 explicit NotifyUndoListener( UndoListenerVoidMethod i_notificationMethod
)
218 :m_notificationMethod( i_notificationMethod
)
219 ,m_altNotificationMethod( nullptr )
224 NotifyUndoListener( UndoListenerStringMethod i_notificationMethod
, const OUString
& i_actionComment
)
225 :m_notificationMethod( nullptr )
226 ,m_altNotificationMethod( i_notificationMethod
)
227 ,m_sActionComment( i_actionComment
)
233 return ( m_notificationMethod
!= nullptr ) || ( m_altNotificationMethod
!= nullptr );
236 void operator()( SfxUndoListener
* i_listener
) const
238 assert( is() && "NotifyUndoListener: this will crash!" );
239 if ( m_altNotificationMethod
!= nullptr )
241 ( i_listener
->*m_altNotificationMethod
)( m_sActionComment
);
245 ( i_listener
->*m_notificationMethod
)();
250 UndoListenerVoidMethod m_notificationMethod
;
251 UndoListenerStringMethod m_altNotificationMethod
;
252 OUString m_sActionComment
;
255 class UndoManagerGuard
258 explicit UndoManagerGuard( SfxUndoManager_Data
& i_managerData
)
259 :m_rManagerData( i_managerData
)
260 ,m_aGuard( i_managerData
.aMutex
)
277 void cancelNotifications()
282 /** marks the given Undo action for deletion
284 The Undo action will be put into a list, whose members will be deleted from within the destructor of the
285 UndoManagerGuard. This deletion will happen without the UndoManager's mutex locked.
287 void markForDeletion( std::unique_ptr
<SfxUndoAction
> i_action
)
291 m_aUndoActionsCleanup
.emplace_back( std::move(i_action
) );
294 /** schedules the given SfxUndoListener method to be called for all registered listeners.
296 The notification will happen after the Undo manager's mutex has been released, and after all pending
297 deletions of Undo actions are done.
299 void scheduleNotification( UndoListenerVoidMethod i_notificationMethod
)
301 m_notifiers
.emplace_back( i_notificationMethod
);
304 void scheduleNotification( UndoListenerStringMethod i_notificationMethod
, const OUString
& i_actionComment
)
306 m_notifiers
.emplace_back( i_notificationMethod
, i_actionComment
);
310 SfxUndoManager_Data
& m_rManagerData
;
311 ::osl::ResettableMutexGuard m_aGuard
;
312 ::std::vector
< std::unique_ptr
<SfxUndoAction
> > m_aUndoActionsCleanup
;
313 ::std::vector
< NotifyUndoListener
> m_notifiers
;
316 UndoManagerGuard::~UndoManagerGuard()
319 UndoListeners
aListenersCopy( m_rManagerData
.aListeners
);
324 // delete all actions
325 m_aUndoActionsCleanup
.clear();
327 // handle scheduled notification
328 for (auto const& notifier
: m_notifiers
)
331 ::std::for_each( aListenersCopy
.begin(), aListenersCopy
.end(), notifier
);
336 using namespace ::svl::undo::impl
;
339 SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount
)
340 :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount
) )
342 m_xData
->mbEmptyActions
= !ImplIsEmptyActions();
346 SfxUndoManager::~SfxUndoManager()
348 UndoListeners aListenersCopy
;
350 UndoManagerGuard
aGuard( *m_xData
);
351 aListenersCopy
= m_xData
->aListeners
;
354 ::std::for_each( aListenersCopy
.begin(), aListenersCopy
.end(),
355 NotifyUndoListener( &SfxUndoListener::undoManagerDying
) );
359 void SfxUndoManager::EnableUndo( bool i_enable
)
361 UndoManagerGuard
aGuard( *m_xData
);
362 ImplEnableUndo_Lock( i_enable
);
367 void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable
)
369 if ( m_xData
->mbUndoEnabled
== i_enable
)
371 m_xData
->mbUndoEnabled
= i_enable
;
375 bool SfxUndoManager::IsUndoEnabled() const
377 UndoManagerGuard
aGuard( *m_xData
);
378 return ImplIsUndoEnabled_Lock();
382 bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
384 return m_xData
->mbUndoEnabled
;
388 void SfxUndoManager::SetMaxUndoActionCount( size_t nMaxUndoActionCount
)
390 UndoManagerGuard
aGuard( *m_xData
);
392 // Remove entries from the pActUndoArray when we have to reduce
393 // the number of entries due to a lower nMaxUndoActionCount.
394 // Both redo and undo action entries will be removed until we reached the
395 // new nMaxUndoActionCount.
397 long nNumToDelete
= m_xData
->pActUndoArray
->maUndoActions
.size() - nMaxUndoActionCount
;
398 while ( nNumToDelete
> 0 )
400 size_t nPos
= m_xData
->pActUndoArray
->maUndoActions
.size();
401 if ( nPos
> m_xData
->pActUndoArray
->nCurUndoAction
)
403 aGuard
.markForDeletion( m_xData
->pActUndoArray
->Remove( nPos
-1 ) );
407 if ( nNumToDelete
> 0 && m_xData
->pActUndoArray
->nCurUndoAction
> 0 )
409 aGuard
.markForDeletion( m_xData
->pActUndoArray
->Remove(0) );
410 --m_xData
->pActUndoArray
->nCurUndoAction
;
414 if ( nPos
== m_xData
->pActUndoArray
->maUndoActions
.size() )
415 break; // Cannot delete more entries
418 m_xData
->pActUndoArray
->nMaxUndoActions
= nMaxUndoActionCount
;
419 ImplCheckEmptyActions();
423 void SfxUndoManager::ImplClearCurrentLevel_NoNotify( UndoManagerGuard
& i_guard
)
426 while ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
428 size_t deletePos
= m_xData
->pActUndoArray
->maUndoActions
.size() - 1;
429 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( deletePos
) );
432 m_xData
->pActUndoArray
->nCurUndoAction
= 0;
434 m_xData
->mnMarks
= 0;
435 m_xData
->mnEmptyMark
= MARK_INVALID
;
436 ImplCheckEmptyActions();
440 void SfxUndoManager::Clear()
442 UndoManagerGuard
aGuard( *m_xData
);
444 SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
445 "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
446 ImplClearCurrentLevel_NoNotify( aGuard
);
449 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
453 void SfxUndoManager::ClearAllLevels()
455 UndoManagerGuard
aGuard( *m_xData
);
456 ImplClearCurrentLevel_NoNotify( aGuard
);
458 if ( ImplIsInListAction_Lock() )
460 m_xData
->mbClearUntilTopLevel
= true;
464 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
469 void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel
)
471 UndoManagerGuard
aGuard( *m_xData
);
472 ImplClearRedo( aGuard
, i_currentLevel
);
476 void SfxUndoManager::ClearRedo()
478 SAL_WARN_IF( IsInListAction(), "svl",
479 "SfxUndoManager::ClearRedo: suspicious call - do you really wish to clear the current level?" );
480 ImplClearRedo_NoLock( CurrentLevel
);
484 void SfxUndoManager::Reset()
486 UndoManagerGuard
aGuard( *m_xData
);
489 while ( !ImplIsUndoEnabled_Lock() )
490 ImplEnableUndo_Lock( true );
492 // cancel all list actions
493 while ( IsInListAction() )
494 ImplLeaveListAction( false, aGuard
);
497 ImplClearCurrentLevel_NoNotify( aGuard
);
499 // cancel the notifications scheduled by ImplLeaveListAction,
500 // as we want to do an own, dedicated notification
501 aGuard
.cancelNotifications();
503 // schedule notification
504 aGuard
.scheduleNotification( &SfxUndoListener::resetAll
);
508 void SfxUndoManager::ImplClearUndo( UndoManagerGuard
& i_guard
)
510 while ( m_xData
->pActUndoArray
->nCurUndoAction
> 0 )
512 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( 0 ) );
513 --m_xData
->pActUndoArray
->nCurUndoAction
;
515 ImplCheckEmptyActions();
516 // TODO: notifications? We don't have clearedUndo, only cleared and clearedRedo at the SfxUndoListener
520 void SfxUndoManager::ImplClearRedo( UndoManagerGuard
& i_guard
, bool const i_currentLevel
)
522 SfxUndoArray
* pUndoArray
= ( i_currentLevel
== SfxUndoManager::CurrentLevel
) ? m_xData
->pActUndoArray
: m_xData
->pUndoArray
.get();
525 while ( pUndoArray
->maUndoActions
.size() > pUndoArray
->nCurUndoAction
)
527 size_t deletePos
= pUndoArray
->maUndoActions
.size() - 1;
528 i_guard
.markForDeletion( pUndoArray
->Remove( deletePos
) );
531 ImplCheckEmptyActions();
532 // notification - only if the top level's stack was cleared
533 if ( i_currentLevel
== SfxUndoManager::TopLevel
)
534 i_guard
.scheduleNotification( &SfxUndoListener::clearedRedo
);
538 bool SfxUndoManager::ImplAddUndoAction_NoNotify( std::unique_ptr
<SfxUndoAction
> pAction
, bool bTryMerge
, bool bClearRedo
, UndoManagerGuard
& i_guard
)
540 if ( !ImplIsUndoEnabled_Lock() || ( m_xData
->pActUndoArray
->nMaxUndoActions
== 0 ) )
542 i_guard
.markForDeletion( std::move(pAction
) );
546 // merge, if required
547 SfxUndoAction
* pMergeWithAction
= m_xData
->pActUndoArray
->nCurUndoAction
?
548 m_xData
->pActUndoArray
->maUndoActions
[m_xData
->pActUndoArray
->nCurUndoAction
-1].pAction
.get() : nullptr;
549 if ( bTryMerge
&& pMergeWithAction
)
551 bool bMerged
= pMergeWithAction
->Merge( pAction
.get() );
554 i_guard
.markForDeletion( std::move(pAction
) );
559 // clear redo stack, if requested
560 if ( bClearRedo
&& ( ImplGetRedoActionCount_Lock() > 0 ) )
561 ImplClearRedo( i_guard
, SfxUndoManager::CurrentLevel
);
563 // respect max number
564 if( m_xData
->pActUndoArray
== m_xData
->pUndoArray
.get() )
566 while(m_xData
->pActUndoArray
->maUndoActions
.size() >= m_xData
->pActUndoArray
->nMaxUndoActions
)
568 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove(0) );
569 if (m_xData
->pActUndoArray
->nCurUndoAction
> 0)
571 --m_xData
->pActUndoArray
->nCurUndoAction
;
575 assert(!"CurrentUndoAction going negative (!)");
577 // fdo#66071 invalidate the current empty mark when removing
578 --m_xData
->mnEmptyMark
;
583 m_xData
->pActUndoArray
->Insert( std::move(pAction
), m_xData
->pActUndoArray
->nCurUndoAction
++ );
584 ImplCheckEmptyActions();
589 void SfxUndoManager::AddUndoAction( std::unique_ptr
<SfxUndoAction
> pAction
, bool bTryMerge
)
591 UndoManagerGuard
aGuard( *m_xData
);
594 auto pActionTmp
= pAction
.get();
595 if ( ImplAddUndoAction_NoNotify( std::move(pAction
), bTryMerge
, true, aGuard
) )
598 aGuard
.scheduleNotification( &SfxUndoListener::undoActionAdded
, pActionTmp
->GetComment() );
603 size_t SfxUndoManager::GetUndoActionCount( bool const i_currentLevel
) const
605 UndoManagerGuard
aGuard( *m_xData
);
606 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: m_xData
->pUndoArray
.get();
607 return pUndoArray
->nCurUndoAction
;
611 OUString
SfxUndoManager::GetUndoActionComment( size_t nNo
, bool const i_currentLevel
) const
613 UndoManagerGuard
aGuard( *m_xData
);
616 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: m_xData
->pUndoArray
.get();
617 assert(nNo
< pUndoArray
->nCurUndoAction
);
618 if( nNo
< pUndoArray
->nCurUndoAction
)
619 sComment
= pUndoArray
->maUndoActions
[ pUndoArray
->nCurUndoAction
- 1 - nNo
].pAction
->GetComment();
624 SfxUndoAction
* SfxUndoManager::GetUndoAction( size_t nNo
) const
626 UndoManagerGuard
aGuard( *m_xData
);
628 assert(nNo
< m_xData
->pActUndoArray
->nCurUndoAction
);
629 if( nNo
>= m_xData
->pActUndoArray
->nCurUndoAction
)
631 return m_xData
->pActUndoArray
->maUndoActions
[m_xData
->pActUndoArray
->nCurUndoAction
-1-nNo
].pAction
.get();
635 /** clears the redo stack and removes the top undo action */
636 void SfxUndoManager::RemoveLastUndoAction()
638 UndoManagerGuard
aGuard( *m_xData
);
640 ENSURE_OR_RETURN_VOID( m_xData
->pActUndoArray
->nCurUndoAction
, "svl::SfxUndoManager::RemoveLastUndoAction(), no action to remove?!" );
642 m_xData
->pActUndoArray
->nCurUndoAction
--;
644 // delete redo-actions and top action
645 for ( size_t nPos
= m_xData
->pActUndoArray
->maUndoActions
.size(); nPos
> m_xData
->pActUndoArray
->nCurUndoAction
; --nPos
)
647 aGuard
.markForDeletion( std::move(m_xData
->pActUndoArray
->maUndoActions
[nPos
-1].pAction
) );
650 m_xData
->pActUndoArray
->Remove(
651 m_xData
->pActUndoArray
->nCurUndoAction
,
652 m_xData
->pActUndoArray
->maUndoActions
.size() - m_xData
->pActUndoArray
->nCurUndoAction
);
653 ImplCheckEmptyActions();
657 bool SfxUndoManager::IsDoing() const
659 UndoManagerGuard
aGuard( *m_xData
);
660 return m_xData
->mbDoing
;
664 bool SfxUndoManager::Undo()
666 return ImplUndo( nullptr );
670 bool SfxUndoManager::UndoWithContext( SfxUndoContext
& i_context
)
672 return ImplUndo( &i_context
);
676 bool SfxUndoManager::ImplUndo( SfxUndoContext
* i_contextOrNull
)
678 UndoManagerGuard
aGuard( *m_xData
);
679 assert( !IsDoing() && "SfxUndoManager::Undo: *nested* Undo/Redo actions? How this?" );
681 ::comphelper::FlagGuard
aDoingGuard( m_xData
->mbDoing
);
682 LockGuard
aLockGuard( *this );
684 if ( ImplIsInListAction_Lock() )
686 assert(!"SfxUndoManager::Undo: not possible when within a list action!");
690 if ( m_xData
->pActUndoArray
->nCurUndoAction
== 0 )
692 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
696 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
[ --m_xData
->pActUndoArray
->nCurUndoAction
].pAction
.get();
697 const OUString sActionComment
= pAction
->GetComment();
700 // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
703 if ( i_contextOrNull
!= nullptr )
704 pAction
->UndoWithContext( *i_contextOrNull
);
713 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
714 // we still find pAction in our current Undo array
715 size_t nCurAction
= 0;
716 while ( nCurAction
< m_xData
->pActUndoArray
->maUndoActions
.size() )
718 if ( m_xData
->pActUndoArray
->maUndoActions
[ nCurAction
++ ].pAction
.get() == pAction
)
720 // the Undo action is still there ...
721 // assume the error is a permanent failure, and clear the Undo stack
722 ImplClearUndo( aGuard
);
726 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
730 aGuard
.scheduleNotification( &SfxUndoListener::actionUndone
, sActionComment
);
736 size_t SfxUndoManager::GetRedoActionCount( bool const i_currentLevel
) const
738 UndoManagerGuard
aGuard( *m_xData
);
739 return ImplGetRedoActionCount_Lock( i_currentLevel
);
743 size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel
) const
745 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: m_xData
->pUndoArray
.get();
746 return pUndoArray
->maUndoActions
.size() - pUndoArray
->nCurUndoAction
;
750 SfxUndoAction
* SfxUndoManager::GetRedoAction() const
752 UndoManagerGuard
aGuard( *m_xData
);
754 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
755 if ( (pUndoArray
->nCurUndoAction
) > pUndoArray
->maUndoActions
.size() )
759 return pUndoArray
->maUndoActions
[ pUndoArray
->nCurUndoAction
].pAction
.get();
763 OUString
SfxUndoManager::GetRedoActionComment( size_t nNo
, bool const i_currentLevel
) const
766 UndoManagerGuard
aGuard( *m_xData
);
767 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: m_xData
->pUndoArray
.get();
768 if ( (pUndoArray
->nCurUndoAction
+ nNo
) < pUndoArray
->maUndoActions
.size() )
770 sComment
= pUndoArray
->maUndoActions
[ pUndoArray
->nCurUndoAction
+ nNo
].pAction
->GetComment();
776 bool SfxUndoManager::Redo()
778 return ImplRedo( nullptr );
782 bool SfxUndoManager::RedoWithContext( SfxUndoContext
& i_context
)
784 return ImplRedo( &i_context
);
788 bool SfxUndoManager::ImplRedo( SfxUndoContext
* i_contextOrNull
)
790 UndoManagerGuard
aGuard( *m_xData
);
791 assert( !IsDoing() && "SfxUndoManager::Redo: *nested* Undo/Redo actions? How this?" );
793 ::comphelper::FlagGuard
aDoingGuard( m_xData
->mbDoing
);
794 LockGuard
aLockGuard( *this );
796 if ( ImplIsInListAction_Lock() )
798 assert(!"SfxUndoManager::Redo: not possible when within a list action!");
802 if ( m_xData
->pActUndoArray
->nCurUndoAction
>= m_xData
->pActUndoArray
->maUndoActions
.size() )
804 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
808 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->nCurUndoAction
++ ].pAction
.get();
809 const OUString sActionComment
= pAction
->GetComment();
812 // clear the guard/mutex before calling into the SfxUndoAction - this can be a extension-implemented UNO component
815 if ( i_contextOrNull
!= nullptr )
816 pAction
->RedoWithContext( *i_contextOrNull
);
825 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
826 // we still find pAction in our current Undo array
827 size_t nCurAction
= 0;
828 while ( nCurAction
< m_xData
->pActUndoArray
->maUndoActions
.size() )
830 if ( m_xData
->pActUndoArray
->maUndoActions
[ nCurAction
].pAction
.get() == pAction
)
832 // the Undo action is still there ...
833 // assume the error is a permanent failure, and clear the Undo stack
834 ImplClearRedo( aGuard
, SfxUndoManager::CurrentLevel
);
839 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
843 ImplCheckEmptyActions();
844 aGuard
.scheduleNotification( &SfxUndoListener::actionRedone
, sActionComment
);
850 size_t SfxUndoManager::GetRepeatActionCount() const
852 UndoManagerGuard
aGuard( *m_xData
);
853 return m_xData
->pActUndoArray
->maUndoActions
.size();
857 OUString
SfxUndoManager::GetRepeatActionComment(SfxRepeatTarget
&rTarget
) const
859 UndoManagerGuard
aGuard( *m_xData
);
860 return m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->maUndoActions
.size() - 1 ].pAction
861 ->GetRepeatComment(rTarget
);
865 bool SfxUndoManager::Repeat( SfxRepeatTarget
&rTarget
)
867 UndoManagerGuard
aGuard( *m_xData
);
868 if ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
870 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
.back().pAction
.get();
872 if ( pAction
->CanRepeat( rTarget
) )
873 pAction
->Repeat( rTarget
);
874 aGuard
.reset(); // allow clearing in guard dtor
882 bool SfxUndoManager::CanRepeat( SfxRepeatTarget
&rTarget
) const
884 UndoManagerGuard
aGuard( *m_xData
);
885 if ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
887 size_t nActionNo
= m_xData
->pActUndoArray
->maUndoActions
.size() - 1;
888 return m_xData
->pActUndoArray
->maUndoActions
[nActionNo
].pAction
->CanRepeat(rTarget
);
894 void SfxUndoManager::AddUndoListener( SfxUndoListener
& i_listener
)
896 UndoManagerGuard
aGuard( *m_xData
);
897 m_xData
->aListeners
.push_back( &i_listener
);
901 void SfxUndoManager::RemoveUndoListener( SfxUndoListener
& i_listener
)
903 UndoManagerGuard
aGuard( *m_xData
);
904 auto lookup
= std::find(m_xData
->aListeners
.begin(), m_xData
->aListeners
.end(), &i_listener
);
905 if (lookup
!= m_xData
->aListeners
.end())
906 m_xData
->aListeners
.erase( lookup
);
910 * Inserts a ListUndoAction and sets its UndoArray as current.
912 void SfxUndoManager::EnterListAction( const OUString
& rComment
,
913 const OUString
&rRepeatComment
, sal_uInt16 nId
,
914 ViewShellId nViewShellId
)
916 UndoManagerGuard
aGuard( *m_xData
);
918 if( !ImplIsUndoEnabled_Lock() )
921 if ( !m_xData
->pUndoArray
->nMaxUndoActions
)
924 SfxListUndoAction
* pAction
= new SfxListUndoAction( rComment
, rRepeatComment
, nId
, nViewShellId
, m_xData
->pActUndoArray
);
925 OSL_VERIFY( ImplAddUndoAction_NoNotify( std::unique_ptr
<SfxUndoAction
>(pAction
), false, false, aGuard
) );
926 // expected to succeed: all conditions under which it could fail should have been checked already
927 m_xData
->pActUndoArray
= pAction
;
930 aGuard
.scheduleNotification( &SfxUndoListener::listActionEntered
, rComment
);
934 bool SfxUndoManager::IsInListAction() const
936 UndoManagerGuard
aGuard( *m_xData
);
937 return ImplIsInListAction_Lock();
941 bool SfxUndoManager::ImplIsInListAction_Lock() const
943 return ( m_xData
->pActUndoArray
!= m_xData
->pUndoArray
.get() );
947 size_t SfxUndoManager::GetListActionDepth() const
949 UndoManagerGuard
aGuard( *m_xData
);
952 SfxUndoArray
* pLookup( m_xData
->pActUndoArray
);
953 while ( pLookup
!= m_xData
->pUndoArray
.get() )
955 pLookup
= pLookup
->pFatherUndoArray
;
963 size_t SfxUndoManager::LeaveListAction()
965 UndoManagerGuard
aGuard( *m_xData
);
966 size_t nCount
= ImplLeaveListAction( false, aGuard
);
968 if ( m_xData
->mbClearUntilTopLevel
)
970 ImplClearCurrentLevel_NoNotify( aGuard
);
971 if ( !ImplIsInListAction_Lock() )
973 m_xData
->mbClearUntilTopLevel
= false;
974 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
983 size_t SfxUndoManager::LeaveAndMergeListAction()
985 UndoManagerGuard
aGuard( *m_xData
);
986 return ImplLeaveListAction( true, aGuard
);
990 size_t SfxUndoManager::ImplLeaveListAction( const bool i_merge
, UndoManagerGuard
& i_guard
)
992 if ( !ImplIsUndoEnabled_Lock() )
995 if ( !m_xData
->pUndoArray
->nMaxUndoActions
)
998 if( !ImplIsInListAction_Lock() )
1000 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1004 assert(m_xData
->pActUndoArray
->pFatherUndoArray
);
1006 // the array/level which we're about to leave
1007 SfxUndoArray
* pArrayToLeave
= m_xData
->pActUndoArray
;
1009 m_xData
->pActUndoArray
= m_xData
->pActUndoArray
->pFatherUndoArray
;
1011 // If no undo actions were added to the list, delete the list action
1012 const size_t nListActionElements
= pArrayToLeave
->nCurUndoAction
;
1013 if ( nListActionElements
== 0 )
1015 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( --m_xData
->pActUndoArray
->nCurUndoAction
) );
1016 i_guard
.scheduleNotification( &SfxUndoListener::listActionCancelled
);
1020 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1022 ImplClearRedo( i_guard
, SfxUndoManager::CurrentLevel
);
1024 SfxUndoAction
* pCurrentAction
= m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->nCurUndoAction
-1 ].pAction
.get();
1025 SfxListUndoAction
* pListAction
= dynamic_cast< SfxListUndoAction
* >( pCurrentAction
);
1026 ENSURE_OR_RETURN( pListAction
, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements
);
1030 // merge the list action with its predecessor on the same level
1031 SAL_WARN_IF( m_xData
->pActUndoArray
->nCurUndoAction
<= 1, "svl",
1032 "SfxUndoManager::ImplLeaveListAction: cannot merge the list action if there's no other action on the same level - check this beforehand!" );
1033 if ( m_xData
->pActUndoArray
->nCurUndoAction
> 1 )
1035 std::unique_ptr
<SfxUndoAction
> pPreviousAction
= m_xData
->pActUndoArray
->Remove( m_xData
->pActUndoArray
->nCurUndoAction
- 2 );
1036 --m_xData
->pActUndoArray
->nCurUndoAction
;
1037 pListAction
->SetComment( pPreviousAction
->GetComment() );
1038 pListAction
->Insert( std::move(pPreviousAction
), 0 );
1039 ++pListAction
->nCurUndoAction
;
1043 // if the undo array has no comment, try to get it from its children
1044 if ( pListAction
->GetComment().isEmpty() )
1046 for( size_t n
= 0; n
< pListAction
->maUndoActions
.size(); n
++ )
1048 if (!pListAction
->maUndoActions
[n
].pAction
->GetComment().isEmpty())
1050 pListAction
->SetComment( pListAction
->maUndoActions
[n
].pAction
->GetComment() );
1056 ImplIsEmptyActions();
1058 i_guard
.scheduleNotification( &SfxUndoListener::listActionLeft
, pListAction
->GetComment() );
1061 return nListActionElements
;
1064 UndoStackMark
SfxUndoManager::MarkTopUndoAction()
1066 UndoManagerGuard
aGuard( *m_xData
);
1068 SAL_WARN_IF( IsInListAction(), "svl",
1069 "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
1070 assert((m_xData
->mnMarks
+ 1) < (m_xData
->mnEmptyMark
- 1) &&
1071 "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
1073 size_t const nActionPos
= m_xData
->pUndoArray
->nCurUndoAction
;
1074 if (0 == nActionPos
)
1076 --m_xData
->mnEmptyMark
;
1077 return m_xData
->mnEmptyMark
;
1080 m_xData
->pUndoArray
->maUndoActions
[ nActionPos
-1 ].aMarks
.push_back(
1081 ++m_xData
->mnMarks
);
1082 return m_xData
->mnMarks
;
1085 void SfxUndoManager::RemoveMark( UndoStackMark
const i_mark
)
1087 UndoManagerGuard
aGuard( *m_xData
);
1089 if ((m_xData
->mnEmptyMark
< i_mark
) || (MARK_INVALID
== i_mark
))
1091 return; // nothing to remove
1093 else if (i_mark
== m_xData
->mnEmptyMark
)
1095 --m_xData
->mnEmptyMark
; // never returned from MarkTop => invalid
1099 for ( size_t i
=0; i
<m_xData
->pUndoArray
->maUndoActions
.size(); ++i
)
1101 MarkedUndoAction
& rAction
= m_xData
->pUndoArray
->maUndoActions
[i
];
1102 auto markPos
= std::find(rAction
.aMarks
.begin(), rAction
.aMarks
.end(), i_mark
);
1103 if (markPos
!= rAction
.aMarks
.end())
1105 rAction
.aMarks
.erase( markPos
);
1109 SAL_WARN("svl", "SfxUndoManager::RemoveMark: mark not found!");
1110 // TODO: this might be too offensive. There are situations where we implicitly remove marks
1111 // without our clients, in particular the client which created the mark, having a chance to know
1115 bool SfxUndoManager::HasTopUndoActionMark( UndoStackMark
const i_mark
)
1117 UndoManagerGuard
aGuard( *m_xData
);
1119 size_t nActionPos
= m_xData
->pUndoArray
->nCurUndoAction
;
1120 if ( nActionPos
== 0 )
1122 return (i_mark
== m_xData
->mnEmptyMark
);
1125 const MarkedUndoAction
& rAction
=
1126 m_xData
->pUndoArray
->maUndoActions
[ nActionPos
-1 ];
1128 return std::find(rAction
.aMarks
.begin(), rAction
.aMarks
.end(), i_mark
) != rAction
.aMarks
.end();
1132 void SfxUndoManager::RemoveOldestUndoAction()
1134 UndoManagerGuard
aGuard( *m_xData
);
1136 if ( IsInListAction() && ( m_xData
->pUndoArray
->nCurUndoAction
== 1 ) )
1138 assert(!"SfxUndoManager::RemoveOldestUndoActions: cannot remove a not-yet-closed list action!");
1142 aGuard
.markForDeletion( m_xData
->pUndoArray
->Remove( 0 ) );
1143 --m_xData
->pUndoArray
->nCurUndoAction
;
1144 ImplCheckEmptyActions();
1147 void SfxUndoManager::dumpAsXml(xmlTextWriterPtr pWriter
) const
1149 UndoManagerGuard
aGuard(*m_xData
);
1154 pWriter
= xmlNewTextWriterFilename("undo.xml", 0);
1155 xmlTextWriterSetIndent(pWriter
,1);
1156 xmlTextWriterSetIndentString(pWriter
, BAD_CAST(" "));
1157 xmlTextWriterStartDocument(pWriter
, nullptr, nullptr, nullptr);
1161 xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxUndoManager"));
1162 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("nUndoActionCount"), BAD_CAST(OString::number(GetUndoActionCount()).getStr()));
1163 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("nRedoActionCount"), BAD_CAST(OString::number(GetRedoActionCount()).getStr()));
1165 xmlTextWriterStartElement(pWriter
, BAD_CAST("undoActions"));
1166 for (size_t i
= 0; i
< GetUndoActionCount(); ++i
)
1168 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1169 pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
- 1 - i
].pAction
->dumpAsXml(pWriter
);
1171 xmlTextWriterEndElement(pWriter
);
1173 xmlTextWriterStartElement(pWriter
, BAD_CAST("redoActions"));
1174 for (size_t i
= 0; i
< GetRedoActionCount(); ++i
)
1176 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1177 pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
+ i
].pAction
->dumpAsXml(pWriter
);
1179 xmlTextWriterEndElement(pWriter
);
1181 xmlTextWriterEndElement(pWriter
);
1184 xmlTextWriterEndDocument(pWriter
);
1185 xmlFreeTextWriter(pWriter
);
1189 /// Returns a JSON representation of pAction.
1190 static boost::property_tree::ptree
lcl_ActionToJson(size_t nIndex
, SfxUndoAction
const * pAction
)
1192 boost::property_tree::ptree aRet
;
1193 aRet
.put("index", nIndex
);
1194 aRet
.put("comment", pAction
->GetComment().toUtf8().getStr());
1195 aRet
.put("viewId", static_cast<sal_Int32
>(pAction
->GetViewShellId()));
1196 aRet
.put("dateTime", utl::toISO8601(pAction
->GetDateTime().GetUNODateTime()).toUtf8().getStr());
1200 OUString
SfxUndoManager::GetUndoActionsInfo() const
1202 boost::property_tree::ptree aActions
;
1203 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1204 for (size_t i
= 0; i
< GetUndoActionCount(); ++i
)
1206 boost::property_tree::ptree aAction
= lcl_ActionToJson(i
, pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
- 1 - i
].pAction
.get());
1207 aActions
.push_back(std::make_pair("", aAction
));
1210 boost::property_tree::ptree aTree
;
1211 aTree
.add_child("actions", aActions
);
1212 std::stringstream aStream
;
1213 boost::property_tree::write_json(aStream
, aTree
);
1214 return OUString::fromUtf8(aStream
.str().c_str());
1217 OUString
SfxUndoManager::GetRedoActionsInfo() const
1219 boost::property_tree::ptree aActions
;
1220 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1221 size_t nCount
= GetRedoActionCount();
1222 for (size_t i
= 0; i
< nCount
; ++i
)
1224 size_t nIndex
= nCount
- i
- 1;
1225 boost::property_tree::ptree aAction
= lcl_ActionToJson(nIndex
, pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
+ nIndex
].pAction
.get());
1226 aActions
.push_back(std::make_pair("", aAction
));
1229 boost::property_tree::ptree aTree
;
1230 aTree
.add_child("actions", aActions
);
1231 std::stringstream aStream
;
1232 boost::property_tree::write_json(aStream
, aTree
);
1233 return OUString::fromUtf8(aStream
.str().c_str());
1236 bool SfxUndoManager::IsEmptyActions() const
1238 UndoManagerGuard
aGuard(*m_xData
);
1240 return ImplIsEmptyActions();
1243 inline bool SfxUndoManager::ImplIsEmptyActions() const
1245 return m_xData
->pUndoArray
->nCurUndoAction
|| m_xData
->pUndoArray
->maUndoActions
.size() - m_xData
->pUndoArray
->nCurUndoAction
;
1248 void SfxUndoManager::ImplCheckEmptyActions()
1250 bool bEmptyActions
= ImplIsEmptyActions();
1251 if (m_xData
->mbEmptyActions
!= bEmptyActions
)
1253 m_xData
->mbEmptyActions
= bEmptyActions
;
1254 EmptyActionsChanged();
1258 void SfxUndoManager::EmptyActionsChanged()
1263 struct SfxListUndoAction::Impl
1265 sal_uInt16
const mnId
;
1266 ViewShellId
const mnViewShellId
;
1269 OUString
const maRepeatComment
;
1271 Impl( sal_uInt16 nId
, ViewShellId nViewShellId
, const OUString
& rComment
, const OUString
& rRepeatComment
) :
1272 mnId(nId
), mnViewShellId(nViewShellId
), maComment(rComment
), maRepeatComment(rRepeatComment
) {}
1275 sal_uInt16
SfxListUndoAction::GetId() const
1277 return mpImpl
->mnId
;
1280 OUString
SfxListUndoAction::GetComment() const
1282 return mpImpl
->maComment
;
1285 ViewShellId
SfxListUndoAction::GetViewShellId() const
1287 return mpImpl
->mnViewShellId
;
1290 void SfxListUndoAction::SetComment(const OUString
& rComment
)
1292 mpImpl
->maComment
= rComment
;
1295 OUString
SfxListUndoAction::GetRepeatComment(SfxRepeatTarget
&) const
1297 return mpImpl
->maRepeatComment
;
1300 SfxListUndoAction::SfxListUndoAction(
1301 const OUString
&rComment
,
1302 const OUString
&rRepeatComment
,
1304 ViewShellId nViewShellId
,
1305 SfxUndoArray
*pFather
) :
1306 mpImpl(new Impl(nId
, nViewShellId
, rComment
, rRepeatComment
))
1308 pFatherUndoArray
= pFather
;
1309 nMaxUndoActions
= USHRT_MAX
;
1312 SfxListUndoAction::~SfxListUndoAction()
1316 void SfxListUndoAction::Undo()
1318 for(size_t i
=nCurUndoAction
;i
>0;)
1319 maUndoActions
[--i
].pAction
->Undo();
1324 void SfxListUndoAction::UndoWithContext( SfxUndoContext
& i_context
)
1326 for(size_t i
=nCurUndoAction
;i
>0;)
1327 maUndoActions
[--i
].pAction
->UndoWithContext( i_context
);
1332 void SfxListUndoAction::Redo()
1334 for(size_t i
=nCurUndoAction
;i
<maUndoActions
.size();i
++)
1335 maUndoActions
[i
].pAction
->Redo();
1336 nCurUndoAction
= maUndoActions
.size();
1340 void SfxListUndoAction::RedoWithContext( SfxUndoContext
& i_context
)
1342 for(size_t i
=nCurUndoAction
;i
<maUndoActions
.size();i
++)
1343 maUndoActions
[i
].pAction
->RedoWithContext( i_context
);
1344 nCurUndoAction
= maUndoActions
.size();
1348 void SfxListUndoAction::Repeat(SfxRepeatTarget
&rTarget
)
1350 for(size_t i
=0;i
<nCurUndoAction
;i
++)
1351 maUndoActions
[i
].pAction
->Repeat(rTarget
);
1355 bool SfxListUndoAction::CanRepeat(SfxRepeatTarget
&r
) const
1357 for(size_t i
=0;i
<nCurUndoAction
;i
++)
1359 if(!maUndoActions
[i
].pAction
->CanRepeat(r
))
1366 bool SfxListUndoAction::Merge( SfxUndoAction
*pNextAction
)
1368 return !maUndoActions
.empty() && maUndoActions
[maUndoActions
.size()-1].pAction
->Merge( pNextAction
);
1371 void SfxListUndoAction::dumpAsXml(xmlTextWriterPtr pWriter
) const
1373 xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxListUndoAction"));
1374 xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("size"), BAD_CAST(OString::number(maUndoActions
.size()).getStr()));
1375 SfxUndoAction::dumpAsXml(pWriter
);
1377 for (size_t i
= 0; i
< maUndoActions
.size(); ++i
)
1378 maUndoActions
[i
].pAction
->dumpAsXml(pWriter
);
1380 xmlTextWriterEndElement(pWriter
);
1383 SfxUndoArray::~SfxUndoArray()
1387 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */