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 
11 using namespace KCalendarCore;
12 using namespace Akonadi;
13 
14 History::History(QObject *parent)
15  : QObject(parent)
16  , d(new HistoryPrivate(this))
17 {
18 }
19 
20 History::~History() = default;
21 
22 HistoryPrivate::HistoryPrivate(History *qq)
23  : mChanger(new IncidenceChanger(/*history=*/false, qq))
24  , mOperationTypeInProgress(TypeNone)
25  , q(qq)
26 {
27  mChanger->setObjectName(QStringLiteral("changer")); // for auto-connects
28 }
29 
30 void 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 
41 void 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 
55 void 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 
63 void 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 
94 void History::undo(QWidget *parent)
95 {
96  d->undoOrRedo(TypeUndo, parent);
97 }
98 
99 void History::redo(QWidget *parent)
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 
147 void 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 
160 void 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 
171 void 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 
204 void 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 
245 void 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 
262 QStack<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 
268 void HistoryPrivate::setEnabled(bool enabled)
269 {
270  mEnabled = enabled;
271 }
272 
273 int HistoryPrivate::redoCount() const
274 {
275  return mRedoStack.count();
276 }
277 
278 int HistoryPrivate::undoCount() const
279 {
280  return mUndoStack.count();
281 }
282 
283 QStack<Entry::Ptr> &HistoryPrivate::stack()
284 {
285  return stack(mOperationTypeInProgress);
286 }
287 
288 QStack<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 
294 void 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 
305 Akonadi::IncidenceChanger *History::incidenceChanger() const
306 {
307  return d->mChanger;
308 }
309 
310 #include "moc_history.cpp"
311 #include "moc_history_p.cpp"
bool isValid() const
void append(const T &value)
QString lastErrorString() const
Returns the last error message.
Definition: history.cpp:132
~History() override
Destroys the History instance.
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
void undoAll(QWidget *parent=nullptr)
Reverts every change in the undo stack.
Definition: history.cpp:104
void undo(QWidget *parent=nullptr)
Reverts the change that's on top of the undo stack.
Definition: history.cpp:94
QString nextRedoDescription() const
Returns the description of the next redo.
Definition: history.cpp:85
Q_EMITQ_EMIT
History class for implementing undo/redo of calendar operations.
Definition: history.h:47
Type type(const QSqlDatabase &db)
void clear()
@ ResultCodeError
An error occurred.
Definition: history.h:59
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
int revision() const
bool hasPayload() const
@ ResultCodeSuccess
Success.
Definition: history.h:58
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
UniqueConnection
bool undoAvailable() const
Returns true if there are changes that can be undone.
Definition: history.cpp:137
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 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
QString nextUndoDescription() const
Returns the description of the next undo.
Definition: history.cpp:76
void redo(QWidget *parent=nullptr)
Reverts the change that's on top of the redo stack.
Definition: history.cpp:99
T payload() const
ResultCode
This enum describes the possible result codes (success/error values) for an undo or redo operation.
Definition: history.h:57
QObject * parent() const const
void changed()
The redo/undo stacks have changed.
FreeBusyManager::Singleton.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Oct 3 2022 03:53:48 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.