Akonadi Calendar

history.cpp
1/*
2 SPDX-FileCopyrightText: 2010-2012 Sérgio Martins <iamsergio@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "history.h"
8using namespace Qt::Literals::StringLiterals;
9
10#include "akonadicalendar_debug.h"
11#include "history_p.h"
12
13using namespace KCalendarCore;
14using namespace Akonadi;
15
16History::History(QObject *parent)
17 : QObject(parent)
18 , d(new HistoryPrivate(this))
19{
20}
21
22History::~History() = default;
23
24HistoryPrivate::HistoryPrivate(History *qq)
25 : mChanger(new IncidenceChanger(/*history=*/false, qq))
26 , mOperationTypeInProgress(TypeNone)
27 , q(qq)
28{
29 mChanger->setObjectName("changer"_L1); // for auto-connects
30}
31
32void History::recordCreation(const Akonadi::Item &item, const QString &description, const uint atomicOperationId)
33{
34 Q_ASSERT_X(item.isValid(), "History::recordCreation()", "Item must be valid.");
35
36 Q_ASSERT_X(item.hasPayload<KCalendarCore::Incidence::Ptr>(), "History::recordCreation()", "Item must have Incidence::Ptr payload.");
37
38 Entry::Ptr entry(new CreationEntry(item, description, this));
39
40 d->stackEntry(entry, atomicOperationId);
41}
42
43void History::recordModification(const Akonadi::Item &oldItem, const Akonadi::Item &newItem, const QString &description, const uint atomicOperationId)
44{
45 Q_ASSERT_X(oldItem.isValid(), "History::recordModification", "old item must be valid");
46 Q_ASSERT_X(newItem.isValid(), "History::recordModification", "newItem item must be valid");
47 Q_ASSERT_X(oldItem.hasPayload<KCalendarCore::Incidence::Ptr>(), "History::recordModification", "old item must have Incidence::Ptr payload");
48 Q_ASSERT_X(newItem.hasPayload<KCalendarCore::Incidence::Ptr>(), "History::recordModification", "newItem item must have Incidence::Ptr payload");
49
50 Entry::Ptr entry(new ModificationEntry(newItem, oldItem.payload<KCalendarCore::Incidence::Ptr>(), description, this));
51
52 Q_ASSERT(newItem.revision() >= oldItem.revision());
53
54 d->stackEntry(entry, atomicOperationId);
55}
56
57void History::recordDeletion(const Akonadi::Item &item, const QString &description, const uint atomicOperationId)
58{
59 Q_ASSERT_X(item.isValid(), "History::recordDeletion", "Item must be valid");
60 Item::List list;
61 list.append(item);
62 recordDeletions(list, description, atomicOperationId);
63}
64
65void History::recordDeletions(const Akonadi::Item::List &items, const QString &description, const uint atomicOperationId)
66{
67 Entry::Ptr entry(new DeletionEntry(items, description, this));
68
69 for (const Akonadi::Item &item : items) {
70 Q_UNUSED(item)
71 Q_ASSERT_X(item.isValid(), "History::recordDeletion()", "Item must be valid.");
72 Q_ASSERT_X(item.hasPayload<Incidence::Ptr>(), "History::recordDeletion()", "Item must have an Incidence::Ptr payload.");
73 }
74
75 d->stackEntry(entry, atomicOperationId);
76}
77
79{
80 if (!d->mUndoStack.isEmpty()) {
81 return d->mUndoStack.top()->mDescription;
82 } else {
83 return {};
84 }
85}
86
88{
89 if (!d->mRedoStack.isEmpty()) {
90 return d->mRedoStack.top()->mDescription;
91 } else {
92 return {};
93 }
94}
95
97{
98 d->undoOrRedo(TypeUndo, parent);
99}
100
102{
103 d->undoOrRedo(TypeRedo, parent);
104}
105
107{
108 if (d->mOperationTypeInProgress != TypeNone) {
109 qCWarning(AKONADICALENDAR_LOG) << "Don't call History::undoAll() while an undo/redo/undoAll is in progress";
110 } else if (d->mEnabled) {
111 d->mUndoAllInProgress = true;
112 d->mCurrentParent = parent;
113 d->doIt(TypeUndo);
114 } else {
115 qCWarning(AKONADICALENDAR_LOG) << "Don't call undo/redo when History is disabled";
116 }
117}
118
120{
121 bool result = true;
122 if (d->mOperationTypeInProgress == TypeNone) {
123 d->mRedoStack.clear();
124 d->mUndoStack.clear();
125 d->mLastErrorString.clear();
126 d->mQueuedEntries.clear();
127 } else {
128 result = false;
129 }
130 Q_EMIT changed();
131 return result;
132}
133
135{
136 return d->mLastErrorString;
137}
138
140{
141 return !d->mUndoStack.isEmpty() && d->mOperationTypeInProgress == TypeNone;
142}
143
145{
146 return !d->mRedoStack.isEmpty() && d->mOperationTypeInProgress == TypeNone;
147}
148
149void HistoryPrivate::updateIds(Item::Id oldId, Item::Id newId)
150{
151 mEntryInProgress->updateIds(oldId, newId);
152
153 for (const Entry::Ptr &entry : std::as_const(mUndoStack)) {
154 entry->updateIds(oldId, newId);
155 }
156
157 for (const Entry::Ptr &entry : std::as_const(mRedoStack)) {
158 entry->updateIds(oldId, newId);
159 }
160}
161
162void HistoryPrivate::doIt(OperationType type)
163{
164 mOperationTypeInProgress = type;
165 Q_EMIT q->changed(); // Application will disable undo/redo buttons because operation is in progress
166 Q_ASSERT(!stack().isEmpty());
167 mEntryInProgress = stack().pop();
168
169 connect(mEntryInProgress.data(), &Entry::finished, this, &HistoryPrivate::handleFinished, Qt::UniqueConnection);
170 mEntryInProgress->doIt(type);
171}
172
173void HistoryPrivate::handleFinished(IncidenceChanger::ResultCode changerResult, const QString &errorString)
174{
175 Q_ASSERT(mOperationTypeInProgress != TypeNone);
176 Q_ASSERT(!(mUndoAllInProgress && mOperationTypeInProgress == TypeRedo));
177
178 const bool success = (changerResult == IncidenceChanger::ResultCodeSuccess);
180
181 if (success) {
182 mLastErrorString.clear();
183 destinationStack().push(mEntryInProgress);
184 } else {
185 mLastErrorString = errorString;
186 stack().push(mEntryInProgress);
187 }
188
189 mCurrentParent = nullptr;
190
191 // Process recordCreation/Modification/Deletions that came in while an operation
192 // was in progress
193 if (!mQueuedEntries.isEmpty()) {
194 mRedoStack.clear();
195 for (const Entry::Ptr &entry : std::as_const(mQueuedEntries)) {
196 mUndoStack.push(entry);
197 }
198 mQueuedEntries.clear();
199 }
200
201 emitDone(mOperationTypeInProgress, resultCode);
202 mOperationTypeInProgress = TypeNone;
203 Q_EMIT q->changed();
204}
205
206void HistoryPrivate::stackEntry(const Entry::Ptr &entry, uint atomicOperationId)
207{
208 const bool useMultiEntry = (atomicOperationId > 0);
209
210 Entry::Ptr entryToPush;
211
212 if (useMultiEntry) {
213 Entry::Ptr topEntry = (mOperationTypeInProgress == TypeNone) ? (mUndoStack.isEmpty() ? Entry::Ptr() : mUndoStack.top())
214 : (mQueuedEntries.isEmpty() ? Entry::Ptr() : mQueuedEntries.last());
215
216 const bool topIsMultiEntry = qobject_cast<MultiEntry *>(topEntry.data());
217
218 if (topIsMultiEntry) {
219 MultiEntry::Ptr multiEntry = topEntry.staticCast<MultiEntry>();
220 if (multiEntry->mAtomicOperationId != atomicOperationId) {
221 multiEntry = MultiEntry::Ptr(new MultiEntry(atomicOperationId, entry->mDescription, q));
222 entryToPush = multiEntry;
223 }
224 multiEntry->addEntry(entry);
225 } else {
226 MultiEntry::Ptr multiEntry = MultiEntry::Ptr(new MultiEntry(atomicOperationId, entry->mDescription, q));
227 multiEntry->addEntry(entry);
228 entryToPush = multiEntry;
229 }
230 } else {
231 entryToPush = entry;
232 }
233
234 if (mOperationTypeInProgress == TypeNone) {
235 if (entryToPush) {
236 mUndoStack.push(entryToPush);
237 }
238 mRedoStack.clear();
239 Q_EMIT q->changed();
240 } else {
241 if (entryToPush) {
242 mQueuedEntries.append(entryToPush);
243 }
244 }
245}
246
247void HistoryPrivate::undoOrRedo(OperationType type, QWidget *parent)
248{
249 // Don't call undo() without the previous one finishing
250 Q_ASSERT(mOperationTypeInProgress == TypeNone);
251
252 if (!stack(type).isEmpty()) {
253 if (mEnabled) {
254 mCurrentParent = parent;
255 doIt(type);
256 } else {
257 qCWarning(AKONADICALENDAR_LOG) << "Don't call undo/redo when History is disabled";
258 }
259 } else {
260 qCWarning(AKONADICALENDAR_LOG) << "Don't call undo/redo when the stack is empty.";
261 }
262}
263
264QStack<Entry::Ptr> &HistoryPrivate::stack(OperationType type)
265{
266 // Entries from the undo stack go to the redo stack, and vice-versa
267 return type == TypeUndo ? mUndoStack : mRedoStack;
268}
269
270void HistoryPrivate::setEnabled(bool enabled)
271{
272 mEnabled = enabled;
273}
274
275int HistoryPrivate::redoCount() const
276{
277 return mRedoStack.count();
278}
279
280int HistoryPrivate::undoCount() const
281{
282 return mUndoStack.count();
283}
284
285QStack<Entry::Ptr> &HistoryPrivate::stack()
286{
287 return stack(mOperationTypeInProgress);
288}
289
290QStack<Entry::Ptr> &HistoryPrivate::destinationStack()
291{
292 // Entries from the undo stack go to the redo stack, and vice-versa
293 return mOperationTypeInProgress == TypeRedo ? mUndoStack : mRedoStack;
294}
295
296void HistoryPrivate::emitDone(OperationType type, History::ResultCode resultCode)
297{
298 if (type == TypeUndo) {
299 Q_EMIT q->undone(resultCode);
300 } else if (type == TypeRedo) {
301 Q_EMIT q->redone(resultCode);
302 } else {
303 Q_ASSERT(false);
304 }
305}
306
307Akonadi::IncidenceChanger *History::incidenceChanger() const
308{
309 return d->mChanger;
310}
311
312#include "moc_history.cpp"
History class for implementing undo/redo of calendar operations.
Definition history.h:48
void recordDeletions(const Akonadi::Item::List &items, const QString &description, const uint atomicOperationId=0)
Pushes a list of incidence deletions onto the undo stack.
Definition history.cpp:65
void recordDeletion(const Akonadi::Item &item, const QString &description, const uint atomicOperationId=0)
Pushes an incidence deletion onto the undo stack.
Definition history.cpp:57
void changed()
The redo/undo stacks have changed.
QString nextUndoDescription() const
Returns the description of the next undo.
Definition history.cpp:78
bool redoAvailable() const
Returns true if there are changes that can be redone.
Definition history.cpp:144
bool clear()
Clears the undo and redo stacks.
Definition history.cpp:119
void recordModification(const Akonadi::Item &oldItem, const Akonadi::Item &newItem, const QString &description, const uint atomicOperationId=0)
Pushes an incidence modification onto the undo stack.
Definition history.cpp:43
void undoAll(QWidget *parent=nullptr)
Reverts every change in the undo stack.
Definition history.cpp:106
void redo(QWidget *parent=nullptr)
Reverts the change that's on top of the redo stack.
Definition history.cpp:101
~History() override
Destroys the History instance.
QString nextRedoDescription() const
Returns the description of the next redo.
Definition history.cpp:87
bool undoAvailable() const
Returns true if there are changes that can be undone.
Definition history.cpp:139
QString lastErrorString() const
Returns the last error message.
Definition history.cpp:134
ResultCode
This enum describes the possible result codes (success/error values) for an undo or redo operation.
Definition history.h:57
@ ResultCodeError
An error occurred.
Definition history.h:59
@ ResultCodeSuccess
Success.
Definition history.h:58
void undo(QWidget *parent=nullptr)
Reverts the change that's on top of the undo stack.
Definition history.cpp:96
void recordCreation(const Akonadi::Item &item, const QString &description, const uint atomicOperationId=0)
Pushes an incidence creation onto the undo stack.
Definition history.cpp:32
bool hasPayload() const
int revision() const
T payload() const
bool isValid() const
Type type(const QSqlDatabase &db)
FreeBusyManager::Singleton.
void append(QList< T > &&value)
Q_EMITQ_EMIT
QObject * parent() const const
UniqueConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:14:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.