File indexing completed on 2025-01-19 04:56:36
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org> 0003 * SPDX-FileCopyrightText: 2014 Rémi Benoit <r3m1.benoit@gmail.com> 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 0008 #include "akonadiserializer.h" 0009 0010 #include <Akonadi/Collection> 0011 #include <Akonadi/EntityDisplayAttribute> 0012 #include <Akonadi/Item> 0013 #include <KCalendarCore/Todo> 0014 #if __has_include(<kcalcore_version.h>) 0015 #include <kcalcore_version.h> 0016 #else 0017 #include <kcalendarcore_version.h> 0018 #endif 0019 #include <QMimeDatabase> 0020 0021 #include <numeric> 0022 0023 #include "utils/mem_fn.h" 0024 0025 #include "akonadi/akonadiapplicationselectedattribute.h" 0026 #include "akonadi/akonaditimestampattribute.h" 0027 0028 using namespace Akonadi; 0029 0030 Serializer::Serializer() 0031 { 0032 } 0033 0034 Serializer::~Serializer() 0035 { 0036 } 0037 0038 bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) 0039 { 0040 return object->property("collectionId").toLongLong() == collection.id(); 0041 } 0042 0043 bool Serializer::representsItem(QObjectPtr object, Item item) 0044 { 0045 return object->property("itemId").toLongLong() == item.id(); 0046 } 0047 0048 QString Serializer::itemUid(const Item &item) 0049 { 0050 if (item.hasPayload<KCalendarCore::Todo::Ptr>()) { 0051 const auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0052 return todo->uid(); 0053 } else { 0054 return QString(); 0055 } 0056 } 0057 0058 Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) 0059 { 0060 if (!collection.isValid()) 0061 return Domain::DataSource::Ptr(); 0062 0063 auto dataSource = Domain::DataSource::Ptr::create(); 0064 updateDataSourceFromCollection(dataSource, collection, naming); 0065 return dataSource; 0066 } 0067 0068 void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) 0069 { 0070 if (!collection.isValid()) 0071 return; 0072 0073 QString name = collection.displayName(); 0074 0075 if (naming == FullPath) { 0076 auto parent = collection.parentCollection(); 0077 while (parent.isValid() && parent != Akonadi::Collection::root()) { 0078 name = parent.displayName() + " » " + name; 0079 parent = parent.parentCollection(); 0080 } 0081 } 0082 0083 dataSource->setName(name); 0084 0085 const auto mimeTypes = collection.contentMimeTypes(); 0086 auto types = Domain::DataSource::ContentTypes(); 0087 if (mimeTypes.contains(KCalendarCore::Todo::todoMimeType())) 0088 types |= Domain::DataSource::Tasks; 0089 dataSource->setContentTypes(types); 0090 0091 if (collection.hasAttribute<Akonadi::EntityDisplayAttribute>()) { 0092 auto iconName = collection.attribute<Akonadi::EntityDisplayAttribute>()->iconName(); 0093 dataSource->setIconName(iconName); 0094 } 0095 0096 if (!collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>()) { 0097 dataSource->setSelected(true); 0098 } else { 0099 auto isSelected = collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected(); 0100 dataSource->setSelected(isSelected); 0101 } 0102 0103 dataSource->setProperty("collectionId", collection.id()); 0104 } 0105 0106 Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) 0107 { 0108 const auto id = dataSource->property("collectionId").value<Collection::Id>(); 0109 auto collection = Collection(id); 0110 0111 collection.attribute<Akonadi::TimestampAttribute>(Akonadi::Collection::AddIfMissing); 0112 0113 auto selectedAttribute = collection.attribute<Akonadi::ApplicationSelectedAttribute>(Akonadi::Collection::AddIfMissing); 0114 selectedAttribute->setSelected(dataSource->isSelected()); 0115 0116 return collection; 0117 } 0118 0119 bool Serializer::isSelectedCollection(Collection collection) 0120 { 0121 if (!isTaskCollection(collection)) 0122 return false; 0123 0124 if (!collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>()) 0125 return true; 0126 0127 return collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected(); 0128 } 0129 0130 bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) 0131 { 0132 return collection.contentMimeTypes().contains(KCalendarCore::Todo::todoMimeType()); 0133 } 0134 0135 bool Serializer::isTaskItem(Item item) 0136 { 0137 if (!item.hasPayload<KCalendarCore::Todo::Ptr>()) 0138 return false; 0139 0140 return !isProjectItem(item) && !isContext(item); 0141 } 0142 0143 Domain::Task::Ptr Serializer::createTaskFromItem(Item item) 0144 { 0145 if (!isTaskItem(item)) 0146 return Domain::Task::Ptr(); 0147 0148 auto task = Domain::Task::Ptr::create(); 0149 updateTaskFromItem(task, item); 0150 return task; 0151 } 0152 0153 void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) 0154 { 0155 if (!isTaskItem(item)) 0156 return; 0157 0158 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0159 0160 task->setTitle(todo->summary()); 0161 task->setText(todo->description()); 0162 task->setDone(todo->isCompleted()); 0163 task->setDoneDate(todo->completed().toLocalTime().date()); 0164 task->setStartDate(todo->dtStart().toLocalTime().date()); 0165 task->setDueDate(todo->dtDue().toLocalTime().date()); 0166 task->setProperty("itemId", item.id()); 0167 task->setProperty("parentCollectionId", item.parentCollection().id()); 0168 task->setProperty("todoUid", todo->uid()); 0169 task->setProperty("relatedUid", todo->relatedTo()); 0170 task->setRunning(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning()) == QLatin1StringView("1")); 0171 const auto contextUids = todo->customProperty(Serializer::customPropertyAppName(), 0172 Serializer::customPropertyContextList()).split(',', Qt::SkipEmptyParts); 0173 task->setProperty("contextUids", contextUids); 0174 0175 switch (todo->recurrence()->recurrenceType()) { 0176 case KCalendarCore::Recurrence::rDaily: 0177 task->setRecurrence(Domain::Task::RecursDaily); 0178 break; 0179 case KCalendarCore::Recurrence::rWeekly: 0180 task->setRecurrence(Domain::Task::RecursWeekly); 0181 break; 0182 case KCalendarCore::Recurrence::rMonthlyDay: 0183 task->setRecurrence(Domain::Task::RecursMonthly); 0184 break; 0185 case KCalendarCore::Recurrence::rYearlyMonth: 0186 task->setRecurrence(Domain::Task::RecursYearly); 0187 break; 0188 default: 0189 // Other cases are not supported for now and as such just ignored 0190 break; 0191 } 0192 0193 QMimeDatabase mimeDb; 0194 const auto attachmentsInput = todo->attachments(); 0195 Domain::Task::Attachments attachments; 0196 attachments.reserve(attachmentsInput.size()); 0197 std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(), 0198 std::back_inserter(attachments), 0199 [&mimeDb] (const KCalendarCore::Attachment &attach) { 0200 Domain::Task::Attachment attachment; 0201 if (attach.isUri()) 0202 attachment.setUri(QUrl(attach.uri())); 0203 else 0204 attachment.setData(attach.decodedData()); 0205 attachment.setLabel(attach.label()); 0206 attachment.setMimeType(attach.mimeType()); 0207 attachment.setIconName(mimeDb.mimeTypeForName(attach.mimeType()).iconName()); 0208 return attachment; 0209 }); 0210 task->setAttachments(attachments); 0211 } 0212 0213 bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) 0214 { 0215 if (!isTaskItem(item)) 0216 return false; 0217 0218 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0219 if (todo->relatedTo() == task->property("todoUid")) 0220 return true; 0221 0222 return false; 0223 } 0224 0225 Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) 0226 { 0227 auto todo = KCalendarCore::Todo::Ptr::create(); 0228 0229 todo->setSummary(task->title()); 0230 todo->setDescription(task->text()); 0231 0232 // We only support all-day todos, so ignore timezone information and possible effect from timezone on dates 0233 // KCalendarCore reads "DUE;VALUE=DATE:20171130" as QDateTime(QDate(2017, 11, 30), QTime(), Qt::LocalTime), for lack of timezone information 0234 // so we should never call toUtc() on that, it would mess up the date. 0235 // If one day we want to support time information, we need to add a task->isAllDay()/setAllDay(). 0236 todo->setDtStart(task->startDate().startOfDay()); 0237 todo->setDtDue(task->dueDate().startOfDay()); 0238 todo->setAllDay(true); 0239 0240 if (task->property("todoUid").isValid()) { 0241 todo->setUid(task->property("todoUid").toString()); 0242 } 0243 0244 if (task->property("relatedUid").isValid()) { 0245 todo->setRelatedTo(task->property("relatedUid").toString()); 0246 } 0247 0248 if (task->property("contextUids").isValid()) { 0249 todo->setCustomProperty(customPropertyAppName(), 0250 customPropertyContextList(), 0251 task->property("contextUids").toStringList().join(',')); 0252 } 0253 0254 switch (task->recurrence()) { 0255 case Domain::Task::NoRecurrence: 0256 break; 0257 case Domain::Task::RecursDaily: 0258 todo->recurrence()->setDaily(1); 0259 break; 0260 case Domain::Task::RecursWeekly: 0261 todo->recurrence()->setWeekly(1); 0262 break; 0263 case Domain::Task::RecursMonthly: 0264 todo->recurrence()->setMonthly(1); 0265 break; 0266 case Domain::Task::RecursYearly: 0267 todo->recurrence()->setYearly(1); 0268 break; 0269 } 0270 0271 for (const auto &attachment : task->attachments()) { 0272 KCalendarCore::Attachment attach(QByteArray{}); 0273 if (attachment.isUri()) 0274 attach.setUri(attachment.uri().toString()); 0275 else 0276 attach.setDecodedData(attachment.data()); 0277 attach.setMimeType(attachment.mimeType()); 0278 attach.setLabel(attachment.label()); 0279 todo->addAttachment(attach); 0280 } 0281 0282 if (task->isRunning()) { 0283 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning(), "1"); 0284 } else { 0285 todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning()); 0286 } 0287 0288 // Needs to be done after all other dates are positioned 0289 // since this applies the recurrence logic 0290 if (task->isDone()) 0291 todo->setCompleted(QDateTime(task->doneDate(), QTime(), Qt::UTC)); 0292 else 0293 todo->setCompleted(false); 0294 0295 Akonadi::Item item; 0296 if (task->property("itemId").isValid()) { 0297 item.setId(task->property("itemId").value<Akonadi::Item::Id>()); 0298 } 0299 if (task->property("parentCollectionId").isValid()) { 0300 auto parentId = task->property("parentCollectionId").value<Akonadi::Collection::Id>(); 0301 item.setParentCollection(Akonadi::Collection(parentId)); 0302 } 0303 item.setMimeType(KCalendarCore::Todo::todoMimeType()); 0304 item.setPayload(todo); 0305 return item; 0306 } 0307 0308 QString Serializer::relatedUidFromItem(Akonadi::Item item) 0309 { 0310 if (isTaskItem(item)) { 0311 const auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0312 return todo->relatedTo(); 0313 } else { 0314 return QString(); 0315 } 0316 } 0317 0318 void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) 0319 { 0320 if (!isTaskItem(item)) 0321 return; 0322 0323 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0324 todo->setRelatedTo(parent->property("todoUid").toString()); 0325 } 0326 0327 void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) 0328 { 0329 if (isTaskItem(item)) { 0330 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0331 todo->setRelatedTo(project->property("todoUid").toString()); 0332 } 0333 } 0334 0335 void Serializer::removeItemParent(Akonadi::Item item) 0336 { 0337 if (!isTaskItem(item)) 0338 return; 0339 0340 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0341 todo->setRelatedTo(QString()); 0342 } 0343 0344 void Serializer::promoteItemToProject(Akonadi::Item item) 0345 { 0346 if (!isTaskItem(item)) 0347 return; 0348 0349 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0350 todo->setRelatedTo(QString()); 0351 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1")); 0352 } 0353 0354 void Serializer::clearItem(Akonadi::Item *item) 0355 { 0356 Q_ASSERT(item); 0357 if (!isTaskItem(*item)) 0358 return; 0359 0360 auto todo = item->payload<KCalendarCore::Todo::Ptr>(); 0361 todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()); 0362 } 0363 0364 Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) 0365 { 0366 if (potentialChildren.isEmpty()) 0367 return Akonadi::Item::List(); 0368 0369 Akonadi::Item::List itemsToProcess = potentialChildren; 0370 Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload<KCalendarCore::Todo::Ptr>()); 0371 KCalendarCore::Todo::Ptr todo = ancestorItem.payload<KCalendarCore::Todo::Ptr>(); 0372 0373 const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), 0374 [ancestorItem, todo](Akonadi::Item currentItem) { 0375 return (!currentItem.hasPayload<KCalendarCore::Todo::Ptr>() 0376 || currentItem == ancestorItem 0377 || currentItem.payload<KCalendarCore::Todo::Ptr>()->relatedTo() != todo->uid()); 0378 }); 0379 0380 Akonadi::Item::List itemsRemoved; 0381 itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound)); 0382 std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); 0383 itemsToProcess.erase(itemsToProcess.begin(), bound); 0384 0385 auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), 0386 [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { 0387 result << currentItem; 0388 return result += filterDescendantItems(itemsRemoved, currentItem); 0389 }); 0390 0391 return result; 0392 } 0393 0394 bool Serializer::isProjectItem(Item item) 0395 { 0396 if (!item.hasPayload<KCalendarCore::Todo::Ptr>()) 0397 return false; 0398 0399 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0400 return !todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty(); 0401 } 0402 0403 Domain::Project::Ptr Serializer::createProjectFromItem(Item item) 0404 { 0405 if (!isProjectItem(item)) 0406 return Domain::Project::Ptr(); 0407 0408 auto project = Domain::Project::Ptr::create(); 0409 updateProjectFromItem(project, item); 0410 return project; 0411 } 0412 0413 void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) 0414 { 0415 if (!isProjectItem(item)) 0416 return; 0417 0418 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0419 0420 project->setName(todo->summary()); 0421 project->setProperty("itemId", item.id()); 0422 project->setProperty("parentCollectionId", item.parentCollection().id()); 0423 project->setProperty("todoUid", todo->uid()); 0424 } 0425 0426 Item Serializer::createItemFromProject(Domain::Project::Ptr project) 0427 { 0428 auto todo = KCalendarCore::Todo::Ptr::create(); 0429 0430 todo->setSummary(project->name()); 0431 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1")); 0432 0433 if (project->property("todoUid").isValid()) { 0434 todo->setUid(project->property("todoUid").toString()); 0435 } 0436 0437 Akonadi::Item item; 0438 if (project->property("itemId").isValid()) { 0439 item.setId(project->property("itemId").value<Akonadi::Item::Id>()); 0440 } 0441 if (project->property("parentCollectionId").isValid()) { 0442 auto parentId = project->property("parentCollectionId").value<Akonadi::Collection::Id>(); 0443 item.setParentCollection(Akonadi::Collection(parentId)); 0444 } 0445 item.setMimeType(KCalendarCore::Todo::todoMimeType()); 0446 item.setPayload(todo); 0447 return item; 0448 } 0449 0450 bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) 0451 { 0452 const QString todoUid = project->property("todoUid").toString(); 0453 const QString relatedUid = relatedUidFromItem(item); 0454 0455 return !todoUid.isEmpty() 0456 && !relatedUid.isEmpty() 0457 && todoUid == relatedUid; 0458 } 0459 0460 static QStringList extractContexts(KCalendarCore::Todo::Ptr todo) 0461 { 0462 const QString contexts = todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()); 0463 return contexts.split(',', Qt::SkipEmptyParts); 0464 } 0465 0466 bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const 0467 { 0468 if (!context->property("todoUid").isValid()) 0469 return false; 0470 0471 if (!item.hasPayload<KCalendarCore::Todo::Ptr>()) 0472 return false; 0473 0474 auto contextUid = context->property("todoUid").toString(); 0475 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0476 const auto contextList = extractContexts(todo); 0477 return contextList.contains(contextUid); 0478 } 0479 0480 bool Serializer::isContext(Item item) 0481 { 0482 if (!item.hasPayload<KCalendarCore::Todo::Ptr>()) 0483 return false; 0484 0485 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0486 return !todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext()).isEmpty(); 0487 } 0488 0489 Domain::Context::Ptr Serializer::createContextFromItem(Item item) 0490 { 0491 if (!isContext(item)) 0492 return {}; 0493 0494 auto context = Domain::Context::Ptr::create(); 0495 updateContextFromItem(context, item); 0496 return context; 0497 } 0498 0499 Akonadi::Item Serializer::createItemFromContext(Domain::Context::Ptr context) 0500 { 0501 auto todo = KCalendarCore::Todo::Ptr::create(); 0502 0503 todo->setSummary(context->name()); 0504 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1")); 0505 0506 if (context->property("todoUid").isValid()) { 0507 todo->setUid(context->property("todoUid").toString()); 0508 } 0509 0510 auto item = Akonadi::Item(); 0511 if (context->property("itemId").isValid()) { 0512 item.setId(context->property("itemId").value<Akonadi::Item::Id>()); 0513 } 0514 if (context->property("parentCollectionId").isValid()) { 0515 auto parentId = context->property("parentCollectionId").value<Akonadi::Collection::Id>(); 0516 item.setParentCollection(Akonadi::Collection(parentId)); 0517 } 0518 item.setMimeType(KCalendarCore::Todo::todoMimeType()); 0519 item.setPayload(todo); 0520 return item; 0521 } 0522 0523 void Serializer::updateContextFromItem(Domain::Context::Ptr context, Item item) 0524 { 0525 if (!isContext(item)) 0526 return; 0527 0528 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0529 0530 context->setName(todo->summary()); 0531 context->setProperty("itemId", item.id()); 0532 context->setProperty("parentCollectionId", item.parentCollection().id()); 0533 context->setProperty("todoUid", todo->uid()); 0534 } 0535 0536 void Serializer::addContextToTask(Domain::Context::Ptr context, Item item) 0537 { 0538 if (!isTaskItem(item)) { 0539 qWarning() << "Cannot add context to a non-task" << item.id(); 0540 return; 0541 } 0542 0543 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0544 0545 if (!context->property("todoUid").isValid()) 0546 return; 0547 0548 auto contextUid = context->property("todoUid").toString(); 0549 auto contextList = extractContexts(todo); 0550 if (!contextList.contains(contextUid)) 0551 contextList.append(contextUid); 0552 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextList.join(',')); 0553 0554 item.setPayload<KCalendarCore::Todo::Ptr>(todo); 0555 } 0556 0557 void Serializer::removeContextFromTask(Domain::Context::Ptr context, Item item) 0558 { 0559 if (!isTaskItem(item)) { 0560 qWarning() << "Cannot remove context from a non-task" << item.id(); 0561 return; 0562 } 0563 0564 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0565 0566 if (!context->property("todoUid").isValid()) 0567 return; 0568 0569 auto contextUid = context->property("todoUid").toString(); 0570 QStringList contextList = extractContexts(todo); 0571 contextList.removeAll(contextUid); 0572 if (contextList.isEmpty()) 0573 todo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()); 0574 else 0575 todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextList.join(',')); 0576 0577 item.setPayload<KCalendarCore::Todo::Ptr>(todo); 0578 } 0579 0580 QString Serializer::contextUid(Item item) 0581 { 0582 if (!isContext(item)) 0583 return {}; 0584 0585 auto todo = item.payload<KCalendarCore::Todo::Ptr>(); 0586 return todo->uid(); 0587 } 0588 0589 // KCalendarCore's CustomProperties doesn't implement case insensitivity 0590 // and some CALDAV servers make everything uppercase. So do like most of kdepim 0591 // and use uppercase property names. 0592 0593 QByteArray Serializer::customPropertyAppName() 0594 { 0595 return QByteArrayLiteral("ZANSHIN"); 0596 } 0597 0598 QByteArray Serializer::customPropertyIsProject() 0599 { 0600 return QByteArrayLiteral("ISPROJECT"); 0601 } 0602 0603 QByteArray Serializer::customPropertyIsContext() 0604 { 0605 return QByteArrayLiteral("ISCONTEXT"); 0606 } 0607 0608 QByteArray Serializer::customPropertyIsRunning() 0609 { 0610 return QByteArrayLiteral("ISRUNNING"); 0611 } 0612 0613 QByteArray Serializer::customPropertyContextList() 0614 { 0615 return QByteArrayLiteral("CONTEXTLIST"); 0616 }