File indexing completed on 2025-02-16 04:48:49

0001 /*
0002  *  fileresourcedatamodel.cpp  -  model containing file system resources and their events
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2007-2022 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "fileresourcedatamodel.h"
0010 
0011 #include "fileresourcecalendarupdater.h"
0012 #include "fileresourcecreator.h"
0013 #include "migration/fileresourcemigrator.h"
0014 #include "eventmodel.h"
0015 #include "resourcemodel.h"
0016 #include "resources.h"
0017 
0018 #include "preferences.h"
0019 #include "lib/synchtimer.h"
0020 #include "kalarm_debug.h"
0021 
0022 // Represents a resource or event within the data model.
0023 struct FileResourceDataModel::Node
0024 {
0025 private:
0026     KAEvent* eitem;  // if type Event, the KAEvent, which is owned by this instance
0027     Resource ritem;  // if type Resource, the resource
0028     Resource owner;  // resource containing this KAEvent, or null
0029 public:
0030     Type type;
0031 
0032     explicit Node(Resource& r) : ritem(r), type(Type::Resource) {}
0033     Node(KAEvent* e, Resource& r) : eitem(e), owner(r), type(Type::Event) {}
0034     ~Node()
0035     {
0036         if (type == Type::Event)
0037             delete eitem;
0038     }
0039     Resource resource() const { return (type == Type::Resource) ? ritem : Resource(); }
0040     KAEvent* event() const    { return (type == Type::Event) ? eitem : nullptr; }
0041     Resource parent() const   { return (type == Type::Event) ? owner : Resource(); }
0042 };
0043 
0044 
0045 bool FileResourceDataModel::mInstanceIsOurs = false;
0046 
0047 /******************************************************************************
0048 * Returns the unique instance, creating it first if necessary.
0049 */
0050 FileResourceDataModel* FileResourceDataModel::instance(QObject* parent)
0051 {
0052     if (!mInstance)
0053     {
0054         auto inst = new FileResourceDataModel(parent);
0055         mInstance = inst;
0056         mInstanceIsOurs = true;
0057         inst->initialise();
0058     }
0059     return mInstanceIsOurs ? (FileResourceDataModel*)mInstance : nullptr;
0060 }
0061 
0062 /******************************************************************************
0063 */
0064 FileResourceDataModel::FileResourceDataModel(QObject* parent)
0065     : QAbstractItemModel(parent)
0066     , ResourceDataModelBase()
0067     , mHaveEvents(false)
0068 {
0069     qCDebug(KALARM_LOG) << "FileResourceDataModel::FileResourceDataModel";
0070 
0071     // Create the vector of resource nodes for the model root.
0072     mResourceNodes[Resource()];
0073 
0074     // Get a list of all resources, and their alarms, if they have already been
0075     // created before this, by a previous call to FileResourceConfigManager::createResources().
0076     const QList<ResourceId> resourceIds = FileResourceConfigManager::resourceIds();
0077     for (ResourceId id : resourceIds)
0078     {
0079         Resource resource = Resources::resource(id);
0080         if (!mResourceNodes.contains(resource))
0081             addResource(resource);
0082     }
0083 
0084     Resources* resources = Resources::instance();
0085     connect(resources, &Resources::resourceAdded,
0086                  this, &FileResourceDataModel::addResource);
0087     connect(resources, &Resources::resourcePopulated,
0088                  this, &FileResourceDataModel::slotResourceLoaded);
0089     connect(resources, &Resources::resourceToBeRemoved,
0090                  this, &FileResourceDataModel::removeResource);
0091     connect(resources, &Resources::eventsAdded,
0092                  this, &FileResourceDataModel::slotEventsAdded);
0093     connect(resources, &Resources::eventUpdated,
0094                  this, &FileResourceDataModel::slotEventUpdated);
0095     connect(resources, &Resources::eventsToBeRemoved,
0096                  this, &FileResourceDataModel::deleteEvents);
0097 
0098     connect(resources, &Resources::settingsChanged,
0099                  this, &FileResourceDataModel::slotResourceSettingsChanged);
0100     connect(resources, &Resources::resourceMessage,
0101                  this, &FileResourceDataModel::slotResourceMessage, Qt::QueuedConnection);
0102 
0103     // Exit now, so that ResourceDataModelBase::mInstance will be set before
0104     // setCalendarsCreated() and setMigrationCompleted() are called in initialise().
0105 }
0106 
0107 /******************************************************************************
0108 * Initialise the instance. To be called immediately after construction.
0109 */
0110 void FileResourceDataModel::initialise()
0111 {
0112     FileResourceConfigManager::createResources(this);
0113     setCalendarsCreated();
0114 
0115     FileResourceMigrator* migrator = FileResourceMigrator::instance();
0116     if (!migrator)
0117         setMigrationComplete();
0118     else
0119     {
0120         connect(migrator, &QObject::destroyed, this, &FileResourceDataModel::slotMigrationCompleted);
0121         setMigrationInitiated();
0122         migrator->start();
0123     }
0124 
0125     MinuteTimer::connect(this, SLOT(slotUpdateTimeTo()));
0126     Preferences::connect(&Preferences::archivedColourChanged, this, &FileResourceDataModel::slotUpdateArchivedColour);
0127     Preferences::connect(&Preferences::disabledColourChanged, this, &FileResourceDataModel::slotUpdateDisabledColour);
0128     Preferences::connect(&Preferences::holidaysChanged, this, &FileResourceDataModel::slotUpdateHolidays);
0129     Preferences::connect(&Preferences::workTimeChanged, this, &FileResourceDataModel::slotUpdateWorkingHours);
0130 }
0131 
0132 /******************************************************************************
0133 */
0134 FileResourceDataModel::~FileResourceDataModel()
0135 {
0136     qCDebug(KALARM_LOG) << "FileResourceDataModel::~FileResourceDataModel";
0137     ResourceFilterCheckListModel::disable();   // prevent resources being disabled when they are removed
0138     while (!mResources.isEmpty())
0139         removeResource(mResources.first());
0140     if (mInstance == this)
0141     {
0142         mInstance = nullptr;
0143         mInstanceIsOurs = false;
0144     }
0145     delete Resources::instance();
0146 }
0147 
0148 /******************************************************************************
0149 * Return whether a model index refers to a resource or an event.
0150 */
0151 FileResourceDataModel::Type FileResourceDataModel::type(const QModelIndex& ix) const
0152 {
0153     if (ix.isValid())
0154     {
0155         const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0156         if (node)
0157             return node->type;
0158     }
0159     return Type::Error;
0160 }
0161 
0162 /******************************************************************************
0163 * Return the resource with the given ID.
0164 */
0165 Resource FileResourceDataModel::resource(ResourceId id) const
0166 {
0167     return Resource(Resources::resource(id));
0168 }
0169 
0170 /******************************************************************************
0171 * Return the resource referred to by an index, or invalid resource if the index
0172 * is not for a resource.
0173 */
0174 Resource FileResourceDataModel::resource(const QModelIndex& ix) const
0175 {
0176     if (ix.isValid())
0177     {
0178         const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0179         if (node)
0180         {
0181             Resource res = node->resource();
0182             if (!res.isNull())
0183                 return res;
0184         }
0185     }
0186     return {};
0187 }
0188 
0189 /******************************************************************************
0190 * Find the QModelIndex of a resource.
0191 */
0192 QModelIndex FileResourceDataModel::resourceIndex(const Resource& resource) const
0193 {
0194     if (resource.isValid())
0195     {
0196         int row = mResources.indexOf(const_cast<Resource&>(resource));
0197         if (row >= 0)
0198             return createIndex(row, 0, mResourceNodes.value(Resource()).at(row));
0199     }
0200     return {};
0201 }
0202 
0203 /******************************************************************************
0204 * Find the QModelIndex of a resource.
0205 */
0206 QModelIndex FileResourceDataModel::resourceIndex(ResourceId id) const
0207 {
0208     return resourceIndex(Resources::resource(id));
0209 }
0210 
0211 /******************************************************************************
0212 * Return the event with the given ID.
0213 */
0214 KAEvent FileResourceDataModel::event(const QString& eventId) const
0215 {
0216     const Node* node = mEventNodes.value(eventId, nullptr);
0217     if (node)
0218     {
0219         KAEvent* event = node->event();
0220         if (event)
0221             return *event;
0222     }
0223     return {};
0224 }
0225 
0226 /******************************************************************************
0227 * Return the event referred to by an index, or invalid event if the index is
0228 * not for an event.
0229 */
0230 KAEvent FileResourceDataModel::event(const QModelIndex& ix) const
0231 {
0232     if (ix.isValid())
0233     {
0234         const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0235         if (node)
0236         {
0237             const KAEvent* event = node->event();
0238             if (event)
0239                 return *event;
0240         }
0241     }
0242     return {};
0243 }
0244 
0245 /******************************************************************************
0246 * Return the index to a specified event.
0247 */
0248 QModelIndex FileResourceDataModel::eventIndex(const QString& eventId) const
0249 {
0250     Node* node = mEventNodes.value(eventId, nullptr);
0251     if (node)
0252     {
0253         Resource resource = node->parent();
0254         if (resource.isValid())
0255         {
0256             const QList<Node*> nodes = mResourceNodes.value(resource);
0257             int row = nodes.indexOf(node);
0258             if (row >= 0)
0259                 return createIndex(row, 0, node);
0260         }
0261     }
0262     return {};
0263 }
0264 
0265 /******************************************************************************
0266 * Return the index to a specified event.
0267 */
0268 QModelIndex FileResourceDataModel::eventIndex(const KAEvent& event) const
0269 {
0270     return eventIndex(event.id());
0271 }
0272 
0273 /******************************************************************************
0274 * Add an event to a specified resource.
0275 * The event's 'updated' flag is cleared.
0276 * Reply = true if item creation has been scheduled.
0277 */
0278 bool FileResourceDataModel::addEvent(KAEvent& event, Resource& resource)
0279 {
0280     qCDebug(KALARM_LOG) << "FileResourceDataModel::addEvent: ID:" << event.id();
0281     return resource.addEvent(event);
0282 }
0283 
0284 /******************************************************************************
0285 * Recursive function to Q_EMIT the dataChanged() signal for all events in a
0286 * specified column range.
0287 */
0288 void FileResourceDataModel::signalDataChanged(bool (*checkFunc)(const KAEvent*), int startColumn, int endColumn, const QModelIndex& parent)
0289 {
0290     int start = -1;
0291     int end   = -1;
0292     for (int row = 0, count = rowCount(parent);  row < count;  ++row)
0293     {
0294         KAEvent* event = nullptr;
0295         const QModelIndex ix = index(row, 0, parent);
0296         const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0297         if (node)
0298         {
0299             event = node->event();
0300             if (event)
0301             {
0302                 if ((*checkFunc)(event))
0303                 {
0304                     // For efficiency, emit a single signal for each group of
0305                     // consecutive events, rather than a separate signal for each event.
0306                     if (start < 0)
0307                         start = row;
0308                     end = row;
0309                     continue;
0310                 }
0311             }
0312         }
0313         if (start >= 0)
0314             Q_EMIT dataChanged(index(start, startColumn, parent), index(end, endColumn, parent));
0315         start = -1;
0316         if (!event)
0317             signalDataChanged(checkFunc, startColumn, endColumn, ix);
0318     }
0319 
0320     if (start >= 0)
0321         Q_EMIT dataChanged(index(start, startColumn, parent), index(end, endColumn, parent));
0322 }
0323 
0324 void FileResourceDataModel::slotMigrationCompleted()
0325 {
0326     qCDebug(KALARM_LOG) << "FileResourceDataModel: Migration completed";
0327     setMigrationComplete();
0328 }
0329 
0330 /******************************************************************************
0331 * Signal every minute that the time-to-alarm values have changed.
0332 */
0333 static bool checkEvent_isActive(const KAEvent* event)
0334 { return event->category() == CalEvent::ACTIVE; }
0335 
0336 void FileResourceDataModel::slotUpdateTimeTo()
0337 {
0338     signalDataChanged(&checkEvent_isActive, TimeToColumn, TimeToColumn, QModelIndex());
0339 }
0340 
0341 /******************************************************************************
0342 * Called when the colour used to display archived alarms has changed.
0343 */
0344 static bool checkEvent_isArchived(const KAEvent* event)
0345 { return event->category() == CalEvent::ARCHIVED; }
0346 
0347 void FileResourceDataModel::slotUpdateArchivedColour(const QColor&)
0348 {
0349     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotUpdateArchivedColour";
0350     signalDataChanged(&checkEvent_isArchived, 0, ColumnCount - 1, QModelIndex());
0351 }
0352 
0353 /******************************************************************************
0354 * Called when the colour used to display disabled alarms has changed.
0355 */
0356 static bool checkEvent_isDisabled(const KAEvent* event)
0357 {
0358     return !event->enabled();
0359 }
0360 
0361 void FileResourceDataModel::slotUpdateDisabledColour(const QColor&)
0362 {
0363     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotUpdateDisabledColour";
0364     signalDataChanged(&checkEvent_isDisabled, 0, ColumnCount - 1, QModelIndex());
0365 }
0366 
0367 /******************************************************************************
0368 * Called when the definition of holidays has changed.
0369 * Update the next trigger time for all alarms which are set to recur only on
0370 * non-holidays.
0371 */
0372 static bool checkEvent_excludesHolidays(const KAEvent* event)
0373 {
0374     return event->holidaysExcluded();
0375 }
0376 
0377 void FileResourceDataModel::slotUpdateHolidays()
0378 {
0379     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotUpdateHolidays";
0380     Q_ASSERT(TimeToColumn == TimeColumn + 1);  // signal should be emitted only for TimeTo and Time columns
0381     signalDataChanged(&checkEvent_excludesHolidays, TimeColumn, TimeToColumn, QModelIndex());
0382 }
0383 
0384 /******************************************************************************
0385 * Called when the definition of working hours has changed.
0386 * Update the next trigger time for all alarms which are set to recur only
0387 * during working hours.
0388 */
0389 static bool checkEvent_workTimeOnly(const KAEvent* event)
0390 {
0391     return event->workTimeOnly();
0392 }
0393 
0394 void FileResourceDataModel::slotUpdateWorkingHours()
0395 {
0396     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotUpdateWorkingHours";
0397     Q_ASSERT(TimeToColumn == TimeColumn + 1);  // signal should be emitted only for TimeTo and Time columns
0398     signalDataChanged(&checkEvent_workTimeOnly, TimeColumn, TimeToColumn, QModelIndex());
0399 }
0400 
0401 /******************************************************************************
0402 * Called when loading of a resource is complete.
0403 */
0404 void FileResourceDataModel::slotResourceLoaded(Resource& resource)
0405 {
0406     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceLoaded:" << resource.displayName();
0407     addResource(resource);
0408 }
0409 
0410 /******************************************************************************
0411 * Called when a resource setting has changed.
0412 */
0413 void FileResourceDataModel::slotResourceSettingsChanged(Resource& res, ResourceType::Changes changes)
0414 {
0415     if (changes & ResourceType::Enabled)
0416     {
0417         if (res.enabledTypes())
0418         {
0419             // If the resource has just been loaded, addResource() will already
0420             // have been called, so don't call it again.
0421             if (!(changes & ResourceType::Loaded))
0422             {
0423                 qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceSettingsChanged: Enabled" << res.displayName();
0424                 addResource(res);
0425             }
0426         }
0427         else
0428         {
0429             qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceSettingsChanged: Disabled" << res.displayName();
0430             removeResourceEvents(res);
0431         }
0432     }
0433     if (changes & (ResourceType::Name | ResourceType::Standard | ResourceType::ReadOnly))
0434     {
0435         qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceSettingsChanged:" << res.displayName();
0436         const QModelIndex resourceIx = resourceIndex(res);
0437         if (resourceIx.isValid())
0438             Q_EMIT dataChanged(resourceIx, resourceIx);
0439     }
0440     if (changes & ResourceType::BackgroundColour)
0441     {
0442         qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceSettingsChanged: Colour" << res.displayName();
0443         const QList<Node*>& eventNodes = mResourceNodes.value(res);
0444         const int lastRow = eventNodes.count() - 1;
0445         if (lastRow >= 0)
0446             Q_EMIT dataChanged(createIndex(0, 0, eventNodes[0]), createIndex(lastRow, ColumnCount - 1, eventNodes[lastRow]));
0447     }
0448 
0449 //    if (changes & (ResourceType::AlarmTypes | ResourceType::KeepFormat | ResourceType::UpdateFormat))
0450 //        qCDebug(KALARM_LOG) << "FileResourceDataModel::slotResourceSettingsChanged: UNHANDLED" << res.displayName();
0451 }
0452 
0453 /******************************************************************************
0454 * Called when events have been added to a resource. Add events to the list.
0455 */
0456 void FileResourceDataModel::slotEventsAdded(Resource& resource, const QList<KAEvent>& events)
0457 {
0458     if (events.isEmpty())
0459         return;
0460     qCDebug(KALARM_LOG) << "FileResourceDataModel::slotEventsAdded:" << resource.displayId() << "Count:" << events.count();
0461     if (mResourceNodes.contains(resource))
0462     {
0463         // If events with the same ID already exist, remove them first.
0464         QList<KAEvent> eventsToAdd = events;
0465         {
0466             QList<KAEvent> eventsToDelete;
0467             for (int i = eventsToAdd.count();  --i >= 0;  )
0468             {
0469                 const KAEvent& event = eventsToAdd.at(i);
0470                 const Node* dnode = mEventNodes.value(event.id(), nullptr);
0471                 if (dnode)
0472                 {
0473                     if (dnode->parent() != resource)
0474                     {
0475                         qCWarning(KALARM_LOG) << "FileResourceDataModel::slotEventsAdded: Event ID already exists in another resource";
0476                         eventsToAdd.removeAt(i);
0477                     }
0478                     else
0479                         eventsToDelete << *dnode->event();
0480                 }
0481             }
0482             if (!eventsToDelete.isEmpty())
0483                 deleteEvents(resource, eventsToDelete);
0484         }
0485 
0486         if (!eventsToAdd.isEmpty())
0487         {
0488             QList<Node*>& resourceEventNodes = mResourceNodes[resource];
0489             int row = resourceEventNodes.count();
0490             resourceEventNodes.reserve(row + eventsToAdd.count());
0491             const QModelIndex resourceIx = resourceIndex(resource);
0492             beginInsertRows(resourceIx, row, row + eventsToAdd.count() - 1);
0493             for (const KAEvent& event : std::as_const(eventsToAdd))
0494             {
0495                 auto ev = new KAEvent(event);
0496                 ev->setResourceId(resource.id());
0497                 Node* node = new Node(ev, resource);
0498                 resourceEventNodes += node;
0499                 mEventNodes[ev->id()] = node;
0500             }
0501             endInsertRows();
0502             if (!mHaveEvents)
0503                 updateHaveEvents(true);
0504         }
0505     }
0506 }
0507 
0508 /******************************************************************************
0509 * Update an event which already exists (and with the same UID) in the model.
0510 */
0511 void FileResourceDataModel::slotEventUpdated(Resource& resource, const KAEvent& event)
0512 {
0513     auto it = mEventNodes.constFind(event.id());
0514     if (it != mEventNodes.constEnd())
0515     {
0516         Node* node = it.value();
0517         if (node  &&  node->parent() == resource)
0518         {
0519             KAEvent* oldEvent = node->event();
0520             if (oldEvent)
0521             {
0522                 *oldEvent = event;
0523 
0524                 const QList<Node *> eventNodes = mResourceNodes.value(resource);
0525                 int row = eventNodes.indexOf(node);
0526                 if (row >= 0)
0527                 {
0528                     const QModelIndex resourceIx = resourceIndex(resource);
0529                     Q_EMIT dataChanged(index(row, 0, resourceIx), index(row, ColumnCount - 1, resourceIx));
0530                 }
0531             }
0532         }
0533     }
0534 }
0535 
0536 /******************************************************************************
0537 * Delete events from their resource.
0538 */
0539 bool FileResourceDataModel::deleteEvents(Resource& resource, const QList<KAEvent>& events)
0540 {
0541     qCDebug(KALARM_LOG) << "FileResourceDataModel::deleteEvents:" << resource.displayName() << "Count:" << events.count();
0542     QModelIndex resourceIx = resourceIndex(resource);
0543     if (!resourceIx.isValid())
0544         return false;
0545     auto it = mResourceNodes.find(resource);
0546     if (it == mResourceNodes.end())
0547         return false;
0548     QList<Node*>& eventNodes = it.value();
0549 
0550     // Find the row numbers of the events to delete.
0551     QList<int> rowsToDelete;
0552     rowsToDelete.reserve(events.count());
0553     for (const KAEvent& event : events)
0554     {
0555         Node* node = mEventNodes.value(event.id(), nullptr);
0556         if (node  &&  node->parent() == resource)
0557         {
0558             int row = eventNodes.indexOf(node);
0559             if (row >= 0)
0560                 rowsToDelete << row;
0561         }
0562     }
0563 
0564     // Delete the events in groups of consecutive rows (if any).
0565     std::sort(rowsToDelete.begin(), rowsToDelete.end());
0566     for (int i = 0, count = rowsToDelete.count();  i < count;  )
0567     {
0568         int row = rowsToDelete.at(i);
0569         int lastRow = row;
0570         while (++i < count  &&  rowsToDelete.at(i) == lastRow + 1)
0571             ++lastRow;
0572 
0573         beginRemoveRows(resourceIx, row, lastRow);
0574         do
0575         {
0576             Node* node = eventNodes.at(row);
0577             eventNodes.removeAt(row);
0578             mEventNodes.remove(node->event()->id());
0579             delete node;
0580         } while (++row <= lastRow);
0581         endRemoveRows();
0582     }
0583 
0584     if (mHaveEvents  &&  mEventNodes.isEmpty())
0585         updateHaveEvents(false);
0586     return true;
0587 }
0588 
0589 /******************************************************************************
0590 * Add a resource and all its events into the model.
0591 */
0592 void FileResourceDataModel::addResource(Resource& resource)
0593 {
0594     // Get the events to add to the model
0595     const QList<KAEvent> events = resource.events();
0596     qCDebug(KALARM_LOG) << "FileResourceDataModel::addResource" << resource.displayName() << ", event count:" << events.count();
0597 
0598     const QModelIndex resourceIx = resourceIndex(resource);
0599     if (resourceIx.isValid())
0600     {
0601         // The resource already exists: remove its existing events from the model.
0602         bool noEvents = events.isEmpty();
0603         removeResourceEvents(resource, noEvents);
0604         if (noEvents)
0605             return;
0606         beginInsertRows(resourceIx, 0, events.count() - 1);
0607     }
0608     else
0609     {
0610         // Add the new resource to the model
0611         QList<Node*>& resourceNodes = mResourceNodes[Resource()];
0612         int row = resourceNodes.count();
0613         beginInsertRows(QModelIndex(), row, row);
0614         mResources += resource;
0615         resourceNodes += new Node(resource);
0616         mResourceNodes.insert(resource, QList<Node*>());
0617     }
0618 
0619     if (!events.isEmpty())
0620     {
0621         QList<Node*>& resourceEventNodes = mResourceNodes[resource];
0622         resourceEventNodes.reserve(resourceEventNodes.count() + events.count());
0623         for (const KAEvent& event : events)
0624         {
0625             Node* node = new Node(new KAEvent(event), resource);
0626             resourceEventNodes += node;
0627             mEventNodes[event.id()] = node;
0628         }
0629     }
0630     endInsertRows();
0631     if (!mHaveEvents  &&  !mEventNodes.isEmpty())
0632         updateHaveEvents(true);
0633     else if (mHaveEvents  &&  mEventNodes.isEmpty())
0634         updateHaveEvents(false);
0635 }
0636 
0637 /******************************************************************************
0638 * Remove a resource and its events from the list.
0639 * This has to be called before the resource is actually deleted or reloaded. If
0640 * not, timer based updates can occur between the resource being deleted and
0641 * slotResourceSettingsChanged(Deleted) being triggered, leading to crashes when
0642 * data from the resource's events is fetched.
0643 */
0644 void FileResourceDataModel::removeResource(Resource& resource)
0645 {
0646     qCDebug(KALARM_LOG) << "FileResourceDataModel::removeResource" << resource.displayName();
0647     int row = mResources.indexOf(resource);
0648     if (row < 0)
0649         return;
0650 
0651     Resource r(resource);   // in case 'resource' is a reference to the copy in mResources
0652     int count = 0;
0653     beginRemoveRows(QModelIndex(), row, row);
0654     mResources.removeAt(row);
0655     QList<Node*>& resourceNodes = mResourceNodes[Resource()];
0656     delete resourceNodes.at(row);
0657     resourceNodes.removeAt(row);
0658     auto it = mResourceNodes.find(r);
0659     if (it != mResourceNodes.end())
0660     {
0661         count = removeResourceEvents(it.value());
0662         mResourceNodes.erase(it);
0663     }
0664     endRemoveRows();
0665 
0666     if (count)
0667     {
0668         if (mHaveEvents  &&  mEventNodes.isEmpty())
0669             updateHaveEvents(false);
0670     }
0671 }
0672 
0673 /******************************************************************************
0674 * Remove a resource's events from the list.
0675 */
0676 void FileResourceDataModel::removeResourceEvents(Resource& resource, bool setHaveEvents)
0677 {
0678     qCDebug(KALARM_LOG) << "FileResourceDataModel::removeResourceEvents" << resource.displayName();
0679     const QModelIndex resourceIx = resourceIndex(resource);
0680     if (resourceIx.isValid())
0681     {
0682         // The resource already exists: remove its existing events from the model.
0683         QList<Node*>& eventNodes = mResourceNodes[resource];
0684         if (!eventNodes.isEmpty())
0685         {
0686             beginRemoveRows(resourceIx, 0, eventNodes.count() - 1);
0687             int count = removeResourceEvents(eventNodes);
0688             endRemoveRows();
0689             if (count  &&  setHaveEvents)
0690             {
0691                 if (mHaveEvents  &&  mEventNodes.isEmpty())
0692                     updateHaveEvents(false);
0693             }
0694         }
0695     }
0696 }
0697 
0698 /******************************************************************************
0699 * Remove a resource's events from the list.
0700 * beginRemoveRows() must be called before this method, and endRemoveRows()
0701 * afterwards. Then, removeConfigEvents() must be called with the return value
0702 * from this method as a parameter.
0703 * Return - number of events which have been removed.
0704 */
0705 int FileResourceDataModel::removeResourceEvents(QList<Node*>& eventNodes)
0706 {
0707     qCDebug(KALARM_LOG) << "FileResourceDataModel::removeResourceEvents";
0708     int count = 0;
0709     for (Node* node : eventNodes)
0710     {
0711         KAEvent* event = node->event();
0712         if (event)
0713         {
0714             const QString eventId = event->id();
0715             mEventNodes.remove(eventId);
0716             ++count;
0717         }
0718         delete node;
0719     }
0720     eventNodes.clear();
0721     return count;
0722 }
0723 
0724 /******************************************************************************
0725 * Terminate access to the data model, and tidy up.
0726 */
0727 void FileResourceDataModel::terminate()
0728 {
0729     delete mInstance;
0730 }
0731 
0732 /******************************************************************************
0733 * Reload all resources' data from storage.
0734 */
0735 void FileResourceDataModel::reload()
0736 {
0737     for (int i = 0, iMax = mResources.count();  i < iMax;  ++i)
0738         mResources[i].reload();
0739 }
0740 
0741 /******************************************************************************
0742 * Reload a resource's data from storage.
0743 */
0744 bool FileResourceDataModel::reload(Resource& resource)
0745 {
0746     if (!resource.isValid())
0747         return false;
0748     qCDebug(KALARM_LOG) << "FileResourceDataModel::reload:" << resource.displayId();
0749     return resource.reload();
0750 }
0751 
0752 /******************************************************************************
0753 * Create a FileResourceCreator instance.
0754 */
0755 ResourceCreator* FileResourceDataModel::createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent)
0756 {
0757     return new FileResourceCreator(defaultType, parent);
0758 }
0759 
0760 /******************************************************************************
0761 * Update a resource's backend calendar file to the current KAlarm format.
0762 */
0763 void FileResourceDataModel::updateCalendarToCurrentFormat(Resource& resource, bool ignoreKeepFormat, QObject* parent)
0764 {
0765     FileResourceCalendarUpdater::updateToCurrentFormat(resource, ignoreKeepFormat, parent);
0766 }
0767 
0768 /******************************************************************************
0769 * Create model instances which are dependent on the resource data model type.
0770 */
0771 ResourceListModel* FileResourceDataModel::createResourceListModel(QObject* parent)
0772 {
0773     return ResourceListModel::create<FileResourceDataModel>(parent);
0774 }
0775 
0776 ResourceFilterCheckListModel* FileResourceDataModel::createResourceFilterCheckListModel(QObject* parent)
0777 {
0778     return ResourceFilterCheckListModel::create<FileResourceDataModel>(parent);
0779 }
0780 
0781 AlarmListModel* FileResourceDataModel::createAlarmListModel(QObject* parent)
0782 {
0783     return AlarmListModel::create<FileResourceDataModel>(parent);
0784 }
0785 
0786 AlarmListModel* FileResourceDataModel::allAlarmListModel()
0787 {
0788     return AlarmListModel::all<FileResourceDataModel>();
0789 }
0790 
0791 TemplateListModel* FileResourceDataModel::createTemplateListModel(QObject* parent)
0792 {
0793     return TemplateListModel::create<FileResourceDataModel>(parent);
0794 }
0795 
0796 TemplateListModel* FileResourceDataModel::allTemplateListModel()
0797 {
0798     return TemplateListModel::all<FileResourceDataModel>();
0799 }
0800 
0801 /******************************************************************************
0802 * Return the number of children of a model index.
0803 */
0804 int FileResourceDataModel::rowCount(const QModelIndex& parent) const
0805 {
0806     if (!parent.isValid())
0807         return mResourceNodes.count() - 1;
0808     const Node* node = reinterpret_cast<Node*>(parent.internalPointer());
0809     if (node  &&  node->type == Type::Resource)
0810         return mResourceNodes.value(node->resource()).count();
0811     return 0;
0812 }
0813 
0814 /******************************************************************************
0815 * Return the number of columns for children of a model index.
0816 */
0817 int FileResourceDataModel::columnCount(const QModelIndex& parent) const
0818 {
0819     // Although the number of columns differs between resources and events,
0820     // returning different values here doesn't work. So return the maximum
0821     // number of columns.
0822     Q_UNUSED(parent);
0823     return ColumnCount;
0824 }
0825 
0826 /******************************************************************************
0827 * Return the model index for a specified item.
0828 */
0829 QModelIndex FileResourceDataModel::index(int row, int column, const QModelIndex& parent) const
0830 {
0831     if (row >= 0  &&  column >= 0)
0832     {
0833         if (!parent.isValid())
0834         {
0835             if (!column)
0836             {
0837                 const QList<Node*>& nodes = mResourceNodes.value(Resource());
0838                 if (row < nodes.count())
0839                     return createIndex(row, column, nodes[row]);
0840             }
0841         }
0842         else
0843         {
0844             if (column < ColumnCount)
0845             {
0846                 const Node* node = reinterpret_cast<Node*>(parent.internalPointer());
0847                 if (node)
0848                 {
0849                     Resource resource = node->resource();
0850                     if (resource.isValid())
0851                     {
0852                         const QList<Node*>& nodes = mResourceNodes.value(resource);
0853                         if (row < nodes.count())
0854                             return createIndex(row, column, nodes[row]);
0855                     }
0856                 }
0857             }
0858         }
0859     }
0860     return {};
0861 }
0862 
0863 /******************************************************************************
0864 * Return the model index for the parent of a specified item.
0865 */
0866 QModelIndex FileResourceDataModel::parent(const QModelIndex& ix) const
0867 {
0868     const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0869     if (node)
0870     {
0871         Resource resource = node->parent();
0872         if (resource.isValid())
0873         {
0874             int row = mResources.indexOf(resource);
0875             if (row >= 0)
0876                 return createIndex(row, 0, mResourceNodes.value(Resource()).at(row));
0877         }
0878     }
0879     return {};
0880 }
0881 
0882 /******************************************************************************
0883 * Return the indexes which match a data value in the 'start' index column.
0884 */
0885 QModelIndexList FileResourceDataModel::match(const QModelIndex& start, int role, const QVariant& value, int hits, Qt::MatchFlags flags) const
0886 {
0887     switch (role)
0888     {
0889         case ResourceIdRole:
0890         {
0891             QModelIndexList result;               //clazy:exclude=inefficient-qlist
0892             const ResourceId id = value.toLongLong();
0893             if (id >= 0)
0894             {
0895                 const QModelIndex ix = resourceIndex(id);
0896                 if (ix.isValid())
0897                     result += ix;
0898             }
0899             return result;
0900         }
0901         case EventIdRole:
0902         {
0903             QModelIndexList result;               //clazy:exclude=inefficient-qlist
0904             const QModelIndex ix = eventIndex(value.toString());
0905             if (ix.isValid())
0906                 result += ix;
0907             return result;
0908         }
0909         default:
0910             break;
0911     }
0912 
0913     return QAbstractItemModel::match(start, role, value, hits, flags);
0914 }
0915 
0916 /******************************************************************************
0917 * Return the data for a given role, for a specified item.
0918 */
0919 QVariant FileResourceDataModel::data(const QModelIndex& ix, int role) const
0920 {
0921     const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0922     if (node)
0923     {
0924         const Resource res = node->resource();
0925         if (!res.isNull())
0926         {
0927             bool handled;
0928             const QVariant value = resourceData(role, res, handled);
0929             if (handled)
0930                 return value;
0931 
0932             switch (role)
0933             {
0934                 case Qt::CheckStateRole:
0935                     return res.enabledTypes() ? Qt::Checked : Qt::Unchecked;
0936                 default:
0937                     break;
0938             }
0939         }
0940         else
0941         {
0942             KAEvent* event = node->event();
0943             if (event)
0944             {
0945                 // This is an Event row
0946                 if (role == ParentResourceIdRole)
0947                     return node->parent().id();
0948                 const Resource resp = node->parent();
0949                 bool handled;
0950                 const QVariant value = eventData(role, ix.column(), *event, resp, handled);
0951                 if (handled)
0952                     return value;
0953             }
0954         }
0955 
0956         // Return invalid value
0957         switch (role)
0958         {
0959             case ItemTypeRole:
0960                 return static_cast<int>(Type::Error);
0961             case ResourceIdRole:
0962             case ParentResourceIdRole:
0963                 return -1;
0964             case StatusRole:
0965             default:
0966                 break;
0967         }
0968     }
0969     return {};
0970 }
0971 
0972 /******************************************************************************
0973 * Set the data for a given role, for a specified item.
0974 */
0975 bool FileResourceDataModel::setData(const QModelIndex& ix, const QVariant& value, int role)
0976 {
0977     // NOTE: need to Q_EMIT dataChanged() whenever something is updated (except via a job).
0978     const Node* node = reinterpret_cast<Node*>(ix.internalPointer());
0979     if (!node)
0980         return false;
0981     Resource resource = node->resource();
0982     if (resource.isNull())
0983     {
0984         // This is an Event row
0985         KAEvent* event = node->event();
0986         if (event  &&  event->isValid())
0987         {
0988             switch (role)
0989             {
0990                 case Qt::EditRole:
0991                 {
0992                     int row = ix.row();
0993                     Q_EMIT dataChanged(index(row, 0, ix.parent()), index(row, ColumnCount - 1, ix.parent()));
0994                     return true;
0995                 }
0996                 default:
0997                     break;
0998             }
0999         }
1000     }
1001     return QAbstractItemModel::setData(ix, value, role);
1002 }
1003 
1004 /******************************************************************************
1005 * Return data for a column heading.
1006 */
1007 QVariant FileResourceDataModel::headerData(int section, Qt::Orientation orientation, int role) const
1008 {
1009     bool handled;
1010     QVariant value = ResourceDataModelBase::headerData(section, orientation, role, true, handled);
1011     if (handled)
1012         return value;
1013     return {};
1014 }
1015 
1016 Qt::ItemFlags FileResourceDataModel::flags(const QModelIndex& ix) const
1017 {
1018     if (!ix.isValid())
1019         return Qt::ItemIsEnabled;
1020     return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
1021 }
1022 
1023 /******************************************************************************
1024 * Display a message to the user.
1025 */
1026 void FileResourceDataModel::slotResourceMessage(ResourceType::MessageType type, const QString& message, const QString& details)
1027 {
1028     handleResourceMessage(type, message, details);
1029 }
1030 
1031 #include "moc_fileresourcedatamodel.cpp"
1032 
1033 // vim: et sw=4: