kde-playground/kdepim/incidenceeditor-ng/editoritemmanager.cpp

403 lines
13 KiB
C++
Raw Permalink Normal View History

/*
Copyright (c) 2010 Bertjan Broeksema <broeksema@kde.org>
Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
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 <calendarsupport/utils.h>
#include <calendarsupport/kcalprefs.h>
#include <Akonadi/Item>
#include <Akonadi/ItemDeleteJob>
#include <Akonadi/ItemFetchJob>
#include <Akonadi/ItemFetchScope>
#include <Akonadi/ItemMoveJob>
#include <Akonadi/Monitor>
#include <Akonadi/Session>
#include <KJob>
#include <KLocalizedString>
#include <QMessageBox>
#include <QPointer>
/// 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<QByteArray> & );
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 );
};
ItemEditorPrivate::ItemEditorPrivate( Akonadi::IncidenceChanger *changer, EditorItemManager *qq )
: q_ptr( qq ), mItemMonitor( 0 ), mIsCounterProposal( false )
{
mFetchScope.fetchFullPayload();
mFetchScope.setAncestorRetrieval( Akonadi::ItemFetchScope::Parent );
mChanger = changer ? changer : new Akonadi::IncidenceChanger( 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() ) {
kError() << "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<Akonadi::ItemFetchJob*>( job );
if ( fetchJob->items().isEmpty() ) {
mItemUi->reject( ItemEditorUi::ItemFetchFailed );
return;
}
Akonadi::Item item = fetchJob->items().first();
if ( mItemUi->hasSupportedPayload( item ) ) {
q->load( item );
if ( action != EditorItemManager::None ) {
// Finally enable ok/apply buttons, we've finished loading
emit q->itemSaveFinished( action );
}
} else {
mItemUi->reject( ItemEditorUi::ItemHasInvalidPayload );
}
}
void ItemEditorPrivate::itemMoveResult( KJob *job )
{
Q_ASSERT( job );
Q_Q( EditorItemManager );
if ( job->error() ) {
Akonadi::ItemMoveJob *moveJob = qobject_cast<Akonadi::ItemMoveJob*>( job );
Q_ASSERT( moveJob );
Q_UNUSED( moveJob );
//Q_ASSERT( !moveJob->items().isEmpty() );
// TODO: What is reasonable behavior at this point?
kError() << "Error while moving item ";// << moveJob->items().first().id() << " to collection "
//<< moveJob->destinationCollection() << job->errorString();
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 = item;
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 ) {
emit q->itemSaveFailed( EditorItemManager::Modify, QString() );
q->load( Akonadi::Item( mItem.id() ) );
} else {
kError() << "Modify failed " << errorString;
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 ) {
q->load( item );
emit q->itemSaveFinished( EditorItemManager::Create );
setupMonitor();
} else {
kError() << "Creation failed " << errorString;
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<QByteArray>)),
// SLOT(itemChanged(Akonadi::Item,QSet<QByteArray>)) );
}
void ItemEditorPrivate::itemChanged( const Akonadi::Item &item,
const QSet<QByteArray> &partIdentifiers )
{
Q_Q( EditorItemManager );
if ( mItemUi->containsPayloadIdentifiers( partIdentifiers ) ) {
QPointer<QMessageBox> 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 {
kDebug() << "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 {
kDebug() << "Won't return mPrevItem because isValid = " << d->mPrevItem.isValid()
<< "; and haPayload is " << d->mPrevItem.hasPayload();
}
break;
default:
kDebug() << "state = " << state;
Q_ASSERT_X( false, "EditorItemManager::item", "Unknown enum value" ) ;
}
return Akonadi::Item();
}
void EditorItemManager::load( const Akonadi::Item &item )
{
Q_D( ItemEditor );
if ( item.hasPayload() ) {
d->mPrevItem = item;
d->mItem = item;
d->mItemUi->load( item );
d->setupMonitor();
} else {
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() ) {
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
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 incidence = CalendarSupport::incidence( d->mItem );
KCalCore::Incidence::Ptr oldPayload = CalendarSupport::incidence( d->mPrevItem );
if ( d->mItem.parentCollection() == d->mItemUi->selectedCollection() ) {
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.
kError() << "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.
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"