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
21 #include "editoritemmanager.h"
22 #include "individualmailcomponentfactory.h"
24 #include <calendarsupport/utils.h>
25 #include <calendarsupport/kcalprefs.h>
28 #include <ItemDeleteJob>
29 #include <ItemFetchJob>
30 #include <ItemFetchScope>
31 #include <ItemMoveJob>
34 #include <AkonadiCore/TagFetchScope>
37 #include <KLocalizedString>
38 #include "incidenceeditor_debug.h"
40 #include <QMessageBox>
45 namespace IncidenceEditorNG
48 class ItemEditorPrivate
50 EditorItemManager
*q_ptr
;
51 Q_DECLARE_PUBLIC(EditorItemManager
)
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
;
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
);
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
);
104 qCCritical(INCIDENCEEDITOR_LOG
) << "Error while moving and modifying " << job
->errorString();
105 mItemUi
->reject(ItemEditorUi::ItemMoveFailed
, job
->errorString());
107 Akonadi::Item
item(mItem
.id());
108 currentAction
= EditorItemManager::MoveAndModify
;
113 void ItemEditorPrivate::itemFetchResult(KJob
*job
)
116 Q_Q(EditorItemManager
);
118 EditorItemManager::SaveAction action
= currentAction
;
119 currentAction
= EditorItemManager::None
;
122 mItemUi
->reject(ItemEditorUi::ItemFetchFailed
, job
->errorString());
126 Akonadi::ItemFetchJob
*fetchJob
= qobject_cast
<Akonadi::ItemFetchJob
*>(job
);
127 if (fetchJob
->items().isEmpty()) {
128 mItemUi
->reject(ItemEditorUi::ItemFetchFailed
);
132 Akonadi::Item item
= fetchJob
->items().at(0);
133 if (mItemUi
->hasSupportedPayload(item
)) {
135 if (action
!= EditorItemManager::None
) {
136 // Finally enable ok/apply buttons, we've finished loading
137 Q_EMIT q
->itemSaveFinished(action
);
140 mItemUi
->reject(ItemEditorUi::ItemHasInvalidPayload
);
144 void ItemEditorPrivate::setItem(const Akonadi::Item
&item
)
146 Q_ASSERT(item
.hasPayload());
153 void ItemEditorPrivate::itemMoveResult(KJob
*job
)
156 Q_Q(EditorItemManager
);
159 Akonadi::ItemMoveJob
*moveJob
= qobject_cast
<Akonadi::ItemMoveJob
*>(job
);
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());
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
173 currentAction
= EditorItemManager::Move
;
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()) {
187 Q_EMIT q
->itemSaveFinished(EditorItemManager::Modify
);
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()));
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
;
213 qCCritical(INCIDENCEEDITOR_LOG
) << "Creation failed " << errorString
;
214 Q_EMIT q
->itemSaveFailed(EditorItemManager::Create
, errorString
);
218 void ItemEditorPrivate::setupMonitor()
220 // Q_Q(EditorItemManager);
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
);
253 mItem
.setRevision(item
.revision());
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());
267 EditorItemManager::EditorItemManager(ItemEditorUi
*ui
, Akonadi::IncidenceChanger
*changer
)
268 : d_ptr(new ItemEditorPrivate(changer
, this))
274 EditorItemManager::~EditorItemManager()
279 Akonadi::Item
EditorItemManager::item(ItemState state
) const
281 Q_D(const ItemEditor
);
284 case EditorItemManager::AfterSave
:
285 if (d
->mItem
.hasPayload()) {
288 qCDebug(INCIDENCEEDITOR_LOG
) << "Won't return mItem because isValid = " << d
->mItem
.isValid()
289 << "; and haPayload is " << d
->mItem
.hasPayload();
292 case EditorItemManager::BeforeSave
:
293 if (d
->mPrevItem
.hasPayload()) {
296 qCDebug(INCIDENCEEDITOR_LOG
) << "Won't return mPrevItem because isValid = " << d
->mPrevItem
.isValid()
297 << "; and haPayload is " << d
->mPrevItem
.hasPayload();
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
)
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()
321 if (!d
->mItemUi
->isValid()) {
322 Q_EMIT
itemSaveFailed(d
->mItem
.isValid() ? Modify
: Create
, QString());
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
);
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
);
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
);
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
);
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
)
381 d
->mFetchScope
= fetchScope
;
384 Akonadi::ItemFetchScope
&EditorItemManager::fetchScope()
387 return d
->mFetchScope
;
390 void EditorItemManager::setIsCounterProposal(bool isCounterProposal
)
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
413 #include "moc_editoritemmanager.cpp"