bump product version to 5.0.4.1
[LibreOffice.git] / svl / source / undo / undo.cxx
blob0d967f86f24e663e08d189cfe543b4822655c820
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>
30 #include <vector>
31 #include <list>
32 #include <limits>
34 using ::com::sun::star::uno::Exception;
36 TYPEINIT0(SfxUndoAction);
37 TYPEINIT0(SfxListUndoAction);
38 TYPEINIT0(SfxLinkUndoAction);
39 TYPEINIT0(SfxRepeatTarget);
42 SfxRepeatTarget::~SfxRepeatTarget()
47 SfxUndoContext::~SfxUndoContext()
52 void SfxUndoAction::SetLinkToSfxLinkUndoAction(SfxLinkUndoAction* pSfxLinkUndoAction)
54 mpSfxLinkUndoAction = pSfxLinkUndoAction;
58 SfxUndoAction::~SfxUndoAction()
60 if(mpSfxLinkUndoAction)
62 mpSfxLinkUndoAction->LinkedSfxUndoActionDestructed(*this);
63 mpSfxLinkUndoAction = 0;
68 SfxUndoAction::SfxUndoAction()
69 : mpSfxLinkUndoAction(0)
74 bool SfxUndoAction::Merge( SfxUndoAction * )
76 return false;
80 OUString SfxUndoAction::GetComment() const
82 return OUString();
87 sal_uInt16 SfxUndoAction::GetId() const
89 return 0;
93 OUString SfxUndoAction::GetRepeatComment(SfxRepeatTarget&) const
95 return GetComment();
99 void SfxUndoAction::Undo()
101 // These are only conceptually pure virtual
102 assert(!"pure virtual function called: SfxUndoAction::Undo()");
106 void SfxUndoAction::UndoWithContext( SfxUndoContext& i_context )
108 (void)i_context;
109 Undo();
113 void SfxUndoAction::Redo()
115 // These are only conceptually pure virtual
116 assert(!"pure virtual function called: SfxUndoAction::Redo()");
120 void SfxUndoAction::RedoWithContext( SfxUndoContext& i_context )
122 (void)i_context;
123 Redo();
127 void SfxUndoAction::Repeat(SfxRepeatTarget&)
129 // These are only conceptually pure virtual
130 assert(!"pure virtual function called: SfxUndoAction::Repeat()");
135 bool SfxUndoAction::CanRepeat(SfxRepeatTarget&) const
137 return true;
140 void SfxUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
142 xmlTextWriterStartElement(pWriter, BAD_CAST("sfxUndoAction"));
143 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
144 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("comment"), BAD_CAST(GetComment().toUtf8().getStr()));
145 xmlTextWriterEndElement(pWriter);
148 struct MarkedUndoAction
150 SfxUndoAction* pAction;
151 ::std::vector< UndoStackMark > aMarks;
153 MarkedUndoAction( SfxUndoAction* i_action )
154 :pAction( i_action )
155 ,aMarks()
160 struct SfxUndoActions::Impl
162 std::vector<MarkedUndoAction> maActions;
165 SfxUndoActions::SfxUndoActions() : mpImpl(new Impl) {}
167 SfxUndoActions::SfxUndoActions( const SfxUndoActions& r ) :
168 mpImpl(new Impl)
170 mpImpl->maActions = r.mpImpl->maActions;
173 SfxUndoActions::~SfxUndoActions()
175 delete mpImpl;
178 bool SfxUndoActions::empty() const
180 return mpImpl->maActions.empty();
183 size_t SfxUndoActions::size() const
185 return mpImpl->maActions.size();
188 const MarkedUndoAction& SfxUndoActions::operator[]( size_t i ) const
190 return mpImpl->maActions[i];
193 MarkedUndoAction& SfxUndoActions::operator[]( size_t i )
195 return mpImpl->maActions[i];
198 const SfxUndoAction* SfxUndoActions::GetUndoAction( size_t i ) const
200 return mpImpl->maActions[i].pAction;
203 SfxUndoAction* SfxUndoActions::GetUndoAction( size_t i )
205 return mpImpl->maActions[i].pAction;
208 void SfxUndoActions::Remove( size_t i_pos )
210 mpImpl->maActions.erase( mpImpl->maActions.begin() + i_pos );
213 void SfxUndoActions::Remove( size_t i_pos, size_t i_count )
215 mpImpl->maActions.erase(
216 mpImpl->maActions.begin() + i_pos, mpImpl->maActions.begin() + i_pos + i_count);
219 void SfxUndoActions::Insert( SfxUndoAction* i_action, size_t i_pos )
221 mpImpl->maActions.insert(
222 mpImpl->maActions.begin() + i_pos, MarkedUndoAction( i_action ) );
225 typedef ::std::vector< SfxUndoListener* > UndoListeners;
227 struct SVL_DLLPRIVATE SfxUndoManager_Data
229 ::osl::Mutex aMutex;
230 SfxUndoArray* pUndoArray;
231 SfxUndoArray* pActUndoArray;
232 SfxUndoArray* pFatherUndoArray;
234 sal_Int32 mnMarks;
235 sal_Int32 mnEmptyMark;
236 bool mbUndoEnabled;
237 bool mbDoing;
238 bool mbClearUntilTopLevel;
240 UndoListeners aListeners;
242 SfxUndoManager_Data( size_t i_nMaxUndoActionCount )
243 :pUndoArray( new SfxUndoArray( i_nMaxUndoActionCount ) )
244 ,pActUndoArray( NULL )
245 ,pFatherUndoArray( NULL )
246 ,mnMarks( 0 )
247 ,mnEmptyMark(MARK_INVALID)
248 ,mbUndoEnabled( true )
249 ,mbDoing( false )
250 ,mbClearUntilTopLevel( false )
252 pActUndoArray = pUndoArray;
255 ~SfxUndoManager_Data()
257 delete pUndoArray;
262 namespace svl { namespace undo { namespace impl
264 class SVL_DLLPRIVATE LockGuard
266 public:
267 LockGuard( SfxUndoManager& i_manager )
268 :m_manager( i_manager )
270 m_manager.ImplEnableUndo_Lock( false );
273 ~LockGuard()
275 m_manager.ImplEnableUndo_Lock( true );
278 private:
279 SfxUndoManager& m_manager;
282 typedef void ( SfxUndoListener::*UndoListenerVoidMethod )();
283 typedef void ( SfxUndoListener::*UndoListenerStringMethod )( const OUString& );
285 struct SVL_DLLPRIVATE NotifyUndoListener : public ::std::unary_function< SfxUndoListener*, void >
287 NotifyUndoListener( UndoListenerVoidMethod i_notificationMethod )
288 :m_notificationMethod( i_notificationMethod )
289 ,m_altNotificationMethod( NULL )
290 ,m_sActionComment()
294 NotifyUndoListener( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
295 :m_notificationMethod( NULL )
296 ,m_altNotificationMethod( i_notificationMethod )
297 ,m_sActionComment( i_actionComment )
301 bool is() const
303 return ( m_notificationMethod != 0 ) || ( m_altNotificationMethod != 0 );
306 void operator()( SfxUndoListener* i_listener ) const
308 assert( is() && "NotifyUndoListener: this will crash!" );
309 if ( m_altNotificationMethod != 0 )
311 ( i_listener->*m_altNotificationMethod )( m_sActionComment );
313 else
315 ( i_listener->*m_notificationMethod )();
319 private:
320 UndoListenerVoidMethod m_notificationMethod;
321 UndoListenerStringMethod m_altNotificationMethod;
322 OUString m_sActionComment;
325 class SVL_DLLPRIVATE UndoManagerGuard
327 public:
328 UndoManagerGuard( SfxUndoManager_Data& i_managerData )
329 :m_rManagerData( i_managerData )
330 ,m_aGuard( i_managerData.aMutex )
331 ,m_notifiers()
335 ~UndoManagerGuard();
337 void clear()
339 m_aGuard.clear();
342 void reset()
344 m_aGuard.reset();
347 void cancelNotifications()
349 m_notifiers.clear();
352 /** marks the given Undo action for deletion
354 The Undo action will be put into a list, whose members will be deleted from within the destructor of the
355 UndoManagerGuard. This deletion will happen without the UndoManager's mutex locked.
357 void markForDeletion( SfxUndoAction* i_action )
359 // remember
360 if ( i_action )
361 m_aUndoActionsCleanup.push_back( i_action );
364 /** schedules the given SfxUndoListener method to be called for all registered listeners.
366 The notification will happen after the Undo manager's mutex has been released, and after all pending
367 deletions of Undo actions are done.
369 void scheduleNotification( UndoListenerVoidMethod i_notificationMethod )
371 m_notifiers.push_back( NotifyUndoListener( i_notificationMethod ) );
374 void scheduleNotification( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
376 m_notifiers.push_back( NotifyUndoListener( i_notificationMethod, i_actionComment ) );
379 private:
380 SfxUndoManager_Data& m_rManagerData;
381 ::osl::ResettableMutexGuard m_aGuard;
382 ::std::list< SfxUndoAction* > m_aUndoActionsCleanup;
383 ::std::list< NotifyUndoListener > m_notifiers;
386 UndoManagerGuard::~UndoManagerGuard()
388 // copy members
389 UndoListeners aListenersCopy( m_rManagerData.aListeners );
391 // release mutex
392 m_aGuard.clear();
394 // delete all actions
395 while ( !m_aUndoActionsCleanup.empty() )
397 SfxUndoAction* pAction = m_aUndoActionsCleanup.front();
398 m_aUndoActionsCleanup.pop_front();
401 delete pAction;
403 catch( const Exception& )
405 DBG_UNHANDLED_EXCEPTION();
409 // handle scheduled notification
410 for ( ::std::list< NotifyUndoListener >::const_iterator notifier = m_notifiers.begin();
411 notifier != m_notifiers.end();
412 ++notifier
415 if ( notifier->is() )
416 ::std::for_each( aListenersCopy.begin(), aListenersCopy.end(), *notifier );
419 } } }
421 using namespace ::svl::undo::impl;
424 SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount )
425 :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount ) )
430 SfxUndoManager::~SfxUndoManager()
432 UndoListeners aListenersCopy;
434 UndoManagerGuard aGuard( *m_xData );
435 aListenersCopy = m_xData->aListeners;
438 ::std::for_each( aListenersCopy.begin(), aListenersCopy.end(),
439 NotifyUndoListener( &SfxUndoListener::undoManagerDying ) );
443 void SfxUndoManager::EnableUndo( bool i_enable )
445 UndoManagerGuard aGuard( *m_xData );
446 ImplEnableUndo_Lock( i_enable );
451 void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable )
453 if ( m_xData->mbUndoEnabled == i_enable )
454 return;
455 m_xData->mbUndoEnabled = i_enable;
459 bool SfxUndoManager::IsUndoEnabled() const
461 UndoManagerGuard aGuard( *m_xData );
462 return ImplIsUndoEnabled_Lock();
466 bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
468 return m_xData->mbUndoEnabled;
472 void SfxUndoManager::SetMaxUndoActionCount( size_t nMaxUndoActionCount )
474 UndoManagerGuard aGuard( *m_xData );
476 // Remove entries from the pActUndoArray when we have to reduce
477 // the number of entries due to a lower nMaxUndoActionCount.
478 // Both redo and undo action entries will be removed until we reached the
479 // new nMaxUndoActionCount.
481 long nNumToDelete = m_xData->pActUndoArray->aUndoActions.size() - nMaxUndoActionCount;
482 while ( nNumToDelete > 0 )
484 size_t nPos = m_xData->pActUndoArray->aUndoActions.size();
485 if ( nPos > m_xData->pActUndoArray->nCurUndoAction )
487 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[nPos-1].pAction;
488 aGuard.markForDeletion( pAction );
489 m_xData->pActUndoArray->aUndoActions.Remove( nPos-1 );
490 --nNumToDelete;
493 if ( nNumToDelete > 0 && m_xData->pActUndoArray->nCurUndoAction > 0 )
495 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[0].pAction;
496 aGuard.markForDeletion( pAction );
497 m_xData->pActUndoArray->aUndoActions.Remove(0);
498 --m_xData->pActUndoArray->nCurUndoAction;
499 --nNumToDelete;
502 if ( nPos == m_xData->pActUndoArray->aUndoActions.size() )
503 break; // Cannot delete more entries
506 m_xData->pActUndoArray->nMaxUndoActions = nMaxUndoActionCount;
510 size_t SfxUndoManager::GetMaxUndoActionCount() const
512 UndoManagerGuard aGuard( *m_xData );
513 return m_xData->pActUndoArray->nMaxUndoActions;
517 void SfxUndoManager::ImplClearCurrentLevel_NoNotify( UndoManagerGuard& i_guard )
519 // clear array
520 while ( !m_xData->pActUndoArray->aUndoActions.empty() )
522 size_t deletePos = m_xData->pActUndoArray->aUndoActions.size() - 1;
523 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[ deletePos ].pAction;
524 i_guard.markForDeletion( pAction );
525 m_xData->pActUndoArray->aUndoActions.Remove( deletePos );
528 m_xData->pActUndoArray->nCurUndoAction = 0;
530 m_xData->mnMarks = 0;
531 m_xData->mnEmptyMark = MARK_INVALID;
535 void SfxUndoManager::Clear()
537 UndoManagerGuard aGuard( *m_xData );
539 SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
540 "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
541 ImplClearCurrentLevel_NoNotify( aGuard );
543 // notify listeners
544 aGuard.scheduleNotification( &SfxUndoListener::cleared );
548 void SfxUndoManager::ClearAllLevels()
550 UndoManagerGuard aGuard( *m_xData );
551 ImplClearCurrentLevel_NoNotify( aGuard );
553 if ( ImplIsInListAction_Lock() )
555 m_xData->mbClearUntilTopLevel = true;
557 else
559 aGuard.scheduleNotification( &SfxUndoListener::cleared );
564 void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel )
566 UndoManagerGuard aGuard( *m_xData );
567 ImplClearRedo( aGuard, i_currentLevel );
571 void SfxUndoManager::ClearRedo()
573 SAL_WARN_IF( IsInListAction(), "svl",
574 "SfxUndoManager::ClearRedo: suspicious call - do you really wish to clear the current level?" );
575 ImplClearRedo_NoLock( CurrentLevel );
579 void SfxUndoManager::Reset()
581 UndoManagerGuard aGuard( *m_xData );
583 // clear all locks
584 while ( !ImplIsUndoEnabled_Lock() )
585 ImplEnableUndo_Lock( true );
587 // cancel all list actions
588 while ( IsInListAction() )
589 ImplLeaveListAction( false, aGuard );
591 // clear both stacks
592 ImplClearCurrentLevel_NoNotify( aGuard );
594 // cancel the notifications scheduled by ImplLeaveListAction,
595 // as we want to do an own, dedicated notification
596 aGuard.cancelNotifications();
598 // schedule notification
599 aGuard.scheduleNotification( &SfxUndoListener::resetAll );
603 void SfxUndoManager::ImplClearUndo( UndoManagerGuard& i_guard )
605 while ( m_xData->pActUndoArray->nCurUndoAction > 0 )
607 SfxUndoAction* pUndoAction = m_xData->pActUndoArray->aUndoActions[0].pAction;
608 m_xData->pActUndoArray->aUndoActions.Remove( 0 );
609 i_guard.markForDeletion( pUndoAction );
610 --m_xData->pActUndoArray->nCurUndoAction;
612 // TODO: notifications? We don't have clearedUndo, only cleared and clearedRedo at the SfxUndoListener
616 void SfxUndoManager::ImplClearRedo( UndoManagerGuard& i_guard, bool const i_currentLevel )
618 SfxUndoArray* pUndoArray = ( i_currentLevel == IUndoManager::CurrentLevel ) ? m_xData->pActUndoArray : m_xData->pUndoArray;
620 // clearance
621 while ( pUndoArray->aUndoActions.size() > pUndoArray->nCurUndoAction )
623 size_t deletePos = pUndoArray->aUndoActions.size() - 1;
624 SfxUndoAction* pAction = pUndoArray->aUndoActions[ deletePos ].pAction;
625 pUndoArray->aUndoActions.Remove( deletePos );
626 i_guard.markForDeletion( pAction );
629 // notification - only if the top level's stack was cleared
630 if ( i_currentLevel == IUndoManager::TopLevel )
631 i_guard.scheduleNotification( &SfxUndoListener::clearedRedo );
635 bool SfxUndoManager::ImplAddUndoAction_NoNotify( SfxUndoAction *pAction, bool bTryMerge, bool bClearRedo, UndoManagerGuard& i_guard )
637 if ( !ImplIsUndoEnabled_Lock() || ( m_xData->pActUndoArray->nMaxUndoActions == 0 ) )
639 i_guard.markForDeletion( pAction );
640 return false;
643 // merge, if required
644 SfxUndoAction* pMergeWithAction = m_xData->pActUndoArray->nCurUndoAction ?
645 m_xData->pActUndoArray->aUndoActions[m_xData->pActUndoArray->nCurUndoAction-1].pAction : NULL;
646 if ( bTryMerge && pMergeWithAction )
648 bool bMerged = pMergeWithAction->Merge( pAction );
649 if ( bMerged )
651 i_guard.markForDeletion( pAction );
652 return false;
656 // clear redo stack, if requested
657 if ( bClearRedo && ( ImplGetRedoActionCount_Lock( CurrentLevel ) > 0 ) )
658 ImplClearRedo( i_guard, IUndoManager::CurrentLevel );
660 // respect max number
661 if( m_xData->pActUndoArray == m_xData->pUndoArray )
663 while(m_xData->pActUndoArray->aUndoActions.size() >= m_xData->pActUndoArray->nMaxUndoActions)
665 i_guard.markForDeletion( m_xData->pActUndoArray->aUndoActions[0].pAction );
666 m_xData->pActUndoArray->aUndoActions.Remove(0);
667 if (m_xData->pActUndoArray->nCurUndoAction > 0)
669 --m_xData->pActUndoArray->nCurUndoAction;
671 else
673 assert(!"CurrentUndoAction going negative (!)");
675 // fdo#66071 invalidate the current empty mark when removing
676 --m_xData->mnEmptyMark;
680 // append new action
681 m_xData->pActUndoArray->aUndoActions.Insert( pAction, m_xData->pActUndoArray->nCurUndoAction++ );
682 return true;
686 void SfxUndoManager::AddUndoAction( SfxUndoAction *pAction, bool bTryMerge )
688 UndoManagerGuard aGuard( *m_xData );
690 // add
691 if ( ImplAddUndoAction_NoNotify( pAction, bTryMerge, true, aGuard ) )
693 // notify listeners
694 aGuard.scheduleNotification( &SfxUndoListener::undoActionAdded, pAction->GetComment() );
699 size_t SfxUndoManager::GetUndoActionCount( bool const i_currentLevel ) const
701 UndoManagerGuard aGuard( *m_xData );
702 const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : m_xData->pUndoArray;
703 return pUndoArray->nCurUndoAction;
707 OUString SfxUndoManager::GetUndoActionComment( size_t nNo, bool const i_currentLevel ) const
709 UndoManagerGuard aGuard( *m_xData );
711 OUString sComment;
712 const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : m_xData->pUndoArray;
713 assert(nNo < pUndoArray->nCurUndoAction);
714 if( nNo < pUndoArray->nCurUndoAction )
715 sComment = pUndoArray->aUndoActions[ pUndoArray->nCurUndoAction - 1 - nNo ].pAction->GetComment();
716 return sComment;
720 sal_uInt16 SfxUndoManager::GetUndoActionId() const
722 UndoManagerGuard aGuard( *m_xData );
724 assert(m_xData->pActUndoArray->nCurUndoAction > 0);
725 if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
726 return 0;
727 return m_xData->pActUndoArray->aUndoActions[m_xData->pActUndoArray->nCurUndoAction-1].pAction->GetId();
731 SfxUndoAction* SfxUndoManager::GetUndoAction( size_t nNo ) const
733 UndoManagerGuard aGuard( *m_xData );
735 assert(nNo < m_xData->pActUndoArray->nCurUndoAction);
736 if( nNo >= m_xData->pActUndoArray->nCurUndoAction )
737 return NULL;
738 return m_xData->pActUndoArray->aUndoActions[m_xData->pActUndoArray->nCurUndoAction-1-nNo].pAction;
742 /** clears the redo stack and removes the top undo action */
743 void SfxUndoManager::RemoveLastUndoAction()
745 UndoManagerGuard aGuard( *m_xData );
747 ENSURE_OR_RETURN_VOID( m_xData->pActUndoArray->nCurUndoAction, "svl::SfxUndoManager::RemoveLastUndoAction(), no action to remove?!" );
749 m_xData->pActUndoArray->nCurUndoAction--;
751 // delete redo-actions and top action
752 for ( size_t nPos = m_xData->pActUndoArray->aUndoActions.size(); nPos > m_xData->pActUndoArray->nCurUndoAction; --nPos )
754 aGuard.markForDeletion( m_xData->pActUndoArray->aUndoActions[nPos-1].pAction );
757 m_xData->pActUndoArray->aUndoActions.Remove(
758 m_xData->pActUndoArray->nCurUndoAction,
759 m_xData->pActUndoArray->aUndoActions.size() - m_xData->pActUndoArray->nCurUndoAction );
763 bool SfxUndoManager::IsDoing() const
765 UndoManagerGuard aGuard( *m_xData );
766 return m_xData->mbDoing;
770 bool SfxUndoManager::Undo()
772 return ImplUndo( NULL );
776 bool SfxUndoManager::UndoWithContext( SfxUndoContext& i_context )
778 return ImplUndo( &i_context );
782 bool SfxUndoManager::ImplUndo( SfxUndoContext* i_contextOrNull )
784 UndoManagerGuard aGuard( *m_xData );
785 assert( !IsDoing() && "SfxUndoManager::Undo: *nested* Undo/Redo actions? How this?" );
787 ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
788 LockGuard aLockGuard( *this );
790 if ( ImplIsInListAction_Lock() )
792 assert(!"SfxUndoManager::Undo: not possible when within a list action!");
793 return false;
796 if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
798 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
799 return false;
802 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[ --m_xData->pActUndoArray->nCurUndoAction ].pAction;
803 const OUString sActionComment = pAction->GetComment();
806 // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
807 // nowadays ...
808 aGuard.clear();
809 if ( i_contextOrNull != NULL )
810 pAction->UndoWithContext( *i_contextOrNull );
811 else
812 pAction->Undo();
813 aGuard.reset();
815 catch( ... )
817 aGuard.reset();
819 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
820 // we still find pAction in our current Undo array
821 size_t nCurAction = 0;
822 while ( nCurAction < m_xData->pActUndoArray->aUndoActions.size() )
824 if ( m_xData->pActUndoArray->aUndoActions[ nCurAction++ ].pAction == pAction )
826 // the Undo action is still there ...
827 // assume the error is a permanent failure, and clear the Undo stack
828 ImplClearUndo( aGuard );
829 throw;
832 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
833 throw;
836 aGuard.scheduleNotification( &SfxUndoListener::actionUndone, sActionComment );
838 return true;
842 size_t SfxUndoManager::GetRedoActionCount( bool const i_currentLevel ) const
844 UndoManagerGuard aGuard( *m_xData );
845 return ImplGetRedoActionCount_Lock( i_currentLevel );
849 size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel ) const
851 const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : m_xData->pUndoArray;
852 return pUndoArray->aUndoActions.size() - pUndoArray->nCurUndoAction;
856 SfxUndoAction* SfxUndoManager::GetRedoAction( size_t nNo, bool const i_currentLevel ) const
858 UndoManagerGuard aGuard( *m_xData );
860 const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : m_xData->pUndoArray;
861 if ( (pUndoArray->nCurUndoAction + nNo) > pUndoArray->aUndoActions.size() )
863 return NULL;
865 return pUndoArray->aUndoActions[ pUndoArray->nCurUndoAction + nNo ].pAction;
869 OUString SfxUndoManager::GetRedoActionComment( size_t nNo, bool const i_currentLevel ) const
871 OUString sComment;
872 UndoManagerGuard aGuard( *m_xData );
873 const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : m_xData->pUndoArray;
874 if ( (pUndoArray->nCurUndoAction + nNo) < pUndoArray->aUndoActions.size() )
876 sComment = pUndoArray->aUndoActions[ pUndoArray->nCurUndoAction + nNo ].pAction->GetComment();
878 return sComment;
882 bool SfxUndoManager::Redo()
884 return ImplRedo( NULL );
888 bool SfxUndoManager::RedoWithContext( SfxUndoContext& i_context )
890 return ImplRedo( &i_context );
894 bool SfxUndoManager::ImplRedo( SfxUndoContext* i_contextOrNull )
896 UndoManagerGuard aGuard( *m_xData );
897 assert( !IsDoing() && "SfxUndoManager::Redo: *nested* Undo/Redo actions? How this?" );
899 ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
900 LockGuard aLockGuard( *this );
902 if ( ImplIsInListAction_Lock() )
904 assert(!"SfxUndoManager::Redo: not possible when within a list action!");
905 return false;
908 if ( m_xData->pActUndoArray->nCurUndoAction >= m_xData->pActUndoArray->aUndoActions.size() )
910 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
911 return false;
914 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->nCurUndoAction++ ].pAction;
915 const OUString sActionComment = pAction->GetComment();
918 // clear the guard/mutex before calling into the SfxUndoAction - this can be a extension-implemented UNO component
919 // nowadays ...
920 aGuard.clear();
921 if ( i_contextOrNull != NULL )
922 pAction->RedoWithContext( *i_contextOrNull );
923 else
924 pAction->Redo();
925 aGuard.reset();
927 catch( ... )
929 aGuard.reset();
931 // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
932 // we still find pAction in our current Undo array
933 size_t nCurAction = 0;
934 while ( nCurAction < m_xData->pActUndoArray->aUndoActions.size() )
936 if ( m_xData->pActUndoArray->aUndoActions[ nCurAction ].pAction == pAction )
938 // the Undo action is still there ...
939 // assume the error is a permanent failure, and clear the Undo stack
940 ImplClearRedo( aGuard, IUndoManager::CurrentLevel );
941 throw;
943 ++nCurAction;
945 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
946 throw;
949 aGuard.scheduleNotification( &SfxUndoListener::actionRedone, sActionComment );
951 return true;
955 size_t SfxUndoManager::GetRepeatActionCount() const
957 UndoManagerGuard aGuard( *m_xData );
958 return m_xData->pActUndoArray->aUndoActions.size();
962 OUString SfxUndoManager::GetRepeatActionComment(SfxRepeatTarget &rTarget) const
964 UndoManagerGuard aGuard( *m_xData );
965 return m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->aUndoActions.size() - 1 ].pAction
966 ->GetRepeatComment(rTarget);
970 bool SfxUndoManager::Repeat( SfxRepeatTarget &rTarget )
972 UndoManagerGuard aGuard( *m_xData );
973 if ( !m_xData->pActUndoArray->aUndoActions.empty() )
975 SfxUndoAction* pAction = m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->aUndoActions.size() - 1 ].pAction;
976 aGuard.clear();
977 if ( pAction->CanRepeat( rTarget ) )
978 pAction->Repeat( rTarget );
979 return true;
982 return false;
986 bool SfxUndoManager::CanRepeat( SfxRepeatTarget &rTarget ) const
988 UndoManagerGuard aGuard( *m_xData );
989 if ( !m_xData->pActUndoArray->aUndoActions.empty() )
991 size_t nActionNo = m_xData->pActUndoArray->aUndoActions.size() - 1;
992 return m_xData->pActUndoArray->aUndoActions[nActionNo].pAction->CanRepeat(rTarget);
994 return false;
998 void SfxUndoManager::AddUndoListener( SfxUndoListener& i_listener )
1000 UndoManagerGuard aGuard( *m_xData );
1001 m_xData->aListeners.push_back( &i_listener );
1005 void SfxUndoManager::RemoveUndoListener( SfxUndoListener& i_listener )
1007 UndoManagerGuard aGuard( *m_xData );
1008 for ( UndoListeners::iterator lookup = m_xData->aListeners.begin();
1009 lookup != m_xData->aListeners.end();
1010 ++lookup
1013 if ( (*lookup) == &i_listener )
1015 m_xData->aListeners.erase( lookup );
1016 break;
1022 * Inserts a ListUndoAction and sets its UndoArray as current.
1024 void SfxUndoManager::EnterListAction( const OUString& rComment,
1025 const OUString &rRepeatComment, sal_uInt16 nId )
1027 UndoManagerGuard aGuard( *m_xData );
1029 if( !ImplIsUndoEnabled_Lock() )
1030 return;
1032 if ( !m_xData->pUndoArray->nMaxUndoActions )
1033 return;
1035 m_xData->pFatherUndoArray = m_xData->pActUndoArray;
1036 SfxListUndoAction* pAction = new SfxListUndoAction( rComment, rRepeatComment, nId, m_xData->pActUndoArray );
1037 OSL_VERIFY( ImplAddUndoAction_NoNotify( pAction, false, false, aGuard ) );
1038 // expected to succeed: all conditions under which it could fail should have been checked already
1039 m_xData->pActUndoArray = pAction;
1041 // notification
1042 aGuard.scheduleNotification( &SfxUndoListener::listActionEntered, rComment );
1046 bool SfxUndoManager::IsInListAction() const
1048 UndoManagerGuard aGuard( *m_xData );
1049 return ImplIsInListAction_Lock();
1053 bool SfxUndoManager::ImplIsInListAction_Lock() const
1055 return ( m_xData->pActUndoArray != m_xData->pUndoArray );
1059 size_t SfxUndoManager::GetListActionDepth() const
1061 UndoManagerGuard aGuard( *m_xData );
1062 size_t nDepth(0);
1064 SfxUndoArray* pLookup( m_xData->pActUndoArray );
1065 while ( pLookup != m_xData->pUndoArray )
1067 pLookup = pLookup->pFatherUndoArray;
1068 ++nDepth;
1071 return nDepth;
1075 size_t SfxUndoManager::LeaveListAction()
1077 UndoManagerGuard aGuard( *m_xData );
1078 size_t nCount = ImplLeaveListAction( false, aGuard );
1080 if ( m_xData->mbClearUntilTopLevel )
1082 ImplClearCurrentLevel_NoNotify( aGuard );
1083 if ( !ImplIsInListAction_Lock() )
1085 m_xData->mbClearUntilTopLevel = false;
1086 aGuard.scheduleNotification( &SfxUndoListener::cleared );
1088 nCount = 0;
1091 return nCount;
1095 size_t SfxUndoManager::LeaveAndMergeListAction()
1097 UndoManagerGuard aGuard( *m_xData );
1098 return ImplLeaveListAction( true, aGuard );
1102 size_t SfxUndoManager::ImplLeaveListAction( const bool i_merge, UndoManagerGuard& i_guard )
1104 if ( !ImplIsUndoEnabled_Lock() )
1105 return 0;
1107 if ( !m_xData->pUndoArray->nMaxUndoActions )
1108 return 0;
1110 if( !ImplIsInListAction_Lock() )
1112 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1113 return 0;
1116 assert(m_xData->pActUndoArray->pFatherUndoArray);
1118 // the array/level which we're about to leave
1119 SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
1120 // one step up
1121 m_xData->pActUndoArray = m_xData->pActUndoArray->pFatherUndoArray;
1123 // If no undo actions were added to the list, delete the list action
1124 const size_t nListActionElements = pArrayToLeave->nCurUndoAction;
1125 if ( nListActionElements == 0 )
1127 SfxUndoAction* pCurrentAction= m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->nCurUndoAction-1 ].pAction;
1128 m_xData->pActUndoArray->aUndoActions.Remove( --m_xData->pActUndoArray->nCurUndoAction );
1129 i_guard.markForDeletion( pCurrentAction );
1131 i_guard.scheduleNotification( &SfxUndoListener::listActionCancelled );
1132 return 0;
1135 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1136 // the redo stack
1137 ImplClearRedo( i_guard, IUndoManager::CurrentLevel );
1139 SfxUndoAction* pCurrentAction= m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->nCurUndoAction-1 ].pAction;
1140 SfxListUndoAction* pListAction = dynamic_cast< SfxListUndoAction * >( pCurrentAction );
1141 ENSURE_OR_RETURN( pListAction, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements );
1143 if ( i_merge )
1145 // merge the list action with its predecessor on the same level
1146 SAL_WARN_IF( m_xData->pActUndoArray->nCurUndoAction <= 1, "svl",
1147 "SfxUndoManager::ImplLeaveListAction: cannot merge the list action if there's no other action on the same level - check this beforehand!" );
1148 if ( m_xData->pActUndoArray->nCurUndoAction > 1 )
1150 SfxUndoAction* pPreviousAction = m_xData->pActUndoArray->aUndoActions[ m_xData->pActUndoArray->nCurUndoAction - 2 ].pAction;
1151 m_xData->pActUndoArray->aUndoActions.Remove( m_xData->pActUndoArray->nCurUndoAction - 2 );
1152 --m_xData->pActUndoArray->nCurUndoAction;
1153 pListAction->aUndoActions.Insert( pPreviousAction, 0 );
1154 ++pListAction->nCurUndoAction;
1156 pListAction->SetComment( pPreviousAction->GetComment() );
1160 // if the undo array has no comment, try to get it from its children
1161 if ( pListAction->GetComment().isEmpty() )
1163 for( size_t n = 0; n < pListAction->aUndoActions.size(); n++ )
1165 if (!pListAction->aUndoActions[n].pAction->GetComment().isEmpty())
1167 pListAction->SetComment( pListAction->aUndoActions[n].pAction->GetComment() );
1168 break;
1173 // notify listeners
1174 i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
1176 // outta here
1177 return nListActionElements;
1180 UndoStackMark SfxUndoManager::MarkTopUndoAction()
1182 UndoManagerGuard aGuard( *m_xData );
1184 SAL_WARN_IF( IsInListAction(), "svl",
1185 "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
1186 assert((m_xData->mnMarks + 1) < (m_xData->mnEmptyMark - 1) &&
1187 "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
1189 size_t const nActionPos = m_xData->pUndoArray->nCurUndoAction;
1190 if (0 == nActionPos)
1192 --m_xData->mnEmptyMark;
1193 return m_xData->mnEmptyMark;
1196 m_xData->pUndoArray->aUndoActions[ nActionPos-1 ].aMarks.push_back(
1197 ++m_xData->mnMarks );
1198 return m_xData->mnMarks;
1201 void SfxUndoManager::RemoveMark( UndoStackMark const i_mark )
1203 UndoManagerGuard aGuard( *m_xData );
1205 if ((m_xData->mnEmptyMark < i_mark) || (MARK_INVALID == i_mark))
1207 return; // nothing to remove
1209 else if (i_mark == m_xData->mnEmptyMark)
1211 --m_xData->mnEmptyMark; // never returned from MarkTop => invalid
1212 return;
1215 for ( size_t i=0; i<m_xData->pUndoArray->aUndoActions.size(); ++i )
1217 MarkedUndoAction& rAction = m_xData->pUndoArray->aUndoActions[i];
1218 for ( ::std::vector< UndoStackMark >::iterator markPos = rAction.aMarks.begin();
1219 markPos != rAction.aMarks.end();
1220 ++markPos
1223 if ( *markPos == i_mark )
1225 rAction.aMarks.erase( markPos );
1226 return;
1230 SAL_WARN("svl", "SfxUndoManager::RemoveMark: mark not found!");
1231 // TODO: this might be too offensive. There are situations where we implicitly remove marks
1232 // without our clients, in particular the client which created the mark, having a chance to know
1233 // about this.
1236 bool SfxUndoManager::HasTopUndoActionMark( UndoStackMark const i_mark )
1238 UndoManagerGuard aGuard( *m_xData );
1240 size_t nActionPos = m_xData->pUndoArray->nCurUndoAction;
1241 if ( nActionPos == 0 )
1243 return (i_mark == m_xData->mnEmptyMark);
1246 const MarkedUndoAction& rAction =
1247 m_xData->pUndoArray->aUndoActions[ nActionPos-1 ];
1248 for ( ::std::vector< UndoStackMark >::const_iterator markPos = rAction.aMarks.begin();
1249 markPos != rAction.aMarks.end();
1250 ++markPos
1253 if ( *markPos == i_mark )
1254 return true;
1257 return false;
1261 void SfxUndoManager::RemoveOldestUndoActions( size_t const i_count )
1263 UndoManagerGuard aGuard( *m_xData );
1265 size_t nActionsToRemove = i_count;
1266 while ( nActionsToRemove )
1268 SfxUndoAction* pActionToRemove = m_xData->pUndoArray->aUndoActions[0].pAction;
1270 if ( IsInListAction() && ( m_xData->pUndoArray->nCurUndoAction == 1 ) )
1272 assert(!"SfxUndoManager::RemoveOldestUndoActions: cannot remove a not-yet-closed list action!");
1273 return;
1276 aGuard.markForDeletion( pActionToRemove );
1277 m_xData->pUndoArray->aUndoActions.Remove( 0 );
1278 --m_xData->pUndoArray->nCurUndoAction;
1279 --nActionsToRemove;
1283 void SfxUndoManager::dumpAsXml(xmlTextWriterPtr pWriter) const
1285 xmlTextWriterStartElement(pWriter, BAD_CAST("sfxUndoManager"));
1286 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUndoActionCount"), BAD_CAST(OString::number(GetUndoActionCount()).getStr()));
1288 for (size_t i = 0; i < GetUndoActionCount(); ++i)
1289 GetUndoAction(i)->dumpAsXml(pWriter);
1291 xmlTextWriterEndElement(pWriter);
1294 struct SfxListUndoAction::Impl
1296 sal_uInt16 mnId;
1298 OUString maComment;
1299 OUString maRepeatComment;
1301 Impl( sal_uInt16 nId, const OUString& rComment, const OUString& rRepeatComment ) :
1302 mnId(nId), maComment(rComment), maRepeatComment(rRepeatComment) {}
1305 sal_uInt16 SfxListUndoAction::GetId() const
1307 return mpImpl->mnId;
1310 OUString SfxListUndoAction::GetComment() const
1312 return mpImpl->maComment;
1315 void SfxListUndoAction::SetComment(const OUString& rComment)
1317 mpImpl->maComment = rComment;
1320 OUString SfxListUndoAction::GetRepeatComment(SfxRepeatTarget &) const
1322 return mpImpl->maRepeatComment;
1325 SfxListUndoAction::SfxListUndoAction(
1326 const OUString &rComment,
1327 const OUString &rRepeatComment,
1328 sal_uInt16 nId,
1329 SfxUndoArray *pFather ) :
1330 mpImpl(new Impl(nId, rComment, rRepeatComment))
1332 pFatherUndoArray = pFather;
1333 nMaxUndoActions = USHRT_MAX;
1336 SfxListUndoAction::~SfxListUndoAction()
1338 delete mpImpl;
1341 void SfxListUndoAction::Undo()
1343 for(size_t i=nCurUndoAction;i>0;)
1344 aUndoActions[--i].pAction->Undo();
1345 nCurUndoAction=0;
1349 void SfxListUndoAction::UndoWithContext( SfxUndoContext& i_context )
1351 for(size_t i=nCurUndoAction;i>0;)
1352 aUndoActions[--i].pAction->UndoWithContext( i_context );
1353 nCurUndoAction=0;
1357 void SfxListUndoAction::Redo()
1359 for(size_t i=nCurUndoAction;i<aUndoActions.size();i++)
1360 aUndoActions[i].pAction->Redo();
1361 nCurUndoAction = aUndoActions.size();
1365 void SfxListUndoAction::RedoWithContext( SfxUndoContext& i_context )
1367 for(size_t i=nCurUndoAction;i<aUndoActions.size();i++)
1368 aUndoActions[i].pAction->RedoWithContext( i_context );
1369 nCurUndoAction = aUndoActions.size();
1373 void SfxListUndoAction::Repeat(SfxRepeatTarget&rTarget)
1375 for(size_t i=0;i<nCurUndoAction;i++)
1376 aUndoActions[i].pAction->Repeat(rTarget);
1380 bool SfxListUndoAction::CanRepeat(SfxRepeatTarget&r) const
1382 for(size_t i=0;i<nCurUndoAction;i++)
1384 if(!aUndoActions[i].pAction->CanRepeat(r))
1385 return false;
1387 return true;
1391 bool SfxListUndoAction::Merge( SfxUndoAction *pNextAction )
1393 return !aUndoActions.empty() && aUndoActions[aUndoActions.size()-1].pAction->Merge( pNextAction );
1396 void SfxListUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
1398 xmlTextWriterStartElement(pWriter, BAD_CAST("sfxListUndoAction"));
1399 xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(aUndoActions.size()).getStr()));
1400 SfxUndoAction::dumpAsXml(pWriter);
1402 for (size_t i = 0; i < aUndoActions.size(); ++i)
1403 aUndoActions.GetUndoAction(i)->dumpAsXml(pWriter);
1405 xmlTextWriterEndElement(pWriter);
1409 * Creates a LinkAction which points to another UndoManager.
1410 * Gets that UndoManagers current Action and sets it as that UndoManager's
1411 * associated Action.
1413 SfxLinkUndoAction::SfxLinkUndoAction(::svl::IUndoManager *pManager)
1415 pUndoManager = pManager;
1416 SfxUndoManager* pUndoManagerImplementation = dynamic_cast< SfxUndoManager* >( pManager );
1417 ENSURE_OR_THROW( pUndoManagerImplementation != NULL, "unsupported undo manager implementation!" );
1419 // yes, this cast is dirty. But reaching into the SfxUndoManager's implementation,
1420 // directly accessing its internal stack, and tampering with an action on that stack
1421 // is dirty, too.
1422 if ( pManager->GetMaxUndoActionCount() )
1424 size_t nPos = pManager->GetUndoActionCount()-1;
1425 pAction = pUndoManagerImplementation->m_xData->pActUndoArray->aUndoActions[nPos].pAction;
1426 pAction->SetLinkToSfxLinkUndoAction(this);
1428 else
1429 pAction = 0;
1433 void SfxLinkUndoAction::Undo()
1435 if ( pAction )
1436 pUndoManager->Undo();
1440 void SfxLinkUndoAction::Redo()
1442 if ( pAction )
1443 pUndoManager->Redo();
1448 bool SfxLinkUndoAction::CanRepeat(SfxRepeatTarget& r) const
1450 return pAction && pAction->CanRepeat(r);
1456 void SfxLinkUndoAction::Repeat(SfxRepeatTarget&r)
1458 if ( pAction && pAction->CanRepeat( r ) )
1459 pAction->Repeat( r );
1464 OUString SfxLinkUndoAction::GetComment() const
1466 if ( pAction )
1467 return pAction->GetComment();
1468 return OUString();
1473 OUString SfxLinkUndoAction::GetRepeatComment(SfxRepeatTarget&r) const
1475 if ( pAction )
1476 return pAction->GetRepeatComment(r);
1477 return OUString();
1481 SfxLinkUndoAction::~SfxLinkUndoAction()
1483 if( pAction )
1484 pAction->SetLinkToSfxLinkUndoAction(0);
1488 void SfxLinkUndoAction::LinkedSfxUndoActionDestructed(const SfxUndoAction& rCandidate)
1490 assert(0 != pAction);
1491 assert(pAction == &rCandidate && "Oops, the destroyed and linked UndoActions differ (!)");
1492 (void)rCandidate;
1493 pAction = 0;
1497 SfxUndoArray::~SfxUndoArray()
1499 while ( !aUndoActions.empty() )
1501 SfxUndoAction *pAction = aUndoActions[ aUndoActions.size() - 1 ].pAction;
1502 aUndoActions.Remove( aUndoActions.size() - 1 );
1503 delete pAction;
1508 sal_uInt16 SfxLinkUndoAction::GetId() const
1510 return pAction ? pAction->GetId() : 0;
1513 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */