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