Coverage Report

Created: 2026-08-14 10:22

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