File indexing completed on 2024-05-12 05:10:43

0001 /*
0002    SPDX-FileCopyrightText: 2011-2013 Sérgio Martins <iamsergio@gmail.com>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "etmcalendar.h"
0008 #include "blockalarmsattribute.h"
0009 #include "calendarmodel_p.h"
0010 #include "calendarutils.h"
0011 #include "calfilterpartstatusproxymodel_p.h"
0012 #include "calfilterproxymodel_p.h"
0013 #include "etmcalendar_p.h"
0014 #include "kcolumnfilterproxymodel_p.h"
0015 
0016 #include <Akonadi/CollectionFilterProxyModel>
0017 #include <Akonadi/CollectionUtils>
0018 #include <Akonadi/EntityDisplayAttribute>
0019 #include <Akonadi/EntityMimeTypeFilterModel>
0020 #include <Akonadi/Item>
0021 #include <Akonadi/ItemFetchScope>
0022 #include <Akonadi/Monitor>
0023 #include <Akonadi/Session>
0024 #include <KDescendantsProxyModel>
0025 #include <KSelectionProxyModel>
0026 
0027 #include <QItemSelectionModel>
0028 #include <QTreeView>
0029 
0030 using namespace Akonadi;
0031 using namespace KCalendarCore;
0032 
0033 // TODO: implement batchAdding
0034 
0035 ETMCalendarPrivate::ETMCalendarPrivate(ETMCalendar *qq)
0036     : CalendarBasePrivate(qq)
0037     , mETM(nullptr)
0038     , q(qq)
0039 {
0040     mListensForNewItems = true;
0041 }
0042 
0043 void ETMCalendarPrivate::init()
0044 {
0045     if (!mETM) {
0046         auto session = new Akonadi::Session("ETMCalendar", q);
0047         auto monitor = new Akonadi::Monitor(q);
0048         monitor->setObjectName(QLatin1StringView("ETMCalendarMonitor"));
0049         connect(monitor,
0050                 qOverload<const Akonadi::Collection &, const QSet<QByteArray> &>(&Monitor::collectionChanged),
0051                 this,
0052                 [this](const Akonadi::Collection &cols, const QSet<QByteArray> &set) {
0053                     onCollectionChanged(cols, set);
0054                 });
0055 
0056         Akonadi::ItemFetchScope scope;
0057         scope.fetchFullPayload(true);
0058         scope.fetchAttribute<Akonadi::EntityDisplayAttribute>();
0059 
0060         monitor->setSession(session);
0061         monitor->setCollectionMonitored(Akonadi::Collection::root());
0062         monitor->fetchCollection(true);
0063         monitor->setItemFetchScope(scope);
0064         monitor->setAllMonitored(true);
0065 
0066         const QStringList allMimeTypes = {KCalendarCore::Event::eventMimeType(),
0067                                           KCalendarCore::Todo::todoMimeType(),
0068                                           KCalendarCore::Journal::journalMimeType()};
0069 
0070         for (const QString &mimetype : allMimeTypes) {
0071             monitor->setMimeTypeMonitored(mimetype, mMimeTypes.isEmpty() || mMimeTypes.contains(mimetype));
0072         }
0073 
0074         mETM = CalendarModel::create(monitor);
0075         mETM->setObjectName(QLatin1StringView("ETM"));
0076         mETM->setListFilter(Akonadi::CollectionFetchScope::Display);
0077     }
0078 
0079     setupFilteredETM();
0080 
0081     connect(q, &Calendar::filterChanged, this, &ETMCalendarPrivate::onFilterChanged);
0082 
0083     connect(mETM.data(), &EntityTreeModel::collectionPopulated, this, &ETMCalendarPrivate::onCollectionPopulated);
0084     connect(mETM.data(), &QAbstractItemModel::rowsInserted, this, &ETMCalendarPrivate::onRowsInserted);
0085     connect(mETM.data(), &QAbstractItemModel::dataChanged, this, &ETMCalendarPrivate::onDataChanged);
0086     connect(mETM.data(), &QAbstractItemModel::rowsMoved, this, &ETMCalendarPrivate::onRowsMoved);
0087     connect(mETM.data(), &QAbstractItemModel::rowsRemoved, this, &ETMCalendarPrivate::onRowsRemoved);
0088 
0089     connect(mFilteredETM, &QAbstractItemModel::dataChanged, this, &ETMCalendarPrivate::onDataChangedInFilteredModel);
0090     connect(mFilteredETM, &QAbstractItemModel::layoutChanged, this, &ETMCalendarPrivate::onLayoutChangedInFilteredModel);
0091     connect(mFilteredETM, &QAbstractItemModel::modelReset, this, &ETMCalendarPrivate::onModelResetInFilteredModel);
0092     connect(mFilteredETM, &QAbstractItemModel::rowsInserted, this, &ETMCalendarPrivate::onRowsInsertedInFilteredModel);
0093     connect(mFilteredETM, &QAbstractItemModel::rowsAboutToBeRemoved, this, &ETMCalendarPrivate::onRowsAboutToBeRemovedInFilteredModel);
0094 
0095     loadFromETM();
0096     updateLoadingState();
0097 }
0098 
0099 void ETMCalendarPrivate::onCollectionChanged(const Akonadi::Collection &collection, const QSet<QByteArray> &attributeNames)
0100 {
0101     Q_ASSERT(collection.isValid());
0102     // Is the collection changed to read-only, we update all Incidences
0103     if (attributeNames.contains("AccessRights")) {
0104         const Akonadi::Item::List items = q->items();
0105         for (const Akonadi::Item &item : items) {
0106             if (item.storageCollectionId() == collection.id()) {
0107                 KCalendarCore::Incidence::Ptr incidence = CalendarUtils::incidence(item);
0108                 if (incidence) {
0109                     incidence->setReadOnly(!(collection.rights() & Akonadi::Collection::CanChangeItem));
0110                 }
0111             }
0112         }
0113     }
0114 
0115     Q_EMIT q->collectionChanged(collection, attributeNames);
0116 }
0117 
0118 void ETMCalendarPrivate::setupFilteredETM()
0119 {
0120     // We're only interested in the CollectionTitle column
0121     auto columnFilterProxy = new KColumnFilterProxyModel(this);
0122     columnFilterProxy->setSourceModel(mETM.data());
0123     columnFilterProxy->setVisibleColumn(CalendarModel::CollectionTitle);
0124     columnFilterProxy->setObjectName(QLatin1StringView("Remove columns"));
0125 
0126     mCollectionProxyModel = new Akonadi::CollectionFilterProxyModel(this);
0127     mCollectionProxyModel->setObjectName(QLatin1StringView("Only show collections"));
0128     mCollectionProxyModel->setDynamicSortFilter(true);
0129     mCollectionProxyModel->addMimeTypeFilter(QStringLiteral("text/calendar"));
0130     mCollectionProxyModel->setExcludeVirtualCollections(false);
0131     mCollectionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
0132     mCollectionProxyModel->setSourceModel(columnFilterProxy);
0133 
0134     // Keep track of selected items.
0135     auto selectionModel = new QItemSelectionModel(mCollectionProxyModel);
0136     selectionModel->setObjectName(QLatin1StringView("Calendar Selection Model"));
0137 
0138     // Make item selection work by means of checkboxes.
0139     mCheckableProxyModel = new CheckableProxyModel(this);
0140     mCheckableProxyModel->setSelectionModel(selectionModel);
0141     mCheckableProxyModel->setSourceModel(mCollectionProxyModel);
0142     mCheckableProxyModel->setObjectName(QLatin1StringView("Add checkboxes"));
0143 
0144     mSelectionProxy = new KSelectionProxyModel(selectionModel, /**parent=*/this);
0145     mSelectionProxy->setObjectName(QLatin1StringView("Only show items of selected collection"));
0146     mSelectionProxy->setFilterBehavior(KSelectionProxyModel::ChildrenOfExactSelection);
0147     mSelectionProxy->setSourceModel(mETM.data());
0148 
0149     mCalFilterProxyModel = new CalFilterProxyModel(this);
0150     mCalFilterProxyModel->setFilter(q->filter());
0151     mCalFilterProxyModel->setSourceModel(mSelectionProxy);
0152     mCalFilterProxyModel->setObjectName(QLatin1StringView("KCalendarCore::CalFilter filtering"));
0153 
0154     mCalFilterPartStatusProxyModel = new CalFilterPartStatusProxyModel(this);
0155     mCalFilterPartStatusProxyModel->setFilterVirtual(false);
0156     QList<KCalendarCore::Attendee::PartStat> blockedStatusList;
0157     blockedStatusList << KCalendarCore::Attendee::NeedsAction;
0158     blockedStatusList << KCalendarCore::Attendee::Declined;
0159     mCalFilterPartStatusProxyModel->setDynamicSortFilter(true);
0160     mCalFilterPartStatusProxyModel->setBlockedStatusList(blockedStatusList);
0161     mCalFilterPartStatusProxyModel->setSourceModel(mCalFilterProxyModel);
0162     mCalFilterPartStatusProxyModel->setObjectName(QLatin1StringView("PartStatus filtering"));
0163 
0164     mFilteredETM = new Akonadi::EntityMimeTypeFilterModel(this);
0165     mFilteredETM->setSourceModel(mCalFilterPartStatusProxyModel);
0166     mFilteredETM->setHeaderGroup(Akonadi::EntityTreeModel::ItemListHeaders);
0167     mFilteredETM->setSortRole(CalendarModel::SortRole);
0168     mFilteredETM->setObjectName(QLatin1StringView("Show headers"));
0169 
0170 #ifdef AKONADI_CALENDAR_DEBUG_MODEL
0171     QTreeView *view = new QTreeView;
0172     view->setModel(mFilteredETM);
0173     view->show();
0174 #endif
0175 }
0176 
0177 ETMCalendarPrivate::~ETMCalendarPrivate() = default;
0178 
0179 void ETMCalendarPrivate::loadFromETM()
0180 {
0181     itemsAdded(itemsFromModel(mFilteredETM));
0182 }
0183 
0184 void ETMCalendarPrivate::clear()
0185 {
0186     mCollectionMap.clear();
0187     mItemsByCollection.clear();
0188 
0189     Akonadi::Item::List removedItems;
0190     removedItems.reserve(mItemById.size());
0191     for (auto it = mItemById.cbegin(), end = mItemById.cend(); it != end; ++it) {
0192         removedItems.push_back(it.value());
0193     }
0194 
0195     itemsRemoved(removedItems);
0196 
0197     if (!mItemById.isEmpty()) {
0198         mItemById.clear();
0199         // Q_ASSERT(false); // TODO: discover why this happens
0200     }
0201 
0202     if (!mItemIdByUid.isEmpty()) {
0203         mItemIdByUid.clear();
0204         // Q_ASSERT(false);
0205     }
0206     mParentUidToChildrenUid.clear();
0207     // m_virtualItems.clear();
0208 }
0209 
0210 Akonadi::Item::List ETMCalendarPrivate::itemsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
0211 {
0212     const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
0213     Akonadi::Item::List items;
0214     int row = start;
0215     QModelIndex i = model->index(row, 0, parentIndex);
0216     while (row <= endRow) {
0217         const Akonadi::Item item = itemFromIndex(i);
0218         if (item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0219             items << item;
0220         } else {
0221             const QModelIndex childIndex = model->index(0, 0, i);
0222             if (childIndex.isValid()) {
0223                 items << itemsFromModel(model, i);
0224             }
0225         }
0226         ++row;
0227         i = i.sibling(row, 0);
0228     }
0229     return items;
0230 }
0231 
0232 Akonadi::Collection::List ETMCalendarPrivate::collectionsFromModel(const QAbstractItemModel *model, const QModelIndex &parentIndex, int start, int end)
0233 {
0234     const int endRow = end >= 0 ? end : model->rowCount(parentIndex) - 1;
0235     Akonadi::Collection::List collections;
0236     int row = start;
0237     QModelIndex i = model->index(row, 0, parentIndex);
0238     while (row <= endRow) {
0239         const Akonadi::Collection collection = CollectionUtils::fromIndex(i);
0240         if (collection.isValid()) {
0241             collections << collection;
0242             QModelIndex childIndex = model->index(0, 0, i);
0243             if (childIndex.isValid()) {
0244                 collections << collectionsFromModel(model, i);
0245             }
0246         }
0247         ++row;
0248         i = i.sibling(row, 0);
0249     }
0250     return collections;
0251 }
0252 
0253 Akonadi::Item ETMCalendarPrivate::itemFromIndex(const QModelIndex &idx)
0254 {
0255     auto item = idx.data(Akonadi::EntityTreeModel::ItemRole).value<Akonadi::Item>();
0256     item.setParentCollection(idx.data(Akonadi::EntityTreeModel::ParentCollectionRole).value<Akonadi::Collection>());
0257     return item;
0258 }
0259 
0260 void ETMCalendarPrivate::itemsAdded(const Akonadi::Item::List &items)
0261 {
0262     if (!items.isEmpty()) {
0263         for (const Akonadi::Item &item : items) {
0264             internalInsert(item);
0265         }
0266 
0267         Akonadi::Collection::Id id = items.first().storageCollectionId();
0268         if (mPopulatedCollectionIds.contains(id)) {
0269             // If the collection isn't populated yet, it will be sent later
0270             // Saves some cpu cycles
0271             Q_EMIT q->calendarChanged();
0272         }
0273     }
0274 }
0275 
0276 void ETMCalendarPrivate::itemsRemoved(const Akonadi::Item::List &items)
0277 {
0278     for (const Akonadi::Item &item : items) {
0279         internalRemove(item);
0280     }
0281     Q_EMIT q->calendarChanged();
0282 }
0283 
0284 void ETMCalendarPrivate::onRowsInserted(const QModelIndex &index, int start, int end)
0285 {
0286     const Akonadi::Collection::List collections = collectionsFromModel(mETM.data(), index, start, end);
0287 
0288     for (const Akonadi::Collection &collection : collections) {
0289         mCollectionMap[collection.id()] = collection;
0290     }
0291 
0292     if (!collections.isEmpty()) {
0293         Q_EMIT q->collectionsAdded(collections);
0294     }
0295 }
0296 
0297 void ETMCalendarPrivate::onCollectionPopulated(Akonadi::Collection::Id id)
0298 {
0299     mPopulatedCollectionIds.insert(id);
0300     Q_EMIT q->calendarChanged();
0301     updateLoadingState();
0302 }
0303 
0304 void ETMCalendarPrivate::onRowsRemoved(const QModelIndex &index, int start, int end)
0305 {
0306     const Akonadi::Collection::List collections = collectionsFromModel(mETM.data(), index, start, end);
0307     for (const Akonadi::Collection &collection : collections) {
0308         mCollectionMap.remove(collection.id());
0309     }
0310 
0311     if (!collections.isEmpty()) {
0312         Q_EMIT q->collectionsRemoved(collections);
0313     }
0314 }
0315 
0316 void ETMCalendarPrivate::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0317 {
0318     // We only update collections, because items are handled in the filtered model
0319     Q_ASSERT(topLeft.row() <= bottomRight.row());
0320     const int endRow = bottomRight.row();
0321     for (int row = topLeft.row(); row <= endRow; ++row) {
0322         const Akonadi::Collection col = CollectionUtils::fromIndex(topLeft.sibling(row, 0));
0323         if (col.isValid()) {
0324             // Attributes might have changed, store the new collection and discard the old one
0325             mCollectionMap.insert(col.id(), col);
0326         }
0327     }
0328 }
0329 
0330 void ETMCalendarPrivate::onRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
0331 {
0332     // TODO
0333     Q_UNUSED(sourceParent)
0334     Q_UNUSED(sourceStart)
0335     Q_UNUSED(sourceEnd)
0336     Q_UNUSED(destinationParent)
0337     Q_UNUSED(destinationRow)
0338 }
0339 
0340 void ETMCalendarPrivate::onLayoutChangedInFilteredModel()
0341 {
0342     clear();
0343     loadFromETM();
0344 }
0345 
0346 void ETMCalendarPrivate::onModelResetInFilteredModel()
0347 {
0348     clear();
0349     loadFromETM();
0350 }
0351 
0352 void ETMCalendarPrivate::onDataChangedInFilteredModel(const QModelIndex &topLeft, const QModelIndex &bottomRight)
0353 {
0354     Q_ASSERT(topLeft.row() <= bottomRight.row());
0355     const int endRow = bottomRight.row();
0356     QModelIndex i(topLeft);
0357     int row = i.row();
0358     while (row <= endRow) {
0359         const Akonadi::Item item = itemFromIndex(i);
0360         if (item.isValid() && item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0361             updateItem(item);
0362         }
0363 
0364         ++row;
0365         i = i.sibling(row, topLeft.column());
0366     }
0367 
0368     Q_EMIT q->calendarChanged();
0369 }
0370 
0371 void ETMCalendarPrivate::updateItem(const Akonadi::Item &item)
0372 {
0373     Incidence::Ptr newIncidence = CalendarUtils::incidence(item);
0374     Q_ASSERT(newIncidence);
0375     Q_ASSERT(!newIncidence->uid().isEmpty());
0376     newIncidence->setCustomProperty("VOLATILE", "AKONADI-ID", QString::number(item.id()));
0377     IncidenceBase::Ptr existingIncidence = q->incidence(newIncidence->uid(), newIncidence->recurrenceId());
0378 
0379     if (!existingIncidence && !mItemById.contains(item.id())) {
0380         // We don't know about this one because it was discarded, for example because of not having DTSTART
0381         return;
0382     }
0383 
0384     mItemsByCollection.insert(item.storageCollectionId(), item);
0385     Akonadi::Item oldItem = mItemById.value(item.id());
0386 
0387     if (existingIncidence) {
0388         // We set the payload so that the internal incidence pointer and the one in mItemById stay the same
0389         Akonadi::Item updatedItem = item;
0390         updatedItem.setPayload<KCalendarCore::Incidence::Ptr>(existingIncidence.staticCast<KCalendarCore::Incidence>());
0391         mItemById.insert(item.id(), updatedItem); // The item needs updating too, revision changed.
0392 
0393         // Check if RELATED-TO changed, updating parenting information
0394         handleParentChanged(newIncidence);
0395         *(existingIncidence.data()) = *(newIncidence.data());
0396     } else {
0397         mItemById.insert(item.id(), item); // The item needs updating too, revision changed.
0398         // The item changed it's UID, update our maps, the Google resource changes the UID when we create incidences.
0399         handleUidChange(oldItem, item, newIncidence->instanceIdentifier());
0400     }
0401 }
0402 
0403 void ETMCalendarPrivate::onRowsInsertedInFilteredModel(const QModelIndex &index, int start, int end)
0404 {
0405     itemsAdded(itemsFromModel(mFilteredETM, index, start, end));
0406 }
0407 
0408 void ETMCalendarPrivate::onRowsAboutToBeRemovedInFilteredModel(const QModelIndex &index, int start, int end)
0409 {
0410     itemsRemoved(itemsFromModel(mFilteredETM, index, start, end));
0411 }
0412 
0413 void ETMCalendarPrivate::onFilterChanged()
0414 {
0415     mCalFilterProxyModel->setFilter(q->filter());
0416 }
0417 
0418 ETMCalendar::ETMCalendar(QObject *parent)
0419     : CalendarBase(new ETMCalendarPrivate(this), parent)
0420 {
0421     Q_D(ETMCalendar);
0422     d->init();
0423 }
0424 
0425 ETMCalendar::ETMCalendar(const QStringList &mimeTypes, QObject *parent)
0426     : CalendarBase(new ETMCalendarPrivate(this), parent)
0427 {
0428     Q_D(ETMCalendar);
0429     d->mMimeTypes = mimeTypes;
0430     d->init();
0431 }
0432 
0433 ETMCalendar::ETMCalendar(ETMCalendar *other, QObject *parent)
0434     : CalendarBase(new ETMCalendarPrivate(this), parent)
0435 {
0436     Q_D(ETMCalendar);
0437 
0438     auto model = qobject_cast<Akonadi::CalendarModel *>(other->entityTreeModel());
0439     if (model) {
0440         d->mETM = model->weakPointer().toStrongRef();
0441     }
0442 
0443     d->init();
0444 }
0445 
0446 ETMCalendar::ETMCalendar(Monitor *monitor, QObject *parent)
0447     : CalendarBase(new ETMCalendarPrivate(this), parent)
0448 {
0449     Q_D(ETMCalendar);
0450 
0451     if (monitor) {
0452         QObject::connect(monitor,
0453                          qOverload<const Akonadi::Collection &, const QSet<QByteArray> &>(&Akonadi::Monitor::collectionChanged),
0454                          d,
0455                          &ETMCalendarPrivate::onCollectionChanged);
0456         d->mETM = CalendarModel::create(monitor);
0457         d->mETM->setObjectName(QLatin1StringView("ETM"));
0458         d->mETM->setListFilter(Akonadi::CollectionFetchScope::Display);
0459     }
0460 
0461     d->init();
0462 }
0463 
0464 ETMCalendar::~ETMCalendar() = default;
0465 
0466 // TODO: move this up?
0467 Akonadi::Collection ETMCalendar::collection(Akonadi::Collection::Id id) const
0468 {
0469     Q_D(const ETMCalendar);
0470     return d->mCollectionMap.value(id);
0471 }
0472 
0473 bool ETMCalendar::hasRight(const QString &uid, Akonadi::Collection::Right right) const
0474 {
0475     return hasRight(item(uid), right);
0476 }
0477 
0478 bool ETMCalendar::hasRight(const Akonadi::Item &item, Akonadi::Collection::Right right) const
0479 {
0480     // if the users changes the rights, item.parentCollection()
0481     // can still have the old rights, so we use call collection()
0482     // which returns the updated one
0483     const Akonadi::Collection col = collection(item.storageCollectionId());
0484     return col.rights() & right;
0485 }
0486 
0487 QAbstractItemModel *ETMCalendar::model() const
0488 {
0489     Q_D(const ETMCalendar);
0490     return d->mFilteredETM;
0491 }
0492 
0493 KCheckableProxyModel *ETMCalendar::checkableProxyModel() const
0494 {
0495     Q_D(const ETMCalendar);
0496     return d->mCheckableProxyModel;
0497 }
0498 
0499 KCalendarCore::Alarm::List ETMCalendar::alarms(const QDateTime &from, const QDateTime &to, bool excludeBlockedAlarms) const
0500 {
0501     Q_D(const ETMCalendar);
0502     KCalendarCore::Alarm::List alarmList;
0503     QHashIterator<Akonadi::Item::Id, Akonadi::Item> i(d->mItemById);
0504     while (i.hasNext()) {
0505         const Akonadi::Item item = i.next().value();
0506 
0507         Akonadi::Collection parentCollection; // must have same lifetime as blockedAttr
0508         BlockAlarmsAttribute *blockedAttr = nullptr;
0509 
0510         if (excludeBlockedAlarms) {
0511             // take the collection from m_collectionMap, because we need the up-to-date collection attrs
0512             parentCollection = d->mCollectionMap.value(item.storageCollectionId());
0513             if (parentCollection.isValid() && parentCollection.hasAttribute<BlockAlarmsAttribute>()) {
0514                 blockedAttr = parentCollection.attribute<BlockAlarmsAttribute>();
0515                 if (blockedAttr->isEverythingBlocked()) {
0516                     continue;
0517                 }
0518             }
0519         }
0520 
0521         KCalendarCore::Incidence::Ptr incidence;
0522         if (item.isValid() && item.hasPayload<KCalendarCore::Incidence::Ptr>()) {
0523             incidence = item.payload<KCalendarCore::Incidence::Ptr>();
0524         }
0525         if (!incidence || incidence->alarms().isEmpty()) {
0526             continue;
0527         }
0528 
0529         Alarm::List tmpList;
0530         if (incidence->recurs()) {
0531             appendRecurringAlarms(tmpList, incidence, from, to);
0532         } else {
0533             appendAlarms(tmpList, incidence, from, to);
0534         }
0535 
0536         if (blockedAttr) {
0537             tmpList.erase(std::remove_if(tmpList.begin(),
0538                                          tmpList.end(),
0539                                          [blockedAttr](const auto &alarm) {
0540                                              return blockedAttr->isAlarmTypeBlocked(alarm->type());
0541                                          }),
0542                           tmpList.end());
0543         }
0544 
0545         alarmList += tmpList;
0546     }
0547     return alarmList;
0548 }
0549 
0550 Akonadi::EntityTreeModel *ETMCalendar::entityTreeModel() const
0551 {
0552     Q_D(const ETMCalendar);
0553     return d->mETM.data();
0554 }
0555 
0556 void ETMCalendar::setCollectionFilteringEnabled(bool enable)
0557 {
0558     Q_D(ETMCalendar);
0559     if (d->mCollectionFilteringEnabled != enable) {
0560         d->mCollectionFilteringEnabled = enable;
0561         if (enable) {
0562             d->mSelectionProxy->setSourceModel(d->mETM.data());
0563             QAbstractItemModel *oldModel = d->mCalFilterProxyModel->sourceModel();
0564             d->mCalFilterProxyModel->setSourceModel(d->mSelectionProxy);
0565             delete qobject_cast<KDescendantsProxyModel *>(oldModel);
0566         } else {
0567             auto flatner = new KDescendantsProxyModel(this);
0568             flatner->setSourceModel(d->mETM.data());
0569             d->mCalFilterProxyModel->setSourceModel(flatner);
0570         }
0571     }
0572 }
0573 
0574 bool ETMCalendar::collectionFilteringEnabled() const
0575 {
0576     Q_D(const ETMCalendar);
0577     return d->mCollectionFilteringEnabled;
0578 }
0579 
0580 void ETMCalendarPrivate::updateLoadingState()
0581 {
0582     if (!q->entityTreeModel()->isCollectionTreeFetched()) {
0583         return q->setIsLoading(true);
0584     }
0585 
0586     for (const Akonadi::Collection &collection : std::as_const(mCollectionMap)) {
0587         if (!q->entityTreeModel()->isCollectionPopulated(collection.id())) {
0588             return q->setIsLoading(true);
0589         }
0590     }
0591 
0592     q->setIsLoading(false);
0593 }
0594 
0595 #include "moc_etmcalendar.cpp"
0596 #include "moc_etmcalendar_p.cpp"