diff --git a/src/resources/akonadidatamodel.cpp b/src/resources/akonadidatamodel.cpp index 802ac477..756beb36 100644 --- a/src/resources/akonadidatamodel.cpp +++ b/src/resources/akonadidatamodel.cpp @@ -1,956 +1,1035 @@ /* * akonadidatamodel.cpp - KAlarm calendar file access using Akonadi * Program: kalarm * Copyright © 2007-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadidatamodel.h" -#include "preferences.h" +#include "resources/akonadicalendarupdater.h" +#include "resources/akonadiresourcecreator.h" #include "resources/akonadiresourcemigrator.h" +#include "resources/eventmodel.h" +#include "resources/resourcemodel.h" #include "resources/resources.h" + +#include "preferences.h" #include "lib/synchtimer.h" #include "kalarm_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include using namespace Akonadi; using namespace KAlarmCal; // Ensure ResourceDataModelBase::UserRole is valid. ResourceDataModelBase does // not include Akonadi headers, so here we check that it has been set to be // compatible with EntityTreeModel::UserRole. static_assert((int)ResourceDataModelBase::UserRole>=(int)Akonadi::EntityTreeModel::UserRole, "ResourceDataModelBase::UserRole wrong value"); /*============================================================================= = Class: AkonadiDataModel =============================================================================*/ -AkonadiDataModel* AkonadiDataModel::mInstance = nullptr; -int AkonadiDataModel::mTimeHourPos = -2; +bool AkonadiDataModel::mInstanceIsOurs = false; +int AkonadiDataModel::mTimeHourPos = -2; /****************************************************************************** * Construct and return the singleton. */ AkonadiDataModel* AkonadiDataModel::instance() { if (!mInstance) + { mInstance = new AkonadiDataModel(new ChangeRecorder(qApp), qApp); - return mInstance; + mInstanceIsOurs = true; + } + return mInstanceIsOurs ? (AkonadiDataModel*)mInstance : nullptr; } /****************************************************************************** * Constructor. */ AkonadiDataModel::AkonadiDataModel(ChangeRecorder* monitor, QObject* parent) : EntityTreeModel(monitor, parent) , ResourceDataModelBase() , mMonitor(monitor) { // Populate all collections, selected/enabled or unselected/disabled. setItemPopulationStrategy(ImmediatePopulation); // Restrict monitoring to collections containing the KAlarm mime types monitor->setCollectionMonitored(Collection::root()); monitor->setResourceMonitored("akonadi_kalarm_resource"); monitor->setResourceMonitored("akonadi_kalarm_dir_resource"); monitor->setMimeTypeMonitored(KAlarmCal::MIME_ACTIVE); monitor->setMimeTypeMonitored(KAlarmCal::MIME_ARCHIVED); monitor->setMimeTypeMonitored(KAlarmCal::MIME_TEMPLATE); monitor->itemFetchScope().fetchFullPayload(); monitor->itemFetchScope().fetchAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); connect(monitor, SIGNAL(collectionChanged(Akonadi::Collection,QSet)), SLOT(slotCollectionChanged(Akonadi::Collection,QSet))); connect(monitor, &Monitor::collectionRemoved, this, &AkonadiDataModel::slotCollectionRemoved); initResourceMigrator(); MinuteTimer::connect(this, SLOT(slotUpdateTimeTo())); Preferences::connect(SIGNAL(archivedColourChanged(QColor)), this, SLOT(slotUpdateArchivedColour(QColor))); Preferences::connect(SIGNAL(disabledColourChanged(QColor)), this, SLOT(slotUpdateDisabledColour(QColor))); Preferences::connect(SIGNAL(holidaysChanged(KHolidays::HolidayRegion)), this, SLOT(slotUpdateHolidays())); Preferences::connect(SIGNAL(workTimeChanged(QTime,QTime,QBitArray)), this, SLOT(slotUpdateWorkingHours())); connect(Resources::instance(), &Resources::resourceMessage, this, &AkonadiDataModel::slotResourceMessage, Qt::QueuedConnection); connect(this, &AkonadiDataModel::rowsInserted, this, &AkonadiDataModel::slotRowsInserted); connect(this, &AkonadiDataModel::rowsAboutToBeRemoved, this, &AkonadiDataModel::slotRowsAboutToBeRemoved); connect(this, &Akonadi::EntityTreeModel::collectionTreeFetched, this, &AkonadiDataModel::slotCollectionTreeFetched); connect(this, &Akonadi::EntityTreeModel::collectionPopulated, this, &AkonadiDataModel::slotCollectionPopulated); connect(monitor, &Monitor::itemChanged, this, &AkonadiDataModel::slotMonitoredItemChanged); connect(ServerManager::self(), &ServerManager::stateChanged, this, &AkonadiDataModel::checkResources); checkResources(ServerManager::state()); } /****************************************************************************** * Destructor. */ AkonadiDataModel::~AkonadiDataModel() { if (mInstance == this) + { mInstance = nullptr; + mInstanceIsOurs = false; + } } /****************************************************************************** * Called when the server manager changes state. * If it is now running, i.e. the agent manager knows about * all existing resources. * Once it is running, i.e. the agent manager knows about * all existing resources, if necessary migrate any KResources alarm calendars from * pre-Akonadi versions of KAlarm, or create default Akonadi calendar resources * if any are missing. */ void AkonadiDataModel::checkResources(ServerManager::State state) { switch (state) { case ServerManager::Running: if (!isMigrating() && !isMigrationComplete()) { qCDebug(KALARM_LOG) << "AkonadiDataModel::checkResources: Server running"; setMigrationInitiated(); AkonadiResourceMigrator::execute(); } break; case ServerManager::NotRunning: qCDebug(KALARM_LOG) << "AkonadiDataModel::checkResources: Server stopped"; setMigrationInitiated(false); initResourceMigrator(); Q_EMIT serverStopped(); break; default: break; } } /****************************************************************************** * Initialise the calendar migrator so that it can be run (either for the first * time, or again). */ void AkonadiDataModel::initResourceMigrator() { AkonadiResourceMigrator::reset(); connect(AkonadiResourceMigrator::instance(), &AkonadiResourceMigrator::creating, this, &AkonadiDataModel::slotCollectionBeingCreated); connect(AkonadiResourceMigrator::instance(), &QObject::destroyed, this, &AkonadiDataModel::slotMigrationCompleted); } ChangeRecorder* AkonadiDataModel::monitor() { return instance()->mMonitor; } /****************************************************************************** * Return the data for a given role, for a specified item. */ QVariant AkonadiDataModel::data(const QModelIndex& index, int role) const { if (role == ResourceIdRole) role = CollectionIdRole; // use the base model for this if (roleHandled(role) || role == ParentResourceIdRole) { const Collection collection = EntityTreeModel::data(index, CollectionRole).value(); if (collection.isValid()) { // This is a Collection row // Update the collection's resource with the current collection value. const Resource& res = updateResource(collection); bool handled; const QVariant value = resourceData(role, res, handled); if (handled) return value; } else { Item item = EntityTreeModel::data(index, ItemRole).value(); if (item.isValid()) { // This is an Item row const QString mime = item.mimeType(); if ((mime != KAlarmCal::MIME_ACTIVE && mime != KAlarmCal::MIME_ARCHIVED && mime != KAlarmCal::MIME_TEMPLATE) || !item.hasPayload()) return QVariant(); Resource res; const KAEvent ev(event(item, index, res)); // this sets item.parentCollection() if (role == ParentResourceIdRole) return item.parentCollection().id(); bool handled; const QVariant value = eventData(role, index.column(), ev, res, handled); if (handled) return value; } } } return EntityTreeModel::data(index, role); } /****************************************************************************** * Return the number of columns for either a collection or an item. */ int AkonadiDataModel::entityColumnCount(HeaderGroup group) const { switch (group) { case CollectionTreeHeaders: return 1; case ItemListHeaders: return ColumnCount; default: return EntityTreeModel::entityColumnCount(group); } } /****************************************************************************** * Return offset to add to headerData() role, for item models. */ int AkonadiDataModel::headerDataEventRoleOffset() const { return TerminalUserRole * ItemListHeaders; } /****************************************************************************** * Return data for a column heading. */ QVariant AkonadiDataModel::entityHeaderData(int section, Qt::Orientation orientation, int role, HeaderGroup group) const { bool eventHeaders = false; switch (group) { case ItemListHeaders: eventHeaders = true; Q_FALLTHROUGH(); // fall through to CollectionTreeHeaders case CollectionTreeHeaders: { bool handled; const QVariant value = ResourceDataModelBase::headerData(section, orientation, role, eventHeaders, handled); if (handled) return value; break; } default: break; } return EntityTreeModel::entityHeaderData(section, orientation, role, group); } /****************************************************************************** * Recursive function to Q_EMIT the dataChanged() signal for all items in a * specified column range. */ void AkonadiDataModel::signalDataChanged(bool (*checkFunc)(const Item&), int startColumn, int endColumn, const QModelIndex& parent) { int start = -1; int end = -1; for (int row = 0, count = rowCount(parent); row < count; ++row) { const QModelIndex ix = index(row, 0, parent); const Item item = ix.data(ItemRole).value(); const bool isItem = item.isValid(); if (isItem) { if ((*checkFunc)(item)) { // For efficiency, Q_EMIT a single signal for each group of // consecutive items, rather than a separate signal for each item. if (start < 0) start = row; end = row; continue; } } if (start >= 0) Q_EMIT dataChanged(index(start, startColumn, parent), index(end, endColumn, parent)); start = -1; if (!isItem) signalDataChanged(checkFunc, startColumn, endColumn, ix); } if (start >= 0) Q_EMIT dataChanged(index(start, startColumn, parent), index(end, endColumn, parent)); } /****************************************************************************** * Signal every minute that the time-to-alarm values have changed. */ static bool checkItem_isActive(const Item& item) { return item.mimeType() == KAlarmCal::MIME_ACTIVE; } void AkonadiDataModel::slotUpdateTimeTo() { signalDataChanged(&checkItem_isActive, TimeToColumn, TimeToColumn, QModelIndex()); } /****************************************************************************** * Called when the colour used to display archived alarms has changed. */ static bool checkItem_isArchived(const Item& item) { return item.mimeType() == KAlarmCal::MIME_ARCHIVED; } void AkonadiDataModel::slotUpdateArchivedColour(const QColor&) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotUpdateArchivedColour"; signalDataChanged(&checkItem_isArchived, 0, ColumnCount - 1, QModelIndex()); } /****************************************************************************** * Called when the colour used to display disabled alarms has changed. */ static bool checkItem_isDisabled(const Item& item) { if (item.hasPayload()) { const KAEvent event = item.payload(); if (event.isValid()) return !event.enabled(); } return false; } void AkonadiDataModel::slotUpdateDisabledColour(const QColor&) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotUpdateDisabledColour"; signalDataChanged(&checkItem_isDisabled, 0, ColumnCount - 1, QModelIndex()); } /****************************************************************************** * Called when the definition of holidays has changed. */ static bool checkItem_excludesHolidays(const Item& item) { if (item.hasPayload()) { const KAEvent event = item.payload(); if (event.isValid() && event.holidaysExcluded()) return true; } return false; } void AkonadiDataModel::slotUpdateHolidays() { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotUpdateHolidays"; Q_ASSERT(TimeToColumn == TimeColumn + 1); // signal should be emitted only for TimeTo and Time columns signalDataChanged(&checkItem_excludesHolidays, TimeColumn, TimeToColumn, QModelIndex()); } /****************************************************************************** * Called when the definition of working hours has changed. */ static bool checkItem_workTimeOnly(const Item& item) { if (item.hasPayload()) { const KAEvent event = item.payload(); if (event.isValid() && event.workTimeOnly()) return true; } return false; } void AkonadiDataModel::slotUpdateWorkingHours() { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotUpdateWorkingHours"; Q_ASSERT(TimeToColumn == TimeColumn + 1); // signal should be emitted only for TimeTo and Time columns signalDataChanged(&checkItem_workTimeOnly, TimeColumn, TimeToColumn, QModelIndex()); } +/****************************************************************************** +* Reload all collections from Akonadi storage. The backend data is not reloaded. +*/ +void AkonadiDataModel::reload() +{ + qCDebug(KALARM_LOG) << "AkonadiDataModel::reload"; + const Collection::List collections = mMonitor->collectionsMonitored(); + for (const Collection& collection : collections) + { + mMonitor->setCollectionMonitored(collection, false); + mMonitor->setCollectionMonitored(collection, true); + } +} + /****************************************************************************** * Reload a collection from Akonadi storage. The backend data is not reloaded. */ bool AkonadiDataModel::reload(Resource& resource) { if (!resource.isValid()) return false; qCDebug(KALARM_LOG) << "AkonadiDataModel::reload:" << resource.displayId(); Collection collection(resource.id()); mMonitor->setCollectionMonitored(collection, false); mMonitor->setCollectionMonitored(collection, true); return true; } /****************************************************************************** -* Reload all collections from Akonadi storage. The backend data is not reloaded. +* Disable the widget if the database engine is not available, and display an +* error overlay. */ -void AkonadiDataModel::reload() +void AkonadiDataModel::widgetNeedsDatabase(QWidget* widget) { - qCDebug(KALARM_LOG) << "AkonadiDataModel::reload"; - const Collection::List collections = mMonitor->collectionsMonitored(); - for (const Collection& collection : collections) - { - mMonitor->setCollectionMonitored(collection, false); - mMonitor->setCollectionMonitored(collection, true); - } + Akonadi::ControlGui::widgetNeedsAkonadi(widget); +} + +/****************************************************************************** +* Check for, and remove, any duplicate Akonadi resources, i.e. those which use +* the same calendar file/directory. +*/ +void AkonadiDataModel::removeDuplicateResources() +{ + AkonadiResource::removeDuplicateResources(); +} + +/****************************************************************************** +* Create an AkonadiResourceCreator instance. +*/ +ResourceCreator* AkonadiDataModel::createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent) +{ + return new AkonadiResourceCreator(defaultType, parent); +} + +/****************************************************************************** +* Update a resource's backend calendar file to the current KAlarm format. +*/ +void AkonadiDataModel::updateCalendarToCurrentFormat(Resource& resource, bool ignoreKeepFormat, QObject* parent) +{ + AkonadiCalendarUpdater::updateToCurrentFormat(resource, ignoreKeepFormat, parent); +} + +/****************************************************************************** +* Create model instances which are dependent on the resource data model type. +*/ +ResourceListModel* AkonadiDataModel::createResourceListModel(QObject* parent) +{ + return ResourceListModel::create(parent); +} + +ResourceFilterCheckListModel* AkonadiDataModel::createResourceFilterCheckListModel(QObject* parent) +{ + return ResourceFilterCheckListModel::create(parent); +} + +AlarmListModel* AkonadiDataModel::createAlarmListModel(QObject* parent) +{ + return AlarmListModel::create(parent); +} + +AlarmListModel* AkonadiDataModel::allAlarmListModel() +{ + return AlarmListModel::all(); +} + +TemplateListModel* AkonadiDataModel::createTemplateListModel(QObject* parent) +{ + return TemplateListModel::create(parent); +} + +TemplateListModel* AkonadiDataModel::allTemplateListModel() +{ + return TemplateListModel::all(); } /****************************************************************************** * Returns the index to a specified event. */ QModelIndex AkonadiDataModel::eventIndex(const KAEvent& event) const { return itemIndex(Item(mEventIds.value(event.id()).itemId)); } /****************************************************************************** * Returns the index to a specified event. */ QModelIndex AkonadiDataModel::eventIndex(const QString& eventId) const { return itemIndex(Item(mEventIds.value(eventId).itemId)); } /****************************************************************************** * Return all events belonging to a collection. */ QList AkonadiDataModel::events(ResourceId id) const { QList list; const QModelIndex ix = modelIndexForCollection(this, Collection(id)); if (ix.isValid()) getChildEvents(ix, list); for (KAEvent& ev : list) ev.setResourceId(id); return list; } /****************************************************************************** * Recursive function to append all child Events with a given mime type. */ void AkonadiDataModel::getChildEvents(const QModelIndex& parent, QList& events) const { for (int row = 0, count = rowCount(parent); row < count; ++row) { const QModelIndex ix = index(row, 0, parent); const Item item = ix.data(ItemRole).value(); if (item.isValid()) { if (item.hasPayload()) { KAEvent event = item.payload(); if (event.isValid()) events += event; } } else { const Collection c = ix.data(CollectionRole).value(); if (c.isValid()) getChildEvents(ix, events); } } } KAEvent AkonadiDataModel::event(const QString& eventId) const { return event(eventIndex(eventId)); } KAEvent AkonadiDataModel::event(const QModelIndex& ix) const { if (!ix.isValid()) return KAEvent(); Item item = ix.data(ItemRole).value(); Resource r; return event(item, ix, r); } /****************************************************************************** * Return the event for an Item at a specified model index. * The item's parent collection is set, as is the event's collection ID. */ KAEvent AkonadiDataModel::event(Akonadi::Item& item, const QModelIndex& ix, Resource& res) const { //TODO: Tune performance: This function is called very frequently with the same parameters if (ix.isValid()) { const Collection pc = ix.data(ParentCollectionRole).value(); item.setParentCollection(pc); res = resource(pc.id()); if (res.isValid()) { // Fetch the KAEvent defined by the Item, including commandError. return AkonadiResource::event(res, item); } } res = Resource::null(); return KAEvent(); } /****************************************************************************** * Return the up to date Item for a specified Akonadi ID. */ Item AkonadiDataModel::itemById(Item::Id id) const { Item item(id); if (!refresh(item)) return Item(); return item; } /****************************************************************************** * Return the Item for a given event. */ Item AkonadiDataModel::itemForEvent(const QString& eventId) const { const QModelIndex ix = eventIndex(eventId); if (!ix.isValid()) return Item(); return ix.data(ItemRole).value(); } #if 0 /****************************************************************************** * Add an event to a specified Collection. * If the event is scheduled to be added to the collection, it is updated with * its Akonadi item ID. * The event's 'updated' flag is cleared. * Reply = true if item creation has been scheduled. */ bool AkonadiDataModel::addEvent(KAEvent& event, Resource& resource) { qCDebug(KALARM_LOG) << "AkonadiDataModel::addEvent: ID:" << event.id(); if (!resource.addEvent(event)) return false; // Note that the item ID will be inserted in mEventIds after the Akonadi // Item has been created by ItemCreateJob, when slotRowsInserted() is called. mEventIds[event.id()] = EventIds(resource.id()); return true; } #endif /****************************************************************************** * Called when rows have been inserted into the model. */ void AkonadiDataModel::slotRowsInserted(const QModelIndex& parent, int start, int end) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotRowsInserted:" << start << "-" << end << "(parent =" << parent << ")"; QHash> events; for (int row = start; row <= end; ++row) { const QModelIndex ix = index(row, 0, parent); const Collection collection = ix.data(CollectionRole).value(); if (collection.isValid()) { // A collection has been inserted. Create a new resource to hold it. qCDebug(KALARM_LOG) << "AkonadiDataModel::slotRowsInserted: Collection" << collection.id() << collection.name(); Resource& resource = updateResource(collection); // Ignore it if it isn't owned by a valid Akonadi resource. if (resource.isValid()) { setCollectionChanged(resource, collection, true); Resources::notifyNewResourceInitialised(resource); if (!collection.hasAttribute()) { // If the compatibility attribute is missing at this point, // it doesn't always get notified later, so fetch the // collection to ensure that we see it. AgentInstance agent = AgentManager::self()->instance(collection.resource()); CollectionFetchJob* job = new CollectionFetchJob(Collection::root(), CollectionFetchJob::Recursive); job->fetchScope().setResource(agent.identifier()); connect(job, &CollectionFetchJob::result, instance(), &AkonadiDataModel::collectionFetchResult); } } } else { // An item has been inserted Item item = ix.data(ItemRole).value(); if (item.isValid()) { qCDebug(KALARM_LOG) << "item id=" << item.id() << ", revision=" << item.revision(); Resource res; const KAEvent evnt = event(item, ix, res); // this sets item.parentCollection() if (evnt.isValid()) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotRowsInserted: Event" << evnt.id(); // Only notify new events if the collection is already populated. // If not populated, all events will be notified when it is // eventually populated. if (res.isPopulated()) events[res] += evnt; mEventIds[evnt.id()] = EventIds(item.parentCollection().id(), item.id()); } // Notify the resource containing the item. AkonadiResource::notifyItemChanged(res, item, true); } } } for (auto it = events.constBegin(); it != events.constEnd(); ++it) { Resource res = it.key(); AkonadiResource::notifyEventsChanged(res, it.value()); } } /****************************************************************************** * Called when a CollectionFetchJob has completed. * Check for and process changes in attribute values. */ void AkonadiDataModel::collectionFetchResult(KJob* j) { CollectionFetchJob* job = qobject_cast(j); if (j->error()) qCWarning(KALARM_LOG) << "AkonadiDataModel::collectionFetchResult: CollectionFetchJob" << job->fetchScope().resource()<< "error: " << j->errorString(); else { const Collection::List collections = job->collections(); for (const Collection& c : collections) { qCDebug(KALARM_LOG) << "AkonadiDataModel::collectionFetchResult:" << c.id(); auto it = mResources.find(c.id()); if (it == mResources.end()) continue; Resource& resource = it.value(); setCollectionChanged(resource, c, false); } } } /****************************************************************************** * Called when rows are about to be removed from the model. */ void AkonadiDataModel::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotRowsAboutToBeRemoved:" << start << "-" << end << "(parent =" << parent << ")"; QHash> events; for (int row = start; row <= end; ++row) { const QModelIndex ix = index(row, 0, parent); Item item = ix.data(ItemRole).value(); Resource res; const KAEvent evnt = event(item, ix, res); // this sets item.parentCollection() if (evnt.isValid()) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotRowsAboutToBeRemoved: Collection:" << item.parentCollection().id() << ", Event ID:" << evnt.id(); events[res] += evnt; mEventIds.remove(evnt.id()); } } for (auto it = events.constBegin(); it != events.constEnd(); ++it) { Resource res = it.key(); AkonadiResource::notifyEventsToBeDeleted(res, it.value()); } } /****************************************************************************** * Called when a monitored collection has changed. * Updates the collection held by the collection's resource, and notifies * changes of interest. */ void AkonadiDataModel::slotCollectionChanged(const Akonadi::Collection& c, const QSet& attributeNames) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotCollectionChanged:" << c.id() << attributeNames; auto it = mResources.find(c.id()); if (it != mResources.end()) { // The Monitor::collectionChanged() signal is not always emitted when // attributes are created! So check whether any attributes not // included in 'attributeNames' have been created. Resource& resource = it.value(); setCollectionChanged(resource, c, attributeNames.contains(CompatibilityAttribute::name())); } } /****************************************************************************** * Called when a monitored collection's properties or content have changed. * Optionally emits a signal if properties of interest have changed. */ void AkonadiDataModel::setCollectionChanged(Resource& resource, const Collection& collection, bool checkCompat) { AkonadiResource::notifyCollectionChanged(resource, collection, checkCompat); if (isMigrating()) { mCollectionIdsBeingCreated.removeAll(collection.id()); if (mCollectionsBeingCreated.isEmpty() && mCollectionIdsBeingCreated.isEmpty() && AkonadiResourceMigrator::completed()) { qCDebug(KALARM_LOG) << "AkonadiDataModel::setCollectionChanged: Migration completed"; setMigrationComplete(); } } } /****************************************************************************** * Called when a monitored collection is removed. */ void AkonadiDataModel::slotCollectionRemoved(const Collection& collection) { const Collection::Id id = collection.id(); qCDebug(KALARM_LOG) << "AkonadiDataModel::slotCollectionRemoved:" << id; mResources.remove(collection.id()); // AkonadiResource will remove the resource from Resources. } /****************************************************************************** * Called when a collection creation is about to start, or has completed. */ void AkonadiDataModel::slotCollectionBeingCreated(const QString& path, Akonadi::Collection::Id id, bool finished) { if (finished) { mCollectionsBeingCreated.removeAll(path); mCollectionIdsBeingCreated << id; } else mCollectionsBeingCreated << path; } /****************************************************************************** * Called when the collection tree has been fetched for the first time. */ void AkonadiDataModel::slotCollectionTreeFetched() { Resources::notifyResourcesCreated(); } /****************************************************************************** * Called when a collection has been populated. */ void AkonadiDataModel::slotCollectionPopulated(Akonadi::Collection::Id id) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotCollectionPopulated:" << id; AkonadiResource::notifyCollectionLoaded(id, events(id)); } /****************************************************************************** * Called when calendar migration has completed. */ void AkonadiDataModel::slotMigrationCompleted() { if (mCollectionsBeingCreated.isEmpty() && mCollectionIdsBeingCreated.isEmpty()) { qCDebug(KALARM_LOG) << "AkonadiDataModel: Migration completed"; setMigrationComplete(); } } /****************************************************************************** * Called when an item in the monitored collections has changed. */ void AkonadiDataModel::slotMonitoredItemChanged(const Akonadi::Item& item, const QSet&) { qCDebug(KALARM_LOG) << "AkonadiDataModel::slotMonitoredItemChanged: item id=" << item.id() << ", revision=" << item.revision(); const QModelIndex ix = itemIndex(item); if (ix.isValid()) { Resource res; Item itm = item; KAEvent evnt = event(itm, ix, res); // this sets item.parentCollection() if (evnt.isValid()) { // Notify the resource containing the item. if (res.isValid()) AkonadiResource::notifyItemChanged(res, itm, false); // Wait to ensure that the base EntityTreeModel has processed the // itemChanged() signal first, before we notify AkonadiResource // that the event has changed. mPendingEventChanges.enqueue(evnt); QTimer::singleShot(0, this, &AkonadiDataModel::slotEmitEventUpdated); } } } /****************************************************************************** * Called to Q_EMIT a signal when an event in the monitored collections has * changed. */ void AkonadiDataModel::slotEmitEventUpdated() { while (!mPendingEventChanges.isEmpty()) { const KAEvent event = mPendingEventChanges.dequeue(); Resource res = Resources::resource(event.resourceId()); AkonadiResource::notifyEventsChanged(res, {event}); } } /****************************************************************************** * Refresh the specified Collection with up to date data. * Return: true if successful, false if collection not found. */ bool AkonadiDataModel::refresh(Akonadi::Collection& collection) const { const QModelIndex ix = modelIndexForCollection(this, collection); if (!ix.isValid()) return false; collection = ix.data(CollectionRole).value(); // Also update our own copy of the collection. updateResource(collection); return true; } /****************************************************************************** * Refresh the specified Item with up to date data. * Return: true if successful, false if item not found. */ bool AkonadiDataModel::refresh(Akonadi::Item& item) const { const QModelIndex ix = itemIndex(item); if (!ix.isValid()) return false; item = ix.data(ItemRole).value(); return true; } /****************************************************************************** * Return the AkonadiResource object for a collection ID. */ Resource AkonadiDataModel::resource(Collection::Id id) const { return mResources.value(id, AkonadiResource::nullResource()); } /****************************************************************************** * Return the resource at a specified index, with up to date data. */ Resource AkonadiDataModel::resource(const QModelIndex& ix) const { return mResources.value(ix.data(CollectionIdRole).toLongLong(), AkonadiResource::nullResource()); } /****************************************************************************** * Find the QModelIndex of a resource. */ QModelIndex AkonadiDataModel::resourceIndex(const Resource& resource) const { const Collection& collection = AkonadiResource::collection(resource); const QModelIndex ix = modelIndexForCollection(this, collection); if (!ix.isValid()) return QModelIndex(); return ix; } /****************************************************************************** * Find the QModelIndex of a resource with a given ID. */ QModelIndex AkonadiDataModel::resourceIndex(Akonadi::Collection::Id id) const { const QModelIndex ix = modelIndexForCollection(this, Collection(id)); if (!ix.isValid()) return QModelIndex(); return ix; } /****************************************************************************** * Return a reference to the collection held in a Resource. This is the * definitive copy of the collection used by this model. * Return: the collection held by the model, or null if not found. */ Collection* AkonadiDataModel::collection(Collection::Id id) const { auto it = mResources.find(id); if (it != mResources.end()) { Collection& c = AkonadiResource::collection(it.value()); if (c.isValid()) return &c; } return nullptr; } /****************************************************************************** * Return a reference to the collection held in a Resource. This is the * definitive copy of the collection used by this model. * Return: the collection held by the model, or null if not found. */ Collection* AkonadiDataModel::collection(const Resource& resource) const { return collection(resource.id()); } /****************************************************************************** * Find the QModelIndex of an item. */ QModelIndex AkonadiDataModel::itemIndex(const Akonadi::Item& item) const { const QModelIndexList ixs = modelIndexesForItem(this, item); for (const QModelIndex& ix : ixs) { if (ix.isValid()) return ix; } return QModelIndex(); } /****************************************************************************** * Update the resource which holds a given Collection, by copying the Collection * value into it. If there is no resource, a new resource is created. * Param: collection - this should have been fetched from the model to ensure * that its value is up to date. */ Resource& AkonadiDataModel::updateResource(const Collection& collection) const { auto it = mResources.find(collection.id()); if (it != mResources.end()) { Collection& resourceCol = AkonadiResource::collection(it.value()); if (&collection != &resourceCol) resourceCol = collection; } else { // Create a new resource for the collection. it = mResources.insert(collection.id(), AkonadiResource::create(collection)); } return it.value(); } /****************************************************************************** * Display a message to the user. */ void AkonadiDataModel::slotResourceMessage(ResourceType::MessageType type, const QString& message, const QString& details) { handleResourceMessage(type, message, details); } // vim: et sw=4: diff --git a/src/resources/akonadidatamodel.h b/src/resources/akonadidatamodel.h index 706ec363..61b53908 100644 --- a/src/resources/akonadidatamodel.h +++ b/src/resources/akonadidatamodel.h @@ -1,192 +1,222 @@ /* * akonadidatamodel.h - KAlarm calendar file access using Akonadi * Program: kalarm * Copyright © 2010-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADIDATAMODEL_H #define AKONADIDATAMODEL_H #include "resources/resourcedatamodelbase.h" #include "resources/akonadiresource.h" #include #include #include #include #include #include namespace Akonadi { class ChangeRecorder; } class KJob; using namespace KAlarmCal; class AkonadiDataModel : public Akonadi::EntityTreeModel, public ResourceDataModelBase { Q_OBJECT public: enum Change { Enabled, ReadOnly, AlarmTypes }; static AkonadiDataModel* instance(); ~AkonadiDataModel() override; static Akonadi::ChangeRecorder* monitor(); /** Refresh the specified collection instance with up to date data. */ bool refresh(Akonadi::Collection&) const; /** Refresh the specified item instance with up to date data. */ bool refresh(Akonadi::Item&) const; Resource resource(Akonadi::Collection::Id) const; Resource resource(const QModelIndex&) const; QModelIndex resourceIndex(const Resource&) const; QModelIndex resourceIndex(Akonadi::Collection::Id) const; Akonadi::Collection* collection(Akonadi::Collection::Id id) const; Akonadi::Collection* collection(const Resource&) const; - /** Reload a collection's data from Akonadi storage (not from the backend). */ - bool reload(Resource&); - - /** Reload all collections' data from Akonadi storage (not from the backend). */ - void reload(); - KAEvent event(const QString& eventId) const; KAEvent event(const QModelIndex&) const; using QObject::event; // prevent warning about hidden virtual method /** Return an event's model index, based on its ID. */ QModelIndex eventIndex(const KAEvent&) const; QModelIndex eventIndex(const QString& eventId) const; /** Return the up-to-date Item, given its ID. * If not found, an invalid Item is returned. */ Akonadi::Item itemById(Akonadi::Item::Id) const; /** Return the Item for a given event. */ Akonadi::Item itemForEvent(const QString& eventId) const; QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const override; int headerDataEventRoleOffset() const override; - /** Return the data storage backend type used by this model. */ - Preferences::Backend dataStorageBackend() const override { return Preferences::Akonadi; } - private Q_SLOTS: /** Called when a resource notifies a message to display to the user. */ void slotResourceMessage(ResourceType::MessageType, const QString& message, const QString& details); Q_SIGNALS: /** Signal emitted when the Akonadi server has stopped. */ void serverStopped(); protected: + /** Terminate access to the data model, and tidy up. Not necessary for Akonadi. */ + void terminate() override {} + + /** Reload all resources' data from storage. + * @note This reloads data from Akonadi storage, not from the backend storage. + */ + void reload() override; + + /** Reload a resource's data from storage. + * @note This reloads data from Akonadi storage, not from the backend storage. + */ + bool reload(Resource&) override; + + /** Check for, and remove, any duplicate resources, i.e. those which use + * the same calendar file/directory. + */ + void removeDuplicateResources() override; + + /** Disable the widget if the database engine is not available, and display + * an error overlay. + */ + void widgetNeedsDatabase(QWidget*) override; + + /** Create an AkonadiResourceCreator instance. */ + ResourceCreator* createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent) override; + + /** Update a resource's backend calendar file to the current KAlarm format. */ + void updateCalendarToCurrentFormat(Resource&, bool ignoreKeepFormat, QObject* parent) override; + + ResourceListModel* createResourceListModel(QObject* parent) override; + ResourceFilterCheckListModel* createResourceFilterCheckListModel(QObject* parent) override; + AlarmListModel* createAlarmListModel(QObject* parent) override; + AlarmListModel* allAlarmListModel() override; + TemplateListModel* createTemplateListModel(QObject* parent) override; + TemplateListModel* allTemplateListModel() override; + + /** Return the data storage backend type used by this model. */ + Preferences::Backend dataStorageBackend() const override { return Preferences::Akonadi; } + QVariant entityHeaderData(int section, Qt::Orientation, int role, HeaderGroup) const override; int entityColumnCount(HeaderGroup) const override; private Q_SLOTS: void checkResources(Akonadi::ServerManager::State); void slotMigrationCompleted(); void collectionFetchResult(KJob*); void slotCollectionChanged(const Akonadi::Collection& c, const QSet& attrNames); void slotCollectionRemoved(const Akonadi::Collection&); void slotCollectionBeingCreated(const QString& path, Akonadi::Collection::Id, bool finished); void slotCollectionTreeFetched(); void slotCollectionPopulated(Akonadi::Collection::Id); void slotUpdateTimeTo(); void slotUpdateArchivedColour(const QColor&); void slotUpdateDisabledColour(const QColor&); void slotUpdateHolidays(); void slotUpdateWorkingHours(); void slotRowsInserted(const QModelIndex& parent, int start, int end); void slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); void slotMonitoredItemChanged(const Akonadi::Item&, const QSet&); void slotEmitEventUpdated(); private: struct CalData // data per collection { CalData() : enabled(false) { } CalData(bool e, const QColor& c) : colour(c), enabled(e) { } QColor colour; // user selected color for the calendar bool enabled; // whether the collection is enabled }; struct CollJobData // collection data for jobs in progress { CollJobData() : id(-1) {} CollJobData(Akonadi::Collection::Id i, const QString& d) : id(i), displayName(d) {} Akonadi::Collection::Id id; QString displayName; }; struct CollTypeData // data for configuration dialog for collection creation job { CollTypeData() : parent(nullptr), alarmType(CalEvent::EMPTY) {} CollTypeData(CalEvent::Type t, QWidget* p) : parent(p), alarmType(t) {} QWidget* parent; CalEvent::Type alarmType; }; AkonadiDataModel(Akonadi::ChangeRecorder*, QObject* parent); void initResourceMigrator(); Resource& updateResource(const Akonadi::Collection&) const; /** Return the alarm for the specified Akonadi Item. * The item's parentCollection() is set. * @param res Set the resource for the item's parent collection. * @return the event, or invalid event if no such event exists. */ KAEvent event(Akonadi::Item&, const QModelIndex&, Resource& res) const; QModelIndex itemIndex(const Akonadi::Item&) const; void signalDataChanged(bool (*checkFunc)(const Akonadi::Item&), int startColumn, int endColumn, const QModelIndex& parent); void setCollectionChanged(Resource&, const Akonadi::Collection&, bool checkCompat); QList events(ResourceId) const; void getChildEvents(const QModelIndex& parent, QList&) const; - static AkonadiDataModel* mInstance; - static int mTimeHourPos; // position of hour within time string, or -1 if leading zeroes included + static bool mInstanceIsOurs; // mInstance is an AkonadiDataModel instance + static int mTimeHourPos; // position of hour within time string, or -1 if leading zeroes included Akonadi::ChangeRecorder* mMonitor; QHash mPendingCollectionJobs; // pending collection creation/deletion jobs, with collection ID & name QHash mPendingColCreateJobs; // default alarm type for pending collection creation jobs QList mCollectionsBeingCreated; // path names of new collections being created by migrator QList mCollectionIdsBeingCreated; // ids of new collections being created by migrator struct EventIds { Akonadi::Collection::Id collectionId {-1}; Akonadi::Item::Id itemId {-1}; explicit EventIds(Akonadi::Collection::Id c = -1, Akonadi::Item::Id i = -1) : collectionId(c), itemId(i) {} }; QHash mEventIds; // collection and item ID for each event ID mutable QHash mResources; QQueue mPendingEventChanges; // changed events with changedEvent() signal pending }; #endif // AKONADIDATAMODEL_H // vim: et sw=4: diff --git a/src/resources/datamodel.cpp b/src/resources/datamodel.cpp index 9f4a4ef8..daf205ec 100644 --- a/src/resources/datamodel.cpp +++ b/src/resources/datamodel.cpp @@ -1,113 +1,116 @@ /* - * datamodel.cpp - calendar data model dependent functions + * datamodel.cpp - model independent access to calendar functions * Program: kalarm - * Copyright © 2019-2020 David Jarvie + * Copyright © 2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "datamodel.h" +//#define USE_AKONADI +#ifdef USE_AKONADI #include "akonadidatamodel.h" -#include "akonadiresource.h" -#include "akonadiresourcecreator.h" -#include "akonadicalendarupdater.h" -#include "eventmodel.h" -#include "resourcemodel.h" +#define DATA_MODEL AkonadiDataModel +#else +#include "fileresourcedatamodel.h" +#define DATA_MODEL FileResourceDataModel +#endif -#include -namespace DataModel +void DataModel::initialise() { - -void initialise() -{ - AkonadiDataModel* model = AkonadiDataModel::instance(); - Preferences::setBackend(model->dataStorageBackend()); + DATA_MODEL::instance(); + // Record in kalarmrc, for information only, which backend is in use. + Preferences::setBackend(ResourceDataModelBase::mInstance->dataStorageBackend()); Preferences::self()->save(); } -void terminate() +void DataModel::terminate() { + if (ResourceDataModelBase::mInstance) + ResourceDataModelBase::mInstance->terminate(); } -void widgetNeedsDatabase(QWidget* widget) +void DataModel::reload() { - Akonadi::ControlGui::widgetNeedsAkonadi(widget); + if (ResourceDataModelBase::mInstance) + ResourceDataModelBase::mInstance->reload(); } -void reload() +bool DataModel::reload(Resource& resource) { - AkonadiDataModel::instance()->reload(); + return ResourceDataModelBase::mInstance && ResourceDataModelBase::mInstance->reload(resource); } -bool reload(Resource& resource) +bool DataModel::isMigrationComplete() { - return AkonadiDataModel::instance()->reload(resource); + return ResourceDataModelBase::mInstance && ResourceDataModelBase::mInstance->isMigrationComplete(); } -bool isMigrationComplete() +void DataModel::removeDuplicateResources() { - return AkonadiDataModel::instance()->isMigrationComplete(); + if (ResourceDataModelBase::mInstance) + ResourceDataModelBase::mInstance->removeDuplicateResources(); } -void removeDuplicateResources() +void DataModel::widgetNeedsDatabase(QWidget* widget) { - AkonadiResource::removeDuplicateResources(); + if (ResourceDataModelBase::mInstance) + ResourceDataModelBase::mInstance->widgetNeedsDatabase(widget); } -ResourceListModel* createResourceListModel(QObject* parent) +ResourceCreator* DataModel::createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent) { - return ResourceListModel::create(parent); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->createResourceCreator(defaultType, parent) : nullptr; } -ResourceFilterCheckListModel* createResourceFilterCheckListModel(QObject* parent) +void DataModel::updateCalendarToCurrentFormat(Resource& resource, bool ignoreKeepFormat, QObject* parent) { - return ResourceFilterCheckListModel::create(parent); + if (ResourceDataModelBase::mInstance) + ResourceDataModelBase::mInstance->updateCalendarToCurrentFormat(resource, ignoreKeepFormat, parent); } -AlarmListModel* createAlarmListModel(QObject* parent) +ResourceListModel* DataModel::createResourceListModel(QObject* parent) { - return AlarmListModel::create(parent); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->createResourceListModel(parent) : nullptr; } -AlarmListModel* allAlarmListModel() +ResourceFilterCheckListModel* DataModel::createResourceFilterCheckListModel(QObject* parent) { - return AlarmListModel::all(); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->createResourceFilterCheckListModel(parent) : nullptr; } -TemplateListModel* createTemplateListModel(QObject* parent) +AlarmListModel* DataModel::createAlarmListModel(QObject* parent) { - return TemplateListModel::create(parent); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->createAlarmListModel(parent) : nullptr; } -TemplateListModel* allTemplateListModel() +AlarmListModel* DataModel::allAlarmListModel() { - return TemplateListModel::all(); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->allAlarmListModel() : nullptr; } -ResourceCreator* createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent) +TemplateListModel* DataModel::createTemplateListModel(QObject* parent) { - return new AkonadiResourceCreator(defaultType, parent); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->createTemplateListModel(parent) : nullptr; } -void updateCalendarToCurrentFormat(Resource& resource, bool ignoreKeepFormat, QObject* parent) +TemplateListModel* DataModel::allTemplateListModel() { - AkonadiCalendarUpdater::updateToCurrentFormat(resource, ignoreKeepFormat, parent); + return ResourceDataModelBase::mInstance ? ResourceDataModelBase::mInstance->allTemplateListModel() : nullptr; } -} // namespace DataModel - // vim: et sw=4: diff --git a/src/resources/datamodel.h b/src/resources/datamodel.h index 27b4ac68..ca0fb3d9 100644 --- a/src/resources/datamodel.h +++ b/src/resources/datamodel.h @@ -1,79 +1,104 @@ /* - * datamodel.h - calendar data model dependent functions + * datamodel.h - model independent access to calendar functions * Program: kalarm * Copyright © 2019-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DATAMODEL_H #define DATAMODEL_H #include class Resource; class ResourceListModel; class ResourceFilterCheckListModel; class AlarmListModel; class TemplateListModel; class ResourceCreator; -class QObject; -class QWidget; -/** Class to create objects dependent on data model. */ -namespace DataModel -{ - -void initialise(); - -void terminate(); - -/** Disable the widget if the database engine is not available, and display an - * error overlay. - */ -void widgetNeedsDatabase(QWidget*); - -/** Reload all resources' data from storage. - * @note In the case of Akonadi, this does not reload from the backend storage. - */ -void reload(); -/** Reload a resource's data from storage. - * @note In the case of Akonadi, this does not reload from the backend storage. - */ -bool reload(Resource&); - -bool isMigrationComplete(); - -/** Check for, and remove, any duplicate Akonadi resources, i.e. those which - * use the same calendar file/directory. - */ -void removeDuplicateResources(); - -ResourceListModel* createResourceListModel(QObject* parent); -ResourceFilterCheckListModel* createResourceFilterCheckListModel(QObject* parent); -AlarmListModel* createAlarmListModel(QObject* parent); -AlarmListModel* allAlarmListModel(); -TemplateListModel* createTemplateListModel(QObject* parent); -TemplateListModel* allTemplateListModel(); - -ResourceCreator* createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent); -void updateCalendarToCurrentFormat(Resource&, bool ignoreKeepFormat, QObject* parent); - -} // namespace DataModel +/*============================================================================= += Class: DataModel += Static methods providing model independent access to the resource data model. +=============================================================================*/ +class DataModel +{ +public: + /** Initialise the data model. */ + static void initialise(); + + /** Terminate access to the data model, and tidy up. */ + static void terminate(); + + /** Reload all resources' data from storage. + * @note In the case of Akonadi, this does not reload from the backend storage. + */ + static void reload(); + + /** Reload a resource's data from storage. + * @note In the case of Akonadi, this does not reload from the backend storage. + */ + static bool reload(Resource&); + + /** Return whether calendar migration/creation at initialisation has completed. */ + static bool isMigrationComplete(); + + /** Check for, and remove, any duplicate Akonadi resources, i.e. those which + * use the same calendar file/directory. + */ + static void removeDuplicateResources(); + + /** Disable the widget if the database engine is not available, and display an + * error overlay. + */ + static void widgetNeedsDatabase(QWidget*); + + /** Create a ResourceCreator instance for the model. */ + static ResourceCreator* createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent); + + /** Update a resource's backend calendar file to the current KAlarm format. */ + static void updateCalendarToCurrentFormat(Resource&, bool ignoreKeepFormat, QObject* parent); + + static ResourceListModel* createResourceListModel(QObject* parent); + static ResourceFilterCheckListModel* createResourceFilterCheckListModel(QObject* parent); + static AlarmListModel* createAlarmListModel(QObject* parent); + static AlarmListModel* allAlarmListModel(); + static TemplateListModel* createTemplateListModel(QObject* parent); + static TemplateListModel* allTemplateListModel(); + +#if 0 + static QSize iconSize() { return mIconSize; } + + /** Return a bulleted list of alarm types for inclusion in an i18n message. */ + static QString typeListForDisplay(CalEvent::Types); + + /** Get the tooltip for a resource. The resource's enabled status is + * evaluated for specified alarm types. */ + QString tooltip(const Resource&, CalEvent::Types) const; + + /** Return the read-only status tooltip for a resource. + * A null string is returned if the resource is fully writable. */ + static QString readOnlyTooltip(const Resource&); + + /** Return offset to add to headerData() role, for item models. */ + virtual int headerDataEventRoleOffset() const { return 0; } +#endif +}; #endif // DATAMODEL_H // vim: et sw=4: diff --git a/src/resources/resourcedatamodelbase.cpp b/src/resources/resourcedatamodelbase.cpp index 901b22f5..898fafd7 100644 --- a/src/resources/resourcedatamodelbase.cpp +++ b/src/resources/resourcedatamodelbase.cpp @@ -1,798 +1,800 @@ /* * resourcedatamodelbase.cpp - base for models containing calendars and events * Program: kalarm * Copyright © 2007-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "resourcedatamodelbase.h" #include "resources.h" #include "preferences.h" #include "lib/desktop.h" #include "lib/messagebox.h" #include "kalarm_debug.h" #include #include #include #include #include #include #include #include #include namespace { QString alarmTimeText(const DateTime& dateTime, char leadingZero = '\0'); QString timeToAlarmText(const DateTime& dateTime); } /*============================================================================= = Class: ResourceDataModelBase =============================================================================*/ +ResourceDataModelBase* ResourceDataModelBase::mInstance = nullptr; + QPixmap* ResourceDataModelBase::mTextIcon = nullptr; QPixmap* ResourceDataModelBase::mFileIcon = nullptr; QPixmap* ResourceDataModelBase::mCommandIcon = nullptr; QPixmap* ResourceDataModelBase::mEmailIcon = nullptr; QPixmap* ResourceDataModelBase::mAudioIcon = nullptr; QSize ResourceDataModelBase::mIconSize; /****************************************************************************** * Constructor. */ ResourceDataModelBase::ResourceDataModelBase() { if (!mTextIcon) { mTextIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(16, 16)); mFileIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("document-open")).pixmap(16, 16)); mCommandIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("system-run")).pixmap(16, 16)); mEmailIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("mail-unread")).pixmap(16, 16)); mAudioIcon = new QPixmap(QIcon::fromTheme(QStringLiteral("audio-x-generic")).pixmap(16, 16)); mIconSize = mTextIcon->size().expandedTo(mFileIcon->size()).expandedTo(mCommandIcon->size()).expandedTo(mEmailIcon->size()).expandedTo(mAudioIcon->size()); } } ResourceDataModelBase::~ResourceDataModelBase() { } /****************************************************************************** * Create a bulleted list of alarm types for insertion into .... */ QString ResourceDataModelBase::typeListForDisplay(CalEvent::Types alarmTypes) { QString list; if (alarmTypes & CalEvent::ACTIVE) list += QLatin1String("") + i18nc("@info", "Active Alarms") + QLatin1String(""); if (alarmTypes & CalEvent::ARCHIVED) list += QLatin1String("") + i18nc("@info", "Archived Alarms") + QLatin1String(""); if (alarmTypes & CalEvent::TEMPLATE) list += QLatin1String("") + i18nc("@info", "Alarm Templates") + QLatin1String(""); if (!list.isEmpty()) list = QLatin1String("") + list + QLatin1String(""); return list; } /****************************************************************************** * Return the read-only status tooltip for a collection, determined by the * read-write permissions and the KAlarm calendar format compatibility. * A null string is returned if the collection is read-write and compatible. */ QString ResourceDataModelBase::readOnlyTooltip(const Resource& resource) { switch (resource.compatibility()) { case KACalendar::Current: return resource.readOnly() ? i18nc("@info", "Read-only") : QString(); case KACalendar::Converted: case KACalendar::Convertible: return i18nc("@info", "Read-only (old format)"); case KACalendar::Incompatible: default: return i18nc("@info", "Read-only (other format)"); } } /****************************************************************************** * Return data for a column heading. */ QVariant ResourceDataModelBase::headerData(int section, Qt::Orientation orientation, int role, bool eventHeaders, bool& handled) { if (orientation == Qt::Horizontal) { handled = true; if (eventHeaders) { // Event column headers if (section < 0 || section >= ColumnCount) return QVariant(); if (role == Qt::DisplayRole || role == ColumnTitleRole) { switch (section) { case TimeColumn: return i18nc("@title:column", "Time"); case TimeToColumn: return i18nc("@title:column", "Time To"); case RepeatColumn: return i18nc("@title:column", "Repeat"); case ColourColumn: return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Color"); case TypeColumn: return (role == Qt::DisplayRole) ? QString() : i18nc("@title:column", "Type"); case TextColumn: return i18nc("@title:column", "Message, File or Command"); case TemplateNameColumn: return i18nc("@title:column Template name", "Name"); } } else if (role == Qt::WhatsThisRole) return whatsThisText(section); } else { // Calendar column headers if (section != 0) return QVariant(); if (role == Qt::DisplayRole) return i18nc("@title:column", "Calendars"); } } handled = false; return QVariant(); } /****************************************************************************** * Return whether resourceData() or eventData() handle a role. */ bool ResourceDataModelBase::roleHandled(int role) const { switch (role) { case Qt::WhatsThisRole: case Qt::ForegroundRole: case Qt::BackgroundRole: case Qt::DisplayRole: case Qt::TextAlignmentRole: case Qt::DecorationRole: case Qt::SizeHintRole: case Qt::AccessibleTextRole: case Qt::ToolTipRole: case ItemTypeRole: case ResourceIdRole: case BaseColourRole: case TimeDisplayRole: case SortRole: case StatusRole: case ValueRole: case EventIdRole: case ParentResourceIdRole: case EnabledRole: case AlarmActionsRole: case AlarmSubActionRole: case CommandErrorRole: return true; default: return false; } } /****************************************************************************** * Return the data for a given role, for a specified resource. */ QVariant ResourceDataModelBase::resourceData(int& role, const Resource& resource, bool& handled) const { if (roleHandled(role)) // Ensure that roleHandled() is coded correctly { handled = true; switch (role) { case Qt::DisplayRole: return resource.displayName(); case BaseColourRole: role = Qt::BackgroundRole; // use base model background colour break; case Qt::BackgroundRole: { const QColor colour = resource.backgroundColour(); if (colour.isValid()) return colour; break; // use base model background colour } case Qt::ForegroundRole: return resource.foregroundColour(); case Qt::ToolTipRole: return tooltip(resource, CalEvent::ACTIVE | CalEvent::ARCHIVED | CalEvent::TEMPLATE); case ItemTypeRole: return static_cast(Type::Resource); case ResourceIdRole: return resource.id(); default: break; } } handled = false; return QVariant(); } /****************************************************************************** * Return the data for a given role, for a specified event. */ QVariant ResourceDataModelBase::eventData(int role, int column, const KAEvent& event, const Resource& resource, bool& handled) const { if (roleHandled(role)) // Ensure that roleHandled() is coded correctly { handled = true; bool calendarColour = false; switch (role) { case Qt::WhatsThisRole: return whatsThisText(column); case ItemTypeRole: return static_cast(Type::Event); default: break; } if (!event.isValid()) return QVariant(); switch (role) { case EventIdRole: return event.id(); case StatusRole: return event.category(); case AlarmActionsRole: return event.actionTypes(); case AlarmSubActionRole: return event.actionSubType(); case CommandErrorRole: return event.commandError(); default: break; } switch (column) { case TimeColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DisplayRole: if (event.expired()) return alarmTimeText(event.startDateTime(), '0'); return alarmTimeText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER), '0'); case TimeDisplayRole: if (event.expired()) return alarmTimeText(event.startDateTime(), '~'); return alarmTimeText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER), '~'); case Qt::TextAlignmentRole: return Qt::AlignRight; case SortRole: { DateTime due; if (event.expired()) due = event.startDateTime(); else due = event.nextTrigger(KAEvent::DISPLAY_TRIGGER); return due.isValid() ? due.effectiveKDateTime().toUtc().qDateTime() : QDateTime(QDate(9999,12,31), QTime(0,0,0)); } default: break; } break; case TimeToColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DisplayRole: if (event.expired()) return QString(); return timeToAlarmText(event.nextTrigger(KAEvent::DISPLAY_TRIGGER)); case Qt::TextAlignmentRole: return Qt::AlignRight; case SortRole: { if (event.expired()) return -1; const DateTime due = event.nextTrigger(KAEvent::DISPLAY_TRIGGER); const KADateTime now = KADateTime::currentUtcDateTime(); if (due.isDateOnly()) return now.date().daysTo(due.date()) * 1440; return (now.secsTo(due.effectiveKDateTime()) + 59) / 60; } } break; case RepeatColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DisplayRole: return repeatText(event); case Qt::TextAlignmentRole: return Qt::AlignHCenter; case SortRole: return repeatOrder(event); } break; case ColourColumn: switch (role) { case Qt::BackgroundRole: { const KAEvent::Actions type = event.actionTypes(); if (type & KAEvent::ACT_DISPLAY) return event.bgColour(); if (type == KAEvent::ACT_COMMAND) { if (event.commandError() != KAEvent::CMD_NO_ERROR) return QColor(Qt::red); } break; } case Qt::ForegroundRole: if (event.commandError() != KAEvent::CMD_NO_ERROR) { if (event.actionTypes() == KAEvent::ACT_COMMAND) return QColor(Qt::white); QColor colour = Qt::red; int r, g, b; event.bgColour().getRgb(&r, &g, &b); if (r > 128 && g <= 128 && b <= 128) colour = QColor(Qt::white); return colour; } break; case Qt::DisplayRole: if (event.commandError() != KAEvent::CMD_NO_ERROR) return QLatin1String("!"); break; case SortRole: { const unsigned i = (event.actionTypes() == KAEvent::ACT_DISPLAY) ? event.bgColour().rgb() : 0; return QStringLiteral("%1").arg(i, 6, 10, QLatin1Char('0')); } default: break; } break; case TypeColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DecorationRole: { QVariant v; v.setValue(*eventIcon(event)); return v; } case Qt::TextAlignmentRole: return Qt::AlignHCenter; case Qt::SizeHintRole: return mIconSize; case Qt::AccessibleTextRole: //TODO: Implement accessibility return QString(); case ValueRole: return static_cast(event.actionSubType()); case SortRole: return QStringLiteral("%1").arg(event.actionSubType(), 2, 10, QLatin1Char('0')); } break; case TextColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DisplayRole: case SortRole: return AlarmText::summary(event, 1); case Qt::ToolTipRole: return AlarmText::summary(event, 10); default: break; } break; case TemplateNameColumn: switch (role) { case Qt::BackgroundRole: calendarColour = true; break; case Qt::DisplayRole: return event.templateName(); case SortRole: return event.templateName().toUpper(); } break; default: break; } if (calendarColour) { const QColor colour = resource.backgroundColour(); if (colour.isValid()) return colour; } switch (role) { case Qt::ForegroundRole: if (!event.enabled()) return Preferences::disabledColour(); if (event.expired()) return Preferences::archivedColour(); break; // use the default for normal active alarms case Qt::ToolTipRole: // Show the last command execution error message switch (event.commandError()) { case KAEvent::CMD_ERROR: return i18nc("@info:tooltip", "Command execution failed"); case KAEvent::CMD_ERROR_PRE: return i18nc("@info:tooltip", "Pre-alarm action execution failed"); case KAEvent::CMD_ERROR_POST: return i18nc("@info:tooltip", "Post-alarm action execution failed"); case KAEvent::CMD_ERROR_PRE_POST: return i18nc("@info:tooltip", "Pre- and post-alarm action execution failed"); default: case KAEvent::CMD_NO_ERROR: break; } break; case EnabledRole: return event.enabled(); default: break; } } handled = false; return QVariant(); } /****************************************************************************** * Return a resource's tooltip text. The resource's enabled status is * evaluated for specified alarm types. */ QString ResourceDataModelBase::tooltip(const Resource& resource, CalEvent::Types types) const { const QString name = QLatin1Char('@') + resource.displayName(); // insert markers for stripping out name const QString type = QLatin1Char('@') + resource.storageTypeString(false); // file/directory/URL etc. const QString locn = resource.displayLocation(); const bool inactive = !(resource.enabledTypes() & types); const QString readonly = readOnlyTooltip(resource); const bool writable = readonly.isEmpty(); const QString disabled = i18nc("@info", "Disabled"); if (inactive || !writable) return xi18nc("@info:tooltip", "%1" "%2: %3" "%4", name, type, locn, (inactive ? disabled : readonly)); return xi18nc("@info:tooltip", "%1" "%2: %3", name, type, locn); } /****************************************************************************** * Return the repetition text. */ QString ResourceDataModelBase::repeatText(const KAEvent& event) { const QString repText = event.recurrenceText(true); return repText.isEmpty() ? event.repetitionText(true) : repText; } /****************************************************************************** * Return a string for sorting the repetition column. */ QString ResourceDataModelBase::repeatOrder(const KAEvent& event) { int repOrder = 0; int repInterval = 0; if (event.repeatAtLogin()) repOrder = 1; else { repInterval = event.recurInterval(); switch (event.recurType()) { case KARecurrence::MINUTELY: repOrder = 2; break; case KARecurrence::DAILY: repOrder = 3; break; case KARecurrence::WEEKLY: repOrder = 4; break; case KARecurrence::MONTHLY_DAY: case KARecurrence::MONTHLY_POS: repOrder = 5; break; case KARecurrence::ANNUAL_DATE: case KARecurrence::ANNUAL_POS: repOrder = 6; break; case KARecurrence::NO_RECUR: default: break; } } return QStringLiteral("%1%2").arg(static_cast('0' + repOrder)).arg(repInterval, 8, 10, QLatin1Char('0')); } /****************************************************************************** * Returns the QWhatsThis text for a specified column. */ QString ResourceDataModelBase::whatsThisText(int column) { switch (column) { case TimeColumn: return i18nc("@info:whatsthis", "Next scheduled date and time of the alarm"); case TimeToColumn: return i18nc("@info:whatsthis", "How long until the next scheduled trigger of the alarm"); case RepeatColumn: return i18nc("@info:whatsthis", "How often the alarm recurs"); case ColourColumn: return i18nc("@info:whatsthis", "Background color of alarm message"); case TypeColumn: return i18nc("@info:whatsthis", "Alarm type (message, file, command or email)"); case TextColumn: return i18nc("@info:whatsthis", "Alarm message text, URL of text file to display, command to execute, or email subject line"); case TemplateNameColumn: return i18nc("@info:whatsthis", "Name of the alarm template"); default: return QString(); } } /****************************************************************************** * Return the icon associated with an event's action. */ QPixmap* ResourceDataModelBase::eventIcon(const KAEvent& event) { switch (event.actionTypes()) { case KAEvent::ACT_EMAIL: return mEmailIcon; case KAEvent::ACT_AUDIO: return mAudioIcon; case KAEvent::ACT_COMMAND: return mCommandIcon; case KAEvent::ACT_DISPLAY: if (event.actionSubType() == KAEvent::FILE) return mFileIcon; Q_FALLTHROUGH(); // fall through to ACT_DISPLAY_COMMAND case KAEvent::ACT_DISPLAY_COMMAND: default: return mTextIcon; } } /****************************************************************************** * Display a message to the user. */ void ResourceDataModelBase::handleResourceMessage(ResourceType::MessageType type, const QString& message, const QString& details) { if (type == ResourceType::MessageType::Error) { qCDebug(KALARM_LOG) << "Resource Error!" << message << details; KAMessageBox::detailedError(Desktop::mainWindow(), message, details); } else if (type == ResourceType::MessageType::Info) { qCDebug(KALARM_LOG) << "Resource user message:" << message << details; // KMessageBox::informationList looks bad, so use our own formatting. const QString msg = details.isEmpty() ? message : message + QStringLiteral("\n\n") + details; KAMessageBox::information(Desktop::mainWindow(), msg); } } bool ResourceDataModelBase::isMigrationComplete() const { return mMigrationStatus == 1; } bool ResourceDataModelBase::isMigrating() const { return mMigrationStatus == 0; } void ResourceDataModelBase::setMigrationInitiated(bool started) { mMigrationStatus = (started ? 0 : -1); } void ResourceDataModelBase::setMigrationComplete() { mMigrationStatus = 1; Resources::notifyResourcesMigrated(); } namespace { /****************************************************************************** * Return the alarm time text in the form "date time". * Parameters: * dateTime = the date/time to format. * leadingZero = the character to represent a leading zero, or '\0' for no leading zeroes. */ QString alarmTimeText(const DateTime& dateTime, char leadingZero) { // Whether the date and time contain leading zeroes. static bool leadingZeroesChecked = false; static QString dateFormat; // date format for current locale static QString timeFormat; // time format for current locale static QString timeFullFormat; // time format with leading zero, if different from 'timeFormat' static int hourOffset = 0; // offset within time string to the hour if (!dateTime.isValid()) return i18nc("@info Alarm never occurs", "Never"); if (!leadingZeroesChecked && QApplication::isLeftToRight()) // don't try to align right-to-left languages { // Check whether the day number and/or hour have no leading zeroes, if // they are at the start of the date/time. If no leading zeroes, they // will need to be padded when displayed, so that displayed dates/times // can be aligned with each other. // Note that if leading zeroes are not included in other components, no // alignment will be attempted. QLocale locale; { // Check the date format. 'dd' provides leading zeroes; single 'd' // provides no leading zeroes. dateFormat = locale.dateFormat(QLocale::ShortFormat); } { // Check the time format. // Remove all but hours, minutes and AM/PM, since alarms are on minute // boundaries. Preceding separators are also removed. timeFormat = locale.timeFormat(QLocale::ShortFormat); for (int del = 0, predel = 0, c = 0; c < timeFormat.size(); ++c) { char ch = timeFormat.at(c).toLatin1(); switch (ch) { case 'H': case 'h': case 'm': case 'a': case 'A': if (predel == 1) { timeFormat.remove(del, c - del); c = del; } del = c + 1; // start deleting from the next character if ((ch == 'A' && del < timeFormat.size() && timeFormat.at(del).toLatin1() == 'P') || (ch == 'a' && del < timeFormat.size() && timeFormat.at(del).toLatin1() == 'p')) ++c, ++del; predel = -1; break; case 's': case 'z': case 't': timeFormat.remove(del, c + 1 - del); c = del - 1; if (!predel) predel = 1; break; default: break; } } // 'HH' and 'hh' provide leading zeroes; single 'H' or 'h' provide no // leading zeroes. int i = timeFormat.indexOf(QRegExp(QLatin1String("[hH]"))); int first = timeFormat.indexOf(QRegExp(QLatin1String("[hHmaA]"))); if (i >= 0 && i == first && (i == timeFormat.size() - 1 || timeFormat.at(i) != timeFormat.at(i + 1))) { timeFullFormat = timeFormat; timeFullFormat.insert(i, timeFormat.at(i)); // Find index to hour in formatted times const QTime t(1,30,30); const QString nozero = t.toString(timeFormat); const QString zero = t.toString(timeFullFormat); for (int i = 0; i < nozero.size(); ++i) if (nozero[i] != zero[i]) { hourOffset = i; break; } } } } leadingZeroesChecked = true; const KADateTime kdt = dateTime.effectiveKDateTime().toTimeSpec(Preferences::timeSpec()); QString dateTimeText = kdt.date().toString(dateFormat); if (!dateTime.isDateOnly() || kdt.utcOffset() != dateTime.utcOffset()) { // Display the time of day if it's a date/time value, or if it's // a date-only value but it's in a different time zone dateTimeText += QLatin1Char(' '); bool useFullFormat = leadingZero && !timeFullFormat.isEmpty(); QString timeText = kdt.time().toString(useFullFormat ? timeFullFormat : timeFormat); if (useFullFormat && leadingZero != '0' && timeText.at(hourOffset) == QLatin1Char('0')) timeText[hourOffset] = leadingZero; dateTimeText += timeText; } return dateTimeText + QLatin1Char(' '); } /****************************************************************************** * Return the time-to-alarm text. */ QString timeToAlarmText(const DateTime& dateTime) { if (!dateTime.isValid()) return i18nc("@info Alarm never occurs", "Never"); KADateTime now = KADateTime::currentUtcDateTime(); if (dateTime.isDateOnly()) { int days = now.date().daysTo(dateTime.date()); // xgettext: no-c-format return i18nc("@info n days", "%1d", days); } int mins = (now.secsTo(dateTime.effectiveKDateTime()) + 59) / 60; if (mins < 0) return QString(); char minutes[3] = "00"; minutes[0] = (mins%60) / 10 + '0'; minutes[1] = (mins%60) % 10 + '0'; if (mins < 24*60) return i18nc("@info hours:minutes", "%1:%2", mins/60, QLatin1String(minutes)); // If we render a day count, then we zero-pad the hours, to make the days line up and be more scanable. int hrs = mins / 60; char hours[3] = "00"; hours[0] = (hrs%24) / 10 + '0'; hours[1] = (hrs%24) % 10 + '0'; int days = hrs / 24; return i18nc("@info days hours:minutes", "%1d %2:%3", days, QLatin1String(hours), QLatin1String(minutes)); } } // vim: et sw=4: diff --git a/src/resources/resourcedatamodelbase.h b/src/resources/resourcedatamodelbase.h index 7ee8e1f2..d3e5fccc 100644 --- a/src/resources/resourcedatamodelbase.h +++ b/src/resources/resourcedatamodelbase.h @@ -1,154 +1,198 @@ /* * resourcedatamodelbase.h - base for models containing calendars and events * Program: kalarm * Copyright © 2007-2020 David Jarvie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef RESOURCEDATAMODELBASE_H #define RESOURCEDATAMODELBASE_H #include "resourcetype.h" #include "preferences.h" #include #include class Resource; +class ResourceListModel; +class ResourceFilterCheckListModel; +class AlarmListModel; +class TemplateListModel; +class ResourceCreator; class QModelIndex; class QPixmap; namespace KAlarmCal { class KAEvent; } using namespace KAlarmCal; /*============================================================================= = Class: ResourceDataModelBase = Base class for models containing all calendars and events. =============================================================================*/ class ResourceDataModelBase { public: /** Data column numbers. */ enum { // Item columns TimeColumn = 0, TimeToColumn, RepeatColumn, ColourColumn, TypeColumn, TextColumn, TemplateNameColumn, ColumnCount }; /** Additional model data roles. */ enum { UserRole = Qt::UserRole + 500, // copied from Akonadi::EntityTreeModel ItemTypeRole = UserRole, // item's type: calendar or event // Calendar roles ResourceIdRole, // the resource ID BaseColourRole, // background colour ignoring collection colour // Event roles EventIdRole, // the event ID ParentResourceIdRole, // the parent resource ID of the event EnabledRole, // true for enabled alarm, false for disabled StatusRole, // KAEvent::ACTIVE/ARCHIVED/TEMPLATE AlarmActionsRole, // KAEvent::Actions AlarmSubActionRole, // KAEvent::Action ValueRole, // numeric value SortRole, // the value to use for sorting TimeDisplayRole, // time column value with '~' representing omitted leading zeroes ColumnTitleRole, // column titles (whether displayed or not) CommandErrorRole // last command execution error for alarm (per user) }; /** The type of a model row. */ enum class Type { Error = 0, Event, Resource }; virtual ~ResourceDataModelBase(); -public: - /** Return the data storage backend type used by this model. */ - virtual Preferences::Backend dataStorageBackend() const = 0; - - static QSize iconSize() { return mIconSize; } + static QSize iconSize() { return mIconSize; } /** Return a bulleted list of alarm types for inclusion in an i18n message. */ static QString typeListForDisplay(CalEvent::Types); /** Get the tooltip for a resource. The resource's enabled status is * evaluated for specified alarm types. */ QString tooltip(const Resource&, CalEvent::Types) const; /** Return the read-only status tooltip for a resource. * A null string is returned if the resource is fully writable. */ static QString readOnlyTooltip(const Resource&); /** Return offset to add to headerData() role, for item models. */ virtual int headerDataEventRoleOffset() const { return 0; } - /** Return whether calendar migration/creation at initialisation has completed. */ - bool isMigrationComplete() const; - protected: ResourceDataModelBase(); + /** Terminate access to the data model, and tidy up. */ + virtual void terminate() = 0; + + /** Reload all resources' data from storage. + * @note In the case of Akonadi, this does not reload from the backend storage. + */ + virtual void reload() = 0; + + /** Reload a resource's data from storage. + * @note In the case of Akonadi, this does not reload from the backend storage. + */ + virtual bool reload(Resource&) = 0; + + /** Check for, and remove, any duplicate resources, i.e. those which use + * the same calendar file/directory. + */ + virtual void removeDuplicateResources() = 0; + + /** Disable the widget if the database engine is not available, and display + * an error overlay. + */ + virtual void widgetNeedsDatabase(QWidget*) = 0; + + /** Create a ResourceCreator instance for the model. */ + virtual ResourceCreator* createResourceCreator(KAlarmCal::CalEvent::Type defaultType, QWidget* parent) = 0; + + /** Update a resource's backend calendar file to the current KAlarm format. */ + virtual void updateCalendarToCurrentFormat(Resource&, bool ignoreKeepFormat, QObject* parent) = 0; + + virtual ResourceListModel* createResourceListModel(QObject* parent) = 0; + virtual ResourceFilterCheckListModel* createResourceFilterCheckListModel(QObject* parent) = 0; + virtual AlarmListModel* createAlarmListModel(QObject* parent) = 0; + virtual AlarmListModel* allAlarmListModel() = 0; + virtual TemplateListModel* createTemplateListModel(QObject* parent) = 0; + virtual TemplateListModel* allTemplateListModel() = 0; + + /** Return the data storage backend type used by this model. */ + virtual Preferences::Backend dataStorageBackend() const = 0; + static QVariant headerData(int section, Qt::Orientation, int role, bool eventHeaders, bool& handled); /** Return whether resourceData() and/or eventData() handle a role. */ bool roleHandled(int role) const; /** Return the model data for a resource. * @param role may be updated for calling the base model. * @param handled updated to true if the reply is valid, else set to false. */ QVariant resourceData(int& role, const Resource&, bool& handled) const; /** Return the model data for an event. * @param handled updated to true if the reply is valid, else set to false. */ QVariant eventData(int role, int column, const KAEvent& event, const Resource&, bool& handled) const; /** Called when a resource notifies a message to display to the user. */ void handleResourceMessage(ResourceType::MessageType, const QString& message, const QString& details); + /** Return whether calendar migration/creation at initialisation has completed. */ + bool isMigrationComplete() const; + /** Return whether calendar migration is currently in progress. */ bool isMigrating() const; /** To be called when calendar migration has been initiated (or reset). */ void setMigrationInitiated(bool started = true); /** To be called when calendar migration has been initiated (or reset). */ void setMigrationComplete(); static QString repeatText(const KAEvent&); static QString repeatOrder(const KAEvent&); static QString whatsThisText(int column); static QPixmap* eventIcon(const KAEvent&); + static ResourceDataModelBase* mInstance; + private: static QPixmap* mTextIcon; static QPixmap* mFileIcon; static QPixmap* mCommandIcon; static QPixmap* mEmailIcon; static QPixmap* mAudioIcon; static QSize mIconSize; // maximum size of any icon int mMigrationStatus {-1}; // migration status, -1 = no, 0 = initiated, 1 = complete + +friend class DataModel; }; #endif // RESOURCEDATAMODELBASE_H // vim: et sw=4: