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

KDE's Doxygen guidelines are available online.