kde-playground/kdepimlibs/akonadi/collectionmodel_p.cpp
2015-04-14 21:49:29 +00:00

334 lines
12 KiB
C++

/*
Copyright (c) 2006 - 2008 Volker Krause <vkrause@kde.org>
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.
*/
//@cond PRIVATE
#include "collectionmodel_p.h"
#include "collectionmodel.h"
#include "collectionutils_p.h"
#include "collectionfetchjob.h"
#include "collectionstatistics.h"
#include "collectionstatisticsjob.h"
#include "monitor.h"
#include "session.h"
#include <kdebug.h>
#include <kjob.h>
#include <kiconloader.h>
#include <QCoreApplication>
#include <QtCore/QTimer>
#include "collectionfetchscope.h"
using namespace Akonadi;
void CollectionModelPrivate::collectionRemoved(const Akonadi::Collection &collection)
{
Q_Q(CollectionModel);
QModelIndex colIndex = indexForId(collection.id());
if (colIndex.isValid()) {
QModelIndex parentIndex = q->parent(colIndex);
// collection is still somewhere in the hierarchy
removeRowFromModel(colIndex.row(), parentIndex);
} else {
if (collections.contains(collection.id())) {
// collection is orphan, ie. the parent has been removed already
collections.remove(collection.id());
childCollections.remove(collection.id());
}
}
}
void CollectionModelPrivate::collectionChanged(const Akonadi::Collection &collection)
{
Q_Q(CollectionModel);
// What kind of change is it ?
Collection::Id oldParentId = collections.value(collection.id()).parentCollection().id();
Collection::Id newParentId = collection.parentCollection().id();
if (newParentId != oldParentId && oldParentId >= 0) { // It's a move
removeRowFromModel(indexForId(collections[collection.id()].id()).row(), indexForId(oldParentId));
Collection newParent;
if (newParentId == Collection::root().id()) {
newParent = Collection::root();
} else {
newParent = collections.value(newParentId);
}
CollectionFetchJob *job = new CollectionFetchJob(newParent, CollectionFetchJob::Recursive, session);
job->fetchScope().setIncludeUnsubscribed(unsubscribed);
job->fetchScope().setIncludeStatistics(fetchStatistics);
q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
q, SLOT(collectionsChanged(Akonadi::Collection::List)));
q->connect(job, SIGNAL(result(KJob*)),
q, SLOT(listDone(KJob*)));
} else { // It's a simple change
CollectionFetchJob *job = new CollectionFetchJob(collection, CollectionFetchJob::Base, session);
job->fetchScope().setIncludeUnsubscribed(unsubscribed);
job->fetchScope().setIncludeStatistics(fetchStatistics);
q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
q, SLOT(collectionsChanged(Akonadi::Collection::List)));
q->connect(job, SIGNAL(result(KJob*)),
q, SLOT(listDone(KJob*)));
}
}
void CollectionModelPrivate::updateDone(KJob *job)
{
if (job->error()) {
// TODO: handle job errors
kWarning() << "Job error:" << job->errorString();
} else {
CollectionStatisticsJob *csjob = static_cast<CollectionStatisticsJob *>(job);
Collection result = csjob->collection();
collectionStatisticsChanged(result.id(), csjob->statistics());
}
}
void CollectionModelPrivate::collectionStatisticsChanged(Collection::Id collection,
const Akonadi::CollectionStatistics &statistics)
{
Q_Q(CollectionModel);
if (!collections.contains(collection)) {
kWarning() << "Got statistics response for non-existing collection:" << collection;
} else {
collections[collection].setStatistics(statistics);
Collection col = collections.value(collection);
QModelIndex startIndex = indexForId(col.id());
QModelIndex endIndex = indexForId(col.id(), q->columnCount(q->parent(startIndex)) - 1);
emit q->dataChanged(startIndex, endIndex);
}
}
void CollectionModelPrivate::listDone(KJob *job)
{
if (job->error()) {
kWarning() << "Job error: " << job->errorString() << endl;
}
}
void CollectionModelPrivate::editDone(KJob *job)
{
if (job->error()) {
kWarning() << "Edit failed: " << job->errorString();
}
}
void CollectionModelPrivate::dropResult(KJob *job)
{
if (job->error()) {
kWarning() << "Paste failed:" << job->errorString();
// TODO: error handling
}
}
void CollectionModelPrivate::collectionsChanged(const Collection::List &cols)
{
Q_Q(CollectionModel);
foreach (Collection col, cols) { //krazy:exclude=foreach non-const is needed here
if (collections.contains(col.id())) {
// If the collection is already known to the model, we simply update it...
col.setStatistics(collections.value(col.id()).statistics());
collections[col.id()] = col;
QModelIndex startIndex = indexForId(col.id());
QModelIndex endIndex = indexForId(col.id(), q->columnCount(q->parent(startIndex)) - 1);
emit q->dataChanged(startIndex, endIndex);
continue;
}
// ... otherwise we add it to the set of collections we need to handle.
m_newChildCollections[col.parentCollection().id()].append(col.id());
m_newCollections.insert(col.id(), col);
}
// Handle the collections in m_newChildCollections. If the collections
// parent is already in the model, the collection can be added to the model.
// Otherwise it is persisted until it has a valid parent in the model.
int currentSize = m_newChildCollections.size();
int lastSize = -1;
while (currentSize > 0) {
lastSize = currentSize;
QMutableHashIterator< Collection::Id, QVector< Collection::Id > > i(m_newChildCollections);
while (i.hasNext()) {
i.next();
// the key is the parent of new collections. It may itself also be new,
// but that will be handled later.
Collection::Id colId = i.key();
QVector< Collection::Id > newChildCols = i.value();
int newChildCount = newChildCols.size();
// if ( newChildCount == 0 )
// {
// // Sanity check.
// kDebug() << "No new child collections have been added to the collection:" << colId;
// i.remove();
// currentSize--;
// break;
// }
if (collections.contains(colId) || colId == Collection::root().id()) {
QModelIndex parentIndex = indexForId(colId);
int currentChildCount = childCollections.value(colId).size();
q->beginInsertRows(parentIndex,
currentChildCount, // Start index is at the end of existing collections.
currentChildCount + newChildCount - 1); // End index is the result of the insertion.
foreach (Collection::Id id, newChildCols) {
Collection c = m_newCollections.take(id);
collections.insert(id, c);
}
childCollections[colId] << newChildCols;
q->endInsertRows();
i.remove();
currentSize--;
break;
}
}
// We iterated through once without adding any more collections to the model.
if (currentSize == lastSize) {
// The remaining collections in the list do not have a valid parent in the model yet. They
// might arrive in the next batch from the monitor, so they're still in m_newCollections
// and m_newChildCollections.
kDebug() << "Some collections did not have a parent in the model yet!";
break;
}
}
}
QModelIndex CollectionModelPrivate::indexForId(Collection::Id id, int column) const
{
Q_Q(const CollectionModel);
if (!collections.contains(id)) {
return QModelIndex();
}
Collection::Id parentId = collections.value(id).parentCollection().id();
// check if parent still exist or if this is an orphan collection
if (parentId != Collection::root().id() && !collections.contains(parentId)) {
return QModelIndex();
}
QVector<Collection::Id> list = childCollections.value(parentId);
int row = list.indexOf(id);
if (row >= 0) {
return q->createIndex(row, column, reinterpret_cast<void *>(collections.value(list.at(row)).id()));
}
return QModelIndex();
}
bool CollectionModelPrivate::removeRowFromModel(int row, const QModelIndex &parent)
{
Q_Q(CollectionModel);
QVector<Collection::Id> list;
Collection parentCol;
if (parent.isValid()) {
parentCol = collections.value(parent.internalId());
Q_ASSERT(parentCol.id() == parent.internalId());
list = childCollections.value(parentCol.id());
} else {
parentCol = Collection::root();
list = childCollections.value(Collection::root().id());
}
if (row < 0 || row >= list.size()) {
kWarning() << "Index out of bounds:" << row << " parent:" << parentCol.id();
return false;
}
q->beginRemoveRows(parent, row, row);
const Collection::Id delColId = list[row];
list.remove(row);
foreach (Collection::Id childColId, childCollections[delColId]) {
collections.remove(childColId);
}
collections.remove(delColId);
childCollections.remove(delColId); // remove children of deleted collection
childCollections.insert(parentCol.id(), list); // update children of parent
q->endRemoveRows();
return true;
}
bool CollectionModelPrivate::supportsContentType(const QModelIndex &index, const QStringList &contentTypes)
{
if (!index.isValid()) {
return false;
}
Collection col = collections.value(index.internalId());
Q_ASSERT(col.isValid());
QStringList ct = col.contentMimeTypes();
foreach (const QString &a, ct) {
if (contentTypes.contains(a)) {
return true;
}
}
return false;
}
void CollectionModelPrivate::init()
{
Q_Q(CollectionModel);
session = new Session(QCoreApplication::instance()->applicationName().toUtf8()
+ QByteArray("-CollectionModel-") + QByteArray::number(qrand()), q);
QTimer::singleShot(0, q, SLOT(startFirstListJob()));
// monitor collection changes
monitor = new Monitor();
monitor->setCollectionMonitored(Collection::root());
monitor->fetchCollection(true);
// ### Hack to get the kmail resource folder icons
KIconLoader::global()->addAppDir(QLatin1String("kmail"));
KIconLoader::global()->addAppDir(QLatin1String("kdepim"));
// monitor collection changes
q->connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection)),
q, SLOT(collectionChanged(Akonadi::Collection)));
q->connect(monitor, SIGNAL(collectionAdded(Akonadi::Collection,Akonadi::Collection)),
q, SLOT(collectionChanged(Akonadi::Collection)));
q->connect(monitor, SIGNAL(collectionRemoved(Akonadi::Collection)),
q, SLOT(collectionRemoved(Akonadi::Collection)));
q->connect(monitor, SIGNAL(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)),
q, SLOT(collectionStatisticsChanged(Akonadi::Collection::Id,Akonadi::CollectionStatistics)));
}
void CollectionModelPrivate::startFirstListJob()
{
Q_Q(CollectionModel);
// start a list job
CollectionFetchJob *job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive, session);
job->fetchScope().setIncludeUnsubscribed(unsubscribed);
job->fetchScope().setIncludeStatistics(fetchStatistics);
q->connect(job, SIGNAL(collectionsReceived(Akonadi::Collection::List)),
q, SLOT(collectionsChanged(Akonadi::Collection::List)));
q->connect(job, SIGNAL(result(KJob*)), q, SLOT(listDone(KJob*)));
}
//@endcond