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