controller/UndoManager.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "UndoManager.h" | ||
| 2 | |||
| 3 | #include <QDateTime> | ||
| 4 | #include <QLoggingCategory> | ||
| 5 | #include <QPointer> | ||
| 6 | #include <algorithm> | ||
| 7 | |||
| 8 |
4/7✓ Branch 2 → 3 taken 13 times.
✓ Branch 2 → 8 taken 507 times.
✓ Branch 4 → 5 taken 13 times.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 13 times.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
|
520 | Q_LOGGING_CATEGORY(lcUndo, "mailjd.undo") |
| 9 | |||
| 10 | 1051 | UndoManager::UndoManager(QObject *parent) : QObject(parent) {} | |
| 11 | |||
| 12 | 359 | void UndoManager::push(const QString &description, | |
| 13 | std::function<void()> undoAction) { | ||
| 14 | 359 | UndoEntry entry; | |
| 15 | 359 | entry.description = description; | |
| 16 | 359 | entry.undo = std::move(undoAction); | |
| 17 |
1/1✓ Branch 8 → 9 taken 359 times.
|
359 | append(std::move(entry)); |
| 18 | 359 | } | |
| 19 | |||
| 20 | 37 | bool UndoManager::pushAsync(const QString &description, | |
| 21 | std::function<void(Completion)> undoAction, | ||
| 22 | qint64 retainedBytes) { | ||
| 23 |
8/8✓ Branch 3 → 4 taken 36 times.
✓ Branch 3 → 6 taken 1 time.
✓ Branch 4 → 5 taken 35 times.
✓ Branch 4 → 6 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 34 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 8 → 10 taken 34 times.
|
37 | if (!undoAction || retainedBytes < 0 || retainedBytes > kMaxAsyncRetainedBytes) return false; |
| 24 | 34 | UndoEntry entry; | |
| 25 | 34 | entry.description = description; | |
| 26 | 34 | entry.asynchronousUndo = std::move(undoAction); | |
| 27 | 34 | entry.retainedBytes = retainedBytes; | |
| 28 |
1/1✓ Branch 16 → 17 taken 34 times.
|
34 | return append(std::move(entry)); |
| 29 | 34 | } | |
| 30 | |||
| 31 | 393 | bool UndoManager::append(UndoEntry entry) { | |
| 32 | 393 | qint64 retainedBytes = entry.retainedBytes; | |
| 33 | 393 | qint64 activeBytes = 0; | |
| 34 |
2/2✓ Branch 10 → 5 taken 5140 times.
✓ Branch 10 → 11 taken 393 times.
|
5533 | for (const auto &existing : std::as_const(m_stack)) { |
| 35 | 5140 | retainedBytes += existing.retainedBytes; | |
| 36 |
2/2✓ Branch 6 → 7 taken 61 times.
✓ Branch 6 → 8 taken 5079 times.
|
5140 | if (existing.id == m_activeId) activeBytes = existing.retainedBytes; |
| 37 | } | ||
| 38 |
2/2✓ Branch 11 → 12 taken 1 time.
✓ Branch 11 → 13 taken 392 times.
|
393 | if (activeBytes + entry.retainedBytes > kMaxAsyncRetainedBytes) return false; |
| 39 | 392 | entry.timestamp = QDateTime::currentMSecsSinceEpoch(); | |
| 40 | 392 | entry.id = ++m_nextId; | |
| 41 | |||
| 42 | 392 | m_stack.append(std::move(entry)); | |
| 43 | |||
| 44 | // Enforce max depth — discard oldest entries | ||
| 45 |
6/6✓ Branch 24 → 25 taken 393 times.
✓ Branch 24 → 26 taken 26 times.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 25 → 27 taken 392 times.
✓ Branch 28 → 17 taken 27 times.
✓ Branch 28 → 29 taken 392 times.
|
419 | while (m_stack.size() > kMaxUndoDepth || retainedBytes > kMaxAsyncRetainedBytes) { |
| 46 | // An accepted asynchronous operation retains its entry until completion. | ||
| 47 |
2/2✓ Branch 18 → 19 taken 11 times.
✓ Branch 18 → 20 taken 16 times.
|
27 | const int oldest = m_stack.first().id == m_activeId ? 1 : 0; |
| 48 | 27 | retainedBytes -= m_stack.at(oldest).retainedBytes; | |
| 49 | 27 | m_stack.removeAt(oldest); | |
| 50 | } | ||
| 51 | |||
| 52 |
7/7✓ Branch 29 → 30 taken 392 times.
✓ Branch 33 → 34 taken 392 times.
✓ Branch 34 → 35 taken 392 times.
✓ Branch 35 → 36 taken 392 times.
✓ Branch 36 → 37 taken 392 times.
✓ Branch 44 → 31 taken 392 times.
✓ Branch 44 → 45 taken 392 times.
|
784 | qCInfo(lcUndo) << "Push:" << m_stack.last().description |
| 53 |
3/3✓ Branch 37 → 38 taken 392 times.
✓ Branch 39 → 40 taken 392 times.
✓ Branch 40 → 41 taken 392 times.
|
392 | << "(stack size:" << m_stack.size() << ")"; |
| 54 | 392 | emit undoAvailable(canUndo()); | |
| 55 | 392 | return true; | |
| 56 | } | ||
| 57 | |||
| 58 | 43 | bool UndoManager::undo() { | |
| 59 |
2/2✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 15 taken 41 times.
|
43 | if (!canUndo()) { |
| 60 |
5/5✓ Branch 4 → 5 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 13 → 6 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
|
4 | qCInfo(lcUndo) << "Undo: stack empty"; |
| 61 | 2 | return false; | |
| 62 | } | ||
| 63 | |||
| 64 |
3/3✓ Branch 15 → 16 taken 41 times.
✓ Branch 17 → 18 taken 16 times.
✓ Branch 17 → 39 taken 25 times.
|
41 | if (m_stack.last().asynchronousUndo) { |
| 65 |
2/2✓ Branch 18 → 19 taken 16 times.
✓ Branch 19 → 20 taken 16 times.
|
16 | const auto entry = m_stack.last(); |
| 66 | 16 | m_activeId = entry.id; | |
| 67 | 16 | const auto attempt = m_activeAttempt = ++m_nextAttempt; | |
| 68 |
1/1✓ Branch 20 → 21 taken 16 times.
|
16 | const QPointer<UndoManager> owner(this); |
| 69 |
1/1✓ Branch 21 → 22 taken 16 times.
|
16 | emit undoAvailable(false); |
| 70 |
3/6✓ Branch 23 → 24 taken 16 times.
✗ Branch 23 → 26 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 16 times.
✗ Branch 28 → 29 not taken.
✓ Branch 28 → 30 taken 16 times.
|
16 | if (!owner || owner->m_activeId != entry.id) return true; |
| 71 |
2/4✓ Branch 31 → 32 taken 16 times.
✓ Branch 32 → 33 taken 16 times.
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 81 not taken.
|
16 | entry.asynchronousUndo([owner, id = entry.id, attempt](bool succeeded) { |
| 72 |
2/2✓ Branch 3 → 4 taken 16 times.
✓ Branch 3 → 6 taken 2 times.
|
18 | if (owner) owner->finishAsync(id, attempt, succeeded); |
| 73 | 18 | }); | |
| 74 | 16 | return true; | |
| 75 | 16 | } | |
| 76 | |||
| 77 |
1/1✓ Branch 39 → 40 taken 25 times.
|
25 | UndoEntry entry = m_stack.takeLast(); |
| 78 |
1/1✓ Branch 40 → 41 taken 25 times.
|
25 | const QPointer<UndoManager> owner(this); |
| 79 |
6/6✓ Branch 41 → 42 taken 25 times.
✓ Branch 45 → 46 taken 25 times.
✓ Branch 46 → 47 taken 25 times.
✓ Branch 47 → 48 taken 25 times.
✓ Branch 55 → 43 taken 25 times.
✓ Branch 55 → 56 taken 25 times.
|
50 | qCInfo(lcUndo) << "Undo:" << entry.description |
| 80 |
3/3✓ Branch 48 → 49 taken 25 times.
✓ Branch 50 → 51 taken 25 times.
✓ Branch 51 → 52 taken 25 times.
|
25 | << "(stack size:" << m_stack.size() << ")"; |
| 81 | |||
| 82 |
1/2✓ Branch 57 → 58 taken 25 times.
✗ Branch 57 → 59 not taken.
|
25 | if (entry.undo) { |
| 83 |
1/1✓ Branch 58 → 59 taken 25 times.
|
25 | entry.undo(); |
| 84 | } | ||
| 85 | |||
| 86 |
2/3✓ Branch 60 → 61 taken 25 times.
✗ Branch 60 → 62 not taken.
✓ Branch 61 → 62 taken 25 times.
|
25 | if (owner) emit undoPerformed(entry.description); |
| 87 |
2/3✓ Branch 63 → 64 taken 25 times.
✗ Branch 63 → 66 not taken.
✓ Branch 65 → 66 taken 25 times.
|
25 | if (owner) emit undoAvailable(canUndo()); |
| 88 | 25 | return true; | |
| 89 | 25 | } | |
| 90 | |||
| 91 | 16 | void UndoManager::finishAsync(quint64 id, quint64 attempt, bool succeeded) { | |
| 92 |
4/4✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 13 times.
|
16 | if (m_activeId != id || m_activeAttempt != attempt) return; |
| 93 |
1/1✓ Branch 7 → 8 taken 13 times.
|
13 | const auto entry = std::find_if(m_stack.cbegin(), m_stack.cend(), |
| 94 | 13 | [id](const UndoEntry &value) { return value.id == id; }); | |
| 95 |
1/2✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 13 times.
|
13 | if (entry == m_stack.cend()) return; |
| 96 | 13 | const QString description = entry->description; | |
| 97 |
3/3✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 23 taken 5 times.
✓ Branch 22 → 23 taken 8 times.
|
21 | if (succeeded) m_stack.removeAt(std::distance(m_stack.cbegin(), entry)); |
| 98 | 13 | m_activeId = 0; | |
| 99 |
1/1✓ Branch 23 → 24 taken 13 times.
|
13 | const QPointer<UndoManager> owner(this); |
| 100 |
3/3✓ Branch 24 → 25 taken 8 times.
✓ Branch 24 → 26 taken 5 times.
✓ Branch 25 → 27 taken 8 times.
|
13 | if (succeeded) emit undoPerformed(description); |
| 101 |
1/1✓ Branch 26 → 27 taken 5 times.
|
5 | else emit undoFailed(description); |
| 102 |
2/3✓ Branch 28 → 29 taken 13 times.
✗ Branch 28 → 31 not taken.
✓ Branch 30 → 31 taken 13 times.
|
13 | if (owner) emit undoAvailable(canUndo()); |
| 103 | 13 | } | |
| 104 | |||
| 105 |
4/4✓ Branch 3 → 4 taken 468 times.
✓ Branch 3 → 7 taken 62 times.
✓ Branch 5 → 6 taken 431 times.
✓ Branch 5 → 7 taken 37 times.
|
530 | bool UndoManager::canUndo() const { return !busy() && !m_stack.isEmpty(); } |
| 106 | |||
| 107 | 7 | QString UndoManager::lastDescription() const { | |
| 108 |
2/2✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 6 times.
|
7 | if (m_stack.isEmpty()) |
| 109 | 1 | return {}; | |
| 110 | 6 | return m_stack.last().description; | |
| 111 | } | ||
| 112 | |||
| 113 | 59 | int UndoManager::stackSize() const { return m_stack.size(); } | |
| 114 | |||
| 115 | 1910 | void UndoManager::clear() { | |
| 116 |
2/2✓ Branch 3 → 4 taken 1809 times.
✓ Branch 3 → 5 taken 101 times.
|
1910 | if (m_stack.isEmpty()) |
| 117 | 1809 | return; | |
| 118 | 101 | m_stack.clear(); | |
| 119 | 101 | m_activeId = 0; | |
| 120 |
5/5✓ Branch 6 → 7 taken 101 times.
✓ Branch 10 → 11 taken 101 times.
✓ Branch 11 → 12 taken 101 times.
✓ Branch 15 → 8 taken 101 times.
✓ Branch 15 → 16 taken 101 times.
|
202 | qCInfo(lcUndo) << "Stack cleared"; |
| 121 | 101 | emit undoAvailable(false); | |
| 122 | } | ||
| 123 |