diff --git a/src/editoritemmanager.cpp b/src/editoritemmanager.cpp index 4743bc4..e3c934a 100644 --- a/src/editoritemmanager.cpp +++ b/src/editoritemmanager.cpp @@ -1,423 +1,411 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "editoritemmanager.h" #include "individualmailcomponentfactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "incidenceeditor_debug.h" #include #include /// ItemEditorPrivate namespace IncidenceEditorNG { class ItemEditorPrivate { EditorItemManager *q_ptr; Q_DECLARE_PUBLIC(EditorItemManager) public: Akonadi::Item mItem; Akonadi::Item mPrevItem; Akonadi::ItemFetchScope mFetchScope; Akonadi::Monitor *mItemMonitor; ItemEditorUi *mItemUi; bool mIsCounterProposal; EditorItemManager::SaveAction currentAction; Akonadi::IncidenceChanger *mChanger; public: ItemEditorPrivate(Akonadi::IncidenceChanger *changer, EditorItemManager *qq); void itemChanged(const Akonadi::Item &, const QSet &); void itemFetchResult(KJob *job); void itemMoveResult(KJob *job); void onModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString); void onCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString); void setupMonitor(); void moveJobFinished(KJob *job); void setItem(const Akonadi::Item &item); }; ItemEditorPrivate::ItemEditorPrivate(Akonadi::IncidenceChanger *changer, EditorItemManager *qq) : q_ptr(qq) , mItemMonitor(nullptr) , mIsCounterProposal(false) , currentAction(EditorItemManager::None) { mFetchScope.fetchFullPayload(); mFetchScope.setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); mFetchScope.setFetchTags(true); mFetchScope.tagFetchScope().setFetchIdOnly(false); mChanger = changer ? changer : new Akonadi::IncidenceChanger(new IndividualMailComponentFactory( qq), qq); qq->connect(mChanger, SIGNAL(modifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode, QString)), qq, SLOT(onModifyFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode, QString))); qq->connect(mChanger, SIGNAL(createFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode, QString)), qq, SLOT(onCreateFinished(int,Akonadi::Item,Akonadi::IncidenceChanger::ResultCode, QString))); } void ItemEditorPrivate::moveJobFinished(KJob *job) { Q_Q(EditorItemManager); if (job->error()) { qCCritical(INCIDENCEEDITOR_LOG) << "Error while moving and modifying " << job->errorString(); mItemUi->reject(ItemEditorUi::ItemMoveFailed, job->errorString()); } else { Akonadi::Item item(mItem.id()); currentAction = EditorItemManager::MoveAndModify; q->load(item); } } void ItemEditorPrivate::itemFetchResult(KJob *job) { Q_ASSERT(job); Q_Q(EditorItemManager); EditorItemManager::SaveAction action = currentAction; currentAction = EditorItemManager::None; if (job->error()) { mItemUi->reject(ItemEditorUi::ItemFetchFailed, job->errorString()); return; } Akonadi::ItemFetchJob *fetchJob = qobject_cast(job); if (fetchJob->items().isEmpty()) { mItemUi->reject(ItemEditorUi::ItemFetchFailed); return; } Akonadi::Item item = fetchJob->items().at(0); if (mItemUi->hasSupportedPayload(item)) { setItem(item); if (action != EditorItemManager::None) { // Finally enable ok/apply buttons, we've finished loading Q_EMIT q->itemSaveFinished(action); } } else { mItemUi->reject(ItemEditorUi::ItemHasInvalidPayload); } } void ItemEditorPrivate::setItem(const Akonadi::Item &item) { Q_ASSERT(item.hasPayload()); mPrevItem = item; mItem = item; mItemUi->load(item); setupMonitor(); } void ItemEditorPrivate::itemMoveResult(KJob *job) { Q_ASSERT(job); Q_Q(EditorItemManager); if (job->error()) { Akonadi::ItemMoveJob *moveJob = qobject_cast(job); Q_ASSERT(moveJob); Q_UNUSED(moveJob); //Q_ASSERT(!moveJob->items().isEmpty()); // TODO: What is reasonable behavior at this point? qCCritical(INCIDENCEEDITOR_LOG) << "Error while moving item ";// << moveJob->items().first().id() << " to collection " //<< moveJob->destinationCollection() << job->errorString(); Q_EMIT q->itemSaveFailed(EditorItemManager::Move, job->errorString()); } else { // Fetch the item again, we want a new mItem, which has an updated parentCollection Akonadi::Item item(mItem.id()); // set currentAction, so the fetchResult slot emits itemSavedFinished(Move); // We could emit it here, but we should only enable ok/apply buttons after the loading // is complete currentAction = EditorItemManager::Move; q->load(item); } } void ItemEditorPrivate::onModifyFinished(int, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) { Q_Q(EditorItemManager); if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) { if (mItem.parentCollection() == mItemUi->selectedCollection() || mItem.storageCollectionId() == mItemUi->selectedCollection().id()) { mItem = item; Q_EMIT q->itemSaveFinished(EditorItemManager::Modify); setupMonitor(); } else { // There's a collection move too. Akonadi::ItemMoveJob *moveJob = new Akonadi::ItemMoveJob(mItem, mItemUi->selectedCollection()); q->connect(moveJob, SIGNAL(result(KJob *)), SLOT(moveJobFinished(KJob *))); } } else if (resultCode == Akonadi::IncidenceChanger::ResultCodeUserCanceled) { Q_EMIT q->itemSaveFailed(EditorItemManager::Modify, QString()); q->load(Akonadi::Item(mItem.id())); } else { qCCritical(INCIDENCEEDITOR_LOG) << "Modify failed " << errorString; Q_EMIT q->itemSaveFailed(EditorItemManager::Modify, errorString); } } void ItemEditorPrivate::onCreateFinished(int, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString) { Q_Q(EditorItemManager); if (resultCode == Akonadi::IncidenceChanger::ResultCodeSuccess) { currentAction = EditorItemManager::Create; q->load(item); setupMonitor(); } else { qCCritical(INCIDENCEEDITOR_LOG) << "Creation failed " << errorString; Q_EMIT q->itemSaveFailed(EditorItemManager::Create, errorString); } } void ItemEditorPrivate::setupMonitor() { // Q_Q(EditorItemManager); delete mItemMonitor; mItemMonitor = new Akonadi::Monitor; mItemMonitor->ignoreSession(Akonadi::Session::defaultSession()); mItemMonitor->itemFetchScope().fetchFullPayload(); if (mItem.isValid()) { mItemMonitor->setItemMonitored(mItem); } // q->connect(mItemMonitor, SIGNAL(itemChanged(Akonadi::Item,QSet)), // SLOT(itemChanged(Akonadi::Item,QSet))); } void ItemEditorPrivate::itemChanged(const Akonadi::Item &item, const QSet &partIdentifiers) { Q_Q(EditorItemManager); if (mItemUi->containsPayloadIdentifiers(partIdentifiers)) { QPointer dlg = new QMessageBox; //krazy:exclude=qclasses dlg->setIcon(QMessageBox::Question); dlg->setInformativeText(i18n("The item has been changed by another application.\n" "What should be done?")); dlg->addButton(i18n("Take over changes"), QMessageBox::AcceptRole); dlg->addButton(i18n("Ignore and Overwrite changes"), QMessageBox::RejectRole); if (dlg->exec() == QMessageBox::AcceptRole) { Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(mItem); job->setFetchScope(mFetchScope); mItem = item; q->load(mItem); } else { mItem.setRevision(item.revision()); q->save(); } delete dlg; } // Overwrite or not, we need to update the revision and the remote id to be able // to store item later on. mItem.setRevision(item.revision()); } /// ItemEditor EditorItemManager::EditorItemManager(ItemEditorUi *ui, Akonadi::IncidenceChanger *changer) : d_ptr(new ItemEditorPrivate(changer, this)) { Q_D(ItemEditor); d->mItemUi = ui; } EditorItemManager::~EditorItemManager() { delete d_ptr; } Akonadi::Item EditorItemManager::item(ItemState state) const { Q_D(const ItemEditor); switch (state) { case EditorItemManager::AfterSave: if (d->mItem.hasPayload()) { return d->mItem; } else { qCDebug(INCIDENCEEDITOR_LOG) << "Won't return mItem because isValid = " << d->mItem.isValid() << "; and haPayload is " << d->mItem.hasPayload(); } break; case EditorItemManager::BeforeSave: if (d->mPrevItem.hasPayload()) { return d->mPrevItem; } else { qCDebug(INCIDENCEEDITOR_LOG) << "Won't return mPrevItem because isValid = " << d->mPrevItem.isValid() << "; and haPayload is " << d->mPrevItem.hasPayload(); } break; default: qCDebug(INCIDENCEEDITOR_LOG) << "state = " << state; Q_ASSERT_X(false, "EditorItemManager::item", "Unknown enum value"); } return Akonadi::Item(); } void EditorItemManager::load(const Akonadi::Item &item) { Q_D(ItemEditor); //We fetch anyways to make sure we have everything required including tags Akonadi::ItemFetchJob *job = new Akonadi::ItemFetchJob(item, this); job->setFetchScope(d->mFetchScope); connect(job, SIGNAL(result(KJob *)), SLOT(itemFetchResult(KJob *))); } void EditorItemManager::save() { Q_D(ItemEditor); if (!d->mItemUi->isValid()) { Q_EMIT itemSaveFailed(d->mItem.isValid() ? Modify : Create, QString()); return; } if (!d->mItemUi->isDirty() && d->mItemUi->selectedCollection() == d->mItem.parentCollection()) { // Item did not change and was not moved Q_EMIT itemSaveFinished(None); return; } d->mChanger->setGroupwareCommunication( CalendarSupport::KCalPrefs::instance()->useGroupwareCommunication()); Akonadi::Item updateItem = d->mItemUi->save(d->mItem); Q_ASSERT(updateItem.id() == d->mItem.id()); d->mItem = updateItem; if (d->mItem.isValid()) { // A valid item. Means we're modifying. Q_ASSERT(d->mItem.parentCollection().isValid()); KCalCore::Incidence::Ptr oldPayload = CalendarSupport::incidence(d->mPrevItem); if (d->mItem.parentCollection() == d->mItemUi->selectedCollection() || d->mItem.storageCollectionId() == d->mItemUi->selectedCollection().id()) { d->mChanger->modifyIncidence(d->mItem, oldPayload); } else { Q_ASSERT(d->mItemUi->selectedCollection().isValid()); Q_ASSERT(d->mItem.parentCollection().isValid()); // ETM and the KSelectionProxyModel has a bug wrt collections moves, so this is disabled. // To test this, enable the collection combo-box and remove the following assert. qCCritical(INCIDENCEEDITOR_LOG) << "Moving between collections is disabled for now: " << d->mItemUi->selectedCollection().id() << d->mItem.parentCollection().id(); Q_ASSERT_X(false, "save()", "Moving between collections is disabled for now"); if (d->mItemUi->isDirty()) { d->mChanger->modifyIncidence(d->mItem, oldPayload); } else { Akonadi::ItemMoveJob *itemMoveJob = new Akonadi::ItemMoveJob(d->mItem, d->mItemUi->selectedCollection()); connect(itemMoveJob, SIGNAL(result(KJob *)), SLOT(itemMoveResult(KJob *))); } } } else { // An invalid item. Means we're creating. if (d->mIsCounterProposal) { // We don't write back to akonadi, that will be done in ITipHandler. Q_EMIT itemSaveFinished(EditorItemManager::Modify); } else { Q_ASSERT(d->mItemUi->selectedCollection().isValid()); KCalCore::Incidence::Ptr incidence = CalendarSupport::incidence(d->mItem); d->mChanger->createIncidence(incidence, d->mItemUi->selectedCollection()); } } } -void EditorItemManager::setFetchScope(const Akonadi::ItemFetchScope &fetchScope) -{ - Q_D(ItemEditor); - d->mFetchScope = fetchScope; -} - -Akonadi::ItemFetchScope &EditorItemManager::fetchScope() -{ - Q_D(ItemEditor); - return d->mFetchScope; -} - void EditorItemManager::setIsCounterProposal(bool isCounterProposal) { Q_D(ItemEditor); d->mIsCounterProposal = isCounterProposal; } Akonadi::Collection EditorItemManager::collection() const { Q_D(const ItemEditor); return d->mChanger->lastCollectionUsed(); } ItemEditorUi::~ItemEditorUi() { } bool ItemEditorUi::isValid() const { return true; } } // namespace #include "moc_editoritemmanager.cpp" diff --git a/src/editoritemmanager.h b/src/editoritemmanager.h index c4d927b..65d6756 100644 --- a/src/editoritemmanager.h +++ b/src/editoritemmanager.h @@ -1,221 +1,195 @@ /* Copyright (c) 2010 Bertjan Broeksema Copyright (C) 2010 Klaralvdalens Datakonsult AB, a KDAB Group company This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef INCIDENCEEDITOR_EDITORITEMMANAGER_H #define INCIDENCEEDITOR_EDITORITEMMANAGER_H #include #include #include namespace Akonadi { class Collection; class Item; class ItemFetchScope; } class KJob; namespace IncidenceEditorNG { class ItemEditorUi; class ItemEditorPrivate; /** * Helper class for creating dialogs that let the user create and edit the payload * of Akonadi items (e.g. events, contacts, etc). This class supports editting of * one item at a time and hanldes all Akonadi specific logic like Item creation, * Item modifying and monitoring of changes to the item during editing. */ // template class EditorItemManager : public QObject { Q_OBJECT public: /** * Creates an ItemEditor for a new Item. * Receives an option IncidenceChanger, so you can share the undo/redo stack with your * application. */ EditorItemManager(ItemEditorUi *ui, Akonadi::IncidenceChanger *changer = nullptr); /** * Destructs the ItemEditor. Unsaved changes will get lost at this point. */ ~EditorItemManager(); enum ItemState { AfterSave, /**< Returns the last saved item */ BeforeSave /**< Returns an item with the original payload before the last save call */ }; /** * Returns the last saved item with payload or an invalid item when save is * not called yet. */ Akonadi::Item item(ItemState state = AfterSave) const; /** * Loads the @param item into the editor. The item passed must be * a valid item. */ void load(const Akonadi::Item &item); /** * Saves the new or modified item. This method does nothing when the * ui is not dirty. */ void save(); - /** - * Sets the item fetch scope. - * - * Controls how much of an item's data is fetched from the server, e.g. - * whether to fetch the full item payload or only meta data. - * - * @param fetchScope The new scope for item fetch operations. - * - * @see fetchScope() - */ - void setFetchScope(const Akonadi::ItemFetchScope &fetchScope); - - /** - * Returns the item fetch scope. - * - * Since this returns a reference it can be used to conveniently modify the - * current scope in-place, i.e. by calling a method on the returned reference - * without storing it in a local variable. See the ItemFetchScope documentation - * for an example. - * - * @return a reference to the current item fetch scope - * - * @see setFetchScope() for replacing the current item fetch scope - */ - Akonadi::ItemFetchScope &fetchScope(); - /** * Returns the collection where the last item was created. * Or an invalid collection if none was created. */ Akonadi::Collection collection() const; enum SaveAction { Create, /**< A new item was created */ Modify, /**< An existing item was modified */ None, /**< Nothing happened. */ Move, /**< An existing item was moved to another collection */ MoveAndModify /**< An existing item was moved to another collection and modified */ }; void setIsCounterProposal(bool isCounterProposal); Q_SIGNALS: void itemSaveFinished(IncidenceEditorNG::EditorItemManager::SaveAction action); void itemSaveFailed(IncidenceEditorNG::EditorItemManager::SaveAction action, const QString &message); void revertFinished(); void revertFailed(const QString &message); private: ItemEditorPrivate *const d_ptr; Q_DECLARE_PRIVATE(ItemEditor) Q_DISABLE_COPY(EditorItemManager) Q_PRIVATE_SLOT(d_ptr, void itemChanged(const Akonadi::Item &, const QSet &)) Q_PRIVATE_SLOT(d_ptr, void itemFetchResult(KJob *)) Q_PRIVATE_SLOT(d_ptr, void itemMoveResult(KJob *)) Q_PRIVATE_SLOT(d_ptr, void onModifyFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)) Q_PRIVATE_SLOT(d_ptr, void onCreateFinished(int changeId, const Akonadi::Item &item, Akonadi::IncidenceChanger::ResultCode resultCode, const QString &errorString)) Q_PRIVATE_SLOT(d_ptr, void moveJobFinished(KJob *job)) }; class ItemEditorUi { public: enum RejectReason { ItemFetchFailed, ///> Either the fetchjob failed or no items where returned ItemHasInvalidPayload, ///> The fetched item has an invalid payload ItemMoveFailed ///> Item move failed }; virtual ~ItemEditorUi(); /** * Returns whether or not the identifier set contains payload identifiers that * are displayed/editable in the Gui. */ virtual bool containsPayloadIdentifiers(const QSet &partIdentifiers) const = 0; /** * Returns whether or not @param item has a payload type that is supported by * the gui. */ virtual bool hasSupportedPayload(const Akonadi::Item &item) const = 0; /** * Returns whether or not the values in the ui differ from the original (i.e. * either an empty or a loaded item). This method only involves * payload fields. I.e. if only the collection in which the item should be * stored has changed, this method should return false. */ virtual bool isDirty() const = 0; /** * Returns whether or not the values in the ui are valid. This method can also * be used to update the ui if necessary. The default implementation returns * true, so if the ui doesn't need validation there is no need to reimplement * this method. */ virtual bool isValid() const; /** * Fills the ui with the values of the payload of @param item. The item is * guaranteed to have a payload. */ virtual void load(const Akonadi::Item &item) = 0; /** * Stores the values of the ui into the payload of @param item and returns the * item with an updated payload. The returned item must have a valid mimetype * too. */ virtual Akonadi::Item save(const Akonadi::Item &item) = 0; /** * Returns the currently sellected collection in which the item will be stored. */ virtual Akonadi::Collection selectedCollection() const = 0; /** * This function is called if for some reason the creation or editting of the * item cannot be continued. The implementing class must abort editting at * this point. */ virtual void reject(RejectReason reason, const QString &errorMessage = QString()) = 0; }; } #endif