Akonadi Calendar

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

KDE's Doxygen guidelines are available online.