xref: /core/svl/source/undo/undo.cxx (revision 466156f1)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include <svl/undo.hxx>
21 
22 #include <osl/mutex.hxx>
23 #include <sal/log.hxx>
24 #include <comphelper/flagguard.hxx>
25 #include <comphelper/diagnose_ex.hxx>
26 #include <tools/long.hxx>
27 #include <libxml/xmlwriter.h>
28 #include <boost/property_tree/json_parser.hpp>
29 #include <unotools/datetime.hxx>
30 
31 #include <memory>
32 #include <utility>
33 #include <vector>
34 #include <limits.h>
35 #include <algorithm>
36 
37 namespace
38 {
39 class SfxMarkedUndoContext final : public SfxUndoContext
40 {
41 public:
SfxMarkedUndoContext(SfxUndoManager & manager,UndoStackMark mark)42     SfxMarkedUndoContext(SfxUndoManager& manager, UndoStackMark mark)
43     {
44         m_offset = manager.RemoveMark(mark);
45         size_t count = manager.GetUndoActionCount();
46         if (m_offset < count)
47             m_offset = count - m_offset - 1;
48         else
49             m_offset = std::numeric_limits<size_t>::max();
50     }
GetUndoOffset()51     size_t GetUndoOffset() override { return m_offset; }
52 
53 private:
54     size_t m_offset;
55 };
56 }
57 
~SfxRepeatTarget()58 SfxRepeatTarget::~SfxRepeatTarget()
59 {
60 }
61 
62 
~SfxUndoContext()63 SfxUndoContext::~SfxUndoContext()
64 {
65 }
66 
67 
~SfxUndoAction()68 SfxUndoAction::~SfxUndoAction() COVERITY_NOEXCEPT_FALSE
69 {
70 }
71 
72 
SfxUndoAction()73 SfxUndoAction::SfxUndoAction()
74     : m_aDateTime(DateTime::SYSTEM)
75 {
76     m_aDateTime.ConvertToUTC();
77 }
78 
79 
Merge(SfxUndoAction *)80 bool SfxUndoAction::Merge( SfxUndoAction * )
81 {
82     return false;
83 }
84 
85 
GetComment() const86 OUString SfxUndoAction::GetComment() const
87 {
88     return OUString();
89 }
90 
91 
GetViewShellId() const92 ViewShellId SfxUndoAction::GetViewShellId() const
93 {
94     return ViewShellId(-1);
95 }
96 
GetDateTime() const97 const DateTime& SfxUndoAction::GetDateTime() const
98 {
99     return m_aDateTime;
100 }
101 
GetRepeatComment(SfxRepeatTarget &) const102 OUString SfxUndoAction::GetRepeatComment(SfxRepeatTarget&) const
103 {
104     return GetComment();
105 }
106 
107 
Undo()108 void SfxUndoAction::Undo()
109 {
110     // These are only conceptually pure virtual
111     assert(!"pure virtual function called: SfxUndoAction::Undo()");
112 }
113 
114 
UndoWithContext(SfxUndoContext &)115 void SfxUndoAction::UndoWithContext( SfxUndoContext& )
116 {
117     Undo();
118 }
119 
120 
Redo()121 void SfxUndoAction::Redo()
122 {
123     // These are only conceptually pure virtual
124     assert(!"pure virtual function called: SfxUndoAction::Redo()");
125 }
126 
127 
RedoWithContext(SfxUndoContext &)128 void SfxUndoAction::RedoWithContext( SfxUndoContext& )
129 {
130     Redo();
131 }
132 
133 
Repeat(SfxRepeatTarget &)134 void SfxUndoAction::Repeat(SfxRepeatTarget&)
135 {
136     // These are only conceptually pure virtual
137     assert(!"pure virtual function called: SfxUndoAction::Repeat()");
138 }
139 
140 
CanRepeat(SfxRepeatTarget &) const141 bool SfxUndoAction::CanRepeat(SfxRepeatTarget&) const
142 {
143     return true;
144 }
145 
dumpAsXml(xmlTextWriterPtr pWriter) const146 void SfxUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
147 {
148     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUndoAction"));
149     (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
150     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("symbol"), BAD_CAST(typeid(*this).name()));
151     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("comment"), BAD_CAST(GetComment().toUtf8().getStr()));
152     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("viewShellId"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetViewShellId())).getStr()));
153     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("dateTime"), BAD_CAST(utl::toISO8601(m_aDateTime.GetUNODateTime()).toUtf8().getStr()));
154     (void)xmlTextWriterEndElement(pWriter);
155 }
156 
Remove(int idx)157 std::unique_ptr<SfxUndoAction> SfxUndoArray::Remove(int idx)
158 {
159     auto ret = std::move(maUndoActions[idx].pAction);
160     maUndoActions.erase(maUndoActions.begin() + idx);
161     return ret;
162 }
163 
Remove(size_t i_pos,size_t i_count)164 void SfxUndoArray::Remove( size_t i_pos, size_t i_count )
165 {
166     maUndoActions.erase(maUndoActions.begin() + i_pos, maUndoActions.begin() + i_pos + i_count);
167 }
168 
Insert(std::unique_ptr<SfxUndoAction> i_action,size_t i_pos)169 void SfxUndoArray::Insert( std::unique_ptr<SfxUndoAction> i_action, size_t i_pos )
170 {
171     maUndoActions.insert( maUndoActions.begin() + i_pos, MarkedUndoAction(std::move(i_action)) );
172 }
173 
174 typedef ::std::vector< SfxUndoListener* >   UndoListeners;
175 
176 struct SfxUndoManager_Data
177 {
178     ::osl::Mutex    aMutex;
179     SfxUndoArray    maUndoArray;
180     SfxUndoArray*   pActUndoArray;
181 
182     sal_Int32       mnMarks;
183     sal_Int32       mnEmptyMark;
184     bool            mbUndoEnabled;
185     bool            mbDoing;
186     bool            mbClearUntilTopLevel;
187     bool            mbEmptyActions;
188 
189     UndoListeners   aListeners;
190 
SfxUndoManager_DataSfxUndoManager_Data191     explicit SfxUndoManager_Data( size_t i_nMaxUndoActionCount )
192         :maUndoArray( i_nMaxUndoActionCount )
193         ,pActUndoArray( nullptr )
194         ,mnMarks( 0 )
195         ,mnEmptyMark(MARK_INVALID)
196         ,mbUndoEnabled( true )
197         ,mbDoing( false )
198         ,mbClearUntilTopLevel( false )
199         ,mbEmptyActions( true )
200     {
201         pActUndoArray = &maUndoArray;
202     }
203 
204     // Copy assignment is forbidden and not implemented.
205     SfxUndoManager_Data (const SfxUndoManager_Data &) = delete;
206     SfxUndoManager_Data & operator= (const SfxUndoManager_Data &) = delete;
207 };
208 
209 namespace svl::undo::impl
210 {
211     class LockGuard
212     {
213     public:
LockGuard(SfxUndoManager & i_manager)214         explicit LockGuard( SfxUndoManager& i_manager )
215             :m_manager( i_manager )
216         {
217             m_manager.ImplEnableUndo_Lock( false );
218         }
219 
~LockGuard()220         ~LockGuard()
221         {
222             m_manager.ImplEnableUndo_Lock( true );
223         }
224 
225     private:
226         SfxUndoManager& m_manager;
227     };
228 
229     typedef void ( SfxUndoListener::*UndoListenerVoidMethod )();
230     typedef void ( SfxUndoListener::*UndoListenerStringMethod )( const OUString& );
231 
232     namespace {
233 
234     struct NotifyUndoListener
235     {
NotifyUndoListenersvl::undo::impl::__anon5997ca150211::NotifyUndoListener236         explicit NotifyUndoListener( UndoListenerVoidMethod i_notificationMethod )
237             :m_notificationMethod( i_notificationMethod )
238             ,m_altNotificationMethod( nullptr )
239         {
240         }
241 
NotifyUndoListenersvl::undo::impl::__anon5997ca150211::NotifyUndoListener242         NotifyUndoListener( UndoListenerStringMethod i_notificationMethod, OUString i_actionComment )
243             :m_notificationMethod( nullptr )
244             ,m_altNotificationMethod( i_notificationMethod )
245             ,m_sActionComment(std::move( i_actionComment ))
246         {
247         }
248 
issvl::undo::impl::__anon5997ca150211::NotifyUndoListener249         bool is() const
250         {
251             return ( m_notificationMethod != nullptr ) || ( m_altNotificationMethod != nullptr );
252         }
253 
operator ()svl::undo::impl::__anon5997ca150211::NotifyUndoListener254         void operator()( SfxUndoListener* i_listener ) const
255         {
256             assert( is() && "NotifyUndoListener: this will crash!" );
257             if ( m_altNotificationMethod != nullptr )
258             {
259                 ( i_listener->*m_altNotificationMethod )( m_sActionComment );
260             }
261             else
262             {
263                 ( i_listener->*m_notificationMethod )();
264             }
265         }
266 
267     private:
268         UndoListenerVoidMethod      m_notificationMethod;
269         UndoListenerStringMethod    m_altNotificationMethod;
270         OUString                    m_sActionComment;
271     };
272 
273     }
274 
275     class UndoManagerGuard
276     {
277     public:
UndoManagerGuard(SfxUndoManager_Data & i_managerData)278         explicit UndoManagerGuard( SfxUndoManager_Data& i_managerData )
279             :m_rManagerData( i_managerData )
280             ,m_aGuard( i_managerData.aMutex )
281         {
282         }
283 
284         ~UndoManagerGuard();
285 
clear()286         auto clear() { return osl::ResettableMutexGuardScopedReleaser(m_aGuard); }
287 
cancelNotifications()288         void cancelNotifications()
289         {
290             m_notifiers.clear();
291         }
292 
293         /** marks the given Undo action for deletion
294 
295             The Undo action will be put into a list, whose members will be deleted from within the destructor of the
296             UndoManagerGuard. This deletion will happen without the UndoManager's mutex locked.
297         */
markForDeletion(std::unique_ptr<SfxUndoAction> i_action)298         void    markForDeletion( std::unique_ptr<SfxUndoAction> i_action )
299         {
300             // remember
301             assert ( i_action );
302             m_aUndoActionsCleanup.emplace_back( std::move(i_action) );
303         }
304 
305         /** schedules the given SfxUndoListener method to be called for all registered listeners.
306 
307             The notification will happen after the Undo manager's mutex has been released, and after all pending
308             deletions of Undo actions are done.
309         */
scheduleNotification(UndoListenerVoidMethod i_notificationMethod)310         void    scheduleNotification( UndoListenerVoidMethod i_notificationMethod )
311         {
312             m_notifiers.emplace_back( i_notificationMethod );
313         }
314 
scheduleNotification(UndoListenerStringMethod i_notificationMethod,const OUString & i_actionComment)315         void    scheduleNotification( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
316         {
317             m_notifiers.emplace_back( i_notificationMethod, i_actionComment );
318         }
319 
320     private:
321         SfxUndoManager_Data&                m_rManagerData;
322         ::osl::ResettableMutexGuard         m_aGuard;
323         ::std::vector< std::unique_ptr<SfxUndoAction> > m_aUndoActionsCleanup;
324         ::std::vector< NotifyUndoListener > m_notifiers;
325     };
326 
~UndoManagerGuard()327     UndoManagerGuard::~UndoManagerGuard()
328     {
329         // copy members
330         UndoListeners aListenersCopy( m_rManagerData.aListeners );
331 
332         // release mutex
333         m_aGuard.clear();
334 
335         // delete all actions
336         m_aUndoActionsCleanup.clear();
337 
338         // handle scheduled notification
339         for (auto const& notifier : m_notifiers)
340         {
341             if ( notifier.is() )
342                 ::std::for_each( aListenersCopy.begin(), aListenersCopy.end(), notifier );
343         }
344     }
345 }
346 
347 using namespace ::svl::undo::impl;
348 
349 
SfxUndoManager(size_t nMaxUndoActionCount)350 SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount )
351     :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount ) )
352 {
353     m_xData->mbEmptyActions = !ImplIsEmptyActions();
354 }
355 
356 
~SfxUndoManager()357 SfxUndoManager::~SfxUndoManager()
358 {
359 }
360 
361 
EnableUndo(bool i_enable)362 void SfxUndoManager::EnableUndo( bool i_enable )
363 {
364     UndoManagerGuard aGuard( *m_xData );
365     ImplEnableUndo_Lock( i_enable );
366 
367 }
368 
369 
ImplEnableUndo_Lock(bool const i_enable)370 void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable )
371 {
372     if ( m_xData->mbUndoEnabled == i_enable )
373         return;
374     m_xData->mbUndoEnabled = i_enable;
375 }
376 
377 
IsUndoEnabled() const378 bool SfxUndoManager::IsUndoEnabled() const
379 {
380     UndoManagerGuard aGuard( *m_xData );
381     return ImplIsUndoEnabled_Lock();
382 }
383 
384 
ImplIsUndoEnabled_Lock() const385 bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
386 {
387     return m_xData->mbUndoEnabled;
388 }
389 
SetMaxUndoActionCount(size_t nMaxUndoActionCount)390 void SfxUndoManager::SetMaxUndoActionCount( size_t nMaxUndoActionCount )
391 {
392     UndoManagerGuard aGuard( *m_xData );
393 
394     // Remove entries from the pActUndoArray when we have to reduce
395     // the number of entries due to a lower nMaxUndoActionCount.
396     // Both redo and undo action entries will be removed until we reached the
397     // new nMaxUndoActionCount.
398 
399     tools::Long nNumToDelete = m_xData->pActUndoArray->maUndoActions.size() - nMaxUndoActionCount;
400     while ( nNumToDelete > 0 )
401     {
402         size_t nPos = m_xData->pActUndoArray->maUndoActions.size();
403         if ( nPos > m_xData->pActUndoArray->nCurUndoAction )
404         {
405             aGuard.markForDeletion( m_xData->pActUndoArray->Remove( nPos-1 ) );
406             --nNumToDelete;
407         }
408 
409         if ( nNumToDelete > 0 && m_xData->pActUndoArray->nCurUndoAction > 0 )
410         {
411             aGuard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
412             --m_xData->pActUndoArray->nCurUndoAction;
413             --nNumToDelete;
414         }
415 
416         if ( nPos == m_xData->pActUndoArray->maUndoActions.size() )
417             break; // Cannot delete more entries
418     }
419 
420     m_xData->pActUndoArray->nMaxUndoActions = nMaxUndoActionCount;
421     ImplCheckEmptyActions();
422 }
423 
GetMaxUndoActionCount() const424 size_t SfxUndoManager::GetMaxUndoActionCount() const
425 {
426     return m_xData->pActUndoArray->nMaxUndoActions;
427 }
428 
ImplClearCurrentLevel_NoNotify(UndoManagerGuard & i_guard)429 void SfxUndoManager::ImplClearCurrentLevel_NoNotify( UndoManagerGuard& i_guard )
430 {
431     // clear array
432     while ( !m_xData->pActUndoArray->maUndoActions.empty() )
433     {
434         size_t deletePos = m_xData->pActUndoArray->maUndoActions.size() - 1;
435         i_guard.markForDeletion( m_xData->pActUndoArray->Remove( deletePos ) );
436     }
437 
438     m_xData->pActUndoArray->nCurUndoAction = 0;
439 
440     m_xData->mnMarks = 0;
441     m_xData->mnEmptyMark = MARK_INVALID;
442     ImplCheckEmptyActions();
443 }
444 
445 
Clear()446 void SfxUndoManager::Clear()
447 {
448     UndoManagerGuard aGuard( *m_xData );
449 
450     SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
451         "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
452     ImplClearCurrentLevel_NoNotify( aGuard );
453 
454     // notify listeners
455     aGuard.scheduleNotification( &SfxUndoListener::cleared );
456 }
457 
458 
ClearAllLevels()459 void SfxUndoManager::ClearAllLevels()
460 {
461     UndoManagerGuard aGuard( *m_xData );
462     ImplClearCurrentLevel_NoNotify( aGuard );
463 
464     if ( ImplIsInListAction_Lock() )
465     {
466         m_xData->mbClearUntilTopLevel = true;
467     }
468     else
469     {
470         aGuard.scheduleNotification( &SfxUndoListener::cleared );
471     }
472 }
473 
474 
ImplClearRedo_NoLock(bool const i_currentLevel)475 void SfxUndoManager::ImplClearRedo_NoLock( bool const i_currentLevel )
476 {
477     UndoManagerGuard aGuard( *m_xData );
478     ImplClearRedo( aGuard, i_currentLevel );
479 }
480 
481 
ClearRedo()482 void SfxUndoManager::ClearRedo()
483 {
484     SAL_WARN_IF( IsInListAction(), "svl",
485         "SfxUndoManager::ClearRedo: suspicious call - do you really wish to clear the current level?" );
486     ImplClearRedo_NoLock( CurrentLevel );
487 }
488 
489 
Reset()490 void SfxUndoManager::Reset()
491 {
492     UndoManagerGuard aGuard( *m_xData );
493 
494     // clear all locks
495     while ( !ImplIsUndoEnabled_Lock() )
496         ImplEnableUndo_Lock( true );
497 
498     // cancel all list actions
499     while ( IsInListAction() )
500         ImplLeaveListAction( false, aGuard );
501 
502     // clear both stacks
503     ImplClearCurrentLevel_NoNotify( aGuard );
504 
505     // cancel the notifications scheduled by ImplLeaveListAction,
506     // as we want to do an own, dedicated notification
507     aGuard.cancelNotifications();
508 
509     // schedule notification
510     aGuard.scheduleNotification( &SfxUndoListener::resetAll );
511 }
512 
513 
ImplClearUndo(UndoManagerGuard & i_guard)514 void SfxUndoManager::ImplClearUndo( UndoManagerGuard& i_guard )
515 {
516     while ( m_xData->pActUndoArray->nCurUndoAction > 0 )
517     {
518         i_guard.markForDeletion( m_xData->pActUndoArray->Remove( 0 ) );
519         --m_xData->pActUndoArray->nCurUndoAction;
520     }
521     ImplCheckEmptyActions();
522     // TODO: notifications? We don't have clearedUndo, only cleared and clearedRedo at the SfxUndoListener
523 }
524 
525 
ImplClearRedo(UndoManagerGuard & i_guard,bool const i_currentLevel)526 void SfxUndoManager::ImplClearRedo( UndoManagerGuard& i_guard, bool const i_currentLevel )
527 {
528     SfxUndoArray* pUndoArray = ( i_currentLevel == SfxUndoManager::CurrentLevel ) ? m_xData->pActUndoArray : &m_xData->maUndoArray;
529 
530     // clearance
531     while ( pUndoArray->maUndoActions.size() > pUndoArray->nCurUndoAction )
532     {
533         size_t deletePos = pUndoArray->maUndoActions.size() - 1;
534         i_guard.markForDeletion( pUndoArray->Remove( deletePos ) );
535     }
536 
537     ImplCheckEmptyActions();
538     // notification - only if the top level's stack was cleared
539     if ( i_currentLevel == SfxUndoManager::TopLevel )
540         i_guard.scheduleNotification( &SfxUndoListener::clearedRedo );
541 }
542 
543 
ImplAddUndoAction_NoNotify(std::unique_ptr<SfxUndoAction> pAction,bool bTryMerge,bool bClearRedo,UndoManagerGuard & i_guard)544 bool SfxUndoManager::ImplAddUndoAction_NoNotify( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge, bool bClearRedo, UndoManagerGuard& i_guard )
545 {
546     if ( !ImplIsUndoEnabled_Lock() || ( m_xData->pActUndoArray->nMaxUndoActions == 0 ) )
547     {
548         i_guard.markForDeletion( std::move(pAction) );
549         return false;
550     }
551 
552     // merge, if required
553     SfxUndoAction* pMergeWithAction = m_xData->pActUndoArray->nCurUndoAction ?
554         m_xData->pActUndoArray->maUndoActions[m_xData->pActUndoArray->nCurUndoAction-1].pAction.get() : nullptr;
555     if ( bTryMerge && pMergeWithAction )
556     {
557         bool bMerged = pMergeWithAction->Merge( pAction.get() );
558         if ( bMerged )
559         {
560             i_guard.markForDeletion( std::move(pAction) );
561             return false;
562         }
563     }
564 
565     // clear redo stack, if requested
566     if ( bClearRedo && ( ImplGetRedoActionCount_Lock() > 0 ) )
567         ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
568 
569     // respect max number
570     if( m_xData->pActUndoArray == &m_xData->maUndoArray )
571     {
572         while(m_xData->pActUndoArray->maUndoActions.size() >= m_xData->pActUndoArray->nMaxUndoActions)
573         {
574             i_guard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
575             if (m_xData->pActUndoArray->nCurUndoAction > 0)
576             {
577                 --m_xData->pActUndoArray->nCurUndoAction;
578             }
579             else
580             {
581                 assert(!"CurrentUndoAction going negative (!)");
582             }
583             // fdo#66071 invalidate the current empty mark when removing
584             --m_xData->mnEmptyMark;
585         }
586     }
587 
588     // append new action
589     m_xData->pActUndoArray->Insert( std::move(pAction), m_xData->pActUndoArray->nCurUndoAction++ );
590     ImplCheckEmptyActions();
591     return true;
592 }
593 
594 
AddUndoAction(std::unique_ptr<SfxUndoAction> pAction,bool bTryMerge)595 void SfxUndoManager::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge )
596 {
597     UndoManagerGuard aGuard( *m_xData );
598 
599     // add
600     auto pActionTmp = pAction.get();
601     if ( ImplAddUndoAction_NoNotify( std::move(pAction), bTryMerge, true, aGuard ) )
602     {
603         // notify listeners
604         aGuard.scheduleNotification( &SfxUndoListener::undoActionAdded, pActionTmp->GetComment() );
605     }
606 }
607 
608 
GetUndoActionCount(bool const i_currentLevel) const609 size_t SfxUndoManager::GetUndoActionCount( bool const i_currentLevel ) const
610 {
611     UndoManagerGuard aGuard( *m_xData );
612     const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
613     return pUndoArray->nCurUndoAction;
614 }
615 
616 
GetUndoActionComment(size_t nNo,bool const i_currentLevel) const617 OUString SfxUndoManager::GetUndoActionComment( size_t nNo, bool const i_currentLevel ) const
618 {
619     UndoManagerGuard aGuard( *m_xData );
620 
621     OUString sComment;
622     const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
623     assert(nNo < pUndoArray->nCurUndoAction);
624     if( nNo < pUndoArray->nCurUndoAction )
625         sComment = pUndoArray->maUndoActions[ pUndoArray->nCurUndoAction - 1 - nNo ].pAction->GetComment();
626     return sComment;
627 }
628 
629 
GetUndoAction(size_t nNo) const630 SfxUndoAction* SfxUndoManager::GetUndoAction( size_t nNo ) const
631 {
632     UndoManagerGuard aGuard( *m_xData );
633 
634     assert(nNo < m_xData->pActUndoArray->nCurUndoAction);
635     if( nNo >= m_xData->pActUndoArray->nCurUndoAction )
636         return nullptr;
637     return m_xData->pActUndoArray->maUndoActions[m_xData->pActUndoArray->nCurUndoAction-1-nNo].pAction.get();
638 }
639 
640 
641 /** clears the redo stack and removes the top undo action */
RemoveLastUndoAction()642 void SfxUndoManager::RemoveLastUndoAction()
643 {
644     UndoManagerGuard aGuard( *m_xData );
645 
646     ENSURE_OR_RETURN_VOID( m_xData->pActUndoArray->nCurUndoAction, "svl::SfxUndoManager::RemoveLastUndoAction(), no action to remove?!" );
647 
648     m_xData->pActUndoArray->nCurUndoAction--;
649 
650     // delete redo-actions and top action
651     for ( size_t nPos = m_xData->pActUndoArray->maUndoActions.size(); nPos > m_xData->pActUndoArray->nCurUndoAction; --nPos )
652     {
653         aGuard.markForDeletion( std::move(m_xData->pActUndoArray->maUndoActions[nPos-1].pAction) );
654     }
655 
656     m_xData->pActUndoArray->Remove(
657         m_xData->pActUndoArray->nCurUndoAction,
658         m_xData->pActUndoArray->maUndoActions.size() - m_xData->pActUndoArray->nCurUndoAction );
659     ImplCheckEmptyActions();
660 }
661 
662 
IsDoing() const663 bool SfxUndoManager::IsDoing() const
664 {
665     UndoManagerGuard aGuard( *m_xData );
666     return m_xData->mbDoing;
667 }
668 
669 
Undo()670 bool SfxUndoManager::Undo()
671 {
672     return ImplUndo( nullptr );
673 }
674 
675 
UndoWithContext(SfxUndoContext & i_context)676 bool SfxUndoManager::UndoWithContext( SfxUndoContext& i_context )
677 {
678     return ImplUndo( &i_context );
679 }
680 
681 
ImplUndo(SfxUndoContext * i_contextOrNull)682 bool SfxUndoManager::ImplUndo( SfxUndoContext* i_contextOrNull )
683 {
684     UndoManagerGuard aGuard( *m_xData );
685     assert( !IsDoing() && "SfxUndoManager::Undo: *nested* Undo/Redo actions? How this?" );
686 
687     ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
688     LockGuard aLockGuard( *this );
689 
690     if ( ImplIsInListAction_Lock() )
691     {
692         assert(!"SfxUndoManager::Undo: not possible when within a list action!");
693         return false;
694     }
695 
696     if ( m_xData->pActUndoArray->nCurUndoAction == 0 )
697     {
698         SAL_WARN("svl", "SfxUndoManager::Undo: undo stack is empty!" );
699         return false;
700     }
701 
702     if (i_contextOrNull && i_contextOrNull->GetUndoOffset() > 0)
703     {
704         size_t nCurrent = m_xData->pActUndoArray->nCurUndoAction;
705         size_t nOffset = i_contextOrNull->GetUndoOffset();
706         if (nCurrent >= nOffset + 1)
707         {
708             // Move the action we want to execute to the top of the undo stack.
709             // data() + nCurrent - nOffset - 1 is the start, data() + nCurrent - nOffset is what we
710             // want to move to the top, maUndoActions.data() + nCurrent is past the end/top of the
711             // undo stack.
712             std::rotate(m_xData->pActUndoArray->maUndoActions.data() + nCurrent - nOffset - 1,
713                         m_xData->pActUndoArray->maUndoActions.data() + nCurrent - nOffset,
714                         m_xData->pActUndoArray->maUndoActions.data() + nCurrent);
715         }
716     }
717 
718     SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions[ --m_xData->pActUndoArray->nCurUndoAction ].pAction.get();
719     const OUString sActionComment = pAction->GetComment();
720     try
721     {
722         // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
723         // nowadays ...
724         auto aResetGuard(aGuard.clear());
725         if ( i_contextOrNull != nullptr )
726             pAction->UndoWithContext( *i_contextOrNull );
727         else
728             pAction->Undo();
729     }
730     catch( ... )
731     {
732         // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
733         // we still find pAction in our current Undo array
734         size_t nCurAction = 0;
735         while ( nCurAction < m_xData->pActUndoArray->maUndoActions.size() )
736         {
737             if ( m_xData->pActUndoArray->maUndoActions[ nCurAction++ ].pAction.get() == pAction )
738             {
739                 // the Undo action is still there ...
740                 // assume the error is a permanent failure, and clear the Undo stack
741                 ImplClearUndo( aGuard );
742                 throw;
743             }
744         }
745         SAL_WARN("svl", "SfxUndoManager::Undo: can't clear the Undo stack after the failure - some other party was faster ..." );
746         throw;
747     }
748 
749     aGuard.scheduleNotification( &SfxUndoListener::actionUndone, sActionComment );
750 
751     return true;
752 }
753 
754 
GetRedoActionCount(bool const i_currentLevel) const755 size_t SfxUndoManager::GetRedoActionCount( bool const i_currentLevel ) const
756 {
757     UndoManagerGuard aGuard( *m_xData );
758     return ImplGetRedoActionCount_Lock( i_currentLevel );
759 }
760 
761 
ImplGetRedoActionCount_Lock(bool const i_currentLevel) const762 size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel ) const
763 {
764     const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
765     return pUndoArray->maUndoActions.size() - pUndoArray->nCurUndoAction;
766 }
767 
768 
GetRedoAction(size_t nNo) const769 SfxUndoAction* SfxUndoManager::GetRedoAction(size_t nNo) const
770 {
771     UndoManagerGuard aGuard( *m_xData );
772 
773     const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
774     if ( (pUndoArray->nCurUndoAction) > pUndoArray->maUndoActions.size() )
775     {
776         return nullptr;
777     }
778     return pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + nNo].pAction.get();
779 }
780 
781 
GetRedoActionComment(size_t nNo,bool const i_currentLevel) const782 OUString SfxUndoManager::GetRedoActionComment( size_t nNo, bool const i_currentLevel ) const
783 {
784     OUString sComment;
785     UndoManagerGuard aGuard( *m_xData );
786     const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
787     if ( (pUndoArray->nCurUndoAction + nNo) < pUndoArray->maUndoActions.size() )
788     {
789         sComment = pUndoArray->maUndoActions[ pUndoArray->nCurUndoAction + nNo ].pAction->GetComment();
790     }
791     return sComment;
792 }
793 
794 
Redo()795 bool SfxUndoManager::Redo()
796 {
797     return ImplRedo( nullptr );
798 }
799 
800 
RedoWithContext(SfxUndoContext & i_context)801 bool SfxUndoManager::RedoWithContext( SfxUndoContext& i_context )
802 {
803     return ImplRedo( &i_context );
804 }
805 
806 
ImplRedo(SfxUndoContext * i_contextOrNull)807 bool SfxUndoManager::ImplRedo( SfxUndoContext* i_contextOrNull )
808 {
809     UndoManagerGuard aGuard( *m_xData );
810     assert( !IsDoing() && "SfxUndoManager::Redo: *nested* Undo/Redo actions? How this?" );
811 
812     ::comphelper::FlagGuard aDoingGuard( m_xData->mbDoing );
813     LockGuard aLockGuard( *this );
814 
815     if ( ImplIsInListAction_Lock() )
816     {
817         assert(!"SfxUndoManager::Redo: not possible when within a list action!");
818         return false;
819     }
820 
821     if ( m_xData->pActUndoArray->nCurUndoAction >= m_xData->pActUndoArray->maUndoActions.size() )
822     {
823         SAL_WARN("svl", "SfxUndoManager::Redo: redo stack is empty!");
824         return false;
825     }
826 
827     SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->nCurUndoAction++ ].pAction.get();
828     const OUString sActionComment = pAction->GetComment();
829     try
830     {
831         // clear the guard/mutex before calling into the SfxUndoAction - this can be an extension-implemented UNO component
832         // nowadays ...
833         auto aResetGuard(aGuard.clear());
834         if ( i_contextOrNull != nullptr )
835             pAction->RedoWithContext( *i_contextOrNull );
836         else
837             pAction->Redo();
838     }
839     catch( ... )
840     {
841         // in theory, somebody might have tampered with all of *m_xData while the mutex was unlocked. So, see if
842         // we still find pAction in our current Undo array
843         size_t nCurAction = 0;
844         while ( nCurAction < m_xData->pActUndoArray->maUndoActions.size() )
845         {
846             if ( m_xData->pActUndoArray->maUndoActions[ nCurAction ].pAction.get() == pAction )
847             {
848                 // the Undo action is still there ...
849                 // assume the error is a permanent failure, and clear the Undo stack
850                 ImplClearRedo( aGuard, SfxUndoManager::CurrentLevel );
851                 throw;
852             }
853             ++nCurAction;
854         }
855         SAL_WARN("svl", "SfxUndoManager::Redo: can't clear the Undo stack after the failure - some other party was faster ..." );
856         throw;
857     }
858 
859     ImplCheckEmptyActions();
860     aGuard.scheduleNotification( &SfxUndoListener::actionRedone, sActionComment );
861 
862     return true;
863 }
864 
865 
GetRepeatActionCount() const866 size_t SfxUndoManager::GetRepeatActionCount() const
867 {
868     UndoManagerGuard aGuard( *m_xData );
869     return m_xData->pActUndoArray->maUndoActions.size();
870 }
871 
872 
GetRepeatActionComment(SfxRepeatTarget & rTarget) const873 OUString SfxUndoManager::GetRepeatActionComment(SfxRepeatTarget &rTarget) const
874 {
875     UndoManagerGuard aGuard( *m_xData );
876     return m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->maUndoActions.size() - 1 ].pAction
877         ->GetRepeatComment(rTarget);
878 }
879 
880 
Repeat(SfxRepeatTarget & rTarget)881 bool SfxUndoManager::Repeat( SfxRepeatTarget &rTarget )
882 {
883     UndoManagerGuard aGuard( *m_xData );
884     if ( !m_xData->pActUndoArray->maUndoActions.empty() )
885     {
886         SfxUndoAction* pAction = m_xData->pActUndoArray->maUndoActions.back().pAction.get();
887         auto aResetGuard(aGuard.clear());
888         if ( pAction->CanRepeat( rTarget ) )
889             pAction->Repeat( rTarget );
890         return true;
891     }
892 
893     return false;
894 }
895 
896 
CanRepeat(SfxRepeatTarget & rTarget) const897 bool SfxUndoManager::CanRepeat( SfxRepeatTarget &rTarget ) const
898 {
899     UndoManagerGuard aGuard( *m_xData );
900     if ( !m_xData->pActUndoArray->maUndoActions.empty() )
901     {
902         size_t nActionNo = m_xData->pActUndoArray->maUndoActions.size() - 1;
903         return m_xData->pActUndoArray->maUndoActions[nActionNo].pAction->CanRepeat(rTarget);
904     }
905     return false;
906 }
907 
908 
AddUndoListener(SfxUndoListener & i_listener)909 void SfxUndoManager::AddUndoListener( SfxUndoListener& i_listener )
910 {
911     UndoManagerGuard aGuard( *m_xData );
912     m_xData->aListeners.push_back( &i_listener );
913 }
914 
915 
RemoveUndoListener(SfxUndoListener & i_listener)916 void SfxUndoManager::RemoveUndoListener( SfxUndoListener& i_listener )
917 {
918     UndoManagerGuard aGuard( *m_xData );
919     auto lookup = std::find(m_xData->aListeners.begin(), m_xData->aListeners.end(), &i_listener);
920     if (lookup != m_xData->aListeners.end())
921         m_xData->aListeners.erase( lookup );
922 }
923 
924 /**
925  * Inserts a ListUndoAction and sets its UndoArray as current.
926  */
EnterListAction(const OUString & rComment,const OUString & rRepeatComment,sal_uInt16 nId,ViewShellId nViewShellId)927 void SfxUndoManager::EnterListAction( const OUString& rComment,
928                                       const OUString &rRepeatComment, sal_uInt16 nId,
929                                       ViewShellId nViewShellId )
930 {
931     UndoManagerGuard aGuard( *m_xData );
932 
933     if( !ImplIsUndoEnabled_Lock() )
934         return;
935 
936     if ( !m_xData->maUndoArray.nMaxUndoActions )
937         return;
938 
939     SfxListUndoAction* pAction = new SfxListUndoAction( rComment, rRepeatComment, nId, nViewShellId, m_xData->pActUndoArray );
940     OSL_VERIFY( ImplAddUndoAction_NoNotify( std::unique_ptr<SfxUndoAction>(pAction), false, false, aGuard ) );
941     // expected to succeed: all conditions under which it could fail should have been checked already
942     m_xData->pActUndoArray = pAction;
943 
944     // notification
945     aGuard.scheduleNotification( &SfxUndoListener::listActionEntered, rComment );
946 }
947 
948 
IsInListAction() const949 bool SfxUndoManager::IsInListAction() const
950 {
951     UndoManagerGuard aGuard( *m_xData );
952     return ImplIsInListAction_Lock();
953 }
954 
955 
ImplIsInListAction_Lock() const956 bool SfxUndoManager::ImplIsInListAction_Lock() const
957 {
958     return m_xData->pActUndoArray != &m_xData->maUndoArray;
959 }
960 
961 
GetListActionDepth() const962 size_t SfxUndoManager::GetListActionDepth() const
963 {
964     UndoManagerGuard aGuard( *m_xData );
965     size_t nDepth(0);
966 
967     SfxUndoArray* pLookup( m_xData->pActUndoArray );
968     while ( pLookup != &m_xData->maUndoArray )
969     {
970         pLookup = pLookup->pFatherUndoArray;
971         ++nDepth;
972     }
973 
974     return nDepth;
975 }
976 
977 
LeaveListAction()978 size_t SfxUndoManager::LeaveListAction()
979 {
980     UndoManagerGuard aGuard( *m_xData );
981     size_t nCount = ImplLeaveListAction( false, aGuard );
982 
983     if ( m_xData->mbClearUntilTopLevel )
984     {
985         ImplClearCurrentLevel_NoNotify( aGuard );
986         if ( !ImplIsInListAction_Lock() )
987         {
988             m_xData->mbClearUntilTopLevel = false;
989             aGuard.scheduleNotification( &SfxUndoListener::cleared );
990         }
991         nCount = 0;
992     }
993 
994     return nCount;
995 }
996 
997 
LeaveAndMergeListAction()998 size_t SfxUndoManager::LeaveAndMergeListAction()
999 {
1000     UndoManagerGuard aGuard( *m_xData );
1001     return ImplLeaveListAction( true, aGuard );
1002 }
1003 
1004 
ImplLeaveListAction(const bool i_merge,UndoManagerGuard & i_guard)1005 size_t SfxUndoManager::ImplLeaveListAction( const bool i_merge, UndoManagerGuard& i_guard )
1006 {
1007     if ( !ImplIsUndoEnabled_Lock() )
1008         return 0;
1009 
1010     if ( !m_xData->maUndoArray.nMaxUndoActions )
1011         return 0;
1012 
1013     if( !ImplIsInListAction_Lock() )
1014     {
1015         SAL_WARN("svl", "svl::SfxUndoManager::ImplLeaveListAction, called without calling EnterListAction()!" );
1016         return 0;
1017     }
1018 
1019     assert(m_xData->pActUndoArray->pFatherUndoArray);
1020 
1021     // the array/level which we're about to leave
1022     SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
1023     // one step up
1024     m_xData->pActUndoArray = m_xData->pActUndoArray->pFatherUndoArray;
1025 
1026     // If no undo actions were added to the list, delete the list action
1027     const size_t nListActionElements = pArrayToLeave->nCurUndoAction;
1028     if ( nListActionElements == 0 )
1029     {
1030         i_guard.markForDeletion( m_xData->pActUndoArray->Remove( --m_xData->pActUndoArray->nCurUndoAction ) );
1031         i_guard.scheduleNotification( &SfxUndoListener::listActionCancelled );
1032         return 0;
1033     }
1034 
1035     // now that it is finally clear the list action is non-trivial, and does participate in the Undo stack, clear
1036     // the redo stack
1037     ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
1038 
1039     SfxUndoAction* pCurrentAction= m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->nCurUndoAction-1 ].pAction.get();
1040     SfxListUndoAction* pListAction = dynamic_cast< SfxListUndoAction * >( pCurrentAction );
1041     ENSURE_OR_RETURN( pListAction, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements );
1042 
1043     if ( i_merge )
1044     {
1045         // merge the list action with its predecessor on the same level
1046         SAL_WARN_IF( m_xData->pActUndoArray->nCurUndoAction <= 1, "svl",
1047             "SfxUndoManager::ImplLeaveListAction: cannot merge the list action if there's no other action on the same level - check this beforehand!" );
1048         if ( m_xData->pActUndoArray->nCurUndoAction > 1 )
1049         {
1050             std::unique_ptr<SfxUndoAction> pPreviousAction = m_xData->pActUndoArray->Remove( m_xData->pActUndoArray->nCurUndoAction - 2 );
1051             --m_xData->pActUndoArray->nCurUndoAction;
1052             pListAction->SetComment( pPreviousAction->GetComment() );
1053             pListAction->Insert( std::move(pPreviousAction), 0 );
1054             ++pListAction->nCurUndoAction;
1055         }
1056     }
1057 
1058     // if the undo array has no comment, try to get it from its children
1059     if ( pListAction->GetComment().isEmpty() )
1060     {
1061         for( size_t n = 0; n < pListAction->maUndoActions.size(); n++ )
1062         {
1063             if (!pListAction->maUndoActions[n].pAction->GetComment().isEmpty())
1064             {
1065                 pListAction->SetComment( pListAction->maUndoActions[n].pAction->GetComment() );
1066                 break;
1067             }
1068         }
1069     }
1070 
1071     ImplIsEmptyActions();
1072     // notify listeners
1073     i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
1074 
1075     // outta here
1076     return nListActionElements;
1077 }
1078 
MarkTopUndoAction()1079 UndoStackMark SfxUndoManager::MarkTopUndoAction()
1080 {
1081     UndoManagerGuard aGuard( *m_xData );
1082 
1083     SAL_WARN_IF( IsInListAction(), "svl",
1084             "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
1085     assert((m_xData->mnMarks + 1) < (m_xData->mnEmptyMark - 1) &&
1086             "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
1087 
1088     size_t const nActionPos = m_xData->maUndoArray.nCurUndoAction;
1089     if (0 == nActionPos)
1090     {
1091         --m_xData->mnEmptyMark;
1092         return m_xData->mnEmptyMark;
1093     }
1094 
1095     m_xData->maUndoArray.maUndoActions[ nActionPos-1 ].aMarks.push_back(
1096             ++m_xData->mnMarks );
1097     return m_xData->mnMarks;
1098 }
1099 
RemoveMark(UndoStackMark i_mark)1100 size_t SfxUndoManager::RemoveMark(UndoStackMark i_mark)
1101 {
1102     UndoManagerGuard aGuard( *m_xData );
1103 
1104     if ((m_xData->mnEmptyMark < i_mark) || (MARK_INVALID == i_mark))
1105     {
1106         return std::numeric_limits<size_t>::max(); // nothing to remove
1107     }
1108     else if (i_mark == m_xData->mnEmptyMark)
1109     {
1110         --m_xData->mnEmptyMark; // never returned from MarkTop => invalid
1111         return std::numeric_limits<size_t>::max();
1112     }
1113 
1114     for ( size_t i=0; i<m_xData->maUndoArray.maUndoActions.size(); ++i )
1115     {
1116         MarkedUndoAction& rAction = m_xData->maUndoArray.maUndoActions[i];
1117         auto markPos = std::find(rAction.aMarks.begin(), rAction.aMarks.end(), i_mark);
1118         if (markPos != rAction.aMarks.end())
1119         {
1120             rAction.aMarks.erase( markPos );
1121             return i;
1122         }
1123     }
1124     SAL_WARN("svl", "SfxUndoManager::RemoveMark: mark not found!");
1125         // TODO: this might be too offensive. There are situations where we implicitly remove marks
1126         // without our clients, in particular the client which created the mark, having a chance to know
1127         // about this.
1128 
1129     return std::numeric_limits<size_t>::max();
1130 }
1131 
HasTopUndoActionMark(UndoStackMark const i_mark)1132 bool SfxUndoManager::HasTopUndoActionMark( UndoStackMark const i_mark )
1133 {
1134     UndoManagerGuard aGuard( *m_xData );
1135 
1136     size_t nActionPos = m_xData->maUndoArray.nCurUndoAction;
1137     if ( nActionPos == 0 )
1138     {
1139         return (i_mark == m_xData->mnEmptyMark);
1140     }
1141 
1142     const MarkedUndoAction& rAction =
1143             m_xData->maUndoArray.maUndoActions[ nActionPos-1 ];
1144 
1145     return std::find(rAction.aMarks.begin(), rAction.aMarks.end(), i_mark) != rAction.aMarks.end();
1146 }
1147 
1148 
UndoMark(UndoStackMark i_mark)1149 void SfxUndoManager::UndoMark(UndoStackMark i_mark)
1150 {
1151     SfxMarkedUndoContext context(*this, i_mark); // Removes the mark
1152     if (context.GetUndoOffset() == std::numeric_limits<size_t>::max())
1153         return; // nothing to undo
1154 
1155     UndoWithContext(context);
1156 }
1157 
1158 
RemoveOldestUndoAction()1159 void SfxUndoManager::RemoveOldestUndoAction()
1160 {
1161     UndoManagerGuard aGuard( *m_xData );
1162 
1163     if ( IsInListAction() && ( m_xData->maUndoArray.nCurUndoAction == 1 ) )
1164     {
1165         assert(!"SfxUndoManager::RemoveOldestUndoActions: cannot remove a not-yet-closed list action!");
1166         return;
1167     }
1168 
1169     aGuard.markForDeletion( m_xData->maUndoArray.Remove( 0 ) );
1170     --m_xData->maUndoArray.nCurUndoAction;
1171     ImplCheckEmptyActions();
1172 }
1173 
dumpAsXml(xmlTextWriterPtr pWriter) const1174 void SfxUndoManager::dumpAsXml(xmlTextWriterPtr pWriter) const
1175 {
1176     UndoManagerGuard aGuard(*m_xData);
1177 
1178     bool bOwns = false;
1179     if (!pWriter)
1180     {
1181         pWriter = xmlNewTextWriterFilename("undo.xml", 0);
1182         xmlTextWriterSetIndent(pWriter,1);
1183         (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST("  "));
1184         (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
1185         bOwns = true;
1186     }
1187 
1188     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxUndoManager"));
1189     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nUndoActionCount"), BAD_CAST(OString::number(GetUndoActionCount()).getStr()));
1190     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("nRedoActionCount"), BAD_CAST(OString::number(GetRedoActionCount()).getStr()));
1191 
1192     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("undoActions"));
1193     for (size_t i = 0; i < GetUndoActionCount(); ++i)
1194     {
1195         const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
1196         pUndoArray->maUndoActions[pUndoArray->nCurUndoAction - 1 - i].pAction->dumpAsXml(pWriter);
1197     }
1198     (void)xmlTextWriterEndElement(pWriter);
1199 
1200     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("redoActions"));
1201     for (size_t i = 0; i < GetRedoActionCount(); ++i)
1202     {
1203         const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
1204         pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + i].pAction->dumpAsXml(pWriter);
1205     }
1206     (void)xmlTextWriterEndElement(pWriter);
1207 
1208     (void)xmlTextWriterEndElement(pWriter);
1209     if (bOwns)
1210     {
1211         (void)xmlTextWriterEndDocument(pWriter);
1212         xmlFreeTextWriter(pWriter);
1213     }
1214 }
1215 
1216 /// Returns a JSON representation of pAction.
lcl_ActionToJson(size_t nIndex,SfxUndoAction const * pAction)1217 static boost::property_tree::ptree lcl_ActionToJson(size_t nIndex, SfxUndoAction const * pAction)
1218 {
1219     boost::property_tree::ptree aRet;
1220     aRet.put("index", nIndex);
1221     aRet.put("comment", pAction->GetComment().toUtf8().getStr());
1222     aRet.put("viewId", static_cast<sal_Int32>(pAction->GetViewShellId()));
1223     aRet.put("dateTime", utl::toISO8601(pAction->GetDateTime().GetUNODateTime()).toUtf8().getStr());
1224     return aRet;
1225 }
1226 
GetUndoActionsInfo() const1227 OUString SfxUndoManager::GetUndoActionsInfo() const
1228 {
1229     boost::property_tree::ptree aActions;
1230     const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
1231     for (size_t i = 0; i < GetUndoActionCount(); ++i)
1232     {
1233         boost::property_tree::ptree aAction = lcl_ActionToJson(i, pUndoArray->maUndoActions[pUndoArray->nCurUndoAction - 1 - i].pAction.get());
1234         aActions.push_back(std::make_pair("", aAction));
1235     }
1236 
1237     boost::property_tree::ptree aTree;
1238     aTree.add_child("actions", aActions);
1239     std::stringstream aStream;
1240     boost::property_tree::write_json(aStream, aTree);
1241     return OUString::fromUtf8(aStream.str());
1242 }
1243 
GetRedoActionsInfo() const1244 OUString SfxUndoManager::GetRedoActionsInfo() const
1245 {
1246     boost::property_tree::ptree aActions;
1247     const SfxUndoArray* pUndoArray = m_xData->pActUndoArray;
1248     size_t nCount = GetRedoActionCount();
1249     for (size_t i = 0; i < nCount; ++i)
1250     {
1251         size_t nIndex = nCount - i - 1;
1252         boost::property_tree::ptree aAction = lcl_ActionToJson(nIndex, pUndoArray->maUndoActions[pUndoArray->nCurUndoAction + nIndex].pAction.get());
1253         aActions.push_back(std::make_pair("", aAction));
1254     }
1255 
1256     boost::property_tree::ptree aTree;
1257     aTree.add_child("actions", aActions);
1258     std::stringstream aStream;
1259     boost::property_tree::write_json(aStream, aTree);
1260     return OUString::fromUtf8(aStream.str());
1261 }
1262 
IsEmptyActions() const1263 bool SfxUndoManager::IsEmptyActions() const
1264 {
1265     UndoManagerGuard aGuard(*m_xData);
1266 
1267     return ImplIsEmptyActions();
1268 }
1269 
ImplIsEmptyActions() const1270 inline bool SfxUndoManager::ImplIsEmptyActions() const
1271 {
1272     return m_xData->maUndoArray.nCurUndoAction || m_xData->maUndoArray.maUndoActions.size() - m_xData->maUndoArray.nCurUndoAction;
1273 }
1274 
ImplCheckEmptyActions()1275 void SfxUndoManager::ImplCheckEmptyActions()
1276 {
1277     bool bEmptyActions = ImplIsEmptyActions();
1278     if (m_xData->mbEmptyActions != bEmptyActions)
1279     {
1280         m_xData->mbEmptyActions = bEmptyActions;
1281         EmptyActionsChanged();
1282     }
1283 }
1284 
EmptyActionsChanged()1285 void SfxUndoManager::EmptyActionsChanged()
1286 {
1287 
1288 }
1289 
1290 struct SfxListUndoAction::Impl
1291 {
1292     sal_uInt16 mnId;
1293     ViewShellId mnViewShellId;
1294 
1295     OUString maComment;
1296     OUString maRepeatComment;
1297 
ImplSfxListUndoAction::Impl1298     Impl( sal_uInt16 nId, ViewShellId nViewShellId, OUString aComment, OUString aRepeatComment ) :
1299         mnId(nId), mnViewShellId(nViewShellId), maComment(std::move(aComment)), maRepeatComment(std::move(aRepeatComment)) {}
1300 };
1301 
GetId() const1302 sal_uInt16 SfxListUndoAction::GetId() const
1303 {
1304     return mpImpl->mnId;
1305 }
1306 
GetComment() const1307 OUString SfxListUndoAction::GetComment() const
1308 {
1309     return mpImpl->maComment;
1310 }
1311 
GetViewShellId() const1312 ViewShellId SfxListUndoAction::GetViewShellId() const
1313 {
1314     return mpImpl->mnViewShellId;
1315 }
1316 
SetComment(const OUString & rComment)1317 void SfxListUndoAction::SetComment(const OUString& rComment)
1318 {
1319     mpImpl->maComment = rComment;
1320 }
1321 
GetRepeatComment(SfxRepeatTarget &) const1322 OUString SfxListUndoAction::GetRepeatComment(SfxRepeatTarget &) const
1323 {
1324     return mpImpl->maRepeatComment;
1325 }
1326 
SfxListUndoAction(const OUString & rComment,const OUString & rRepeatComment,sal_uInt16 nId,ViewShellId nViewShellId,SfxUndoArray * pFather)1327 SfxListUndoAction::SfxListUndoAction(
1328     const OUString &rComment,
1329     const OUString &rRepeatComment,
1330     sal_uInt16 nId,
1331     ViewShellId nViewShellId,
1332     SfxUndoArray *pFather ) :
1333     mpImpl(new Impl(nId, nViewShellId, rComment, rRepeatComment))
1334 {
1335     pFatherUndoArray = pFather;
1336     nMaxUndoActions = USHRT_MAX;
1337 }
1338 
~SfxListUndoAction()1339 SfxListUndoAction::~SfxListUndoAction()
1340 {
1341 }
1342 
Undo()1343 void SfxListUndoAction::Undo()
1344 {
1345     for(size_t i=nCurUndoAction;i>0;)
1346         maUndoActions[--i].pAction->Undo();
1347     nCurUndoAction=0;
1348 }
1349 
1350 
UndoWithContext(SfxUndoContext & i_context)1351 void SfxListUndoAction::UndoWithContext( SfxUndoContext& i_context )
1352 {
1353     for(size_t i=nCurUndoAction;i>0;)
1354         maUndoActions[--i].pAction->UndoWithContext( i_context );
1355     nCurUndoAction=0;
1356 }
1357 
1358 
Redo()1359 void SfxListUndoAction::Redo()
1360 {
1361     for(size_t i=nCurUndoAction;i<maUndoActions.size();i++)
1362         maUndoActions[i].pAction->Redo();
1363     nCurUndoAction = maUndoActions.size();
1364 }
1365 
1366 
RedoWithContext(SfxUndoContext & i_context)1367 void SfxListUndoAction::RedoWithContext( SfxUndoContext& i_context )
1368 {
1369     for(size_t i=nCurUndoAction;i<maUndoActions.size();i++)
1370         maUndoActions[i].pAction->RedoWithContext( i_context );
1371     nCurUndoAction = maUndoActions.size();
1372 }
1373 
1374 
Repeat(SfxRepeatTarget & rTarget)1375 void SfxListUndoAction::Repeat(SfxRepeatTarget&rTarget)
1376 {
1377     for(size_t i=0;i<nCurUndoAction;i++)
1378         maUndoActions[i].pAction->Repeat(rTarget);
1379 }
1380 
1381 
CanRepeat(SfxRepeatTarget & r) const1382 bool SfxListUndoAction::CanRepeat(SfxRepeatTarget&r)  const
1383 {
1384     for(size_t i=0;i<nCurUndoAction;i++)
1385     {
1386         if(!maUndoActions[i].pAction->CanRepeat(r))
1387             return false;
1388     }
1389     return true;
1390 }
1391 
1392 
Merge(SfxUndoAction * pNextAction)1393 bool SfxListUndoAction::Merge( SfxUndoAction *pNextAction )
1394 {
1395     return !maUndoActions.empty() && maUndoActions[maUndoActions.size()-1].pAction->Merge( pNextAction );
1396 }
1397 
dumpAsXml(xmlTextWriterPtr pWriter) const1398 void SfxListUndoAction::dumpAsXml(xmlTextWriterPtr pWriter) const
1399 {
1400     (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxListUndoAction"));
1401     (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("size"), BAD_CAST(OString::number(maUndoActions.size()).getStr()));
1402     SfxUndoAction::dumpAsXml(pWriter);
1403 
1404     for (size_t i = 0; i < maUndoActions.size(); ++i)
1405         maUndoActions[i].pAction->dumpAsXml(pWriter);
1406 
1407     (void)xmlTextWriterEndElement(pWriter);
1408 }
1409 
~SfxUndoArray()1410 SfxUndoArray::~SfxUndoArray()
1411 {
1412 }
1413 
1414 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
1415