Coverage Report

Created: 2026-07-10 11:04

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
133k
{
62
133k
}
63
64
65
SfxUndoContext::~SfxUndoContext()
66
0
{
67
0
}
68
69
70
SfxUndoAction::~SfxUndoAction() COVERITY_NOEXCEPT_FALSE
71
4.56M
{
72
4.56M
}
73
74
75
SfxUndoAction::SfxUndoAction()
76
4.56M
    : m_aDateTime(DateTime::SYSTEM)
77
4.56M
{
78
4.56M
    m_aDateTime.ConvertToUTC();
79
4.56M
}
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.38M
{
171
3.38M
    auto ret = std::move(maUndoActions[idx].pAction);
172
3.38M
    maUndoActions.erase(maUndoActions.begin() + idx);
173
3.38M
    return ret;
174
3.38M
}
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.54M
{
183
4.54M
    maUndoActions.insert( maUndoActions.begin() + i_pos, MarkedUndoAction(std::move(i_action)) );
184
4.54M
}
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
563k
        :maUndoArray( i_nMaxUndoActionCount )
206
563k
        ,pActUndoArray( nullptr )
207
563k
        ,mnMarks( 0 )
208
563k
        ,mnEmptyMark(MARK_INVALID)
209
563k
        ,mbUndoEnabled( true )
210
563k
        ,mbDoing( false )
211
563k
        ,mbClearUntilTopLevel( false )
212
563k
        ,mbEmptyActions( true )
213
563k
    {
214
563k
        pActUndoArray = &maUndoArray;
215
563k
    }
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.5M
            :m_notificationMethod( i_notificationMethod )
251
16.5M
            ,m_altNotificationMethod( nullptr )
252
16.5M
        {
253
16.5M
        }
254
255
        NotifyUndoListener( UndoListenerStringMethod i_notificationMethod, OUString i_actionComment )
256
5.53M
            :m_notificationMethod( nullptr )
257
5.53M
            ,m_altNotificationMethod( i_notificationMethod )
258
5.53M
            ,m_sActionComment(std::move( i_actionComment ))
259
5.53M
        {
260
5.53M
        }
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
72.3M
            :m_rManagerData( i_managerData )
293
72.3M
            ,m_aGuard( i_managerData.aMutex )
294
72.3M
        {
295
72.3M
        }
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.38M
        {
313
            // remember
314
3.38M
            assert ( i_action );
315
3.38M
            m_aUndoActionsCleanup.emplace_back( std::move(i_action) );
316
3.38M
        }
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.5M
        {
325
16.5M
            m_notifiers.emplace_back( i_notificationMethod );
326
16.5M
        }
327
328
        void    scheduleNotification( UndoListenerStringMethod i_notificationMethod, const OUString& i_actionComment )
329
5.53M
        {
330
5.53M
            m_notifiers.emplace_back( i_notificationMethod, i_actionComment );
331
5.53M
        }
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
72.3M
    {
342
        // copy members
343
72.3M
        UndoListeners aListenersCopy( m_rManagerData.aListeners );
344
345
        // release mutex
346
72.3M
        m_aGuard.clear();
347
348
        // delete all actions
349
72.3M
        m_aUndoActionsCleanup.clear();
350
351
        // handle scheduled notification
352
72.3M
        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
72.3M
    }
358
}
359
360
using namespace ::svl::undo::impl;
361
362
363
SfxUndoManager::SfxUndoManager( size_t nMaxUndoActionCount )
364
563k
    :m_xData( new SfxUndoManager_Data( nMaxUndoActionCount ) )
365
563k
{
366
563k
    m_xData->mbEmptyActions = !ImplIsEmptyActions();
367
563k
}
368
369
370
SfxUndoManager::~SfxUndoManager()
371
563k
{
372
563k
}
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.15M
{
432
2.15M
    UndoManagerGuard aGuard( *m_xData );
433
2.15M
    ImplEnableUndo_Lock( i_enable );
434
435
2.15M
}
436
437
438
void SfxUndoManager::ImplEnableUndo_Lock( bool const i_enable )
439
2.15M
{
440
2.15M
    if ( m_xData->mbUndoEnabled == i_enable )
441
2.04M
        return;
442
109k
    m_xData->mbUndoEnabled = i_enable;
443
109k
}
444
445
446
bool SfxUndoManager::IsUndoEnabled() const
447
46.9M
{
448
46.9M
    UndoManagerGuard aGuard( *m_xData );
449
46.9M
    return ImplIsUndoEnabled_Lock();
450
46.9M
}
451
452
453
bool SfxUndoManager::ImplIsUndoEnabled_Lock() const
454
57.0M
{
455
57.0M
    return m_xData->mbUndoEnabled;
456
57.0M
}
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
14.7M
{
499
    // clear array
500
15.9M
    while ( !m_xData->pActUndoArray->maUndoActions.empty() )
501
1.17M
    {
502
1.17M
        size_t deletePos = m_xData->pActUndoArray->maUndoActions.size() - 1;
503
1.17M
        i_guard.markForDeletion( m_xData->pActUndoArray->Remove( deletePos ) );
504
1.17M
    }
505
506
14.7M
    m_xData->pActUndoArray->nCurUndoAction = 0;
507
508
14.7M
    m_xData->mnMarks = 0;
509
14.7M
    m_xData->mnEmptyMark = MARK_INVALID;
510
14.7M
    ImplCheckEmptyActions();
511
14.7M
}
512
513
514
void SfxUndoManager::Clear()
515
14.7M
{
516
14.7M
    UndoManagerGuard aGuard( *m_xData );
517
518
14.7M
    SAL_WARN_IF( ImplIsInListAction_Lock(), "svl",
519
14.7M
        "SfxUndoManager::Clear: suspicious call - do you really wish to clear the current level?" );
520
14.7M
    ImplClearCurrentLevel_NoNotify( aGuard );
521
522
    // notify listeners
523
14.7M
    aGuard.scheduleNotification( &SfxUndoListener::cleared );
524
14.7M
}
525
526
527
void SfxUndoManager::ClearAllLevels()
528
68.9k
{
529
68.9k
    UndoManagerGuard aGuard( *m_xData );
530
68.9k
    ImplClearCurrentLevel_NoNotify( aGuard );
531
532
68.9k
    if ( ImplIsInListAction_Lock() )
533
0
    {
534
0
        m_xData->mbClearUntilTopLevel = true;
535
0
    }
536
68.9k
    else
537
68.9k
    {
538
68.9k
        aGuard.scheduleNotification( &SfxUndoListener::cleared );
539
68.9k
    }
540
68.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
986k
{
603
986k
    SfxUndoArray* pUndoArray = ( i_currentLevel == SfxUndoManager::CurrentLevel ) ? m_xData->pActUndoArray : &m_xData->maUndoArray;
604
605
    // clearance
606
986k
    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
986k
    ImplCheckEmptyActions();
613
    // notification - only if the top level's stack was cleared
614
986k
    if ( i_currentLevel == SfxUndoManager::TopLevel )
615
0
        i_guard.scheduleNotification( &SfxUndoListener::clearedRedo );
616
986k
}
617
618
619
bool SfxUndoManager::ImplAddUndoAction_NoNotify( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge, bool bClearRedo, UndoManagerGuard& i_guard )
620
4.54M
{
621
4.54M
    if ( !ImplIsUndoEnabled_Lock() || ( m_xData->pActUndoArray->nMaxUndoActions == 0 ) )
622
120
    {
623
120
        i_guard.markForDeletion( std::move(pAction) );
624
120
        return false;
625
120
    }
626
627
    // merge, if required
628
4.54M
    SfxUndoAction* pMergeWithAction = m_xData->pActUndoArray->nCurUndoAction ?
629
3.26M
        m_xData->pActUndoArray->maUndoActions[m_xData->pActUndoArray->nCurUndoAction-1].pAction.get() : nullptr;
630
4.54M
    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.54M
    if ( bClearRedo && ( ImplGetRedoActionCount_Lock() > 0 ) )
642
0
        ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
643
644
    // respect max number
645
4.54M
    if( m_xData->pActUndoArray == &m_xData->maUndoArray )
646
3.47M
    {
647
3.91M
        while(m_xData->pActUndoArray->maUndoActions.size() >= m_xData->pActUndoArray->nMaxUndoActions)
648
440k
        {
649
440k
            i_guard.markForDeletion( m_xData->pActUndoArray->Remove(0) );
650
440k
            if (m_xData->pActUndoArray->nCurUndoAction > 0)
651
440k
            {
652
440k
                --m_xData->pActUndoArray->nCurUndoAction;
653
                // fdo#66071 invalidate the current empty mark when removing
654
440k
                --m_xData->mnEmptyMark;
655
440k
            }
656
440k
        }
657
3.47M
    }
658
659
    // append new action
660
4.54M
    m_xData->pActUndoArray->Insert( std::move(pAction), m_xData->pActUndoArray->nCurUndoAction++ );
661
4.54M
    ImplCheckEmptyActions();
662
4.54M
    return true;
663
4.54M
}
664
665
666
void SfxUndoManager::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction, bool bTryMerge )
667
1.78M
{
668
1.78M
    UndoManagerGuard aGuard( *m_xData );
669
670
    // add
671
1.78M
    auto pActionTmp = pAction.get();
672
1.78M
    if ( ImplAddUndoAction_NoNotify( std::move(pAction), bTryMerge, true, aGuard ) )
673
1.78M
    {
674
        // notify listeners
675
1.78M
        aGuard.scheduleNotification( &SfxUndoListener::undoActionAdded, pActionTmp->GetComment() );
676
1.78M
    }
677
1.78M
}
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
198k
{
837
198k
    UndoManagerGuard aGuard( *m_xData );
838
198k
    return ImplGetRedoActionCount_Lock( i_currentLevel );
839
198k
}
840
841
842
size_t SfxUndoManager::ImplGetRedoActionCount_Lock( bool const i_currentLevel ) const
843
1.98M
{
844
1.98M
    const SfxUndoArray* pUndoArray = i_currentLevel ? m_xData->pActUndoArray : &m_xData->maUndoArray;
845
1.98M
    return pUndoArray->maUndoActions.size() - pUndoArray->nCurUndoAction;
846
1.98M
}
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.71k
{
995
2.71k
    UndoManagerGuard aGuard( *m_xData );
996
2.71k
    m_xData->aListeners.push_back( &i_listener );
997
2.71k
}
998
999
1000
void SfxUndoManager::RemoveUndoListener( SfxUndoListener& i_listener )
1001
2.71k
{
1002
2.71k
    UndoManagerGuard aGuard( *m_xData );
1003
2.71k
    auto lookup = std::find(m_xData->aListeners.begin(), m_xData->aListeners.end(), &i_listener);
1004
2.71k
    if (lookup != m_xData->aListeners.end())
1005
2.71k
        m_xData->aListeners.erase( lookup );
1006
2.71k
}
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.75M
{
1015
2.75M
    UndoManagerGuard aGuard( *m_xData );
1016
1017
2.75M
    if( !ImplIsUndoEnabled_Lock() )
1018
0
        return;
1019
1020
2.75M
    if ( !m_xData->maUndoArray.nMaxUndoActions )
1021
0
        return;
1022
1023
2.75M
    SfxListUndoAction* pAction = new SfxListUndoAction( rComment, rRepeatComment, nId, nViewShellId, m_xData->pActUndoArray );
1024
2.75M
    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.75M
    m_xData->pActUndoArray = pAction;
1027
1028
    // notification
1029
2.75M
    aGuard.scheduleNotification( &SfxUndoListener::listActionEntered, rComment );
1030
2.75M
}
1031
1032
1033
bool SfxUndoManager::IsInListAction() const
1034
569k
{
1035
569k
    UndoManagerGuard aGuard( *m_xData );
1036
569k
    return ImplIsInListAction_Lock();
1037
569k
}
1038
1039
1040
bool SfxUndoManager::ImplIsInListAction_Lock() const
1041
3.39M
{
1042
3.39M
    return m_xData->pActUndoArray != &m_xData->maUndoArray;
1043
3.39M
}
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.75M
{
1064
2.75M
    UndoManagerGuard aGuard( *m_xData );
1065
2.75M
    size_t nCount = ImplLeaveListAction( false, aGuard );
1066
1067
2.75M
    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.75M
    return nCount;
1079
2.75M
}
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.75M
{
1091
2.75M
    if ( !ImplIsUndoEnabled_Lock() )
1092
0
        return 0;
1093
1094
2.75M
    if ( !m_xData->maUndoArray.nMaxUndoActions )
1095
0
        return 0;
1096
1097
2.75M
    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.75M
    assert(m_xData->pActUndoArray->pFatherUndoArray);
1104
1105
    // the array/level which we're about to leave
1106
2.75M
    SfxUndoArray* pArrayToLeave = m_xData->pActUndoArray;
1107
    // one step up
1108
2.75M
    m_xData->pActUndoArray = m_xData->pActUndoArray->pFatherUndoArray;
1109
1110
    // If no undo actions were added to the list, delete the list action
1111
2.75M
    const size_t nListActionElements = pArrayToLeave->nCurUndoAction;
1112
2.75M
    if ( nListActionElements == 0 )
1113
1.77M
    {
1114
1.77M
        i_guard.markForDeletion( m_xData->pActUndoArray->Remove( --m_xData->pActUndoArray->nCurUndoAction ) );
1115
1.77M
        i_guard.scheduleNotification( &SfxUndoListener::listActionCancelled );
1116
1.77M
        return 0;
1117
1.77M
    }
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
986k
    ImplClearRedo( i_guard, SfxUndoManager::CurrentLevel );
1122
1123
986k
    SfxUndoAction* pCurrentAction= m_xData->pActUndoArray->maUndoActions[ m_xData->pActUndoArray->nCurUndoAction-1 ].pAction.get();
1124
986k
    SfxListUndoAction* pListAction = dynamic_cast< SfxListUndoAction * >( pCurrentAction );
1125
986k
    ENSURE_OR_RETURN( pListAction, "SfxUndoManager::ImplLeaveListAction: list action expected at this position!", nListActionElements );
1126
1127
986k
    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
986k
    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
986k
    ImplIsEmptyActions();
1156
    // notify listeners
1157
986k
    i_guard.scheduleNotification( &SfxUndoListener::listActionLeft, pListAction->GetComment() );
1158
1159
    // outta here
1160
986k
    return nListActionElements;
1161
986k
}
1162
1163
UndoStackMark SfxUndoManager::MarkTopUndoAction()
1164
337k
{
1165
337k
    UndoManagerGuard aGuard( *m_xData );
1166
1167
337k
    SAL_WARN_IF( IsInListAction(), "svl",
1168
337k
            "SfxUndoManager::MarkTopUndoAction(): suspicious call!" );
1169
337k
    assert((m_xData->mnMarks + 1) < (m_xData->mnEmptyMark - 1) &&
1170
337k
            "SfxUndoManager::MarkTopUndoAction(): mark overflow!");
1171
1172
337k
    size_t const nActionPos = m_xData->maUndoArray.nCurUndoAction;
1173
337k
    if (0 == nActionPos)
1174
337k
    {
1175
337k
        --m_xData->mnEmptyMark;
1176
337k
        return m_xData->mnEmptyMark;
1177
337k
    }
1178
1179
0
    m_xData->maUndoArray.maUndoActions[ nActionPos-1 ].aMarks.push_back(
1180
0
            ++m_xData->mnMarks );
1181
0
    return m_xData->mnMarks;
1182
337k
}
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.60M
    {
1366
2.60M
        m_xData->mbEmptyActions = bEmptyActions;
1367
2.60M
        EmptyActionsChanged();
1368
2.60M
    }
1369
20.3M
}
1370
1371
void SfxUndoManager::EmptyActionsChanged()
1372
2.53M
{
1373
1374
2.53M
}
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.75M
        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.97M
{
1395
1.97M
    return mpImpl->maComment;
1396
1.97M
}
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.75M
    mpImpl(new Impl(nId, nViewShellId, rComment, rRepeatComment))
1420
2.75M
{
1421
2.75M
    pFatherUndoArray = pFather;
1422
2.75M
    nMaxUndoActions = USHRT_MAX;
1423
2.75M
}
1424
1425
SfxListUndoAction::~SfxListUndoAction()
1426
2.75M
{
1427
2.75M
}
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.32M
{
1497
3.32M
}
1498
1499
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */