KIO

deletejob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 2000-2009 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "deletejob.h"
11
12#include "../utils_p.h"
13#include "job.h" // buildErrorString
14#include "kcoredirlister.h"
15#include "kprotocolmanager.h"
16#include "listjob.h"
17#include "statjob.h"
18#include <KDirWatch>
19#include <kdirnotify.h>
20
21#include <KLocalizedString>
22#include <kio/jobuidelegatefactory.h>
23
24#include <QDir>
25#include <QFile>
26#include <QFileInfo>
27#include <QMetaObject>
28#include <QPointer>
29#include <QThread>
30#include <QTimer>
31
32#include "job_p.h"
33
34extern bool kio_resolve_local_urls; // from copyjob.cpp, abused here to save a symbol.
35
36static bool isHttpProtocol(const QString &protocol)
37{
38 return (protocol.startsWith(QLatin1String("webdav"), Qt::CaseInsensitive) || protocol.startsWith(QLatin1String("http"), Qt::CaseInsensitive));
39}
40
41namespace KIO
42{
43enum DeleteJobState {
44 DELETEJOB_STATE_STATING,
45 DELETEJOB_STATE_DELETING_FILES,
46 DELETEJOB_STATE_DELETING_DIRS,
47};
48
49class DeleteJobIOWorker : public QObject
50{
52
54 void rmfileResult(bool succeeded, bool isLink);
55 void rmddirResult(bool succeeded);
56
57public Q_SLOTS:
58
59 /**
60 * Deletes the file @p url points to
61 * The file must be a LocalFile
62 */
63 void rmfile(const QUrl &url, bool isLink)
64 {
65 Q_EMIT rmfileResult(QFile::remove(url.toLocalFile()), isLink);
66 }
67
68 /**
69 * Deletes the directory @p url points to
70 * The directory must be a LocalFile
71 */
72 void rmdir(const QUrl &url)
73 {
74 Q_EMIT rmddirResult(QDir().rmdir(url.toLocalFile()));
75 }
76};
77
78class DeleteJobPrivate : public KIO::JobPrivate
79{
80public:
81 explicit DeleteJobPrivate(const QList<QUrl> &src)
82 : state(DELETEJOB_STATE_STATING)
83 , m_processedFiles(0)
84 , m_processedDirs(0)
85 , m_totalFilesDirs(0)
86 , m_srcList(src)
87 , m_currentStat(m_srcList.begin())
88 , m_reportTimer(nullptr)
89 {
90 }
91 DeleteJobState state;
92 int m_processedFiles;
93 int m_processedDirs;
94 int m_totalFilesDirs;
95 QUrl m_currentURL;
96 QList<QUrl> files;
97 QList<QUrl> symlinks;
98 QList<QUrl> dirs;
99 QList<QUrl> m_srcList;
100 QList<QUrl>::iterator m_currentStat;
101 QSet<QString> m_parentDirs;
102 QTimer *m_reportTimer;
103 DeleteJobIOWorker *m_ioworker = nullptr;
104 QThread *m_thread = nullptr;
105
106 void statNextSrc();
107 DeleteJobIOWorker *worker();
108 void currentSourceStated(bool isDir, bool isLink);
109 void finishedStatPhase();
110 void deleteNextFile();
111 void deleteNextDir();
112 void restoreDirWatch() const;
113 void slotReport();
114 void slotStart();
115 void slotEntries(KIO::Job *, const KIO::UDSEntryList &list);
116
117 /// Callback of worker rmfile
118 void rmFileResult(bool result, bool isLink);
119 /// Callback of worker rmdir
120 void rmdirResult(bool result);
121 void deleteFileUsingJob(const QUrl &url, bool isLink);
122 void deleteDirUsingJob(const QUrl &url);
123
124 ~DeleteJobPrivate() override;
125
126 Q_DECLARE_PUBLIC(DeleteJob)
127
128 static inline DeleteJob *newJob(const QList<QUrl> &src, JobFlags flags)
129 {
130 DeleteJob *job = new DeleteJob(*new DeleteJobPrivate(src));
132 if (!(flags & HideProgressInfo)) {
134 }
135 if (!(flags & NoPrivilegeExecution)) {
136 job->d_func()->m_privilegeExecutionEnabled = true;
137 job->d_func()->m_operationType = Delete;
138 }
139 return job;
140 }
141};
142
143} // namespace KIO
144
145using namespace KIO;
146
147DeleteJob::DeleteJob(DeleteJobPrivate &dd)
148 : Job(dd)
149{
150 Q_D(DeleteJob);
151
152 d->m_reportTimer = new QTimer(this);
153 connect(d->m_reportTimer, &QTimer::timeout, this, [d]() {
154 d->slotReport();
155 });
156 // this will update the report dialog with 5 Hz, I think this is fast enough, aleXXX
157 d->m_reportTimer->start(200);
158
159 QTimer::singleShot(0, this, [d]() {
160 d->slotStart();
161 });
162}
163
164DeleteJob::~DeleteJob()
165{
166}
167
168DeleteJobPrivate::~DeleteJobPrivate()
169{
170 if (m_thread) {
171 m_thread->quit();
172 m_thread->wait();
173 delete m_thread;
174 }
175}
176
178{
179 return d_func()->m_srcList;
180}
181
182void DeleteJobPrivate::slotStart()
183{
184 statNextSrc();
185}
186
187DeleteJobIOWorker *DeleteJobPrivate::worker()
188{
189 Q_Q(DeleteJob);
190
191 if (!m_ioworker) {
192 m_thread = new QThread();
193
194 m_ioworker = new DeleteJobIOWorker;
195 m_ioworker->moveToThread(m_thread);
197 QObject::connect(m_ioworker, &DeleteJobIOWorker::rmfileResult, q, [=, this](bool result, bool isLink) {
198 this->rmFileResult(result, isLink);
199 });
200 QObject::connect(m_ioworker, &DeleteJobIOWorker::rmddirResult, q, [=, this](bool result) {
201 this->rmdirResult(result);
202 });
203 m_thread->start();
204 }
205
206 return m_ioworker;
207}
208
209void DeleteJobPrivate::slotReport()
210{
211 Q_Q(DeleteJob);
212 Q_EMIT q->deleting(q, m_currentURL);
213
214 // TODO: maybe we could skip everything else when (flags & HideProgressInfo) ?
215 JobPrivate::emitDeleting(q, m_currentURL);
216
217 switch (state) {
218 case DELETEJOB_STATE_STATING:
219 q->setTotalAmount(KJob::Files, files.count());
220 q->setTotalAmount(KJob::Directories, dirs.count());
221 break;
222 case DELETEJOB_STATE_DELETING_DIRS:
223 q->setProcessedAmount(KJob::Directories, m_processedDirs);
224 q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs);
225 break;
226 case DELETEJOB_STATE_DELETING_FILES:
227 q->setProcessedAmount(KJob::Files, m_processedFiles);
228 q->emitPercent(m_processedFiles, m_totalFilesDirs);
229 break;
230 }
231}
232
233void DeleteJobPrivate::slotEntries(KIO::Job *job, const UDSEntryList &list)
234{
237 for (; it != end; ++it) {
238 const UDSEntry &entry = *it;
240
241 Q_ASSERT(!displayName.isEmpty());
242 if (displayName != QLatin1String("..") && displayName != QLatin1String(".")) {
243 QUrl url;
244 const QString urlStr = entry.stringValue(KIO::UDSEntry::UDS_URL);
245 if (!urlStr.isEmpty()) {
246 url = QUrl(urlStr);
247 } else {
248 url = static_cast<SimpleJob *>(job)->url(); // assumed to be a dir
249 url.setPath(Utils::concatPaths(url.path(), displayName));
250 }
251
252 // qDebug() << displayName << "(" << url << ")";
253 if (entry.isLink()) {
254 symlinks.append(url);
255 } else if (entry.isDir()) {
256 dirs.append(url);
257 } else {
258 files.append(url);
259 }
260 }
261 }
262}
263
264void DeleteJobPrivate::statNextSrc()
265{
266 Q_Q(DeleteJob);
267 // qDebug();
268 if (m_currentStat != m_srcList.end()) {
269 m_currentURL = (*m_currentStat);
270
271 // if the file system doesn't support deleting, we do not even stat
272 if (!KProtocolManager::supportsDeleting(m_currentURL)) {
273 QPointer<DeleteJob> that = q;
274 ++m_currentStat;
275 Q_EMIT q->warning(q, buildErrorString(ERR_CANNOT_DELETE, m_currentURL.toDisplayString()));
276 if (that) {
277 statNextSrc();
278 }
279 return;
280 }
281 // Stat it
282 state = DELETEJOB_STATE_STATING;
283
284 // Fast path for KFileItems in directory views
285 while (m_currentStat != m_srcList.end()) {
286 m_currentURL = (*m_currentStat);
287 const KFileItem cachedItem = KCoreDirLister::cachedItemForUrl(m_currentURL);
288 if (cachedItem.isNull()) {
289 break;
290 }
291 // qDebug() << "Found cached info about" << m_currentURL << "isDir=" << cachedItem.isDir() << "isLink=" << cachedItem.isLink();
292 currentSourceStated(cachedItem.isDir(), cachedItem.isLink());
293 ++m_currentStat;
294 }
295
296 // Hook for unit test to disable the fast path.
297 if (!kio_resolve_local_urls) {
298 // Fast path for local files
299 // (using a loop, instead of a huge recursion)
300 while (m_currentStat != m_srcList.end() && (*m_currentStat).isLocalFile()) {
301 m_currentURL = (*m_currentStat);
302 QFileInfo fileInfo(m_currentURL.toLocalFile());
303 currentSourceStated(fileInfo.isDir(), fileInfo.isSymLink());
304 ++m_currentStat;
305 }
306 }
307 if (m_currentStat == m_srcList.end()) {
308 // Done, jump to the last else of this method
309 statNextSrc();
310 } else {
311 KIO::SimpleJob *job = KIO::stat(m_currentURL, StatJob::SourceSide, KIO::StatBasic, KIO::HideProgressInfo);
312 // qDebug() << "stat'ing" << m_currentURL;
313 q->addSubjob(job);
314 }
315 } else {
316 if (!q->hasSubjobs()) { // don't go there yet if we're still listing some subdirs
317 finishedStatPhase();
318 }
319 }
320}
321
322void DeleteJobPrivate::finishedStatPhase()
323{
324 m_totalFilesDirs = files.count() + symlinks.count() + dirs.count();
325 slotReport();
326 // Now we know which dirs hold the files we're going to delete.
327 // To speed things up and prevent double-notification, we disable KDirWatch
328 // on those dirs temporarily (using KDirWatch::self, that's the instance
329 // used by e.g. kdirlister).
330 for (const QString &dir : std::as_const(m_parentDirs)) {
332 }
333 state = DELETEJOB_STATE_DELETING_FILES;
334 deleteNextFile();
335}
336
337void DeleteJobPrivate::rmFileResult(bool result, bool isLink)
338{
339 if (result) {
340 m_processedFiles++;
341
342 if (isLink) {
343 symlinks.removeFirst();
344 } else {
345 files.removeFirst();
346 }
347
348 deleteNextFile();
349 } else {
350 // fallback if QFile::remove() failed (we'll use the job's error handling in that case)
351 deleteFileUsingJob(m_currentURL, isLink);
352 }
353}
354
355void DeleteJobPrivate::deleteFileUsingJob(const QUrl &url, bool isLink)
356{
357 Q_Q(DeleteJob);
358
359 SimpleJob *job;
360 if (isHttpProtocol(url.scheme())) {
362 } else {
364 job->setParentJob(q);
365 }
366
367 if (isLink) {
368 symlinks.removeFirst();
369 } else {
370 files.removeFirst();
371 }
372
373 q->addSubjob(job);
374}
375
376void DeleteJobPrivate::deleteNextFile()
377{
378 // qDebug();
379
380 // if there is something else to delete
381 // the loop is run using callbacks slotResult and rmFileResult
382 if (!files.isEmpty() || !symlinks.isEmpty()) {
383 // Take first file to delete out of list
384 QList<QUrl>::iterator it = files.begin();
385 const bool isLink = (it == files.end()); // No more files
386 if (isLink) {
387 it = symlinks.begin(); // Pick up a symlink to delete
388 }
389 m_currentURL = (*it);
390
391 // If local file, try do it directly
392 if (m_currentURL.isLocalFile()) {
393 // separate thread will do the work
394 DeleteJobIOWorker *w = worker();
395 auto rmfileFunc = [this, w, isLink]() {
396 w->rmfile(m_currentURL, isLink);
397 };
399 } else {
400 // if remote, use a job
401 deleteFileUsingJob(m_currentURL, isLink);
402 }
403 return;
404 }
405
406 state = DELETEJOB_STATE_DELETING_DIRS;
407 deleteNextDir();
408}
409
410void DeleteJobPrivate::rmdirResult(bool result)
411{
412 if (result) {
413 m_processedDirs++;
414 dirs.removeLast();
415 deleteNextDir();
416 } else {
417 // fallback
418 deleteDirUsingJob(m_currentURL);
419 }
420}
421
422void DeleteJobPrivate::deleteDirUsingJob(const QUrl &url)
423{
424 Q_Q(DeleteJob);
425
426 // Call rmdir - works for KIO workers with canDeleteRecursive too,
427 // CMD_DEL will trigger the recursive deletion in the worker.
428 SimpleJob *job = KIO::rmdir(url);
429 job->setParentJob(q);
430 job->addMetaData(QStringLiteral("recurse"), QStringLiteral("true"));
431 dirs.removeLast();
432 q->addSubjob(job);
433}
434
435void DeleteJobPrivate::deleteNextDir()
436{
437 Q_Q(DeleteJob);
438
439 if (!dirs.isEmpty()) { // some dirs to delete ?
440
441 // the loop is run using callbacks slotResult and rmdirResult
442 // Take first dir to delete out of list - last ones first !
443 QList<QUrl>::iterator it = --dirs.end();
444 m_currentURL = (*it);
445 // If local dir, try to rmdir it directly
446 if (m_currentURL.isLocalFile()) {
447 // delete it on separate worker thread
448 DeleteJobIOWorker *w = worker();
449 auto rmdirFunc = [this, w]() {
450 w->rmdir(m_currentURL);
451 };
453 } else {
454 deleteDirUsingJob(m_currentURL);
455 }
456 return;
457 }
458
459 // Re-enable watching on the dirs that held the deleted files
460 restoreDirWatch();
461
462 // Finished - tell the world
463 if (!m_srcList.isEmpty()) {
464 // qDebug() << "KDirNotify'ing FilesRemoved" << m_srcList;
465#ifdef WITH_QTDBUS
466 org::kde::KDirNotify::emitFilesRemoved(m_srcList);
467#endif
468 }
469 if (m_reportTimer != nullptr) {
470 m_reportTimer->stop();
471 }
472 // display final numbers
473 q->setProcessedAmount(KJob::Directories, m_processedDirs);
474 q->setProcessedAmount(KJob::Files, m_processedFiles);
475 q->emitPercent(m_processedFiles + m_processedDirs, m_totalFilesDirs);
476
477 q->emitResult();
478}
479
480void DeleteJobPrivate::restoreDirWatch() const
481{
482 const auto itEnd = m_parentDirs.constEnd();
483 for (auto it = m_parentDirs.constBegin(); it != itEnd; ++it) {
485 }
486}
487
488void DeleteJobPrivate::currentSourceStated(bool isDir, bool isLink)
489{
490 Q_Q(DeleteJob);
491 const QUrl url = (*m_currentStat);
492 if (isDir && !isLink) {
493 // Add toplevel dir in list of dirs
494 dirs.append(url);
495 if (url.isLocalFile()) {
496 // We are about to delete this dir, no need to watch it
497 // Maybe we should ask kdirwatch to remove all watches recursively?
498 // But then there would be no feedback (things disappearing progressively) during huge deletions
500 }
502 // qDebug() << url << "is a directory, let's list it";
504 newjob->addMetaData(QStringLiteral("details"), QString::number(KIO::StatBasic));
505 newjob->setUnrestricted(true); // No KIOSK restrictions
506 QObject::connect(newjob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
507 slotEntries(job, list);
508 });
509 q->addSubjob(newjob);
510 // Note that this listing job will happen in parallel with other stat jobs.
511 }
512 } else {
513 if (isLink) {
514 // qDebug() << "Target is a symlink";
515 symlinks.append(url);
516 } else {
517 // qDebug() << "Target is a file";
518 files.append(url);
519 }
520 }
521 if (url.isLocalFile()) {
523 m_parentDirs.insert(parentDir);
524 }
525}
526
527void DeleteJob::slotResult(KJob *job)
528{
529 Q_D(DeleteJob);
530 switch (d->state) {
531 case DELETEJOB_STATE_STATING:
532 removeSubjob(job);
533
534 // Was this a stat job or a list job? We do both in parallel.
535 if (StatJob *statJob = qobject_cast<StatJob *>(job)) {
536 // Was there an error while stating ?
537 if (job->error()) {
538 // Probably : doesn't exist
539 Job::slotResult(job); // will set the error and emit result(this)
540 d->restoreDirWatch();
541 return;
542 }
543
544 const UDSEntry &entry = statJob->statResult();
545 // Is it a file or a dir ?
546 const bool isLink = entry.isLink();
547 const bool isDir = entry.isDir();
548 d->currentSourceStated(isDir, isLink);
549
550 ++d->m_currentStat;
551 d->statNextSrc();
552 } else {
553 if (job->error()) {
554 // Try deleting nonetheless, it may be empty (and non-listable)
555 }
556 if (!hasSubjobs()) {
557 d->finishedStatPhase();
558 }
559 }
560 break;
561 case DELETEJOB_STATE_DELETING_FILES:
562 // Propagate the subjob's metadata (a SimpleJob) to the real DeleteJob
563 // FIXME: setMetaData() in the KIO API only allows access to outgoing metadata,
564 // but we need to alter the incoming one
565 d->m_incomingMetaData = dynamic_cast<KIO::Job *>(job)->metaData();
566
567 if (job->error()) {
568 Job::slotResult(job); // will set the error and emit result(this)
569 d->restoreDirWatch();
570 return;
571 }
572 removeSubjob(job);
573 Q_ASSERT(!hasSubjobs());
574 d->m_processedFiles++;
575
576 d->deleteNextFile();
577 break;
578 case DELETEJOB_STATE_DELETING_DIRS:
579 if (job->error()) {
580 Job::slotResult(job); // will set the error and emit result(this)
581 d->restoreDirWatch();
582 return;
583 }
584 removeSubjob(job);
585 Q_ASSERT(!hasSubjobs());
586 d->m_processedDirs++;
587 // emit processedAmount( this, KJob::Directories, d->m_processedDirs );
588 // emitPercent( d->m_processedFiles + d->m_processedDirs, d->m_totalFilesDirs );
589
590 d->deleteNextDir();
591 break;
592 default:
593 Q_ASSERT(0);
594 }
595}
596
597DeleteJob *KIO::del(const QUrl &src, JobFlags flags)
598{
599 QList<QUrl> srcList;
600 srcList.append(src);
601 DeleteJob *job = DeleteJobPrivate::newJob(srcList, flags);
602 if (job->uiDelegateExtension()) {
603 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
604 }
605 return job;
606}
607
609{
610 DeleteJob *job = DeleteJobPrivate::newJob(src, flags);
611 if (job->uiDelegateExtension()) {
612 job->uiDelegateExtension()->createClipboardUpdater(job, JobUiDelegateExtension::RemoveContent);
613 }
614 return job;
615}
616
617#include "deletejob.moc"
618#include "moc_deletejob.cpp"
bool hasSubjobs() const
virtual void slotResult(KJob *job)
static KFileItem cachedItemForUrl(const QUrl &url)
Return the KFileItem for the given URL, if it was listed recently and it's still in the cache,...
bool stopDirScan(const QString &path)
static KDirWatch * self()
bool restartDirScan(const QString &path)
A KFileItem is a generic class to handle a file, local or remote.
Definition kfileitem.h:36
bool isNull() const
Return true if default-constructed.
A more complex Job to delete files and directories.
QList< QUrl > urls() const
Returns the list of URLs.
virtual ClipboardUpdater * createClipboardUpdater(Job *job, ClipboardUpdaterMode mode)
Creates a clipboard updater as a child of the given job.
The base class for all jobs.
void setParentJob(Job *parentJob)
Set the parent Job.
Definition job.cpp:192
JobUiDelegateExtension * uiDelegateExtension() const
Retrieves the UI delegate extension used by this job.
Definition job.cpp:44
bool removeSubjob(KJob *job) override
Mark a sub job as being done.
Definition job.cpp:80
bool addSubjob(KJob *job) override
Add a job that has to be finished before a result is emitted.
Definition job.cpp:56
MetaData metaData() const
Get meta data received from the worker.
Definition job.cpp:205
void addMetaData(const QString &key, const QString &value)
Add key/value pair to the meta data that is sent to the worker.
Definition job.cpp:221
A ListJob is allows you to get the get the content of a directory.
void entries(KIO::Job *job, const KIO::UDSEntryList &list)
This signal emits the entry found by the job while listing.
void setUnrestricted(bool unrestricted)
Do not apply any KIOSK restrictions to this job.
Definition listjob.cpp:249
A simple job (one url and one command).
A KIO job that retrieves information about a file or directory.
Universal Directory Service.
QString stringValue(uint field) const
Definition udsentry.cpp:365
bool isLink() const
Definition udsentry.cpp:380
@ UDS_URL
An alternative URL (If different from the caption).
Definition udsentry.h:251
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition udsentry.h:224
bool isDir() const
Definition udsentry.cpp:375
virtual void registerJob(KJob *job)
int error() const
void setUiDelegate(KJobUiDelegate *delegate)
static bool supportsDeleting(const QUrl &url)
Returns whether the protocol can delete files/objects.
static bool canDeleteRecursive(const QUrl &url)
Returns whether the protocol can recursively delete directories by itself.
AKONADI_CALENDAR_EXPORT QString displayName(Akonadi::ETMCalendar *calendar, const Akonadi::Collection &collection)
A namespace for KIO globals.
KIOCORE_EXPORT DeleteJob * del(const QUrl &src, JobFlags flags=DefaultFlags)
Delete a file or directory.
KIOCORE_EXPORT SimpleJob * rmdir(const QUrl &url)
Removes a single directory.
KIOCORE_EXPORT TransferJob * http_delete(const QUrl &url, JobFlags flags=DefaultFlags)
HTTP DELETE.
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOCORE_EXPORT QString buildErrorString(int errorCode, const QString &errorText)
Returns a translated error message for errorCode using the additional error information provided by e...
Definition job_error.cpp:31
KIOCORE_EXPORT SimpleJob * file_delete(const QUrl &src, JobFlags flags=DefaultFlags)
Delete a single file.
Definition job.cpp:373
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
KIOCORE_EXPORT ListJob * listRecursive(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
The same as the previous method, but recurses subdirectories.
Definition listjob.cpp:244
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ NoPrivilegeExecution
When set, notifies the worker that application/job does not want privilege execution.
Definition job_base.h:276
@ StatBasic
Filename, access, type, size, linkdest.
Definition global.h:255
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
const QList< QKeySequence > & begin()
const QList< QKeySequence > & end()
bool remove()
void append(QList< T > &&value)
iterator begin()
qsizetype count() const const
iterator end()
bool isEmpty() const const
void removeFirst()
void removeLast()
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
Q_SLOTSQ_SLOTS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void moveToThread(QThread *targetThread)
T qobject_cast(QObject *object)
const_iterator constBegin() const const
const_iterator constEnd() const const
iterator insert(const T &value)
bool isEmpty() const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
CaseInsensitive
QueuedConnection
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void finished()
void quit()
void start(Priority priority)
bool wait(QDeadlineTimer deadline)
void stop()
void timeout()
StripTrailingSlash
QUrl adjusted(FormattingOptions options) const const
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
QString scheme() const const
void setPath(const QString &path, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:09:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.