diff --git a/akonadi/calendar/etmcalendar.cpp b/akonadi/calendar/etmcalendar.cpp index 23d4cb96d..bf9bb8e7c 100644 --- a/akonadi/calendar/etmcalendar.cpp +++ b/akonadi/calendar/etmcalendar.cpp @@ -1,834 +1,840 @@ /* Copyright (C) 2011-2013 Sérgio Martins 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 "etmcalendar.h" #include "etmcalendar_p.h" #include "blockalarmsattribute.h" #include "calendarmodel_p.h" #include "kcolumnfilterproxymodel_p.h" #include "calfilterproxymodel_p.h" #include "calfilterpartstatusproxymodel_p.h" #include "utils_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include using namespace Akonadi; using namespace KCalCore; CalendarNamespaceProxyModel::CalendarNamespaceProxyModel(QObject *parent) : QIdentityProxyModel(parent) { } void CalendarNamespaceProxyModel::setSourceModel(QAbstractItemModel *newSourceModel) { if (sourceModel()) { disconnect(sourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(onRowsInserted(QModelIndex, int, int))); disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(onDataChanged(QModelIndex, QModelIndex))); } connect(newSourceModel, SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(onRowsInserted(QModelIndex, int, int))); connect(newSourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(onDataChanged(QModelIndex, QModelIndex))); QIdentityProxyModel::setSourceModel(newSourceModel); onRowsInserted(QModelIndex(), 0, sourceModel()->rowCount()); } bool CalendarNamespaceProxyModel::isLoaded() const { return mCollectionJobs.isEmpty(); } bool CalendarNamespaceProxyModel::applyCollection(const Akonadi::Collection &collection, const Akonadi::Item &item) { if (!collection.isValid()) { kWarning() << "Invalid parent."; return false; } KCalCore::Incidence::Ptr incidence = Akonadi::CalendarUtils::incidence(item); if (incidence) { const bool newReadOnly = !(collection.rights() & Akonadi::Collection::CanChangeItem); QString newCustomProperty; if (collection.attribute()) { newCustomProperty = QString::fromLatin1(collection.attribute()->collectionNamespace()); } else { newCustomProperty = QString::fromLatin1(""); } const QString customProperty = incidence->customProperty("VOLATILE", "CALENDARNAMESPACE"); if (incidence->isReadOnly() != newReadOnly || customProperty != newCustomProperty || customProperty.isNull()) { incidence->interceptUpdates(); incidence->setReadOnly(newReadOnly); incidence->setCustomProperty("VOLATILE", "CALENDARNAMESPACE", newCustomProperty); incidence->cancelUpdates(); return true; } } else { kWarning() << "Not an incidence " << item.id(); } return false; } bool CalendarNamespaceProxyModel::updateItem(Akonadi::Item &item, const Akonadi::Collection &collection) { if (!mCollections.contains(collection.id())) { mCollections.insert(collection.id(), collection); } if (collection.id() == item.storageCollectionId()) { return applyCollection(collection, item); } else { mItemByStorageCollection.insert(item.storageCollectionId(), item); if (mCollections.contains(item.storageCollectionId())) { const Akonadi::Collection collection = mCollections.value(item.storageCollectionId()); return applyCollection(collection, item); } else if (!mCollectionJobs.contains(item.storageCollectionId())) { const Akonadi::Collection collection(item.storageCollectionId()); Akonadi::CollectionFetchJob *job = new Akonadi::CollectionFetchJob(collection, Akonadi::CollectionFetchJob::Base, this); job->fetchScope().fetchAttribute(); connect(job, SIGNAL(result(KJob*)), this, SLOT(collectionFetchResult(KJob*))); mCollectionJobs.insert(collection.id()); } } return false; } void CalendarNamespaceProxyModel::emitDataChanged(const QModelIndex &index) { emit dataChanged(index, index); } void CalendarNamespaceProxyModel::onRowsInserted(const QModelIndex & parent, int start, int end) { for (int row = start; row <= end; row++) { const QModelIndex sourceIndex = sourceModel()->index(row, 0, parent); const QModelIndex index = mapFromSource(sourceIndex); Akonadi::Item item = index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { const Akonadi::Collection collection = parent.data(EntityTreeModel::CollectionRole).value(); updateItem(item, collection); //We're just inserting the item, no dataChanged signal necessary } else { Akonadi::Collection collection = index.data(EntityTreeModel::CollectionRole).value(); if (sourceModel()->hasChildren(sourceIndex)) { onRowsInserted(sourceIndex, 0, sourceModel()->rowCount(sourceIndex) - 1); } } } } void CalendarNamespaceProxyModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { const QModelIndex parent = topLeft.parent(); for (int row = topLeft.row(); row <= bottomRight.row(); row++) { const QModelIndex sourceIndex = sourceModel()->index(row, 0, parent); const QModelIndex index = mapFromSource(sourceIndex); Akonadi::Item item = index.data(EntityTreeModel::ItemRole).value(); if (item.isValid()) { const Akonadi::Collection collection = parent.data(EntityTreeModel::CollectionRole).value(); if (updateItem(item, collection)) { QMetaObject::invokeMethod(this, "emitDataChanged", Qt::QueuedConnection, QGenericReturnArgument(), Q_ARG(QModelIndex, index)); } } } } void CalendarNamespaceProxyModel::collectionFetchResult(KJob* job) { if ( job->error() ) { kWarning() << "Error occurred"; return; } Akonadi::CollectionFetchJob *fetchJob = static_cast( job ); const Akonadi::Collection collection = fetchJob->collections().first(); mCollectionJobs.remove(collection.id()); mCollections.insert(collection.id(), collection); foreach (const Akonadi::Item &item, mItemByStorageCollection.values(collection.id())) { applyCollection(collection, item); //We don't know which index we're looking for, so we emit for all (the item can be in search collections as well) const QModelIndexList indexes = EntityTreeModel::modelIndexesForItem(this, item); foreach (const QModelIndex &index, indexes) { emit dataChanged(index, index); } } } class CollectionFilter : public QSortFilterProxyModel { public: explicit CollectionFilter(QObject *parent = 0): QSortFilterProxyModel (parent) {}; virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const Collection collection = sourceModel()->index(sourceRow, 0, sourceParent).data(Akonadi::EntityTreeModel::CollectionRole).value(); return collection.isValid(); } }; //TODO: implement batchAdding ETMCalendarPrivate::ETMCalendarPrivate(ETMCalendar *qq) : CalendarBasePrivate(qq) , mETM(0) , mFilteredETM(0) , mCheckableProxyModel(0) , mCollectionProxyModel(0) , mCalFilterProxyModel(0) , mCalFilterPartStatusProxyModel(0) , mSelectionProxy(0) , mCollectionFilteringEnabled(true) + , mLoadFromModelTimer(new QTimer) , q(qq) { mListensForNewItems = true; + mLoadFromModelTimer->setInterval(0); + mLoadFromModelTimer->setSingleShot(true); + connect(mLoadFromModelTimer, SIGNAL(timeout()), SLOT(loadFromETM())); } void ETMCalendarPrivate::init() { if (!mETM) { Akonadi::Session *session = new Akonadi::Session("ETMCalendar", q); Akonadi::ChangeRecorder *monitor = new Akonadi::ChangeRecorder(q); connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(onCollectionChanged(Akonadi::Collection,QSet))); Akonadi::ItemFetchScope scope; scope.fetchFullPayload(true); scope.fetchAttribute(); Akonadi::CollectionFetchScope collectionScope; collectionScope.fetchAttribute(); monitor->setSession(session); monitor->setCollectionMonitored(Akonadi::Collection::root()); monitor->fetchCollection(true); monitor->setItemFetchScope(scope); monitor->setCollectionFetchScope(collectionScope); monitor->setAllMonitored(true); QStringList allMimeTypes; allMimeTypes << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType() << KCalCore::Journal::journalMimeType(); foreach(const QString &mimetype, allMimeTypes) { monitor->setMimeTypeMonitored(mimetype, mMimeTypes.isEmpty() || mMimeTypes.contains(mimetype)); } mETM = CalendarModel::create(monitor); mETM->setObjectName("ETM"); mETM->setListFilter(Akonadi::CollectionFetchScope::Display); } setupFilteredETM(); connect(q, SIGNAL(filterChanged()), SLOT(onFilterChanged())); connect(mETM.data(), SIGNAL(collectionPopulated(Akonadi::Collection::Id)), SLOT(onCollectionPopulated(Akonadi::Collection::Id))); connect(mETM.data(), SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(onRowsInserted(QModelIndex,int,int))); connect(mETM.data(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(onDataChanged(QModelIndex,QModelIndex))); connect(mETM.data(), SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), SLOT(onRowsMoved(QModelIndex,int,int,QModelIndex,int))); connect(mETM.data(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(onRowsRemoved(QModelIndex,int,int))); connect(mFilteredETM, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(onDataChangedInFilteredModel(QModelIndex,QModelIndex))); connect(mFilteredETM, SIGNAL(layoutChanged()), SLOT(onLayoutChangedInFilteredModel())); connect(mFilteredETM, SIGNAL(modelReset()), SLOT(onModelResetInFilteredModel())); connect(mFilteredETM, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(onRowsInsertedInFilteredModel(QModelIndex,int,int))); connect(mFilteredETM, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), SLOT(onRowsAboutToBeRemovedInFilteredModel(QModelIndex,int,int))); - loadFromETM(); + //Give the caller a chance to call setCollectionFilteringEnabled before loading data + mLoadFromModelTimer->start(); } void ETMCalendarPrivate::onCollectionChanged(const Akonadi::Collection &collection, const QSet &attributeNames) { Q_ASSERT(collection.isValid()); // Is the collection changed to read-only, we update all Incidences if (attributeNames.contains("AccessRights")) { Akonadi::Item::List items = q->items(); foreach(const Akonadi::Item &item, items) { if (item.storageCollectionId() == collection.id()) { KCalCore::Incidence::Ptr incidence = CalendarUtils::incidence(item); if (incidence) incidence->setReadOnly(!(collection.rights() & Akonadi::Collection::CanChangeItem)); } } } emit q->collectionChanged(collection, attributeNames); } void ETMCalendarPrivate::setupFilteredETM() { mCalendarNamespaceModel = new CalendarNamespaceProxyModel(this); mCalendarNamespaceModel->setSourceModel(mETM.data()); // We're only interested in the CollectionTitle column KColumnFilterProxyModel *columnFilterProxy = new KColumnFilterProxyModel(this); columnFilterProxy->setSourceModel(mCalendarNamespaceModel); columnFilterProxy->setVisibleColumn(CalendarModel::CollectionTitle); columnFilterProxy->setObjectName("Remove columns"); CollectionFilter *mCollectionProxyModel = new CollectionFilter(this); mCollectionProxyModel->setObjectName("Only show collections"); mCollectionProxyModel->setDynamicSortFilter(true); mCollectionProxyModel->setSourceModel(columnFilterProxy); // Keep track of selected items. QItemSelectionModel* selectionModel = new QItemSelectionModel(mCollectionProxyModel); selectionModel->setObjectName("Calendar Selection Model"); // Make item selection work by means of checkboxes. mCheckableProxyModel = new CheckableProxyModel(this); mCheckableProxyModel->setSelectionModel(selectionModel); //FIXME this should be based on the available calendars mCheckableProxyModel->setSourceModel(mCollectionProxyModel); mCheckableProxyModel->setObjectName("Add checkboxes"); mSelectionProxy = new KSelectionProxyModel(selectionModel, /**parent=*/this); mSelectionProxy->setObjectName("Only show items of selected collection"); mSelectionProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection); mSelectionProxy->setSourceModel(mCalendarNamespaceModel); mCalFilterProxyModel = new CalFilterProxyModel(this); mCalFilterProxyModel->setFilter(q->filter()); mCalFilterProxyModel->setSourceModel(mSelectionProxy); mCalFilterProxyModel->setObjectName("KCalCore::CalFilter filtering"); mCalFilterPartStatusProxyModel = new CalFilterPartStatusProxyModel(this); mCalFilterPartStatusProxyModel->setFilterVirtual(false); QList blockedStatusList; blockedStatusList << KCalCore::Attendee::NeedsAction; blockedStatusList << KCalCore::Attendee::Declined; mCalFilterPartStatusProxyModel->setDynamicSortFilter(true); mCalFilterPartStatusProxyModel->setBlockedStatusList(blockedStatusList); mCalFilterPartStatusProxyModel->setSourceModel(mCalFilterProxyModel); mCalFilterPartStatusProxyModel->setObjectName("PartStatus filtering"); mFilteredETM = new Akonadi::EntityMimeTypeFilterModel(this); mFilteredETM->setSourceModel(mCalFilterPartStatusProxyModel); mFilteredETM->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders); mFilteredETM->setSortRole(CalendarModel::SortRole); mFilteredETM->setObjectName("Show headers"); #ifdef AKONADI_CALENDAR_DEBUG_MODEL QTreeView *view = new QTreeView; view->setModel(mFilteredETM); view->show(); #endif } ETMCalendarPrivate::~ETMCalendarPrivate() { } void ETMCalendarPrivate::loadFromETM() { itemsAdded(itemsFromModel(mFilteredETM)); } void ETMCalendarPrivate::clear() { mCollectionMap.clear(); mItemsByCollection.clear(); itemsRemoved(mItemById.values()); if (!mItemById.isEmpty()) { // This never happens kDebug() << "This shouldnt happen: !mItemById.isEmpty()"; foreach(Akonadi::Item::Id id, mItemById.keys()) { kDebug() << "Id = " << id; } mItemById.clear(); //Q_ASSERT(false); // TODO: discover why this happens } if (!mItemIdByUniqueInstanceIdentifier.isEmpty()) { // This never happens kDebug() << "This shouldnt happen: !mItemIdByUid.isEmpty()"; foreach(const QString &uid, mItemIdByUniqueInstanceIdentifier.keys()) { kDebug() << "uid: " << uid; } mItemIdByUniqueInstanceIdentifier.clear(); //Q_ASSERT(false); } } Akonadi::Item::List ETMCalendarPrivate::itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end) { const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1; Akonadi::Item::List items; int row = start; QModelIndex i = model->index(row, 0, parentIndex); while (row <= endRow) { const Akonadi::Item item = itemFromIndex(i); if (item.hasPayload()) { items << item; } else { const QModelIndex childIndex = i.child(0, 0); if (childIndex.isValid()) { items << itemsFromModel(model, i); } } ++row; i = i.sibling(row, 0); } return items; } Akonadi::Collection::List ETMCalendarPrivate::collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end) { const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1; Akonadi::Collection::List collections; int row = start; QModelIndex i = model->index(row, 0, parentIndex); while (row <= endRow) { const Akonadi::Collection collection = collectionFromIndex(i); if (collection.isValid()) { collections << collection; QModelIndex childIndex = i.child(0, 0); if (childIndex.isValid()) { collections << collectionsFromModel(model, i); } } ++row; i = i.sibling(row, 0); } return collections; } Akonadi::Item ETMCalendarPrivate::itemFromIndex(const QModelIndex &idx) { Akonadi::Item item = idx.data(Akonadi::EntityTreeModel::ItemRole).value(); item.setParentCollection( idx.data(Akonadi::EntityTreeModel::ParentCollectionRole).value()); return item; } void ETMCalendarPrivate::itemsAdded(const Akonadi::Item::List &items) { if (!items.isEmpty()) { foreach(const Akonadi::Item &item, items) { internalInsert(item); } Akonadi::Collection::Id id = items.first().storageCollectionId(); if (mPopulatedCollectionIds.contains(id)) { // If the collection isn't populated yet, it will be sent later // Saves some cpu cycles emit q->calendarChanged(); } } } void ETMCalendarPrivate::itemsRemoved(const Akonadi::Item::List &items) { foreach(const Akonadi::Item &item, items) { internalRemove(item); } emit q->calendarChanged(); } Akonadi::Collection ETMCalendarPrivate::collectionFromIndex(const QModelIndex &index) { return index.data(Akonadi::EntityTreeModel::CollectionRole).value(); } void ETMCalendarPrivate::onRowsInserted(const QModelIndex &index, int start, int end) { Akonadi::Collection::List collections = collectionsFromModel(mETM.data(), index, start, end); foreach(const Akonadi::Collection &collection, collections) { mCollectionMap[collection.id()] = collection; } if (!collections.isEmpty()) emit q->collectionsAdded(collections); } void ETMCalendarPrivate::onCollectionPopulated(Akonadi::Collection::Id id) { mPopulatedCollectionIds.insert(id); emit q->calendarChanged(); } void ETMCalendarPrivate::onRowsRemoved(const QModelIndex &index, int start, int end) { Akonadi::Collection::List collections = collectionsFromModel(mETM.data(), index, start, end); foreach(const Akonadi::Collection &collection, collections) { mCollectionMap.remove(collection.id()); } if (!collections.isEmpty()) emit q->collectionsRemoved(collections); } void ETMCalendarPrivate::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) { // We only update collections, because items are handled in the filtered model Q_ASSERT(topLeft.row() <= bottomRight.row()); const int endRow = bottomRight.row(); for (int row = topLeft.row(); row <= endRow; row++) { const Akonadi::Collection col = collectionFromIndex(topLeft.sibling(row, 0)); if (col.isValid()) { // Attributes might have changed, store the new collection and discard the old one mCollectionMap.insert(col.id(), col); } ++row; } } void ETMCalendarPrivate::onRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow) { //TODO Q_UNUSED(sourceParent); Q_UNUSED(sourceStart); Q_UNUSED(sourceEnd); Q_UNUSED(destinationParent); Q_UNUSED(destinationRow); } void ETMCalendarPrivate::onLayoutChangedInFilteredModel() { clear(); - loadFromETM(); + mLoadFromModelTimer->start(); } void ETMCalendarPrivate::onModelResetInFilteredModel() { clear(); - loadFromETM(); + mLoadFromModelTimer->start(); } void ETMCalendarPrivate::onDataChangedInFilteredModel(const QModelIndex &topLeft, const QModelIndex &bottomRight) { Q_ASSERT(topLeft.row() <= bottomRight.row()); const int endRow = bottomRight.row(); QModelIndex i(topLeft); int row = i.row(); while (row <= endRow) { const Akonadi::Item item = itemFromIndex(i); if (item.isValid()) updateItem(item); ++row; i = i.sibling(row, topLeft.column()); } emit q->calendarChanged(); } void ETMCalendarPrivate::updateItem(const Akonadi::Item &item) { Incidence::Ptr newIncidence = CalendarUtils::incidence(item); if (!newIncidence) { return; } Q_ASSERT(!newIncidence->uid().isEmpty()); KDateTime originalModifiedDate = newIncidence->lastModified(); newIncidence->interceptUpdates(); newIncidence->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(item.id())); newIncidence->cancelUpdates(); Q_ASSERT(item.storageCollectionId() >= 0); IncidenceBase::Ptr existingIncidence = q->incidence(QString::number(item.parentCollection().id()), newIncidence->uid(), newIncidence->recurrenceId()); if (!existingIncidence && !mItemById.contains(item.id())) { // We don't know about this one because it was discarded, for example because of not having DTSTART return; } mItemsByCollection.insert(item.parentCollection().id(), item); Akonadi::Item oldItem = mItemById.value(item.id()); if (existingIncidence) { // We set the payload so that the internal incidence pointer and the one in mItemById stay the same Akonadi::Item updatedItem = item; updatedItem.setPayload(existingIncidence.staticCast()); mItemById.insert(item.id(), updatedItem); // The item needs updating too, revision changed. *(existingIncidence.data()) = *(newIncidence.data()); //Reset the last modified date that the calendar just changed. existingIncidence->interceptUpdates(); existingIncidence->setLastModified(originalModifiedDate); existingIncidence->cancelUpdates(); } else { mItemById.insert(item.id(), item); // The item needs updating too, revision changed. // The item changed it's UID, update our maps, the Google resource changes the UID when we create incidences. handleUidChange(oldItem, item); } } void ETMCalendarPrivate::onRowsInsertedInFilteredModel(const QModelIndex &index, int start, int end) { itemsAdded(itemsFromModel(mFilteredETM, index, start, end)); } void ETMCalendarPrivate::onRowsAboutToBeRemovedInFilteredModel(const QModelIndex &index, int start, int end) { itemsRemoved(itemsFromModel(mFilteredETM, index, start, end)); } void ETMCalendarPrivate::onFilterChanged() { mCalFilterProxyModel->setFilter(q->filter()); } ETMCalendar::ETMCalendar(QObject *parent) : CalendarBase(new ETMCalendarPrivate(this), parent) { Q_D(ETMCalendar); d->init(); } ETMCalendar::ETMCalendar(const QStringList &mimeTypes, QObject *parent) : CalendarBase(new ETMCalendarPrivate(this), parent) { Q_D(ETMCalendar); d->mMimeTypes = mimeTypes; d->init(); } ETMCalendar::ETMCalendar(ETMCalendar *other, QObject *parent) : CalendarBase(new ETMCalendarPrivate(this), parent) { Q_D(ETMCalendar); CalendarModel *model = qobject_cast(other->entityTreeModel()); if (model) { d->mETM = model->weakPointer().toStrongRef(); } d->init(); } ETMCalendar::ETMCalendar(ChangeRecorder *monitor, QObject *parent) : CalendarBase(new ETMCalendarPrivate(this), parent) { Q_D(ETMCalendar); if (monitor) { connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection,QSet)), d, SLOT(onCollectionChanged(Akonadi::Collection,QSet))); d->mETM = CalendarModel::create(monitor); d->mETM->setObjectName("ETM"); d->mETM->setListFilter(Akonadi::CollectionFetchScope::Display); } d->init(); } ETMCalendar::~ETMCalendar() { } //TODO: move this up? Akonadi::Collection ETMCalendar::collection(Akonadi::Collection::Id id) const { Q_D(const ETMCalendar); return d->mCollectionMap.value(id); } bool ETMCalendar::hasRight(const KCalCore::Incidence::Ptr &inc, Akonadi::Collection::Right right) const { return hasRight(item(inc), right); } bool ETMCalendar::hasRight(const Akonadi::Item &item, Akonadi::Collection::Right right) const { // if the users changes the rights, item.parentCollection() // can still have the old rights, so we use call collection() // which returns the updated one const Akonadi::Collection col = collection(item.storageCollectionId()); return col.rights() & right; } QAbstractItemModel *ETMCalendar::model() const { Q_D(const ETMCalendar); return d->mFilteredETM; } KCheckableProxyModel *ETMCalendar::checkableProxyModel() const { Q_D(const ETMCalendar); return d->mCheckableProxyModel; } KCalCore::Alarm::List ETMCalendar::alarms(const KDateTime &from, const KDateTime &to, bool excludeBlockedAlarms) const { Q_D(const ETMCalendar); KCalCore::Alarm::List alarmList; QHashIterator i(d->mItemById); while (i.hasNext()) { const Akonadi::Item item = i.next().value(); BlockAlarmsAttribute *blockedAttr = 0; if (excludeBlockedAlarms) { // take the collection from m_collectionMap, because we need the up-to-date collection attrs Akonadi::Collection parentCollection = d->mCollectionMap.value(item.storageCollectionId()); if (parentCollection.isValid() && parentCollection.hasAttribute()) { blockedAttr = parentCollection.attribute(); if (blockedAttr->isEverythingBlocked()) { continue; } } } KCalCore::Incidence::Ptr incidence; if (item.isValid() && item.hasPayload()) { incidence = KCalCore::Incidence::Ptr(item.payload()->clone()); } else { continue; } if (!incidence) { continue; } if (blockedAttr) { // Remove all blocked types of alarms Q_FOREACH(const KCalCore::Alarm::Ptr &alarm, incidence->alarms()) { if (blockedAttr->isAlarmTypeBlocked(alarm->type())) { incidence->removeAlarm(alarm); } } } if (incidence->alarms().isEmpty()) { continue; } Alarm::List tmpList; if (incidence->recurs()) { appendRecurringAlarms(tmpList, incidence, from, to); } else { appendAlarms(tmpList, incidence, from, to); } // We need to tag them with the incidence uid in case // the caller will need it, because when we get out of // this scope the incidence will be destroyed. QVectorIterator a(tmpList); while (a.hasNext()) { a.next()->setCustomProperty("ETMCalendar", "parentUid", incidence->uid()); } alarmList += tmpList; } return alarmList; } Akonadi::EntityTreeModel *ETMCalendar::entityTreeModel() const { Q_D(const ETMCalendar); return d->mETM.data(); } void ETMCalendar::setCollectionFilteringEnabled(bool enable) { Q_D(ETMCalendar); if (d->mCollectionFilteringEnabled != enable) { d->mCollectionFilteringEnabled = enable; if (enable) { d->mSelectionProxy->setSourceModel(d->mCalendarNamespaceModel); QAbstractItemModel *oldModel = d->mCalFilterProxyModel->sourceModel(); d->mCalFilterProxyModel->setSourceModel(d->mSelectionProxy); delete qobject_cast(oldModel); } else { KDescendantsProxyModel *flatner = new KDescendantsProxyModel(this); flatner->setSourceModel(d->mCalendarNamespaceModel); d->mCalFilterProxyModel->setSourceModel(flatner); } } } bool ETMCalendar::collectionFilteringEnabled() const { Q_D(const ETMCalendar); return d->mCollectionFilteringEnabled; } bool ETMCalendar::isLoaded() const { Q_D(const ETMCalendar); if (!entityTreeModel()->isCollectionTreeFetched()) return false; Akonadi::Collection::List collections = d->mCollectionMap.values(); foreach (const Akonadi::Collection &collection, collections) { if (!entityTreeModel()->isCollectionPopulated(collection.id())) return false; } if (!d->mCalendarNamespaceModel->isLoaded()) { return false; } return true; } #include "moc_etmcalendar.cpp" #include "moc_etmcalendar_p.cpp" diff --git a/akonadi/calendar/etmcalendar_p.h b/akonadi/calendar/etmcalendar_p.h index 59a938415..8c548a2ca 100644 --- a/akonadi/calendar/etmcalendar_p.h +++ b/akonadi/calendar/etmcalendar_p.h @@ -1,176 +1,177 @@ /* Copyright (c) 2011-2012 Sérgio Martins 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 AKONADI_ETMCALENDAR_P_H #define AKONADI_ETMCALENDAR_P_H #include "etmcalendar.h" #include "calendarbase_p.h" #include "incidencechanger.h" #include "calendarmodel_p.h" #include #include #include #include #include class QAbstractItemModel; class CheckableProxyModel; class KSelectionProxyModel; namespace Akonadi { class EntityTreeModel; class EntityMimeTypeFilterModel; class CollectionFilterProxyModel; class CalFilterProxyModel; class CalFilterPartStatusProxyModel; class CalendarNamespaceProxyModel; static bool isStructuralCollection(const Akonadi::Collection &collection) { QStringList mimeTypes; mimeTypes << QLatin1String("text/calendar") << KCalCore::Event::eventMimeType() << KCalCore::Todo::todoMimeType() << KCalCore::Journal::journalMimeType(); const QStringList collectionMimeTypes = collection.contentMimeTypes(); foreach(const QString &mimeType, mimeTypes) { if (collectionMimeTypes.contains(mimeType)) return false; } return true; } class CheckableProxyModel : public KCheckableProxyModel { Q_OBJECT public: CheckableProxyModel(QObject *parent = 0) : KCheckableProxyModel(parent) { } QVariant data(const QModelIndex &index, int role) const { if (role == Qt::CheckStateRole) { // Don't show the checkbox if the collection can't contain incidences const Akonadi::Collection collection = index.data(Akonadi::EntityTreeModel::CollectionRole).value(); if (isStructuralCollection(collection)) return QVariant(); } return KCheckableProxyModel::data(index, role); } }; class ETMCalendarPrivate : public CalendarBasePrivate { Q_OBJECT public: explicit ETMCalendarPrivate(ETMCalendar *qq); ~ETMCalendarPrivate(); void init(); void setupFilteredETM(); - void loadFromETM(); public Q_SLOTS: + void loadFromETM(); Akonadi::Item::List itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex = QModelIndex(), int start = 0, int end = -1); Akonadi::Collection::List collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex = QModelIndex(), int start = 0, int end = -1); // KCalCore::CalFilter has changed. void onFilterChanged(); void clear(); void updateItem(const Akonadi::Item &); Akonadi::Item itemFromIndex(const QModelIndex &idx); Akonadi::Collection collectionFromIndex(const QModelIndex &index); void itemsAdded(const Akonadi::Item::List &items); void itemsRemoved(const Akonadi::Item::List &items); void onRowsInserted(const QModelIndex &index, int start, int end); void onRowsRemoved(const QModelIndex &index, int start, int end); void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void onRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow); void onLayoutChangedInFilteredModel(); void onModelResetInFilteredModel(); void onDataChangedInFilteredModel(const QModelIndex &topLeft, const QModelIndex &bottomRight); void onRowsInsertedInFilteredModel(const QModelIndex &index, int start, int end); void onRowsAboutToBeRemovedInFilteredModel(const QModelIndex &index, int start, int end); void onCollectionChanged(const Akonadi::Collection &, const QSet &); void onCollectionPopulated(Akonadi::Collection::Id); public: Akonadi::CalendarModel::Ptr mETM; Akonadi::EntityMimeTypeFilterModel *mFilteredETM; // akonadi id to collections QHash mCollectionMap; CheckableProxyModel *mCheckableProxyModel; Akonadi::CollectionFilterProxyModel *mCollectionProxyModel; Akonadi::CalFilterProxyModel *mCalFilterProxyModel; //KCalCore::CalFilter stuff CalendarNamespaceProxyModel *mCalendarNamespaceModel; //KCalCore::CalFilter stuff //filter out all invitations and declined events Akonadi::CalFilterPartStatusProxyModel *mCalFilterPartStatusProxyModel; KSelectionProxyModel *mSelectionProxy; bool mCollectionFilteringEnabled; QSet mPopulatedCollectionIds; QStringList mMimeTypes; + QTimer *mLoadFromModelTimer; private: ETMCalendar *const q; }; class CalendarNamespaceProxyModel : public QIdentityProxyModel { Q_OBJECT public: CalendarNamespaceProxyModel(QObject *parent); void setSourceModel (QAbstractItemModel *newSourceModel); bool isLoaded() const; private slots: void emitDataChanged(const QModelIndex &index); void onRowsInserted(const QModelIndex & parent, int start, int end); void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); void collectionFetchResult(KJob* job); private: bool applyCollection(const Akonadi::Collection &collection, const Akonadi::Item &item); bool updateItem(Akonadi::Item &item, const Akonadi::Collection &collection); QHash mCollections; QSet mCollectionJobs; QHash mItemByStorageCollection; }; } #endif