Akonadi Calendar

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

KDE's Doxygen guidelines are available online.