KMail: Fix scrolling up/down on the message viewer
[kdepim.git] / incidenceeditor-ng / editoritemmanager.cpp
blobe3241e50a293984047626c89b796f3914b12d03c
1 /*
2 Copyright (c) 2010 Bertjan Broeksema <broeksema@kde.org>
3 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 This library is free software; you can redistribute it and/or modify it
6 under the terms of the GNU Library General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or (at your
8 option) any later version.
10 This library is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13 License for more details.
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 02110-1301, USA.
21 #include "editoritemmanager.h"
22 #include "individualmailcomponentfactory.h"
24 #include <calendarsupport/utils.h>
25 #include <calendarsupport/kcalprefs.h>
27 #include <Item>
28 #include <ItemDeleteJob>
29 #include <ItemFetchJob>
30 #include <ItemFetchScope>
31 #include <ItemMoveJob>
32 #include <Monitor>
33 #include <Session>
34 #include <AkonadiCore/TagFetchScope>
36 #include <KJob>
37 #include <KLocalizedString>
38 #include "incidenceeditor_debug.h"
40 #include <QMessageBox>
41 #include <QPointer>
43 /// ItemEditorPrivate
45 namespace IncidenceEditorNG
48 class ItemEditorPrivate
50 EditorItemManager *q_ptr;
51 Q_DECLARE_PUBLIC(EditorItemManager)
53 public:
54 Akonadi::Item mItem;
55 Akonadi::Item mPrevItem;
56 Akonadi::ItemFetchScope mFetchScope;
57 Akonadi::Monitor *mItemMonitor;
58 ItemEditorUi *mItemUi;
59 bool mIsCounterProposal;
60 EditorItemManager::SaveAction currentAction;
61 Akonadi::IncidenceChanger *mChanger;
63 public:
64 ItemEditorPrivate(Akonadi::IncidenceChanger *changer, EditorItemManager *qq);
65 void itemChanged(const Akonadi::Item &, const QSet<QByteArray> &);
66 void itemFetchResult(KJob *job);
67 void itemMoveResult(KJob *job);
68 void onModifyFinished(int changeId, const Akonadi::Item &item,
69 Akonadi::IncidenceChanger::ResultCode resultCode,
70 const QString &errorString);
72 void onCreateFinished(int changeId,
73 const Akonadi::Item &item,
74 Akonadi::IncidenceChanger::ResultCode resultCode,
75 const QString &errorString);
77 void setupMonitor();
78 void moveJobFinished(KJob *job);
79 void setItem(const Akonadi::Item &item);
82 ItemEditorPrivate::ItemEditorPrivate(Akonadi::IncidenceChanger *changer, EditorItemManager *qq)
83 : q_ptr(qq), mItemMonitor(0), mIsCounterProposal(false)
84 , currentAction(EditorItemManager::None)
86 mFetchScope.fetchFullPayload();
87 mFetchScope.setAncestorRetrieval(Akonadi::ItemFetchScope::Parent);
88 mFetchScope.setFetchTags(true);
89 mFetchScope.tagFetchScope().setFetchIdOnly(false);
91 mChanger = changer ? changer : new Akonadi::IncidenceChanger(new IndividualMailComponentFactory(qq), qq);
93 qq->connect(mChanger, SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)),
94 qq, SLOT(onModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)));
96 qq->connect(mChanger, SIGNAL(createFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)),
97 qq, SLOT(onCreateFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode,QString)));
100 void ItemEditorPrivate::moveJobFinished(KJob *job)
102 Q_Q(EditorItemManager);
103 if (job->error()) {
104 qCCritical(INCIDENCEEDITOR_LOG) << "Error while moving and modifying " << job->errorString();
105 mItemUi->reject(ItemEditorUi::ItemMoveFailed, job->errorString());
106 } else {
107 Akonadi::Item item(mItem.id());
108 currentAction = EditorItemManager::MoveAndModify;
109 q->load(item);
113 void ItemEditorPrivate::itemFetchResult(KJob *job)
115 Q_ASSERT(job);
116 Q_Q(EditorItemManager);
118 EditorItemManager::SaveAction action = currentAction;
119 currentAction = EditorItemManager::None;
121 if (job->error()) {
122 mItemUi->reject(ItemEditorUi::ItemFetchFailed, job->errorString());
123 return;
126 Akonadi::ItemFetchJob *fetchJob = qobject_cast<Akonadi::ItemFetchJob *>(job);
127 if (fetchJob->items().isEmpty()) {
128 mItemUi->reject(ItemEditorUi::ItemFetchFailed);
129 return;
132 Akonadi::Item item = fetchJob->items().at(0);
133 if (mItemUi->hasSupportedPayload(item)) {
134 setItem(item);
135 if (action != EditorItemManager::None) {
136 // Finally enable ok/apply buttons, we've finished loading
137 Q_EMIT q->itemSaveFinished(action);
139 } else {
140 mItemUi->reject(ItemEditorUi::ItemHasInvalidPayload);
144 void ItemEditorPrivate::setItem(const Akonadi::Item &item)
146 Q_ASSERT(item.hasPayload());
147 mPrevItem = item;
148 mItem = item;
149 mItemUi->load(item);
150 setupMonitor();
153 void ItemEditorPrivate::itemMoveResult(KJob *job)
155 Q_ASSERT(job);
156 Q_Q(EditorItemManager);
158 if (job->error()) {
159 Akonadi::ItemMoveJob *moveJob = qobject_cast<Akonadi::ItemMoveJob *>(job);
160 Q_ASSERT(moveJob);
161 Q_UNUSED(moveJob);
162 //Q_ASSERT(!moveJob->items().isEmpty());
163 // TODO: What is reasonable behavior at this point?
164 qCCritical(INCIDENCEEDITOR_LOG) << "Error while moving item ";// << moveJob->items().first().id() << " to collection "
165 //<< moveJob->destinationCollection() << job->errorString();
166 Q_EMIT q->itemSaveFailed(EditorItemManager::Move, job->errorString());
167 } else {
168 // Fetch the item again, we want a new mItem, which has an updated parentCollection
169 Akonadi::Item item(mItem.id());
170 // set currentAction, so the fetchResult slot emits itemSavedFinished(Move);
171 // We could emit it here, but we should only enable ok/apply buttons after the loading
172 // is complete
173 currentAction = EditorItemManager::Move;
174 q->load(item);
178 void ItemEditorPrivate::onModifyFinished(int, const Akonadi::Item &item,
179 Akonadi::IncidenceChanger::ResultCode resultCode,
180 const QString &errorString)
182 Q_Q(EditorItemManager);
183 if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
184 if (mItem.parentCollection() == mItemUi->selectedCollection() ||
185 mItem.storageCollectionId() == mItemUi->selectedCollection().id()) {
186 mItem = item;
187 Q_EMIT q->itemSaveFinished(EditorItemManager::Modify);
188 setupMonitor();
189 } else { // There's a collection move too.
190 Akonadi::ItemMoveJob *moveJob = new Akonadi::ItemMoveJob(mItem, mItemUi->selectedCollection());
191 q->connect(moveJob, SIGNAL(result(KJob*)), SLOT(moveJobFinished(KJob*)));
193 } else if (resultCode == Akonadi::IncidenceChanger::ResultCodeUserCanceled) {
194 Q_EMIT q->itemSaveFailed(EditorItemManager::Modify, QString());
195 q->load(Akonadi::Item(mItem.id()));
196 } else {
197 qCCritical(INCIDENCEEDITOR_LOG) << "Modify failed " << errorString;
198 Q_EMIT q->itemSaveFailed(EditorItemManager::Modify, errorString);
202 void ItemEditorPrivate::onCreateFinished(int,
203 const Akonadi::Item &item,
204 Akonadi::IncidenceChanger::ResultCode resultCode,
205 const QString &errorString)
207 Q_Q(EditorItemManager);
208 if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) {
209 currentAction = EditorItemManager::Create;
210 q->load(item);
211 setupMonitor();
212 } else {
213 qCCritical(INCIDENCEEDITOR_LOG) << "Creation failed " << errorString;
214 Q_EMIT q->itemSaveFailed(EditorItemManager::Create, errorString);
218 void ItemEditorPrivate::setupMonitor()
220 // Q_Q(EditorItemManager);
221 delete mItemMonitor;
222 mItemMonitor = new Akonadi::Monitor;
223 mItemMonitor->ignoreSession(Akonadi::Session::defaultSession());
224 mItemMonitor->itemFetchScope().fetchFullPayload();
225 if (mItem.isValid()) {
226 mItemMonitor->setItemMonitored(mItem);
229 // q->connect(mItemMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet<QByteArray>)),
230 // SLOT(itemChanged(Akonadi::Item,QSet<QByteArray>)));
233 void ItemEditorPrivate::itemChanged(const Akonadi::Item &item,
234 const QSet<QByteArray> &partIdentifiers)
236 Q_Q(EditorItemManager);
237 if (mItemUi->containsPayloadIdentifiers(partIdentifiers)) {
238 QPointer<QMessageBox> dlg = new QMessageBox; //krazy:exclude=qclasses
239 dlg->setIcon(QMessageBox::Question);
240 dlg->setInformativeText(i18n("The item has been changed by another application.\n"
241 "What should be done?"));
242 dlg->addButton(i18n("Take over changes"), QMessageBox::AcceptRole);
243 dlg->addButton(i18n("Ignore and Overwrite changes"), QMessageBox::RejectRole);
245 if (dlg->exec() == QMessageBox::AcceptRole) {
246 Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mItem);
247 job->setFetchScope(mFetchScope);
249 mItem = item;
251 q->load(mItem);
252 } else {
253 mItem.setRevision(item.revision());
254 q->save();
257 delete dlg;
260 // Overwrite or not, we need to update the revision and the remote id to be able
261 // to store item later on.
262 mItem.setRevision(item.revision());
265 /// ItemEditor
267 EditorItemManager::EditorItemManager(ItemEditorUi *ui, Akonadi::IncidenceChanger *changer)
268 : d_ptr(new ItemEditorPrivate(changer, this))
270 Q_D(ItemEditor);
271 d->mItemUi = ui;
274 EditorItemManager::~EditorItemManager()
276 delete d_ptr;
279 Akonadi::Item EditorItemManager::item(ItemState state) const
281 Q_D(const ItemEditor);
283 switch (state) {
284 case EditorItemManager::AfterSave:
285 if (d->mItem.hasPayload()) {
286 return d->mItem;
287 } else {
288 qCDebug(INCIDENCEEDITOR_LOG) << "Won't return mItem because isValid = " << d->mItem.isValid()
289 << "; and haPayload is " << d->mItem.hasPayload();
291 break;
292 case EditorItemManager::BeforeSave:
293 if (d->mPrevItem.hasPayload()) {
294 return d->mPrevItem;
295 } else {
296 qCDebug(INCIDENCEEDITOR_LOG) << "Won't return mPrevItem because isValid = " << d->mPrevItem.isValid()
297 << "; and haPayload is " << d->mPrevItem.hasPayload();
299 break;
300 default:
301 qCDebug(INCIDENCEEDITOR_LOG) << "state = " << state;
302 Q_ASSERT_X(false, "EditorItemManager::item", "Unknown enum value") ;
304 return Akonadi::Item();
307 void EditorItemManager::load(const Akonadi::Item &item)
309 Q_D(ItemEditor);
311 //We fetch anyways to make sure we have everything required including tags
312 Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, this);
313 job->setFetchScope(d->mFetchScope);
314 connect(job, SIGNAL(result(KJob*)), SLOT(itemFetchResult(KJob*)));
317 void EditorItemManager::save()
319 Q_D(ItemEditor);
321 if (!d->mItemUi->isValid()) {
322 Q_EMIT itemSaveFailed(d->mItem.isValid() ? Modify : Create, QString());
323 return;
326 if (!d->mItemUi->isDirty() &&
327 d->mItemUi->selectedCollection() == d->mItem.parentCollection()) {
328 // Item did not change and was not moved
329 Q_EMIT itemSaveFinished(None);
330 return;
333 d->mChanger->setGroupwareCommunication(CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication());
335 Akonadi::Item updateItem = d->mItemUi->save(d->mItem);
336 Q_ASSERT(updateItem.id() == d->mItem.id());
337 d->mItem = updateItem;
339 if (d->mItem.isValid()) { // A valid item. Means we're modifying.
340 Q_ASSERT(d->mItem.parentCollection().isValid());
341 KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence(d->mItem);
343 KCalCore::Incidence::Ptr oldPayload = CalendarSupport::incidence(d->mPrevItem);
344 if (d->mItem.parentCollection() == d->mItemUi->selectedCollection()
345 || d->mItem.storageCollectionId() == d->mItemUi->selectedCollection().id()) {
346 d->mChanger->modifyIncidence(d->mItem, oldPayload);
347 } else {
348 Q_ASSERT(d->mItemUi->selectedCollection().isValid());
349 Q_ASSERT(d->mItem.parentCollection().isValid());
351 // ETM and the KSelectionProxyModel has a bug wrt collections moves, so this is disabled.
352 // To test this, enable the collection combo-box and remove the following assert.
353 qCCritical(INCIDENCEEDITOR_LOG) << "Moving between collections is disabled for now: "
354 << d->mItemUi->selectedCollection().id()
355 << d->mItem.parentCollection().id();
356 Q_ASSERT_X(false, "save()", "Moving between collections is disabled for now");
358 if (d->mItemUi->isDirty()) {
359 d->mChanger->modifyIncidence(d->mItem, oldPayload);
360 } else {
361 Akonadi::ItemMoveJob *itemMoveJob =
362 new Akonadi::ItemMoveJob(d->mItem, d->mItemUi->selectedCollection());
363 connect(itemMoveJob, SIGNAL(result(KJob*)), SLOT(itemMoveResult(KJob*)));
366 } else { // An invalid item. Means we're creating.
367 if (d->mIsCounterProposal) {
368 // We don't write back to akonadi, that will be done in ITipHandler.
369 Q_EMIT itemSaveFinished(EditorItemManager::Modify);
370 } else {
371 Q_ASSERT(d->mItemUi->selectedCollection().isValid());
372 KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence(d->mItem);
373 d->mChanger->createIncidence(incidence, d->mItemUi->selectedCollection());
378 void EditorItemManager::setFetchScope(const Akonadi::ItemFetchScope &fetchScope)
380 Q_D(ItemEditor);
381 d->mFetchScope = fetchScope;
384 Akonadi::ItemFetchScope &EditorItemManager::fetchScope()
386 Q_D(ItemEditor);
387 return d->mFetchScope;
390 void EditorItemManager::setIsCounterProposal(bool isCounterProposal)
392 Q_D(ItemEditor);
393 d->mIsCounterProposal = isCounterProposal;
396 Akonadi::Collection EditorItemManager::collection() const
398 Q_D(const ItemEditor);
399 return d->mChanger->lastCollectionUsed();
402 ItemEditorUi::~ItemEditorUi()
406 bool ItemEditorUi::isValid() const
408 return true;
411 } // namespace
413 #include "moc_editoritemmanager.cpp"