Akonadi

specialcollectionshelperjobs.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "specialcollectionshelperjobs_p.h"
8
9#include "servermanager.h"
10#include "specialcollectionattribute.h"
11#include <QDBusConnection>
12
13#include "agentinstance.h"
14#include "agentinstancecreatejob.h"
15#include "agentmanager.h"
16#include "collectionfetchjob.h"
17#include "collectionfetchscope.h"
18#include "collectionmodifyjob.h"
19#include "entitydisplayattribute.h"
20#include "resourcesynchronizationjob.h"
21
22#include "akonadicore_debug.h"
23
24#include <KCoreConfigSkeleton>
25#include <KLocalizedString>
26
27#include <QDBusConnectionInterface>
28#include <QDBusInterface>
29#include <QDBusServiceWatcher>
30#include <QMetaMethod>
31#include <QTime>
32#include <QTimer>
33
34#define LOCK_WAIT_TIMEOUT_SECONDS 30
35
36using namespace Akonadi;
37
38// convenient methods to get/set the default resource id
39static void setDefaultResourceId(KCoreConfigSkeleton *settings, const QString &value)
40{
41 KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId"));
42 Q_ASSERT(item);
43 item->setProperty(value);
44}
45
46static QString defaultResourceId(KCoreConfigSkeleton *settings)
47{
48 const KConfigSkeletonItem *item = settings->findItem(QStringLiteral("DefaultResourceId"));
49 Q_ASSERT(item);
50 return item->property().toString();
51}
52
53static QString dbusServiceName()
54{
55 const QString service = QStringLiteral("org.kde.pim.SpecialCollections");
56 if (ServerManager::hasInstanceIdentifier()) {
57 return service + ServerManager::instanceIdentifier();
58 }
59 return service;
60}
61
62static QMetaType::Type argumentType(const QMetaObject *metaObject, const QString &method)
63{
64 QMetaMethod metaMethod;
65 for (int i = 0; i < metaObject->methodCount(); ++i) {
66 const QString signature = QString::fromLatin1(metaObject->method(i).methodSignature());
67 if (signature.startsWith(method)) {
68 metaMethod = metaObject->method(i);
69 }
70 }
71
72 if (metaMethod.methodSignature().isEmpty()) {
74 }
75
76 const QList<QByteArray> argTypes = metaMethod.parameterTypes();
77 if (argTypes.count() != 1) {
79 }
80
81 return static_cast<QMetaType::Type>(QMetaType::fromName(argTypes.first().constData()).id());
82}
83
84// ===================== ResourceScanJob ============================
85
86/**
87 @internal
88*/
89class Akonadi::ResourceScanJobPrivate
90{
91public:
92 ResourceScanJobPrivate(KCoreConfigSkeleton *settings, ResourceScanJob *qq);
93
94 void fetchResult(KJob *job); // slot
95
96 ResourceScanJob *const q;
97
98 // Input:
99 QString mResourceId;
100 KCoreConfigSkeleton *mSettings = nullptr;
101
102 // Output:
103 Collection mRootCollection;
104 Collection::List mSpecialCollections;
105};
106
107ResourceScanJobPrivate::ResourceScanJobPrivate(KCoreConfigSkeleton *settings, ResourceScanJob *qq)
108 : q(qq)
109 , mSettings(settings)
110{
111}
112
113void ResourceScanJobPrivate::fetchResult(KJob *job)
114{
115 if (job->error()) {
116 qCWarning(AKONADICORE_LOG) << job->errorText();
117 return;
118 }
119
120 auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
121 Q_ASSERT(fetchJob);
122
123 Q_ASSERT(!mRootCollection.isValid());
124 Q_ASSERT(mSpecialCollections.isEmpty());
125 const Akonadi::Collection::List lstCols = fetchJob->collections();
126 for (const Collection &collection : lstCols) {
127 if (collection.parentCollection() == Collection::root()) {
128 if (mRootCollection.isValid()) {
129 qCWarning(AKONADICORE_LOG) << "Resource has more than one root collection. I don't know what to do.";
130 } else {
131 mRootCollection = collection;
132 }
133 }
134
135 if (collection.hasAttribute<SpecialCollectionAttribute>()) {
136 mSpecialCollections.append(collection);
137 }
138 }
139
140 qCDebug(AKONADICORE_LOG) << "Fetched root collection" << mRootCollection.id() << "and" << mSpecialCollections.count() << "local folders"
141 << "(total" << fetchJob->collections().count() << "collections).";
142
143 if (!mRootCollection.isValid()) {
144 q->setError(ResourceScanJob::Unknown);
145 q->setErrorText(i18n("Could not fetch root collection of resource %1.", mResourceId));
146 q->emitResult();
147 return;
148 }
149
150 // We are done!
151 q->emitResult();
152}
153
154ResourceScanJob::ResourceScanJob(const QString &resourceId, KCoreConfigSkeleton *settings, QObject *parent)
155 : Job(parent)
156 , d(new ResourceScanJobPrivate(settings, this))
157{
158 setResourceId(resourceId);
159}
160
161ResourceScanJob::~ResourceScanJob() = default;
162
163QString ResourceScanJob::resourceId() const
164{
165 return d->mResourceId;
166}
167
168void ResourceScanJob::setResourceId(const QString &resourceId)
169{
170 d->mResourceId = resourceId;
171}
172
173Akonadi::Collection ResourceScanJob::rootResourceCollection() const
174{
175 return d->mRootCollection;
176}
177
178Akonadi::Collection::List ResourceScanJob::specialCollections() const
179{
180 return d->mSpecialCollections;
181}
182
183void ResourceScanJob::doStart()
184{
185 if (d->mResourceId.isEmpty()) {
186 if (!qobject_cast<DefaultResourceJob *>(this)) {
187 qCCritical(AKONADICORE_LOG) << "No resource ID given.";
188 setError(Job::Unknown);
189 setErrorText(i18n("No resource ID given."));
190 }
191 emitResult();
192 return;
193 }
194
196 fetchJob->fetchScope().setResource(d->mResourceId);
197 fetchJob->fetchScope().setIncludeStatistics(true);
198 fetchJob->fetchScope().setListFilter(CollectionFetchScope::Display);
199 connect(fetchJob, &CollectionFetchJob::result, this, [this](KJob *job) {
200 d->fetchResult(job);
201 });
202}
203
204// ===================== DefaultResourceJob ============================
205
206/**
207 @internal
208*/
209class Akonadi::DefaultResourceJobPrivate
210{
211public:
212 DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq);
213
214 void tryFetchResource();
215 void resourceCreateResult(KJob *job); // slot
216 void resourceSyncResult(KJob *job); // slot
217 void collectionFetchResult(KJob *job); // slot
218 void collectionModifyResult(KJob *job); // slot
219
220 DefaultResourceJob *const q;
221 KCoreConfigSkeleton *mSettings = nullptr;
222 QVariantMap mDefaultResourceOptions;
223 QList<QByteArray> mKnownTypes;
224 QMap<QByteArray, QString> mNameForTypeMap;
225 QMap<QByteArray, QString> mIconForTypeMap;
226 QString mDefaultResourceType;
227 int mPendingModifyJobs = 0;
228 bool mResourceWasPreexisting = true;
229};
230
231DefaultResourceJobPrivate::DefaultResourceJobPrivate(KCoreConfigSkeleton *settings, DefaultResourceJob *qq)
232 : q(qq)
233 , mSettings(settings)
234 , mPendingModifyJobs(0)
235 , mResourceWasPreexisting(true /* for safety, so as not to accidentally delete data */)
236{
237}
238
239void DefaultResourceJobPrivate::tryFetchResource()
240{
241 // Get the resourceId from config. Another instance might have changed it in the meantime.
242 mSettings->load();
243
244 const QString resourceId = defaultResourceId(mSettings);
245
246 qCDebug(AKONADICORE_LOG) << "Read defaultResourceId" << resourceId << "from config.";
247
248 const AgentInstance resource = AgentManager::self()->instance(resourceId);
249 if (resource.isValid()) {
250 // The resource exists; scan it.
251 mResourceWasPreexisting = true;
252 qCDebug(AKONADICORE_LOG) << "Found resource" << resourceId;
253 q->setResourceId(resourceId);
254
256 fetchJob->fetchScope().setResource(resourceId);
257 fetchJob->fetchScope().setIncludeStatistics(true);
258 q->connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) {
259 collectionFetchResult(job);
260 });
261 } else {
262 // Try harder: maybe the default resource has been removed and another one added
263 // without updating the config file, in this case search for a resource
264 // of the same type and the default name
265 const AgentInstance::List resources = AgentManager::self()->instances();
266 for (const AgentInstance &resourceInstance : resources) {
267 if (resourceInstance.type().identifier() == mDefaultResourceType) {
268 if (resourceInstance.name() == mDefaultResourceOptions.value(QStringLiteral("Name")).toString()) {
269 // found a matching one...
270 setDefaultResourceId(mSettings, resourceInstance.identifier());
271 mSettings->save();
272 mResourceWasPreexisting = true;
273 qCDebug(AKONADICORE_LOG) << "Found resource" << resourceInstance.identifier();
274 q->setResourceId(resourceInstance.identifier());
275 q->ResourceScanJob::doStart();
276 return;
277 }
278 }
279 }
280
281 // Create the resource.
282 mResourceWasPreexisting = false;
283 qCDebug(AKONADICORE_LOG) << "Creating maildir resource.";
284 const AgentType type = AgentManager::self()->type(mDefaultResourceType);
285 auto job = new AgentInstanceCreateJob(type, q);
287 resourceCreateResult(job);
288 });
289 job->start(); // non-Akonadi::Job
290 }
291}
292
293void DefaultResourceJobPrivate::resourceCreateResult(KJob *job)
294{
295 if (job->error()) {
296 qCWarning(AKONADICORE_LOG) << job->errorText();
297 // fail( i18n( "Failed to create the default resource (%1).", job->errorString() ) );
298 q->setError(job->error());
299 q->setErrorText(job->errorText());
300 q->emitResult();
301 return;
302 }
303
304 AgentInstance agent;
305
306 // Get the resource instance.
307 {
308 auto createJob = qobject_cast<AgentInstanceCreateJob *>(job);
309 Q_ASSERT(createJob);
310 agent = createJob->instance();
311 setDefaultResourceId(mSettings, agent.identifier());
312 qCDebug(AKONADICORE_LOG) << "Created maildir resource with id" << defaultResourceId(mSettings);
313 }
314
315 const QString defaultId = defaultResourceId(mSettings);
316
317 // Configure the resource.
318 {
319 agent.setName(mDefaultResourceOptions.value(QStringLiteral("Name")).toString());
320
321 const auto service = ServerManager::agentServiceName(ServerManager::Resource, defaultId);
322 QDBusInterface conf(service, QStringLiteral("/Settings"), QString());
323
324 if (!conf.isValid()) {
325 q->setError(-1);
326 q->setErrorText(i18n("Invalid resource identifier '%1'", defaultId));
327 q->emitResult();
328 return;
329 }
330
331 QMap<QString, QVariant>::const_iterator it = mDefaultResourceOptions.cbegin();
332 const QMap<QString, QVariant>::const_iterator itEnd = mDefaultResourceOptions.cend();
333 for (; it != itEnd; ++it) {
334 if (it.key() == QLatin1StringView("Name")) {
335 continue;
336 }
337
338 const QString methodName = QStringLiteral("set%1").arg(it.key());
339 const QMetaType::Type argType = argumentType(conf.metaObject(), methodName);
340 if (argType == QMetaType::UnknownType) {
341 q->setError(Job::Unknown);
342 q->setErrorText(i18n("Failed to configure default resource via D-Bus."));
343 q->emitResult();
344 return;
345 }
346
347 QDBusReply<void> reply = conf.call(methodName, it.value());
348 if (!reply.isValid()) {
349 q->setError(Job::Unknown);
350 q->setErrorText(i18n("Failed to configure default resource via D-Bus."));
351 q->emitResult();
352 return;
353 }
354 }
355
356 conf.call(QStringLiteral("save"));
357
358 agent.reconfigure();
359 }
360
361 // Sync the resource.
362 {
363 auto syncJob = new ResourceSynchronizationJob(agent, q);
364 QObject::connect(syncJob, &ResourceSynchronizationJob::result, q, [this](KJob *job) {
365 resourceSyncResult(job);
366 });
367 syncJob->start(); // non-Akonadi
368 }
369}
370
371void DefaultResourceJobPrivate::resourceSyncResult(KJob *job)
372{
373 if (job->error()) {
374 qCWarning(AKONADICORE_LOG) << job->errorText();
375 // fail( i18n( "ResourceSynchronizationJob failed (%1).", job->errorString() ) );
376 return;
377 }
378
379 // Fetch the collections of the resource.
380 qCDebug(AKONADICORE_LOG) << "Fetching maildir collections.";
382 fetchJob->fetchScope().setResource(defaultResourceId(mSettings));
383 QObject::connect(fetchJob, &CollectionFetchJob::result, q, [this](KJob *job) {
384 collectionFetchResult(job);
385 });
386}
387
388void DefaultResourceJobPrivate::collectionFetchResult(KJob *job)
389{
390 if (job->error()) {
391 qCWarning(AKONADICORE_LOG) << job->errorText();
392 // fail( i18n( "Failed to fetch the root maildir collection (%1).", job->errorString() ) );
393 return;
394 }
395
396 auto fetchJob = qobject_cast<CollectionFetchJob *>(job);
397 Q_ASSERT(fetchJob);
398
399 const Collection::List collections = fetchJob->collections();
400 qCDebug(AKONADICORE_LOG) << "Fetched" << collections.count() << "collections.";
401
402 // Find the root maildir collection.
403 Collection::List toRecover;
404 Collection resourceCollection;
405 for (const Collection &collection : collections) {
406 if (collection.parentCollection() == Collection::root()) {
407 resourceCollection = collection;
408 toRecover.append(collection);
409 break;
410 }
411 }
412
413 if (!resourceCollection.isValid()) {
414 q->setError(Job::Unknown);
415 q->setErrorText(i18n("Failed to fetch the resource collection."));
416 q->emitResult();
417 return;
418 }
419
420 // Find all children of the resource collection.
421 for (const Collection &collection : std::as_const(collections)) {
422 if (collection.parentCollection() == resourceCollection) {
423 toRecover.append(collection);
424 }
425 }
426
427 QHash<QString, QByteArray> typeForName;
428 for (const QByteArray &type : std::as_const(mKnownTypes)) {
429 const QString displayName = mNameForTypeMap.value(type);
430 typeForName[displayName] = type;
431 }
432
433 // These collections have been created by the maildir resource, when it
434 // found the folders on disk. So give them the necessary attributes now.
435 Q_ASSERT(mPendingModifyJobs == 0);
436 for (Collection collection : std::as_const(toRecover)) {
437 if (collection.hasAttribute<SpecialCollectionAttribute>()) {
438 continue;
439 }
440
441 // Find the type for the collection.
442 const QString name = collection.displayName();
443 const QByteArray type = typeForName.value(name);
444
445 if (!type.isEmpty()) {
446 qCDebug(AKONADICORE_LOG) << "Recovering collection" << name;
447 setCollectionAttributes(collection, type, mNameForTypeMap, mIconForTypeMap);
448
449 auto modifyJob = new CollectionModifyJob(collection, q);
450 QObject::connect(modifyJob, &CollectionModifyJob::result, q, [this](KJob *job) {
451 collectionModifyResult(job);
452 });
453 mPendingModifyJobs++;
454 } else {
455 qCDebug(AKONADICORE_LOG) << "Searching for names: " << typeForName.keys();
456 qCDebug(AKONADICORE_LOG) << "Unknown collection name" << name << "-- not recovering.";
457 }
458 }
459
460 if (mPendingModifyJobs == 0) {
461 // Scan the resource.
462 q->setResourceId(defaultResourceId(mSettings));
463 q->ResourceScanJob::doStart();
464 }
465}
466
467void DefaultResourceJobPrivate::collectionModifyResult(KJob *job)
468{
469 if (job->error()) {
470 qCWarning(AKONADICORE_LOG) << job->errorText();
471 // fail( i18n( "Failed to modify the root maildir collection (%1).", job->errorString() ) );
472 return;
473 }
474
475 Q_ASSERT(mPendingModifyJobs > 0);
476 mPendingModifyJobs--;
477 qCDebug(AKONADICORE_LOG) << "pendingModifyJobs now" << mPendingModifyJobs;
478 if (mPendingModifyJobs == 0) {
479 // Write the updated config.
480 qCDebug(AKONADICORE_LOG) << "Writing defaultResourceId" << defaultResourceId(mSettings) << "to config.";
481 mSettings->save();
482
483 // Scan the resource.
484 q->setResourceId(defaultResourceId(mSettings));
485 q->ResourceScanJob::doStart();
486 }
487}
488
489DefaultResourceJob::DefaultResourceJob(KCoreConfigSkeleton *settings, QObject *parent)
490 : ResourceScanJob(QString(), settings, parent)
491 , d(new DefaultResourceJobPrivate(settings, this))
492{
493}
494
495DefaultResourceJob::~DefaultResourceJob() = default;
496
497void DefaultResourceJob::setDefaultResourceType(const QString &type)
498{
499 d->mDefaultResourceType = type;
500}
501
502void DefaultResourceJob::setDefaultResourceOptions(const QVariantMap &options)
503{
504 d->mDefaultResourceOptions = options;
505}
506
507void DefaultResourceJob::setTypes(const QList<QByteArray> &types)
508{
509 d->mKnownTypes = types;
510}
511
512void DefaultResourceJob::setNameForTypeMap(const QMap<QByteArray, QString> &map)
513{
514 d->mNameForTypeMap = map;
515}
516
517void DefaultResourceJob::setIconForTypeMap(const QMap<QByteArray, QString> &map)
518{
519 d->mIconForTypeMap = map;
520}
521
522void DefaultResourceJob::doStart()
523{
524 d->tryFetchResource();
525}
526
527void DefaultResourceJob::slotResult(KJob *job)
528{
529 if (job->error()) {
530 qCWarning(AKONADICORE_LOG) << job->errorText();
531 // Do some cleanup.
532 if (!d->mResourceWasPreexisting) {
533 // We only removed the resource instance if we have created it.
534 // Otherwise we might lose the user's data.
535 const AgentInstance resource = AgentManager::self()->instance(defaultResourceId(d->mSettings));
536 qCDebug(AKONADICORE_LOG) << "Removing resource" << resource.identifier();
537 AgentManager::self()->removeInstance(resource);
538 }
539 }
540
541 Job::slotResult(job);
542}
543
544// ===================== GetLockJob ============================
545
546class Akonadi::GetLockJobPrivate
547{
548public:
549 explicit GetLockJobPrivate(GetLockJob *qq);
550
551 void doStart(); // slot
552 void timeout(); // slot
553
554 GetLockJob *const q;
555 QTimer *mSafetyTimer = nullptr;
556};
557
558GetLockJobPrivate::GetLockJobPrivate(GetLockJob *qq)
559 : q(qq)
560 , mSafetyTimer(nullptr)
561{
562}
563
564void GetLockJobPrivate::doStart()
565{
566 // Just doing registerService() and checking its return value is not sufficient,
567 // since we may *already* own the name, and then registerService() returns true.
568
570 const bool alreadyLocked = bus.interface()->isServiceRegistered(dbusServiceName());
571 const bool gotIt = bus.registerService(dbusServiceName());
572
573 if (gotIt && !alreadyLocked) {
574 // qCDebug(AKONADICORE_LOG) << "Got lock immediately.";
575 q->emitResult();
576 } else {
579 if (QDBusConnection::sessionBus().registerService(dbusServiceName())) {
580 mSafetyTimer->stop();
581 q->emitResult();
582 }
583 });
584
585 mSafetyTimer = new QTimer(q);
586 mSafetyTimer->setSingleShot(true);
587 mSafetyTimer->setInterval(LOCK_WAIT_TIMEOUT_SECONDS * 1000);
588 mSafetyTimer->start();
589 QObject::connect(mSafetyTimer, &QTimer::timeout, q, [this]() {
590 timeout();
591 });
592 }
593}
594
595void GetLockJobPrivate::timeout()
596{
597 qCWarning(AKONADICORE_LOG) << "Timeout trying to get lock. Check who has acquired the name" << dbusServiceName() << "on DBus, using qdbus or qdbusviewer.";
598 q->setError(Job::Unknown);
599 q->setErrorText(i18n("Timeout trying to get lock."));
600 q->emitResult();
601}
602
603GetLockJob::GetLockJob(QObject *parent)
604 : KJob(parent)
605 , d(new GetLockJobPrivate(this))
606{
607}
608
609GetLockJob::~GetLockJob() = default;
610
611void GetLockJob::start()
612{
613 QTimer::singleShot(0, this, [this]() {
614 d->doStart();
615 });
616}
617
618void Akonadi::setCollectionAttributes(Akonadi::Collection &collection,
619 const QByteArray &type,
620 const QMap<QByteArray, QString> &nameForType,
621 const QMap<QByteArray, QString> &iconForType)
622{
623 {
624 auto attr = new EntityDisplayAttribute;
625 attr->setIconName(iconForType.value(type));
626 attr->setDisplayName(nameForType.value(type));
627 collection.addAttribute(attr);
628 }
629
630 {
631 auto attr = new SpecialCollectionAttribute;
632 attr->setCollectionType(type);
633 collection.addAttribute(attr);
634 }
635}
636
637bool Akonadi::releaseLock()
638{
639 return QDBusConnection::sessionBus().unregisterService(dbusServiceName());
640}
641
642#include "moc_specialcollectionshelperjobs_p.cpp"
Represents one agent instance and takes care of communication with it.
QString identifier() const
Set/get the unique identifier of this AgentInstance.
Job for creating new agent instances.
AgentType type(const QString &identifier) const
Returns the agent type with the given identifier or an invalid agent type if the identifier does not ...
static AgentManager * self()
Returns the global instance of the agent manager.
AgentInstance instance(const QString &identifier) const
Returns the agent instance with the given identifier or an invalid agent instance if the identifier d...
AgentInstance::List instances() const
Returns the list of all available agent instances.
Job that fetches collections from the Akonadi storage.
@ Recursive
List all sub-collections.
Job that modifies a collection in the Akonadi storage.
Represents a collection of PIM items.
Definition collection.h:62
void addAttribute(Attribute *attribute)
Adds an attribute to the collection.
static Collection root()
Returns the root collection.
Attribute that stores the properties that are used to display an entity.
void setIconName(const QString &name)
Sets the icon name for the default icon.
Base class for all actions in the Akonadi storage.
Definition job.h:81
@ Unknown
Unknown error.
Definition job.h:102
Job that synchronizes a resource.
static QString agentServiceName(ServiceAgentType agentType, const QString &identifier)
Returns the namespaced D-Bus service name for an agent of type agentType with agent identifier identi...
An Attribute that stores the special collection type of a collection.
void setCollectionType(const QByteArray &type)
Sets the special collections type of the collection.
virtual void setProperty(const QVariant &p)=0
virtual QVariant property() const=0
KConfigSkeletonItem * findItem(const QString &name) const
int error() const
void result(KJob *job)
virtual Q_SCRIPTABLE void start()=0
QString errorText() const
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
Helper integration between Akonadi and Qt.
char * toString(const EngineQuery &query)
QString name(GameStandardAction id)
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
bool isEmpty() const const
QDBusConnectionInterface * interface() const const
bool registerService(const QString &serviceName)
QDBusConnection sessionBus()
bool unregisterService(const QString &serviceName)
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const const
bool isValid() const const
void serviceUnregistered(const QString &serviceName)
QList< Key > keys() const const
T value(const Key &key) const const
void append(QList< T > &&value)
qsizetype count() const const
T & first()
bool isEmpty() const const
const_iterator cbegin() const const
const_iterator cend() const const
T value(const Key &key, const T &defaultValue) const const
QByteArray methodSignature() const const
QList< QByteArray > parameterTypes() const const
QMetaMethod method(int index) const const
int methodCount() const const
QMetaType fromName(QByteArrayView typeName)
int id() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString arg(Args &&... args) const const
QString fromLatin1(QByteArrayView str)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void stop()
void timeout()
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:03:33 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.