1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <svl/undo.hxx>
22 #include <com/sun/star/uno/Exception.hpp>
24 #include <osl/mutex.hxx>
25 #include <sal/log.hxx>
26 #include <comphelper/flagguard.hxx>
27 #include <tools/diagnose_ex.h>
28 #include <libxml/xmlwriter.h>
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
* )
80 OUString
SfxUndoAction::GetComment() const
87 sal_uInt16
SfxUndoAction::GetId() const
93 OUString
SfxUndoAction::GetRepeatComment(SfxRepeatTarget
&) const
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
)
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
)
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
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
)
160 struct SfxUndoActions::Impl
162 std::vector
<MarkedUndoAction
> maActions
;
165 SfxUndoActions::SfxUndoActions() : mpImpl(new Impl
) {}
167 SfxUndoActions::SfxUndoActions( const SfxUndoActions
& r
) :
170 mpImpl
->maActions
= r
.mpImpl
->maActions
;
173 SfxUndoActions::~SfxUndoActions()
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
230 SfxUndoArray
* pUndoArray
;
231 SfxUndoArray
* pActUndoArray
;
232 SfxUndoArray
* pFatherUndoArray
;
235 sal_Int32 mnEmptyMark
;
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
)
247 ,mnEmptyMark(MARK_INVALID
)
248 ,mbUndoEnabled( true )
250 ,mbClearUntilTopLevel( false )
252 pActUndoArray
= pUndoArray
;
255 ~SfxUndoManager_Data()
262 namespace svl
{ namespace undo
{ namespace impl
264 class SVL_DLLPRIVATE LockGuard
267 LockGuard( SfxUndoManager
& i_manager
)
268 :m_manager( i_manager
)
270 m_manager
.ImplEnableUndo_Lock( false );
275 m_manager
.ImplEnableUndo_Lock( true );
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
)
294 NotifyUndoListener( UndoListenerStringMethod i_notificationMethod
, const OUString
& i_actionComment
)
295 :m_notificationMethod( NULL
)
296 ,m_altNotificationMethod( i_notificationMethod
)
297 ,m_sActionComment( i_actionComment
)
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
);
315 ( i_listener
->*m_notificationMethod
)();
320 UndoListenerVoidMethod m_notificationMethod
;
321 UndoListenerStringMethod m_altNotificationMethod
;
322 OUString m_sActionComment
;
325 class SVL_DLLPRIVATE UndoManagerGuard
328 UndoManagerGuard( SfxUndoManager_Data
& i_managerData
)
329 :m_rManagerData( i_managerData
)
330 ,m_aGuard( i_managerData
.aMutex
)
347 void cancelNotifications()
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
)
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
) );
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()
389 UndoListeners
aListenersCopy( m_rManagerData
.aListeners
);
394 // delete all actions
395 while ( !m_aUndoActionsCleanup
.empty() )
397 SfxUndoAction
* pAction
= m_aUndoActionsCleanup
.front();
398 m_aUndoActionsCleanup
.pop_front();
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();
415 if ( notifier
->is() )
416 ::std::for_each( aListenersCopy
.begin(), aListenersCopy
.end(), *notifier
);
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
)
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 );
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
;
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
)
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
);
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;
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
);
584 while ( !ImplIsUndoEnabled_Lock() )
585 ImplEnableUndo_Lock( true );
587 // cancel all list actions
588 while ( IsInListAction() )
589 ImplLeaveListAction( false, aGuard
);
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
;
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
);
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
);
651 i_guard
.markForDeletion( pAction
);
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
;
673 assert(!"CurrentUndoAction going negative (!)");
675 // fdo#66071 invalidate the current empty mark when removing
676 --m_xData
->mnEmptyMark
;
681 m_xData
->pActUndoArray
->aUndoActions
.Insert( pAction
, m_xData
->pActUndoArray
->nCurUndoAction
++ );
686 void SfxUndoManager::AddUndoAction( SfxUndoAction
*pAction
, bool bTryMerge
)
688 UndoManagerGuard
aGuard( *m_xData
);
691 if ( ImplAddUndoAction_NoNotify( pAction
, bTryMerge
, true, aGuard
) )
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
);
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();
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 )
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
)
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!");
796 if ( m_xData
->pActUndoArray
->nCurUndoAction
== 0 )
798 SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
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
809 if ( i_contextOrNull
!= NULL
)
810 pAction
->UndoWithContext( *i_contextOrNull
);
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
);
832 SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
836 aGuard
.scheduleNotification( &SfxUndoListener::actionUndone
, sActionComment
);
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() )
865 return pUndoArray
->aUndoActions
[ pUndoArray
->nCurUndoAction
+ nNo
].pAction
;
869 OUString
SfxUndoManager::GetRedoActionComment( size_t nNo
, bool const i_currentLevel
) const
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();
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!");
908 if ( m_xData
->pActUndoArray
->nCurUndoAction
>= m_xData
->pActUndoArray
->aUndoActions
.size() )
910 SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
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
921 if ( i_contextOrNull
!= NULL
)
922 pAction
->RedoWithContext( *i_contextOrNull
);
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
);
945 SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
949 aGuard
.scheduleNotification( &SfxUndoListener::actionRedone
, sActionComment
);
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
;
977 if ( pAction
->CanRepeat( rTarget
) )
978 pAction
->Repeat( rTarget
);
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
);
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();
1013 if ( (*lookup
) == &i_listener
)
1015 m_xData
->aListeners
.erase( lookup
);
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() )
1032 if ( !m_xData
->pUndoArray
->nMaxUndoActions
)
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
;
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
);
1064 SfxUndoArray
* pLookup( m_xData
->pActUndoArray
);
1065 while ( pLookup
!= m_xData
->pUndoArray
)
1067 pLookup
= pLookup
->pFatherUndoArray
;
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
);
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() )
1107 if ( !m_xData
->pUndoArray
->nMaxUndoActions
)
1110 if( !ImplIsInListAction_Lock() )
1112 SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1116 assert(m_xData
->pActUndoArray
->pFatherUndoArray
);
1118 // the array/level which we're about to leave
1119 SfxUndoArray
* pArrayToLeave
= m_xData
->pActUndoArray
;
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
);
1135 // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
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
);
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() );
1174 i_guard
.scheduleNotification( &SfxUndoListener::listActionLeft
, pListAction
->GetComment() );
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
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();
1223 if ( *markPos
== i_mark
)
1225 rAction
.aMarks
.erase( markPos
);
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
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();
1253 if ( *markPos
== i_mark
)
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!");
1276 aGuard
.markForDeletion( pActionToRemove
);
1277 m_xData
->pUndoArray
->aUndoActions
.Remove( 0 );
1278 --m_xData
->pUndoArray
->nCurUndoAction
;
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
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
,
1329 SfxUndoArray
*pFather
) :
1330 mpImpl(new Impl(nId
, rComment
, rRepeatComment
))
1332 pFatherUndoArray
= pFather
;
1333 nMaxUndoActions
= USHRT_MAX
;
1336 SfxListUndoAction::~SfxListUndoAction()
1341 void SfxListUndoAction::Undo()
1343 for(size_t i
=nCurUndoAction
;i
>0;)
1344 aUndoActions
[--i
].pAction
->Undo();
1349 void SfxListUndoAction::UndoWithContext( SfxUndoContext
& i_context
)
1351 for(size_t i
=nCurUndoAction
;i
>0;)
1352 aUndoActions
[--i
].pAction
->UndoWithContext( i_context
);
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
))
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
1422 if ( pManager
->GetMaxUndoActionCount() )
1424 size_t nPos
= pManager
->GetUndoActionCount()-1;
1425 pAction
= pUndoManagerImplementation
->m_xData
->pActUndoArray
->aUndoActions
[nPos
].pAction
;
1426 pAction
->SetLinkToSfxLinkUndoAction(this);
1433 void SfxLinkUndoAction::Undo()
1436 pUndoManager
->Undo();
1440 void SfxLinkUndoAction::Redo()
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
1467 return pAction
->GetComment();
1473 OUString
SfxLinkUndoAction::GetRepeatComment(SfxRepeatTarget
&r
) const
1476 return pAction
->GetRepeatComment(r
);
1481 SfxLinkUndoAction::~SfxLinkUndoAction()
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 (!)");
1497 SfxUndoArray::~SfxUndoArray()
1499 while ( !aUndoActions
.empty() )
1501 SfxUndoAction
*pAction
= aUndoActions
[ aUndoActions
.size() - 1 ].pAction
;
1502 aUndoActions
.Remove( aUndoActions
.size() - 1 );
1508 sal_uInt16
SfxLinkUndoAction::GetId() const
1510 return pAction
? pAction
->GetId() : 0;
1513 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */