bump product version to 6.3.0.0.beta1
[LibreOffice.git] / svl / source / undo / undo.cxx
blob5cccb085d65f7a92f2fec08e96ba6ae847463593
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 <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>
32 #include <memory>
33 #include <vector>
34 #include <limits.h>
35 #include <algorithm>
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 * )
64 return false;
68 OUString SfxUndoAction::GetComment() const
70 return OUString();
74 ViewShellId SfxUndoAction::GetViewShellId() const
76 return ViewShellId(-1);
79 const DateTime& SfxUndoAction::GetDateTime() const
81 return m_aDateTime;
84 OUString SfxUndoAction::GetRepeatComment(SfxRepeatTarget&) const
86 return GetComment();
90 void SfxUndoAction::Undo()
92 // These are only conceptually pure virtual
93 assert(!"pure virtual function called: SfxUndoAction::Undo()");
97 void SfxUndoAction::UndoWithContext( SfxUndoContext& )
99 Undo();
103 void SfxUndoAction::Redo()
105 // These are only conceptually pure virtual
106 assert(!"pure virtual function called: SfxUndoAction::Redo()");
110 void SfxUndoAction::RedoWithContext( SfxUndoContext& )
112 Redo();
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
125 return true;
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);
143 return ret;
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
160 ::osl::Mutex aMutex;
161 std::unique_ptr<SfxUndoArray>
162 pUndoArray;
163 SfxUndoArray* pActUndoArray;
165 sal_Int32 mnMarks;
166 sal_Int32 mnEmptyMark;
167 bool mbUndoEnabled;
168 bool mbDoing;
169 bool mbClearUntilTopLevel;
170 bool mbEmptyActions;
172 UndoListeners aListeners;
174 explicit SfxUndoManager_Data( size_t i_nMaxUndoActionCount )
175 :pUndoArray( new SfxUndoArray( i_nMaxUndoActionCount ) )
176 ,pActUndoArray( nullptr )
177 ,mnMarks( 0 )
178 ,mnEmptyMark(MARK_INVALID)
179 ,mbUndoEnabled( true )
180 ,mbDoing( false )
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
194 class LockGuard
196 public:
197 explicit LockGuard( SfxUndoManager& i_manager )
198 :m_manager( i_manager )
200 m_manager.ImplEnableUndo_Lock( false );
203 ~LockGuard()
205 m_manager.ImplEnableUndo_Lock( true );
208 private:
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 )
220 ,m_sActionComment()
224 NotifyUndoListener( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
225 :m_notificationMethod( nullptr )
226 ,m_altNotificationMethod( i_notificationMethod )
227 ,m_sActionComment( i_actionComment )
231 bool is() const
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 );
243 else
245 ( i_listener->*m_notificationMethod )();
249 private:
250 UndoListenerVoidMethod m_notificationMethod;
251 UndoListenerStringMethod m_altNotificationMethod;
252 OUString m_sActionComment;
255 class UndoManagerGuard
257 public:
258 explicit UndoManagerGuard( SfxUndoManager_Data& i_managerData )
259 :m_rManagerData( i_managerData )
260 ,m_aGuard( i_managerData.aMutex )
261 ,m_notifiers()
265 ~UndoManagerGuard();
267 void clear()
269 m_aGuard.clear();
272 void reset()
274 m_aGuard.reset();
277 void cancelNotifications()
279 m_notifiers.clear();
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 )
289 // remember
290 assert ( 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 );
309 private:
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()
318 // copy members
319 UndoListeners aListenersCopy( m_rManagerData.aListeners );
321 // release mutex
322 m_aGuard.clear();
324 // delete all actions
325 m_aUndoActionsCleanup.clear();
327 // handle scheduled notification
328 for (auto const& notifier : m_notifiers)
330 if ( notifier.is() )
331 ::std::for_each( aListenersCopy.begin(), aListenersCopy.end(), notifier );
334 } } }
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 )
370 return;
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 ) );
404 --nNumToDelete;
407 if ( nNumToDelete > 0 && m_xData->pActUndoArray->nCurUndoAction > 0 )
409 aGuard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
410 --m_xData->pActUndoArray->nCurUndoAction;
411 --nNumToDelete;
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 )
425 // clear array
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 );
448 // notify listeners
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;
462 else
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 );
488 // clear all locks
489 while ( !ImplIsUndoEnabled_Lock() )
490 ImplEnableUndo_Lock( true );
492 // cancel all list actions
493 while ( IsInListAction() )
494 ImplLeaveListAction( false, aGuard );
496 // clear both stacks
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();
524 // clearance
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) );
543 return false;
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() );
552 if ( bMerged )
554 i_guard.markForDeletion( std::move(pAction) );
555 return false;
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;
573 else
575 assert(!"CurrentUndoAction going negative (!)");
577 // fdo#66071 invalidate the current empty mark when removing
578 --m_xData->mnEmptyMark;
582 // append new action
583 m_xData->pActUndoArray->Insert( std::move(pAction), m_xData->pActUndoArray->nCurUndoAction++ );
584 ImplCheckEmptyActions();
585 return true;
589 void SfxUndoManager::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge )
591 UndoManagerGuard aGuard( *m_xData );
593 // add
594 auto pActionTmp = pAction.get();
595 if ( ImplAddUndoAction_NoNotify( std::move(pAction), bTryMerge, true, aGuard ) )
597 // notify listeners
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 );
615 OUString sComment;
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();
620 return sComment;
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 )
630 return nullptr;
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!");
687 return false;
690 if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
692 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
693 return false;
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
701 // nowadays ...
702 aGuard.clear();
703 if ( i_contextOrNull != nullptr )
704 pAction->UndoWithContext( *i_contextOrNull );
705 else
706 pAction->Undo();
707 aGuard.reset();
709 catch( ... )
711 aGuard.reset();
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 );
723 throw;
726 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
727 throw;
730 aGuard.scheduleNotification( &SfxUndoListener::actionUndone, sActionComment );
732 return true;
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() )
757 return nullptr;
759 return pUndoArray->maUndoActions[ pUndoArray->nCurUndoAction ].pAction.get();
763 OUString SfxUndoManager::GetRedoActionComment( size_t nNo, bool const i_currentLevel ) const
765 OUString sComment;
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();
772 return sComment;
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!");
799 return false;
802 if ( m_xData->pActUndoArray->nCurUndoAction >= m_xData->pActUndoArray->maUndoActions.size() )
804 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
805 return false;
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
813 // nowadays ...
814 aGuard.clear();
815 if ( i_contextOrNull != nullptr )
816 pAction->RedoWithContext( *i_contextOrNull );
817 else
818 pAction->Redo();
819 aGuard.reset();
821 catch( ... )
823 aGuard.reset();
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 );
835 throw;
837 ++nCurAction;
839 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
840 throw;
843 ImplCheckEmptyActions();
844 aGuard.scheduleNotification( &SfxUndoListener::actionRedone, sActionComment );
846 return true;
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();
871 aGuard.clear();
872 if ( pAction->CanRepeat( rTarget ) )
873 pAction->Repeat( rTarget );
874 aGuard.reset(); // allow clearing in guard dtor
875 return true;
878 return false;
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);
890 return false;
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() )
919 return;
921 if ( !m_xData->pUndoArray->nMaxUndoActions )
922 return;
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;
929 // notification
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 );
950 size_t nDepth(0);
952 SfxUndoArray* pLookup( m_xData->pActUndoArray );
953 while ( pLookup != m_xData->pUndoArray.get() )
955 pLookup = pLookup->pFatherUndoArray;
956 ++nDepth;
959 return nDepth;
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 );
976 nCount = 0;
979 return nCount;
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() )
993 return 0;
995 if ( !m_xData->pUndoArray->nMaxUndoActions )
996 return 0;
998 if( !ImplIsInListAction_Lock() )
1000 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1001 return 0;
1004 assert(m_xData->pActUndoArray->pFatherUndoArray);
1006 // the array/level which we're about to leave
1007 SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
1008 // one step up
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 );
1017 return 0;
1020 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1021 // the redo stack
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 );
1028 if ( i_merge )
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() );
1051 break;
1056 ImplIsEmptyActions();
1057 // notify listeners
1058 i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
1060 // outta here
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
1096 return;
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 );
1106 return;
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
1112 // about this.
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!");
1139 return;
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);
1151 bool bOwns = false;
1152 if (!pWriter)
1154 pWriter = xmlNewTextWriterFilename("undo.xml", 0);
1155 xmlTextWriterSetIndent(pWriter,1);
1156 xmlTextWriterSetIndentString(pWriter, BAD_CAST(" "));
1157 xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
1158 bOwns = true;
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);
1182 if (bOwns)
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());
1197 return aRet;
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;
1268 OUString maComment;
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,
1303 sal_uInt16 nId,
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();
1320 nCurUndoAction=0;
1324 void SfxListUndoAction::UndoWithContext( SfxUndoContext& i_context )
1326 for(size_t i=nCurUndoAction;i>0;)
1327 maUndoActions[--i].pAction->UndoWithContext( i_context );
1328 nCurUndoAction=0;
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))
1360 return false;
1362 return true;
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: */