tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / svl / source / undo / undo.cxx
blobcb278cf153c31325092a81928d21605a5b452539
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <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>
31 #include <memory>
32 #include <utility>
33 #include <vector>
34 #include <limits.h>
35 #include <algorithm>
37 namespace
39 class SfxMarkedUndoContext final : public SfxUndoContext
41 public:
42 SfxMarkedUndoContext(SfxUndoManager& manager, UndoStackMark mark)
44 m_offset = manager.RemoveMark(mark);
45 size_t count = manager.GetUndoActionCount();
46 if (m_offset < count)
47 m_offset = count - m_offset - 1;
48 else
49 m_offset = std::numeric_limits<size_t>::max();
51 size_t GetUndoOffset() override { return m_offset; }
53 private:
54 size_t 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 * )
82 return false;
86 OUString SfxUndoAction::GetComment() const
88 return OUString();
92 ViewShellId SfxUndoAction::GetViewShellId() const
94 return ViewShellId(-1);
97 const DateTime& SfxUndoAction::GetDateTime() const
99 return m_aDateTime;
102 OUString SfxUndoAction::GetRepeatComment(SfxRepeatTarget&) const
104 return GetComment();
108 void SfxUndoAction::Undo()
110 // These are only conceptually pure virtual
111 assert(!"pure virtual function called: SfxUndoAction::Undo()");
115 void SfxUndoAction::UndoWithContext( SfxUndoContext& )
117 Undo();
121 void SfxUndoAction::Redo()
123 // These are only conceptually pure virtual
124 assert(!"pure virtual function called: SfxUndoAction::Redo()");
128 void SfxUndoAction::RedoWithContext( SfxUndoContext& )
130 Redo();
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
143 return true;
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);
161 return ret;
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
178 ::osl::Mutex aMutex;
179 SfxUndoArray maUndoArray;
180 SfxUndoArray* pActUndoArray;
182 sal_Int32 mnMarks;
183 sal_Int32 mnEmptyMark;
184 bool mbUndoEnabled;
185 bool mbDoing;
186 bool mbClearUntilTopLevel;
187 bool mbEmptyActions;
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 )
195 ,mnMarks( 0 )
196 ,mnEmptyMark(MARK_INVALID)
197 ,mbUndoEnabled( true )
198 ,mbDoing( false )
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
212 class LockGuard
214 public:
215 explicit LockGuard( SfxUndoManager& i_manager )
216 :m_manager( i_manager )
218 m_manager.ImplEnableUndo_Lock( false );
221 ~LockGuard()
223 m_manager.ImplEnableUndo_Lock( true );
226 private:
227 SfxUndoManager& m_manager;
230 typedef void ( SfxUndoListener::*UndoListenerVoidMethod )();
231 typedef void ( SfxUndoListener::*UndoListenerStringMethod )( const OUString& );
233 namespace {
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 ))
250 bool is() const
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 );
262 else
264 ( i_listener->*m_notificationMethod )();
268 private:
269 UndoListenerVoidMethod m_notificationMethod;
270 UndoListenerStringMethod m_altNotificationMethod;
271 OUString m_sActionComment;
276 class UndoManagerGuard
278 public:
279 explicit UndoManagerGuard( SfxUndoManager_Data& i_managerData )
280 :m_rManagerData( i_managerData )
281 ,m_aGuard( i_managerData.aMutex )
285 ~UndoManagerGuard();
287 auto clear() { return osl::ResettableMutexGuardScopedReleaser(m_aGuard); }
289 void cancelNotifications()
291 m_notifiers.clear();
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 )
301 // remember
302 assert ( 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 );
321 private:
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()
330 // copy members
331 UndoListeners aListenersCopy( m_rManagerData.aListeners );
333 // release mutex
334 m_aGuard.clear();
336 // delete all actions
337 m_aUndoActionsCleanup.clear();
339 // handle scheduled notification
340 for (auto const& notifier : m_notifiers)
342 if ( notifier.is() )
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 )
374 return;
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 ) );
407 --nNumToDelete;
410 if ( nNumToDelete > 0 && m_xData->pActUndoArray->nCurUndoAction > 0 )
412 aGuard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
413 --m_xData->pActUndoArray->nCurUndoAction;
414 --nNumToDelete;
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 )
432 // clear array
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 );
455 // notify listeners
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;
469 else
471 aGuard.scheduleNotification( &SfxUndoListener::cleared );
476 void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel )
478 if (IsDoing())
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;
484 return;
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 );
502 // clear all locks
503 while ( !ImplIsUndoEnabled_Lock() )
504 ImplEnableUndo_Lock( true );
506 // cancel all list actions
507 while ( IsInListAction() )
508 ImplLeaveListAction( false, aGuard );
510 // clear both stacks
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;
538 // clearance
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) );
557 return false;
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() );
566 if ( bMerged )
568 i_guard.markForDeletion( std::move(pAction) );
569 return false;
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;
592 // append new action
593 m_xData->pActUndoArray->Insert( std::move(pAction), m_xData->pActUndoArray->nCurUndoAction++ );
594 ImplCheckEmptyActions();
595 return true;
599 void SfxUndoManager::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge )
601 UndoManagerGuard aGuard( *m_xData );
603 // add
604 auto pActionTmp = pAction.get();
605 if ( ImplAddUndoAction_NoNotify( std::move(pAction), bTryMerge, true, aGuard ) )
607 // notify listeners
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 );
625 OUString sComment;
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();
630 return sComment;
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 )
640 return nullptr;
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!");
699 return false;
702 if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
704 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
705 return false;
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
717 // undo stack.
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
729 // nowadays ...
730 auto aResetGuard(aGuard.clear());
731 if ( i_contextOrNull != nullptr )
732 pAction->UndoWithContext( *i_contextOrNull );
733 else
734 pAction->Undo();
736 catch( ... )
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 );
748 throw;
751 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
752 throw;
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 );
764 return true;
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() )
789 return nullptr;
791 return pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + nNo].pAction.get();
795 OUString SfxUndoManager::GetRedoActionComment( size_t nNo, bool const i_currentLevel ) const
797 OUString sComment;
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();
804 return sComment;
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!");
833 return false;
836 if ( m_xData->pActUndoArray->nCurUndoAction >= m_xData->pActUndoArray->maUndoActions.size() )
838 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
839 return false;
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
847 // nowadays ...
848 auto aResetGuard(aGuard.clear());
849 if ( i_contextOrNull != nullptr )
850 pAction->RedoWithContext( *i_contextOrNull );
851 else
852 pAction->Redo();
854 catch( ... )
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 );
866 throw;
868 ++nCurAction;
870 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
871 throw;
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 );
879 return true;
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 );
907 return true;
910 return false;
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);
922 return false;
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() )
951 return;
953 if ( !m_xData->maUndoArray.nMaxUndoActions )
954 return;
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;
961 // notification
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 );
982 size_t nDepth(0);
984 SfxUndoArray* pLookup( m_xData->pActUndoArray );
985 while ( pLookup != &m_xData->maUndoArray )
987 pLookup = pLookup->pFatherUndoArray;
988 ++nDepth;
991 return nDepth;
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 );
1008 nCount = 0;
1011 return nCount;
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() )
1025 return 0;
1027 if ( !m_xData->maUndoArray.nMaxUndoActions )
1028 return 0;
1030 if( !ImplIsInListAction_Lock() )
1032 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1033 return 0;
1036 assert(m_xData->pActUndoArray->pFatherUndoArray);
1038 // the array/level which we're about to leave
1039 SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
1040 // one step up
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 );
1049 return 0;
1052 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1053 // the redo stack
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 );
1060 if ( i_merge )
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() );
1083 break;
1088 ImplIsEmptyActions();
1089 // notify listeners
1090 i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
1092 // outta here
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 );
1138 return i;
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
1144 // about this.
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!");
1183 return;
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);
1195 bool bOwns = false;
1196 if (!pWriter)
1198 pWriter = xmlNewTextWriterFilename("undo.xml", 0);
1199 xmlTextWriterSetIndent(pWriter,1);
1200 (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" "));
1201 (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
1202 bOwns = true;
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);
1226 if (bOwns)
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());
1241 return aRet;
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
1309 sal_uInt16 mnId;
1310 ViewShellId mnViewShellId;
1312 OUString maComment;
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,
1347 sal_uInt16 nId,
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();
1364 nCurUndoAction=0;
1368 void SfxListUndoAction::UndoWithContext( SfxUndoContext& i_context )
1370 for(size_t i=nCurUndoAction;i>0;)
1371 maUndoActions[--i].pAction->UndoWithContext( i_context );
1372 nCurUndoAction=0;
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))
1404 return false;
1406 return true;
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: */