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 <osl/mutex.hxx>
23 #include <sal/log.hxx>
24 #include <comphelper/flagguard.hxx>
25 #include <comphelper/diagnose_ex.hxx>
26 #include <tools/long.hxx>
27 #include <libxml/xmlwriter.h>
28 #include <boost/property_tree/json_parser.hpp>
29 #include <unotools/datetime.hxx>
39 class SfxMarkedUndoContext final
: public SfxUndoContext
42 SfxMarkedUndoContext(SfxUndoManager
& manager
, UndoStackMark mark
)
44 m_offset
= manager
.RemoveMark(mark
);
45 size_t count
= manager
.GetUndoActionCount();
47 m_offset
= count
- m_offset
- 1;
49 m_offset
= std::numeric_limits
<size_t>::max();
51 size_t GetUndoOffset() override
{ return m_offset
; }
58 SfxRepeatTarget::~SfxRepeatTarget()
63 SfxUndoContext::~SfxUndoContext()
68 SfxUndoAction::~SfxUndoAction() COVERITY_NOEXCEPT_FALSE
73 SfxUndoAction::SfxUndoAction()
74 : m_aDateTime(DateTime::SYSTEM
)
76 m_aDateTime
.ConvertToUTC();
80 bool SfxUndoAction::Merge( SfxUndoAction
* )
86 OUString
SfxUndoAction::GetComment() const
92 ViewShellId
SfxUndoAction::GetViewShellId() const
94 return ViewShellId(-1);
97 const DateTime
& SfxUndoAction::GetDateTime() const
102 OUString
SfxUndoAction::GetRepeatComment(SfxRepeatTarget
&) const
108 void SfxUndoAction::Undo()
110 // These are only conceptually pure virtual
111 assert(!"pure virtual function called: SfxUndoAction::Undo()");
115 void SfxUndoAction::UndoWithContext( SfxUndoContext
& )
121 void SfxUndoAction::Redo()
123 // These are only conceptually pure virtual
124 assert(!"pure virtual function called: SfxUndoAction::Redo()");
128 void SfxUndoAction::RedoWithContext( SfxUndoContext
& )
134 void SfxUndoAction::Repeat(SfxRepeatTarget
&)
136 // These are only conceptually pure virtual
137 assert(!"pure virtual function called: SfxUndoAction::Repeat()");
141 bool SfxUndoAction::CanRepeat(SfxRepeatTarget
&) const
146 void SfxUndoAction::dumpAsXml(xmlTextWriterPtr pWriter
) const
148 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxUndoAction"));
149 (void)xmlTextWriterWriteFormatAttribute(pWriter
, BAD_CAST("ptr"), "%p", this);
150 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
151 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("comment"), BAD_CAST(GetComment().toUtf8().getStr()));
152 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("viewShellId"), BAD_CAST(OString::number(static_cast<sal_Int32
>(GetViewShellId())).getStr()));
153 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("dateTime"), BAD_CAST(utl::toISO8601(m_aDateTime
.GetUNODateTime()).toUtf8().getStr()));
154 (void)xmlTextWriterEndElement(pWriter
);
157 std::unique_ptr
<SfxUndoAction
> SfxUndoArray::Remove(int idx
)
159 auto ret
= std::move(maUndoActions
[idx
].pAction
);
160 maUndoActions
.erase(maUndoActions
.begin() + idx
);
164 void SfxUndoArray::Remove( size_t i_pos
, size_t i_count
)
166 maUndoActions
.erase(maUndoActions
.begin() + i_pos
, maUndoActions
.begin() + i_pos
+ i_count
);
169 void SfxUndoArray::Insert( std::unique_ptr
<SfxUndoAction
> i_action
, size_t i_pos
)
171 maUndoActions
.insert( maUndoActions
.begin() + i_pos
, MarkedUndoAction(std::move(i_action
)) );
174 typedef ::std::vector
< SfxUndoListener
* > UndoListeners
;
176 struct SfxUndoManager_Data
179 SfxUndoArray maUndoArray
;
180 SfxUndoArray
* pActUndoArray
;
183 sal_Int32 mnEmptyMark
;
186 bool mbClearUntilTopLevel
;
188 std::optional
<bool> moNeedsClearRedo
; // holds a requested ClearRedo until safe to clear stack
190 UndoListeners aListeners
;
192 explicit SfxUndoManager_Data( size_t i_nMaxUndoActionCount
)
193 :maUndoArray( i_nMaxUndoActionCount
)
194 ,pActUndoArray( nullptr )
196 ,mnEmptyMark(MARK_INVALID
)
197 ,mbUndoEnabled( true )
199 ,mbClearUntilTopLevel( false )
200 ,mbEmptyActions( true )
202 pActUndoArray
= &maUndoArray
;
205 // Copy assignment is forbidden and not implemented.
206 SfxUndoManager_Data (const SfxUndoManager_Data
&) = delete;
207 SfxUndoManager_Data
& operator= (const SfxUndoManager_Data
&) = delete;
210 namespace svl::undo::impl
215 explicit LockGuard( SfxUndoManager
& i_manager
)
216 :m_manager( i_manager
)
218 m_manager
.ImplEnableUndo_Lock( false );
223 m_manager
.ImplEnableUndo_Lock( true );
227 SfxUndoManager
& m_manager
;
230 typedef void ( SfxUndoListener::*UndoListenerVoidMethod
)();
231 typedef void ( SfxUndoListener::*UndoListenerStringMethod
)( const OUString
& );
235 struct NotifyUndoListener
237 explicit NotifyUndoListener( UndoListenerVoidMethod i_notificationMethod
)
238 :m_notificationMethod( i_notificationMethod
)
239 ,m_altNotificationMethod( nullptr )
243 NotifyUndoListener( UndoListenerStringMethod i_notificationMethod
, OUString i_actionComment
)
244 :m_notificationMethod( nullptr )
245 ,m_altNotificationMethod( i_notificationMethod
)
246 ,m_sActionComment(std::move( i_actionComment
))
252 return ( m_notificationMethod
!= nullptr ) || ( m_altNotificationMethod
!= nullptr );
255 void operator()( SfxUndoListener
* i_listener
) const
257 assert( is() && "NotifyUndoListener: this will crash!" );
258 if ( m_altNotificationMethod
!= nullptr )
260 ( i_listener
->*m_altNotificationMethod
)( m_sActionComment
);
264 ( i_listener
->*m_notificationMethod
)();
269 UndoListenerVoidMethod m_notificationMethod
;
270 UndoListenerStringMethod m_altNotificationMethod
;
271 OUString m_sActionComment
;
276 class UndoManagerGuard
279 explicit UndoManagerGuard( SfxUndoManager_Data
& i_managerData
)
280 :m_rManagerData( i_managerData
)
281 ,m_aGuard( i_managerData
.aMutex
)
287 auto clear() { return osl::ResettableMutexGuardScopedReleaser(m_aGuard
); }
289 void cancelNotifications()
294 /** marks the given Undo action for deletion
296 The Undo action will be put into a list, whose members will be deleted from within the destructor of the
297 UndoManagerGuard. This deletion will happen without the UndoManager's mutex locked.
299 void markForDeletion( std::unique_ptr
<SfxUndoAction
> i_action
)
303 m_aUndoActionsCleanup
.emplace_back( std::move(i_action
) );
306 /** schedules the given SfxUndoListener method to be called for all registered listeners.
308 The notification will happen after the Undo manager's mutex has been released, and after all pending
309 deletions of Undo actions are done.
311 void scheduleNotification( UndoListenerVoidMethod i_notificationMethod
)
313 m_notifiers
.emplace_back( i_notificationMethod
);
316 void scheduleNotification( UndoListenerStringMethod i_notificationMethod
, const OUString
& i_actionComment
)
318 m_notifiers
.emplace_back( i_notificationMethod
, i_actionComment
);
322 SfxUndoManager_Data
& m_rManagerData
;
323 ::osl::ResettableMutexGuard m_aGuard
;
324 ::std::vector
< std::unique_ptr
<SfxUndoAction
> > m_aUndoActionsCleanup
;
325 ::std::vector
< NotifyUndoListener
> m_notifiers
;
328 UndoManagerGuard::~UndoManagerGuard()
331 UndoListeners
aListenersCopy( m_rManagerData
.aListeners
);
336 // delete all actions
337 m_aUndoActionsCleanup
.clear();
339 // handle scheduled notification
340 for (auto const& notifier
: m_notifiers
)
343 ::std::for_each( aListenersCopy
.begin(), aListenersCopy
.end(), notifier
);
348 using namespace ::svl::undo::impl
;
351 SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount
)
352 :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount
) )
354 m_xData
->mbEmptyActions
= !ImplIsEmptyActions();
358 SfxUndoManager::~SfxUndoManager()
363 void SfxUndoManager::EnableUndo( bool i_enable
)
365 UndoManagerGuard
aGuard( *m_xData
);
366 ImplEnableUndo_Lock( i_enable
);
371 void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable
)
373 if ( m_xData
->mbUndoEnabled
== i_enable
)
375 m_xData
->mbUndoEnabled
= i_enable
;
379 bool SfxUndoManager::IsUndoEnabled() const
381 UndoManagerGuard
aGuard( *m_xData
);
382 return ImplIsUndoEnabled_Lock();
386 bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
388 return m_xData
->mbUndoEnabled
;
391 void SfxUndoManager::SetMaxUndoActionCount( size_t nMaxUndoActionCount
)
393 UndoManagerGuard
aGuard( *m_xData
);
395 // Remove entries from the pActUndoArray when we have to reduce
396 // the number of entries due to a lower nMaxUndoActionCount.
397 // Both redo and undo action entries will be removed until we reached the
398 // new nMaxUndoActionCount.
400 tools::Long nNumToDelete
= m_xData
->pActUndoArray
->maUndoActions
.size() - nMaxUndoActionCount
;
401 while ( nNumToDelete
> 0 )
403 size_t nPos
= m_xData
->pActUndoArray
->maUndoActions
.size();
404 if ( nPos
> m_xData
->pActUndoArray
->nCurUndoAction
)
406 aGuard
.markForDeletion( m_xData
->pActUndoArray
->Remove( nPos
-1 ) );
410 if ( nNumToDelete
> 0 && m_xData
->pActUndoArray
->nCurUndoAction
> 0 )
412 aGuard
.markForDeletion( m_xData
->pActUndoArray
->Remove(0) );
413 --m_xData
->pActUndoArray
->nCurUndoAction
;
417 if ( nPos
== m_xData
->pActUndoArray
->maUndoActions
.size() )
418 break; // Cannot delete more entries
421 m_xData
->pActUndoArray
->nMaxUndoActions
= nMaxUndoActionCount
;
422 ImplCheckEmptyActions();
425 size_t SfxUndoManager::GetMaxUndoActionCount() const
427 return m_xData
->pActUndoArray
->nMaxUndoActions
;
430 void SfxUndoManager::ImplClearCurrentLevel_NoNotify( UndoManagerGuard
& i_guard
)
433 while ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
435 size_t deletePos
= m_xData
->pActUndoArray
->maUndoActions
.size() - 1;
436 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( deletePos
) );
439 m_xData
->pActUndoArray
->nCurUndoAction
= 0;
441 m_xData
->mnMarks
= 0;
442 m_xData
->mnEmptyMark
= MARK_INVALID
;
443 ImplCheckEmptyActions();
447 void SfxUndoManager::Clear()
449 UndoManagerGuard
aGuard( *m_xData
);
451 SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
452 "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
453 ImplClearCurrentLevel_NoNotify( aGuard
);
456 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
460 void SfxUndoManager::ClearAllLevels()
462 UndoManagerGuard
aGuard( *m_xData
);
463 ImplClearCurrentLevel_NoNotify( aGuard
);
465 if ( ImplIsInListAction_Lock() )
467 m_xData
->mbClearUntilTopLevel
= true;
471 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
476 void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel
)
480 // cannot clear redo while undo/redo is in process. Delay ClearRedo until safe to clear.
481 // (assuming if TopLevel requests a clear, it should have priority over CurrentLevel)
482 if (!m_xData
->moNeedsClearRedo
.has_value() || i_currentLevel
== TopLevel
)
483 m_xData
->moNeedsClearRedo
= i_currentLevel
;
486 UndoManagerGuard
aGuard( *m_xData
);
487 ImplClearRedo( aGuard
, i_currentLevel
);
491 void SfxUndoManager::ClearRedo()
493 SAL_WARN_IF( IsInListAction(), "svl",
494 "SfxUndoManager::ClearRedo: suspicious call - do you really wish to clear the current level?" );
495 ImplClearRedo_NoLock( CurrentLevel
);
498 void SfxUndoManager::Reset()
500 UndoManagerGuard
aGuard( *m_xData
);
503 while ( !ImplIsUndoEnabled_Lock() )
504 ImplEnableUndo_Lock( true );
506 // cancel all list actions
507 while ( IsInListAction() )
508 ImplLeaveListAction( false, aGuard
);
511 ImplClearCurrentLevel_NoNotify( aGuard
);
513 // cancel the notifications scheduled by ImplLeaveListAction,
514 // as we want to do an own, dedicated notification
515 aGuard
.cancelNotifications();
517 // schedule notification
518 aGuard
.scheduleNotification( &SfxUndoListener::resetAll
);
522 void SfxUndoManager::ImplClearUndo( UndoManagerGuard
& i_guard
)
524 while ( m_xData
->pActUndoArray
->nCurUndoAction
> 0 )
526 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( 0 ) );
527 --m_xData
->pActUndoArray
->nCurUndoAction
;
529 ImplCheckEmptyActions();
530 // TODO: notifications? We don't have clearedUndo, only cleared and clearedRedo at the SfxUndoListener
534 void SfxUndoManager::ImplClearRedo( UndoManagerGuard
& i_guard
, bool const i_currentLevel
)
536 SfxUndoArray
* pUndoArray
= ( i_currentLevel
== SfxUndoManager::CurrentLevel
) ? m_xData
->pActUndoArray
: &m_xData
->maUndoArray
;
539 while ( pUndoArray
->maUndoActions
.size() > pUndoArray
->nCurUndoAction
)
541 size_t deletePos
= pUndoArray
->maUndoActions
.size() - 1;
542 i_guard
.markForDeletion( pUndoArray
->Remove( deletePos
) );
545 ImplCheckEmptyActions();
546 // notification - only if the top level's stack was cleared
547 if ( i_currentLevel
== SfxUndoManager::TopLevel
)
548 i_guard
.scheduleNotification( &SfxUndoListener::clearedRedo
);
552 bool SfxUndoManager::ImplAddUndoAction_NoNotify( std::unique_ptr
<SfxUndoAction
> pAction
, bool bTryMerge
, bool bClearRedo
, UndoManagerGuard
& i_guard
)
554 if ( !ImplIsUndoEnabled_Lock() || ( m_xData
->pActUndoArray
->nMaxUndoActions
== 0 ) )
556 i_guard
.markForDeletion( std::move(pAction
) );
560 // merge, if required
561 SfxUndoAction
* pMergeWithAction
= m_xData
->pActUndoArray
->nCurUndoAction
?
562 m_xData
->pActUndoArray
->maUndoActions
[m_xData
->pActUndoArray
->nCurUndoAction
-1].pAction
.get() : nullptr;
563 if ( bTryMerge
&& pMergeWithAction
)
565 bool bMerged
= pMergeWithAction
->Merge( pAction
.get() );
568 i_guard
.markForDeletion( std::move(pAction
) );
573 // clear redo stack, if requested
574 if ( bClearRedo
&& ( ImplGetRedoActionCount_Lock() > 0 ) )
575 ImplClearRedo( i_guard
, SfxUndoManager::CurrentLevel
);
577 // respect max number
578 if( m_xData
->pActUndoArray
== &m_xData
->maUndoArray
)
580 while(m_xData
->pActUndoArray
->maUndoActions
.size() >= m_xData
->pActUndoArray
->nMaxUndoActions
)
582 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove(0) );
583 if (m_xData
->pActUndoArray
->nCurUndoAction
> 0)
585 --m_xData
->pActUndoArray
->nCurUndoAction
;
586 // fdo#66071 invalidate the current empty mark when removing
587 --m_xData
->mnEmptyMark
;
593 m_xData
->pActUndoArray
->Insert( std::move(pAction
), m_xData
->pActUndoArray
->nCurUndoAction
++ );
594 ImplCheckEmptyActions();
599 void SfxUndoManager::AddUndoAction( std::unique_ptr
<SfxUndoAction
> pAction
, bool bTryMerge
)
601 UndoManagerGuard
aGuard( *m_xData
);
604 auto pActionTmp
= pAction
.get();
605 if ( ImplAddUndoAction_NoNotify( std::move(pAction
), bTryMerge
, true, aGuard
) )
608 aGuard
.scheduleNotification( &SfxUndoListener::undoActionAdded
, pActionTmp
->GetComment() );
613 size_t SfxUndoManager::GetUndoActionCount( bool const i_currentLevel
) const
615 UndoManagerGuard
aGuard( *m_xData
);
616 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: &m_xData
->maUndoArray
;
617 return pUndoArray
->nCurUndoAction
;
621 OUString
SfxUndoManager::GetUndoActionComment( size_t nNo
, bool const i_currentLevel
) const
623 UndoManagerGuard
aGuard( *m_xData
);
626 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: &m_xData
->maUndoArray
;
627 assert(nNo
< pUndoArray
->nCurUndoAction
);
628 if( nNo
< pUndoArray
->nCurUndoAction
)
629 sComment
= pUndoArray
->maUndoActions
[ pUndoArray
->nCurUndoAction
- 1 - nNo
].pAction
->GetComment();
634 SfxUndoAction
* SfxUndoManager::GetUndoAction( size_t nNo
) const
636 UndoManagerGuard
aGuard( *m_xData
);
638 assert(nNo
< m_xData
->pActUndoArray
->nCurUndoAction
);
639 if( nNo
>= m_xData
->pActUndoArray
->nCurUndoAction
)
641 return m_xData
->pActUndoArray
->maUndoActions
[m_xData
->pActUndoArray
->nCurUndoAction
-1-nNo
].pAction
.get();
645 /** clears the redo stack and removes the top undo action */
646 void SfxUndoManager::RemoveLastUndoAction()
648 UndoManagerGuard
aGuard( *m_xData
);
650 ENSURE_OR_RETURN_VOID( m_xData
->pActUndoArray
->nCurUndoAction
, "svl::SfxUndoManager::RemoveLastUndoAction(), no action to remove?!" );
652 m_xData
->pActUndoArray
->nCurUndoAction
--;
654 // delete redo-actions and top action
655 for ( size_t nPos
= m_xData
->pActUndoArray
->maUndoActions
.size(); nPos
> m_xData
->pActUndoArray
->nCurUndoAction
; --nPos
)
657 aGuard
.markForDeletion( std::move(m_xData
->pActUndoArray
->maUndoActions
[nPos
-1].pAction
) );
660 m_xData
->pActUndoArray
->Remove(
661 m_xData
->pActUndoArray
->nCurUndoAction
,
662 m_xData
->pActUndoArray
->maUndoActions
.size() - m_xData
->pActUndoArray
->nCurUndoAction
);
663 ImplCheckEmptyActions();
667 bool SfxUndoManager::IsDoing() const
669 UndoManagerGuard
aGuard( *m_xData
);
670 return m_xData
->mbDoing
;
674 bool SfxUndoManager::Undo()
676 return ImplUndo( nullptr );
680 bool SfxUndoManager::UndoWithContext( SfxUndoContext
& i_context
)
682 return ImplUndo( &i_context
);
686 bool SfxUndoManager::ImplUndo( SfxUndoContext
* i_contextOrNull
)
688 UndoManagerGuard
aGuard( *m_xData
);
689 assert( !IsDoing() && "SfxUndoManager::Undo: *nested* Undo/Redo actions? How this?" );
691 ::comphelper::FlagGuard
aDoingGuard( m_xData
->mbDoing
);
692 m_xData
->mbDoing
= true;
694 LockGuard
aLockGuard( *this );
696 if ( ImplIsInListAction_Lock() )
698 assert(!"SfxUndoManager::Undo: not possible when within a list action!");
702 if ( m_xData
->pActUndoArray
->nCurUndoAction
== 0 )
704 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
708 if (i_contextOrNull
&& i_contextOrNull
->GetUndoOffset() > 0)
710 size_t nCurrent
= m_xData
->pActUndoArray
->nCurUndoAction
;
711 size_t nOffset
= i_contextOrNull
->GetUndoOffset();
712 if (nCurrent
>= nOffset
+ 1)
714 // Move the action we want to execute to the top of the undo stack.
715 // data() + nCurrent - nOffset - 1 is the start, data() + nCurrent - nOffset is what we
716 // want to move to the top, maUndoActions.data() + nCurrent is past the end/top of the
718 std::rotate(m_xData
->pActUndoArray
->maUndoActions
.data() + nCurrent
- nOffset
- 1,
719 m_xData
->pActUndoArray
->maUndoActions
.data() + nCurrent
- nOffset
,
720 m_xData
->pActUndoArray
->maUndoActions
.data() + nCurrent
);
724 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
[ --m_xData
->pActUndoArray
->nCurUndoAction
].pAction
.get();
725 const OUString sActionComment
= pAction
->GetComment();
728 // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
730 auto aResetGuard(aGuard
.clear());
731 if ( i_contextOrNull
!= nullptr )
732 pAction
->UndoWithContext( *i_contextOrNull
);
738 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
739 // we still find pAction in our current Undo array
740 size_t nCurAction
= 0;
741 while ( nCurAction
< m_xData
->pActUndoArray
->maUndoActions
.size() )
743 if ( m_xData
->pActUndoArray
->maUndoActions
[ nCurAction
++ ].pAction
.get() == pAction
)
745 // the Undo action is still there ...
746 // assume the error is a permanent failure, and clear the Undo stack
747 ImplClearUndo( aGuard
);
751 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
755 m_xData
->mbDoing
= false;
756 if (m_xData
->moNeedsClearRedo
.has_value())
758 ImplClearRedo_NoLock(*m_xData
->moNeedsClearRedo
);
759 m_xData
->moNeedsClearRedo
.reset();
762 aGuard
.scheduleNotification( &SfxUndoListener::actionUndone
, sActionComment
);
768 size_t SfxUndoManager::GetRedoActionCount( bool const i_currentLevel
) const
770 UndoManagerGuard
aGuard( *m_xData
);
771 return ImplGetRedoActionCount_Lock( i_currentLevel
);
775 size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel
) const
777 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: &m_xData
->maUndoArray
;
778 return pUndoArray
->maUndoActions
.size() - pUndoArray
->nCurUndoAction
;
782 SfxUndoAction
* SfxUndoManager::GetRedoAction(size_t nNo
) const
784 UndoManagerGuard
aGuard( *m_xData
);
786 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
787 if ( (pUndoArray
->nCurUndoAction
) > pUndoArray
->maUndoActions
.size() )
791 return pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
+ nNo
].pAction
.get();
795 OUString
SfxUndoManager::GetRedoActionComment( size_t nNo
, bool const i_currentLevel
) const
798 UndoManagerGuard
aGuard( *m_xData
);
799 const SfxUndoArray
* pUndoArray
= i_currentLevel
? m_xData
->pActUndoArray
: &m_xData
->maUndoArray
;
800 if ( (pUndoArray
->nCurUndoAction
+ nNo
) < pUndoArray
->maUndoActions
.size() )
802 sComment
= pUndoArray
->maUndoActions
[ pUndoArray
->nCurUndoAction
+ nNo
].pAction
->GetComment();
808 bool SfxUndoManager::Redo()
810 return ImplRedo( nullptr );
814 bool SfxUndoManager::RedoWithContext( SfxUndoContext
& i_context
)
816 return ImplRedo( &i_context
);
820 bool SfxUndoManager::ImplRedo( SfxUndoContext
* i_contextOrNull
)
822 UndoManagerGuard
aGuard( *m_xData
);
823 assert( !IsDoing() && "SfxUndoManager::Redo: *nested* Undo/Redo actions? How this?" );
825 ::comphelper::FlagGuard
aDoingGuard( m_xData
->mbDoing
);
826 m_xData
->mbDoing
= true;
828 LockGuard
aLockGuard( *this );
830 if ( ImplIsInListAction_Lock() )
832 assert(!"SfxUndoManager::Redo: not possible when within a list action!");
836 if ( m_xData
->pActUndoArray
->nCurUndoAction
>= m_xData
->pActUndoArray
->maUndoActions
.size() )
838 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
842 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->nCurUndoAction
++ ].pAction
.get();
843 const OUString sActionComment
= pAction
->GetComment();
846 // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
848 auto aResetGuard(aGuard
.clear());
849 if ( i_contextOrNull
!= nullptr )
850 pAction
->RedoWithContext( *i_contextOrNull
);
856 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
857 // we still find pAction in our current Undo array
858 size_t nCurAction
= 0;
859 while ( nCurAction
< m_xData
->pActUndoArray
->maUndoActions
.size() )
861 if ( m_xData
->pActUndoArray
->maUndoActions
[ nCurAction
].pAction
.get() == pAction
)
863 // the Undo action is still there ...
864 // assume the error is a permanent failure, and clear the Undo stack
865 ImplClearRedo( aGuard
, SfxUndoManager::CurrentLevel
);
870 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
874 m_xData
->mbDoing
= false;
875 assert(!m_xData
->moNeedsClearRedo
.has_value() && "Assuming I don't need to handle it here. What about if thrown?");
876 ImplCheckEmptyActions();
877 aGuard
.scheduleNotification( &SfxUndoListener::actionRedone
, sActionComment
);
883 size_t SfxUndoManager::GetRepeatActionCount() const
885 UndoManagerGuard
aGuard( *m_xData
);
886 return m_xData
->pActUndoArray
->maUndoActions
.size();
890 OUString
SfxUndoManager::GetRepeatActionComment(SfxRepeatTarget
&rTarget
) const
892 UndoManagerGuard
aGuard( *m_xData
);
893 return m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->maUndoActions
.size() - 1 ].pAction
894 ->GetRepeatComment(rTarget
);
898 bool SfxUndoManager::Repeat( SfxRepeatTarget
&rTarget
)
900 UndoManagerGuard
aGuard( *m_xData
);
901 if ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
903 SfxUndoAction
* pAction
= m_xData
->pActUndoArray
->maUndoActions
.back().pAction
.get();
904 auto aResetGuard(aGuard
.clear());
905 if ( pAction
->CanRepeat( rTarget
) )
906 pAction
->Repeat( rTarget
);
914 bool SfxUndoManager::CanRepeat( SfxRepeatTarget
&rTarget
) const
916 UndoManagerGuard
aGuard( *m_xData
);
917 if ( !m_xData
->pActUndoArray
->maUndoActions
.empty() )
919 size_t nActionNo
= m_xData
->pActUndoArray
->maUndoActions
.size() - 1;
920 return m_xData
->pActUndoArray
->maUndoActions
[nActionNo
].pAction
->CanRepeat(rTarget
);
926 void SfxUndoManager::AddUndoListener( SfxUndoListener
& i_listener
)
928 UndoManagerGuard
aGuard( *m_xData
);
929 m_xData
->aListeners
.push_back( &i_listener
);
933 void SfxUndoManager::RemoveUndoListener( SfxUndoListener
& i_listener
)
935 UndoManagerGuard
aGuard( *m_xData
);
936 auto lookup
= std::find(m_xData
->aListeners
.begin(), m_xData
->aListeners
.end(), &i_listener
);
937 if (lookup
!= m_xData
->aListeners
.end())
938 m_xData
->aListeners
.erase( lookup
);
942 * Inserts a ListUndoAction and sets its UndoArray as current.
944 void SfxUndoManager::EnterListAction( const OUString
& rComment
,
945 const OUString
&rRepeatComment
, sal_uInt16 nId
,
946 ViewShellId nViewShellId
)
948 UndoManagerGuard
aGuard( *m_xData
);
950 if( !ImplIsUndoEnabled_Lock() )
953 if ( !m_xData
->maUndoArray
.nMaxUndoActions
)
956 SfxListUndoAction
* pAction
= new SfxListUndoAction( rComment
, rRepeatComment
, nId
, nViewShellId
, m_xData
->pActUndoArray
);
957 OSL_VERIFY( ImplAddUndoAction_NoNotify( std::unique_ptr
<SfxUndoAction
>(pAction
), false, false, aGuard
) );
958 // expected to succeed: all conditions under which it could fail should have been checked already
959 m_xData
->pActUndoArray
= pAction
;
962 aGuard
.scheduleNotification( &SfxUndoListener::listActionEntered
, rComment
);
966 bool SfxUndoManager::IsInListAction() const
968 UndoManagerGuard
aGuard( *m_xData
);
969 return ImplIsInListAction_Lock();
973 bool SfxUndoManager::ImplIsInListAction_Lock() const
975 return m_xData
->pActUndoArray
!= &m_xData
->maUndoArray
;
979 size_t SfxUndoManager::GetListActionDepth() const
981 UndoManagerGuard
aGuard( *m_xData
);
984 SfxUndoArray
* pLookup( m_xData
->pActUndoArray
);
985 while ( pLookup
!= &m_xData
->maUndoArray
)
987 pLookup
= pLookup
->pFatherUndoArray
;
995 size_t SfxUndoManager::LeaveListAction()
997 UndoManagerGuard
aGuard( *m_xData
);
998 size_t nCount
= ImplLeaveListAction( false, aGuard
);
1000 if ( m_xData
->mbClearUntilTopLevel
)
1002 ImplClearCurrentLevel_NoNotify( aGuard
);
1003 if ( !ImplIsInListAction_Lock() )
1005 m_xData
->mbClearUntilTopLevel
= false;
1006 aGuard
.scheduleNotification( &SfxUndoListener::cleared
);
1015 size_t SfxUndoManager::LeaveAndMergeListAction()
1017 UndoManagerGuard
aGuard( *m_xData
);
1018 return ImplLeaveListAction( true, aGuard
);
1022 size_t SfxUndoManager::ImplLeaveListAction( const bool i_merge
, UndoManagerGuard
& i_guard
)
1024 if ( !ImplIsUndoEnabled_Lock() )
1027 if ( !m_xData
->maUndoArray
.nMaxUndoActions
)
1030 if( !ImplIsInListAction_Lock() )
1032 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1036 assert(m_xData
->pActUndoArray
->pFatherUndoArray
);
1038 // the array/level which we're about to leave
1039 SfxUndoArray
* pArrayToLeave
= m_xData
->pActUndoArray
;
1041 m_xData
->pActUndoArray
= m_xData
->pActUndoArray
->pFatherUndoArray
;
1043 // If no undo actions were added to the list, delete the list action
1044 const size_t nListActionElements
= pArrayToLeave
->nCurUndoAction
;
1045 if ( nListActionElements
== 0 )
1047 i_guard
.markForDeletion( m_xData
->pActUndoArray
->Remove( --m_xData
->pActUndoArray
->nCurUndoAction
) );
1048 i_guard
.scheduleNotification( &SfxUndoListener::listActionCancelled
);
1052 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1054 ImplClearRedo( i_guard
, SfxUndoManager::CurrentLevel
);
1056 SfxUndoAction
* pCurrentAction
= m_xData
->pActUndoArray
->maUndoActions
[ m_xData
->pActUndoArray
->nCurUndoAction
-1 ].pAction
.get();
1057 SfxListUndoAction
* pListAction
= dynamic_cast< SfxListUndoAction
* >( pCurrentAction
);
1058 ENSURE_OR_RETURN( pListAction
, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements
);
1062 // merge the list action with its predecessor on the same level
1063 SAL_WARN_IF( m_xData
->pActUndoArray
->nCurUndoAction
<= 1, "svl",
1064 "SfxUndoManager::ImplLeaveListAction: cannot merge the list action if there's no other action on the same level - check this beforehand!" );
1065 if ( m_xData
->pActUndoArray
->nCurUndoAction
> 1 )
1067 std::unique_ptr
<SfxUndoAction
> pPreviousAction
= m_xData
->pActUndoArray
->Remove( m_xData
->pActUndoArray
->nCurUndoAction
- 2 );
1068 --m_xData
->pActUndoArray
->nCurUndoAction
;
1069 pListAction
->SetComment( pPreviousAction
->GetComment() );
1070 pListAction
->Insert( std::move(pPreviousAction
), 0 );
1071 ++pListAction
->nCurUndoAction
;
1075 // if the undo array has no comment, try to get it from its children
1076 if ( pListAction
->GetComment().isEmpty() )
1078 for( size_t n
= 0; n
< pListAction
->maUndoActions
.size(); n
++ )
1080 if (!pListAction
->maUndoActions
[n
].pAction
->GetComment().isEmpty())
1082 pListAction
->SetComment( pListAction
->maUndoActions
[n
].pAction
->GetComment() );
1088 ImplIsEmptyActions();
1090 i_guard
.scheduleNotification( &SfxUndoListener::listActionLeft
, pListAction
->GetComment() );
1093 return nListActionElements
;
1096 UndoStackMark
SfxUndoManager::MarkTopUndoAction()
1098 UndoManagerGuard
aGuard( *m_xData
);
1100 SAL_WARN_IF( IsInListAction(), "svl",
1101 "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
1102 assert((m_xData
->mnMarks
+ 1) < (m_xData
->mnEmptyMark
- 1) &&
1103 "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
1105 size_t const nActionPos
= m_xData
->maUndoArray
.nCurUndoAction
;
1106 if (0 == nActionPos
)
1108 --m_xData
->mnEmptyMark
;
1109 return m_xData
->mnEmptyMark
;
1112 m_xData
->maUndoArray
.maUndoActions
[ nActionPos
-1 ].aMarks
.push_back(
1113 ++m_xData
->mnMarks
);
1114 return m_xData
->mnMarks
;
1117 size_t SfxUndoManager::RemoveMark(UndoStackMark i_mark
)
1119 UndoManagerGuard
aGuard( *m_xData
);
1121 if ((m_xData
->mnEmptyMark
< i_mark
) || (MARK_INVALID
== i_mark
))
1123 return std::numeric_limits
<size_t>::max(); // nothing to remove
1125 else if (i_mark
== m_xData
->mnEmptyMark
)
1127 --m_xData
->mnEmptyMark
; // never returned from MarkTop => invalid
1128 return std::numeric_limits
<size_t>::max();
1131 for ( size_t i
=0; i
<m_xData
->maUndoArray
.maUndoActions
.size(); ++i
)
1133 MarkedUndoAction
& rAction
= m_xData
->maUndoArray
.maUndoActions
[i
];
1134 auto markPos
= std::find(rAction
.aMarks
.begin(), rAction
.aMarks
.end(), i_mark
);
1135 if (markPos
!= rAction
.aMarks
.end())
1137 rAction
.aMarks
.erase( markPos
);
1141 SAL_WARN("svl", "SfxUndoManager::RemoveMark: mark not found!");
1142 // TODO: this might be too offensive. There are situations where we implicitly remove marks
1143 // without our clients, in particular the client which created the mark, having a chance to know
1146 return std::numeric_limits
<size_t>::max();
1149 bool SfxUndoManager::HasTopUndoActionMark( UndoStackMark
const i_mark
)
1151 UndoManagerGuard
aGuard( *m_xData
);
1153 size_t nActionPos
= m_xData
->maUndoArray
.nCurUndoAction
;
1154 if ( nActionPos
== 0 )
1156 return (i_mark
== m_xData
->mnEmptyMark
);
1159 const MarkedUndoAction
& rAction
=
1160 m_xData
->maUndoArray
.maUndoActions
[ nActionPos
-1 ];
1162 return std::find(rAction
.aMarks
.begin(), rAction
.aMarks
.end(), i_mark
) != rAction
.aMarks
.end();
1166 void SfxUndoManager::UndoMark(UndoStackMark i_mark
)
1168 SfxMarkedUndoContext
context(*this, i_mark
); // Removes the mark
1169 if (context
.GetUndoOffset() == std::numeric_limits
<size_t>::max())
1170 return; // nothing to undo
1172 UndoWithContext(context
);
1176 void SfxUndoManager::RemoveOldestUndoAction()
1178 UndoManagerGuard
aGuard( *m_xData
);
1180 if ( IsInListAction() && ( m_xData
->maUndoArray
.nCurUndoAction
== 1 ) )
1182 assert(!"SfxUndoManager::RemoveOldestUndoActions: cannot remove a not-yet-closed list action!");
1186 aGuard
.markForDeletion( m_xData
->maUndoArray
.Remove( 0 ) );
1187 --m_xData
->maUndoArray
.nCurUndoAction
;
1188 ImplCheckEmptyActions();
1191 void SfxUndoManager::dumpAsXml(xmlTextWriterPtr pWriter
) const
1193 UndoManagerGuard
aGuard(*m_xData
);
1198 pWriter
= xmlNewTextWriterFilename("undo.xml", 0);
1199 xmlTextWriterSetIndent(pWriter
,1);
1200 (void)xmlTextWriterSetIndentString(pWriter
, BAD_CAST(" "));
1201 (void)xmlTextWriterStartDocument(pWriter
, nullptr, nullptr, nullptr);
1205 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxUndoManager"));
1206 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("nUndoActionCount"), BAD_CAST(OString::number(GetUndoActionCount()).getStr()));
1207 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("nRedoActionCount"), BAD_CAST(OString::number(GetRedoActionCount()).getStr()));
1209 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("undoActions"));
1210 for (size_t i
= 0; i
< GetUndoActionCount(); ++i
)
1212 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1213 pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
- 1 - i
].pAction
->dumpAsXml(pWriter
);
1215 (void)xmlTextWriterEndElement(pWriter
);
1217 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("redoActions"));
1218 for (size_t i
= 0; i
< GetRedoActionCount(); ++i
)
1220 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1221 pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
+ i
].pAction
->dumpAsXml(pWriter
);
1223 (void)xmlTextWriterEndElement(pWriter
);
1225 (void)xmlTextWriterEndElement(pWriter
);
1228 (void)xmlTextWriterEndDocument(pWriter
);
1229 xmlFreeTextWriter(pWriter
);
1233 /// Returns a JSON representation of pAction.
1234 static boost::property_tree::ptree
lcl_ActionToJson(size_t nIndex
, SfxUndoAction
const * pAction
)
1236 boost::property_tree::ptree aRet
;
1237 aRet
.put("index", nIndex
);
1238 aRet
.put("comment", pAction
->GetComment().toUtf8().getStr());
1239 aRet
.put("viewId", static_cast<sal_Int32
>(pAction
->GetViewShellId()));
1240 aRet
.put("dateTime", utl::toISO8601(pAction
->GetDateTime().GetUNODateTime()).toUtf8().getStr());
1244 OUString
SfxUndoManager::GetUndoActionsInfo() const
1246 boost::property_tree::ptree aActions
;
1247 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1248 for (size_t i
= 0; i
< GetUndoActionCount(); ++i
)
1250 boost::property_tree::ptree aAction
= lcl_ActionToJson(i
, pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
- 1 - i
].pAction
.get());
1251 aActions
.push_back(std::make_pair("", aAction
));
1254 boost::property_tree::ptree aTree
;
1255 aTree
.add_child("actions", aActions
);
1256 std::stringstream aStream
;
1257 boost::property_tree::write_json(aStream
, aTree
);
1258 return OUString::fromUtf8(aStream
.str());
1261 OUString
SfxUndoManager::GetRedoActionsInfo() const
1263 boost::property_tree::ptree aActions
;
1264 const SfxUndoArray
* pUndoArray
= m_xData
->pActUndoArray
;
1265 size_t nCount
= GetRedoActionCount();
1266 for (size_t i
= 0; i
< nCount
; ++i
)
1268 size_t nIndex
= nCount
- i
- 1;
1269 boost::property_tree::ptree aAction
= lcl_ActionToJson(nIndex
, pUndoArray
->maUndoActions
[pUndoArray
->nCurUndoAction
+ nIndex
].pAction
.get());
1270 aActions
.push_back(std::make_pair("", aAction
));
1273 boost::property_tree::ptree aTree
;
1274 aTree
.add_child("actions", aActions
);
1275 std::stringstream aStream
;
1276 boost::property_tree::write_json(aStream
, aTree
);
1277 return OUString::fromUtf8(aStream
.str());
1280 bool SfxUndoManager::IsEmptyActions() const
1282 UndoManagerGuard
aGuard(*m_xData
);
1284 return ImplIsEmptyActions();
1287 inline bool SfxUndoManager::ImplIsEmptyActions() const
1289 return m_xData
->maUndoArray
.nCurUndoAction
|| m_xData
->maUndoArray
.maUndoActions
.size() - m_xData
->maUndoArray
.nCurUndoAction
;
1292 void SfxUndoManager::ImplCheckEmptyActions()
1294 bool bEmptyActions
= ImplIsEmptyActions();
1295 if (m_xData
->mbEmptyActions
!= bEmptyActions
)
1297 m_xData
->mbEmptyActions
= bEmptyActions
;
1298 EmptyActionsChanged();
1302 void SfxUndoManager::EmptyActionsChanged()
1307 struct SfxListUndoAction::Impl
1310 ViewShellId mnViewShellId
;
1313 OUString maRepeatComment
;
1315 Impl( sal_uInt16 nId
, ViewShellId nViewShellId
, OUString aComment
, OUString aRepeatComment
) :
1316 mnId(nId
), mnViewShellId(nViewShellId
), maComment(std::move(aComment
)), maRepeatComment(std::move(aRepeatComment
)) {}
1319 sal_uInt16
SfxListUndoAction::GetId() const
1321 return mpImpl
->mnId
;
1324 OUString
SfxListUndoAction::GetComment() const
1326 return mpImpl
->maComment
;
1329 ViewShellId
SfxListUndoAction::GetViewShellId() const
1331 return mpImpl
->mnViewShellId
;
1334 void SfxListUndoAction::SetComment(const OUString
& rComment
)
1336 mpImpl
->maComment
= rComment
;
1339 OUString
SfxListUndoAction::GetRepeatComment(SfxRepeatTarget
&) const
1341 return mpImpl
->maRepeatComment
;
1344 SfxListUndoAction::SfxListUndoAction(
1345 const OUString
&rComment
,
1346 const OUString
&rRepeatComment
,
1348 ViewShellId nViewShellId
,
1349 SfxUndoArray
*pFather
) :
1350 mpImpl(new Impl(nId
, nViewShellId
, rComment
, rRepeatComment
))
1352 pFatherUndoArray
= pFather
;
1353 nMaxUndoActions
= USHRT_MAX
;
1356 SfxListUndoAction::~SfxListUndoAction()
1360 void SfxListUndoAction::Undo()
1362 for(size_t i
=nCurUndoAction
;i
>0;)
1363 maUndoActions
[--i
].pAction
->Undo();
1368 void SfxListUndoAction::UndoWithContext( SfxUndoContext
& i_context
)
1370 for(size_t i
=nCurUndoAction
;i
>0;)
1371 maUndoActions
[--i
].pAction
->UndoWithContext( i_context
);
1376 void SfxListUndoAction::Redo()
1378 for(size_t i
=nCurUndoAction
;i
<maUndoActions
.size();i
++)
1379 maUndoActions
[i
].pAction
->Redo();
1380 nCurUndoAction
= maUndoActions
.size();
1384 void SfxListUndoAction::RedoWithContext( SfxUndoContext
& i_context
)
1386 for(size_t i
=nCurUndoAction
;i
<maUndoActions
.size();i
++)
1387 maUndoActions
[i
].pAction
->RedoWithContext( i_context
);
1388 nCurUndoAction
= maUndoActions
.size();
1392 void SfxListUndoAction::Repeat(SfxRepeatTarget
&rTarget
)
1394 for(size_t i
=0;i
<nCurUndoAction
;i
++)
1395 maUndoActions
[i
].pAction
->Repeat(rTarget
);
1399 bool SfxListUndoAction::CanRepeat(SfxRepeatTarget
&r
) const
1401 for(size_t i
=0;i
<nCurUndoAction
;i
++)
1403 if(!maUndoActions
[i
].pAction
->CanRepeat(r
))
1410 bool SfxListUndoAction::Merge( SfxUndoAction
*pNextAction
)
1412 return !maUndoActions
.empty() && maUndoActions
[maUndoActions
.size()-1].pAction
->Merge( pNextAction
);
1415 void SfxListUndoAction::dumpAsXml(xmlTextWriterPtr pWriter
) const
1417 (void)xmlTextWriterStartElement(pWriter
, BAD_CAST("SfxListUndoAction"));
1418 (void)xmlTextWriterWriteAttribute(pWriter
, BAD_CAST("size"), BAD_CAST(OString::number(maUndoActions
.size()).getStr()));
1419 SfxUndoAction::dumpAsXml(pWriter
);
1421 for (size_t i
= 0; i
< maUndoActions
.size(); ++i
)
1422 maUndoActions
[i
].pAction
->dumpAsXml(pWriter
);
1424 (void)xmlTextWriterEndElement(pWriter
);
1427 SfxUndoArray::~SfxUndoArray()
1431 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */