KIO

kurlcompletion.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
4 SPDX-FileCopyrightText: 2004 Scott Wheeler <wheeler@kde.org>
5
6 This class was inspired by a previous KUrlCompletion by
7 SPDX-FileContributor: Henner Zeller <zeller@think.de>
8
9 SPDX-License-Identifier: LGPL-2.0-or-later
10*/
11
12#include "kurlcompletion.h"
13#include "../utils_p.h"
14#include <assert.h>
15#include <limits.h>
16#include <stdlib.h>
17
18#include <QCollator>
19#include <QDebug>
20#include <QDir>
21#include <QDirIterator>
22#include <QFile>
23#include <QMimeDatabase>
24#include <QMutex>
25#include <QProcessEnvironment>
26#include <QRegularExpression>
27#include <QThread>
28#include <QUrl>
29#include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF
30
31#include <KConfig>
32#include <KConfigGroup>
33#include <KSharedConfig>
34#include <KUser>
35
36#include <kio/listjob.h>
37#include <kio_widgets_debug.h>
38#include <kioglobal_p.h>
39#include <kprotocolmanager.h>
40#include <kurlauthorized.h>
41
42#include <time.h>
43
44#ifdef Q_OS_WIN
45#include <qt_windows.h>
46#else
47#include <pwd.h>
48#include <sys/param.h>
49#endif
50
51static bool expandTilde(QString &);
52static bool expandEnv(QString &);
53
54static QString unescape(const QString &text);
55
56// Permission mask for files that are executable by
57// user, group or other
58static constexpr mode_t s_modeExe = S_IXUSR | S_IXGRP | S_IXOTH;
59
60// Constants for types of completion
61enum ComplType {
62 CTNone = 0,
63 CTEnv,
64 CTUser,
65 CTMan,
66 CTExe,
67 CTFile,
68 CTUrl,
69 CTInfo
70};
71
72class CompletionThread;
73
74// Ensure that we don't end up with "//".
75static void addPathToUrl(QUrl &url, const QString &relPath)
76{
77 url.setPath(Utils::concatPaths(url.path(), relPath));
78}
79
80static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1);
81
82static int initialWaitDuration()
83{
84 if (s_waitDuration.loadRelaxed() == -1) {
85 const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT");
86 if (envVar.isEmpty()) {
87 s_waitDuration = 200; // default: 200 ms
88 } else {
89 s_waitDuration = envVar.toInt();
90 }
91 }
92 return s_waitDuration;
93}
94
95// For local paths we use our custom comparer function that ignores the trailing slash character
96static void sortLocalPaths(QStringList &list)
97{
98 QCollator c;
100 std::sort(list.begin(), list.end(), [c](const QString &a, const QString &b) {
101 return c.compare(a.endsWith(QStringLiteral("/")) ? a.chopped(1) : a, b.endsWith(QStringLiteral("/")) ? b.chopped(1) : b) < 0;
102 });
103}
104
105///////////////////////////////////////////////////////
106///////////////////////////////////////////////////////
107// KUrlCompletionPrivate
108//
109class KUrlCompletionPrivate
110{
111public:
112 explicit KUrlCompletionPrivate(KUrlCompletion *qq, KUrlCompletion::Mode m)
113 : q(qq)
114 , cwd(QUrl::fromLocalFile(QDir::homePath()))
115 , mode(m)
116 {
117 // Read settings
118 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("URLCompletion"));
119 url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
120 popup_append_slash = cg.readEntry("popupAppendSlash", true);
121 onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
122
123 q->setIgnoreCase(true);
124 }
125
126 ~KUrlCompletionPrivate();
127
128 void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
129 void slotIOFinished(KJob *);
130 void slotCompletionThreadDone(QThread *thread, const QStringList &matches);
131
132 class MyURL;
133 bool userCompletion(const MyURL &url, QString *match);
134 bool envCompletion(const MyURL &url, QString *match);
135 bool exeCompletion(const MyURL &url, QString *match);
136 bool fileCompletion(const MyURL &url, QString *match);
137 bool urlCompletion(const MyURL &url, QString *match);
138
139 bool isAutoCompletion();
140
141 // List the next dir in m_dirs
142 QString listDirectories(const QStringList &, const QString &, bool only_exe = false, bool only_dir = false, bool no_hidden = false, bool stat_files = true);
143
144 void listUrls(const QList<QUrl> &urls, const QString &filter = QString(), bool only_exe = false, bool no_hidden = false);
145
146 void addMatches(const QStringList &);
147 QString finished();
148
149 void init();
150
151 void setListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false);
152
153 bool isListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false);
154
155 KUrlCompletion *const q;
156 QList<QUrl> list_urls;
157
158 bool onlyLocalProto = false;
159
160 // urlCompletion() in Auto/Popup mode?
161 bool url_auto_completion = true;
162
163 // Append '/' to directories in Popup mode?
164 // Doing that stat's all files and is slower
165 bool popup_append_slash = true;
166
167 // Keep track of currently listed files to avoid reading them again
168 bool last_no_hidden = false;
169 QString last_path_listed;
170 QString last_file_listed;
171 QString last_prepend;
172 ComplType last_compl_type = CTNone;
173
174 QUrl cwd; // "current directory" = base dir for completion
175
176 KUrlCompletion::Mode mode = KUrlCompletion::FileCompletion;
177 bool replace_env = true;
178 bool replace_home = true;
179 bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
180
181 KIO::ListJob *list_job = nullptr; // kio job to list directories
182
183 QString prepend; // text to prepend to listed items
184 QString compl_text; // text to pass on to KCompletion
185
186 // Filters for files read with kio
187 bool list_urls_only_exe; // true = only list executables
188 bool list_urls_no_hidden;
189 QString list_urls_filter; // filter for listed files
190
191 CompletionThread *userListThread = nullptr;
192 CompletionThread *dirListThread = nullptr;
193
194 QStringList mimeTypeFilters;
195};
196
197class CompletionThread : public QThread
198{
200protected:
201 CompletionThread(KUrlCompletionPrivate *receiver)
202 : QThread()
203 , m_prepend(receiver->prepend)
204 , m_complete_url(receiver->complete_url)
205 , m_terminationRequested(false)
206 {
207 }
208
209public:
210 void requestTermination()
211 {
212 if (!isFinished()) {
213 qCDebug(KIO_WIDGETS) << "stopping thread" << this;
214 }
215 m_terminationRequested.storeRelaxed(true);
216 wait();
217 }
218
219 QStringList matches() const
220 {
221 QMutexLocker locker(&m_mutex);
222 return m_matches;
223 }
224
226 void completionThreadDone(QThread *thread, const QStringList &matches);
227
228protected:
229 void addMatch(const QString &match)
230 {
231 QMutexLocker locker(&m_mutex);
232 m_matches.append(match);
233 }
234 bool terminationRequested() const
235 {
236 return m_terminationRequested.loadRelaxed();
237 }
238 void done()
239 {
240 if (!terminationRequested()) {
241 qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches";
242 Q_EMIT completionThreadDone(this, m_matches);
243 }
244 }
245
246 const QString m_prepend;
247 const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
248
249private:
250 mutable QMutex m_mutex; // protects m_matches
251 QStringList m_matches; // written by secondary thread, read by the matches() method
252 QAtomicInt m_terminationRequested; // used as a bool
253};
254
255/**
256 * A simple thread that fetches a list of tilde-completions and returns this
257 * to the caller via the completionThreadDone signal.
258 */
259
260class UserListThread : public CompletionThread
261{
263public:
264 UserListThread(KUrlCompletionPrivate *receiver)
265 : CompletionThread(receiver)
266 {
267 }
268
269protected:
270 void run() override
271 {
272#ifndef Q_OS_ANDROID
273 const QChar tilde = QLatin1Char('~');
274
275 // we don't need to handle prepend here, right? ~user is always at pos 0
276 assert(m_prepend.isEmpty());
277#ifndef Q_OS_WIN
278 struct passwd *pw;
279 ::setpwent();
280 while ((pw = ::getpwent()) && !terminationRequested()) {
281 addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
282 }
283 ::endpwent();
284#else
285 // TODO: add KUser::allUserNames() with a std::function<bool()> shouldTerminate parameter
286 // currently terminationRequested is ignored on Windows
287 const QStringList allUsers = KUser::allUserNames();
288 for (const QString &s : allUsers) {
289 addMatch(tilde + s);
290 }
291#endif
292 addMatch(QString(tilde));
293#endif
294 done();
295 }
296};
297
298class DirectoryListThread : public CompletionThread
299{
301public:
302 DirectoryListThread(KUrlCompletionPrivate *receiver,
303 const QStringList &dirList,
304 const QString &filter,
305 const QStringList &mimeTypeFilters,
306 bool onlyExe,
307 bool onlyDir,
308 bool noHidden,
309 bool appendSlashToDir)
310 : CompletionThread(receiver)
311 , m_dirList(dirList)
312 , m_filter(filter)
313 , m_mimeTypeFilters(mimeTypeFilters)
314 , m_onlyExe(onlyExe)
315 , m_onlyDir(onlyDir)
316 , m_noHidden(noHidden)
317 , m_appendSlashToDir(appendSlashToDir)
318 {
319 }
320
321 void run() override;
322
323private:
324 QStringList m_dirList;
325 QString m_filter;
326 QStringList m_mimeTypeFilters;
327 bool m_onlyExe;
328 bool m_onlyDir;
329 bool m_noHidden;
330 bool m_appendSlashToDir;
331};
332
333void DirectoryListThread::run()
334{
335 // qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ",
336 // m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size();
337
338 QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
339 if (m_onlyExe) {
340 iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
341 } else if (m_onlyDir) {
342 iterator_filter |= QDir::Dirs;
343 } else {
344 iterator_filter |= (QDir::Dirs | QDir::Files);
345 }
346
347 QMimeDatabase mimeTypes;
348
349 for (const QString &dir : std::as_const(m_dirList)) {
350 if (terminationRequested()) {
351 break;
352 }
353
354 // qDebug() << "Scanning directory" << dir;
355
356 QDirIterator current_dir_iterator(dir, iterator_filter);
357
358 while (current_dir_iterator.hasNext() && !terminationRequested()) {
359 current_dir_iterator.next();
360
361 QFileInfo item_info = current_dir_iterator.fileInfo();
362 QString item_name = item_info.fileName();
363
364 // qDebug() << "Found" << file_name;
365
366 if (!m_filter.isEmpty() && !item_name.startsWith(m_filter)) {
367 continue;
368 }
369
370 if (!m_mimeTypeFilters.isEmpty() && !item_info.isDir()) {
371 auto mimeType = mimeTypes.mimeTypeForFile(item_info);
372 if (!m_mimeTypeFilters.contains(mimeType.name())) {
373 continue;
374 }
375 }
376
377 // Add '/' to directories
378 if (m_appendSlashToDir && item_info.isDir()) {
379 Utils::appendSlash(item_name);
380 }
381
382 if (m_complete_url) {
383 QUrl url(m_prepend);
384 addPathToUrl(url, item_name);
385 addMatch(url.toDisplayString());
386 } else {
387 item_name.prepend(m_prepend);
388 addMatch(item_name);
389 }
390 }
391 }
392
393 done();
394}
395
396KUrlCompletionPrivate::~KUrlCompletionPrivate()
397{
398}
399
400///////////////////////////////////////////////////////
401///////////////////////////////////////////////////////
402// MyURL - wrapper for QUrl with some different functionality
403//
404
405class KUrlCompletionPrivate::MyURL
406{
407public:
408 MyURL(const QString &url, const QUrl &cwd);
409 MyURL(const MyURL &url);
410 ~MyURL();
411
412 QUrl kurl() const
413 {
414 return m_kurl;
415 }
416
417 bool isLocalFile() const
418 {
419 return m_kurl.isLocalFile();
420 }
421 QString scheme() const
422 {
423 return m_kurl.scheme();
424 }
425 // The directory with a trailing '/'
426 QString dir() const
427 {
428 return m_kurl.adjusted(QUrl::RemoveFilename).path();
429 }
430 QString file() const
431 {
432 return m_kurl.fileName();
433 }
434
435 // The initial, unparsed, url, as a string.
436 QString url() const
437 {
438 return m_url;
439 }
440
441 // Is the initial string a URL, or just a path (whether absolute or relative)
442 bool isURL() const
443 {
444 return m_isURL;
445 }
446
447 void filter(bool replace_user_dir, bool replace_env);
448
449private:
450 void init(const QString &url, const QUrl &cwd);
451
452 QUrl m_kurl;
453 QString m_url;
454 bool m_isURL;
455};
456
457KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd)
458{
459 init(_url, cwd);
460}
461
462KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url)
463 : m_kurl(_url.m_kurl)
464{
465 m_url = _url.m_url;
466 m_isURL = _url.m_isURL;
467}
468
469void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd)
470{
471 // Save the original text
472 m_url = _url;
473
474 // Non-const copy
475 QString url_copy = _url;
476
477 // Special shortcuts for "man:" and "info:"
478 if (url_copy.startsWith(QLatin1Char('#'))) {
479 if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) {
480 url_copy.replace(0, 2, QStringLiteral("info:"));
481 } else {
482 url_copy.replace(0, 1, QStringLiteral("man:"));
483 }
484 }
485
486 // Look for a protocol in 'url'
487 const QRegularExpression protocol_regex(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:"));
488
489 // Assume "file:" or whatever is given by 'cwd' if there is
490 // no protocol. (QUrl does this only for absolute paths)
491 if (protocol_regex.match(url_copy).hasMatch()) {
492 m_kurl = QUrl(url_copy);
493 m_isURL = true;
494 } else { // relative path or ~ or $something
495 m_isURL = false;
496 if (Utils::isAbsoluteLocalPath(url_copy) || url_copy.startsWith(QLatin1Char('~')) || url_copy.startsWith(QLatin1Char('$'))) {
497 m_kurl = QUrl::fromLocalFile(url_copy);
498 } else {
499 // Relative path
500 if (cwd.isEmpty()) {
501 m_kurl = QUrl(url_copy);
502 } else {
503 m_kurl = cwd;
504 m_kurl.setPath(Utils::concatPaths(m_kurl.path(), url_copy));
505 }
506 }
507 }
508}
509
510KUrlCompletionPrivate::MyURL::~MyURL()
511{
512}
513
514void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
515{
516 QString d = dir() + file();
517 if (replace_user_dir) {
518 expandTilde(d);
519 }
520 if (replace_env) {
521 expandEnv(d);
522 }
523 m_kurl.setPath(d);
524}
525
526///////////////////////////////////////////////////////
527///////////////////////////////////////////////////////
528// KUrlCompletion
529//
530
532 : KUrlCompletion(FileCompletion)
533{
534}
535
537 : KCompletion()
538 , d(new KUrlCompletionPrivate(this, _mode))
539{
540}
541
546
548{
549 d->cwd = dir;
550}
551
553{
554 return d->cwd;
555}
556
558{
559 return d->mode;
560}
561
563{
564 d->mode = _mode;
565}
566
568{
569 return d->replace_env;
570}
571
573{
574 d->replace_env = replace;
575}
576
578{
579 return d->replace_home;
580}
581
583{
584 d->replace_home = replace;
585}
586
587/*
588 * makeCompletion()
589 *
590 * Entry point for file name completion
591 */
593{
594 qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd;
595
596 KUrlCompletionPrivate::MyURL url(text, d->cwd);
597
598 d->compl_text = text;
599
600 // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
601 // This is what gets prepended to the directory-listing matches.
602 if (url.isURL()) {
603 QUrl directoryUrl(url.kurl());
604 directoryUrl.setQuery(QString());
605 directoryUrl.setFragment(QString());
606 directoryUrl.setPath(url.dir());
607 d->prepend = directoryUrl.toString();
608 } else {
609 d->prepend = text.left(text.length() - url.file().length());
610 }
611
612 d->complete_url = url.isURL();
613
614 // We use our custom sorter function if we are completing local paths
615 setSorterFunction(!d->complete_url ? sortLocalPaths : nullptr);
616
617 // If we typed an exact path to a directory, we block autosuggestion
618 // or else it would autosuggest the first child dir.
619 setShouldAutoSuggest(d->complete_url || (url.dir() != text));
620
621 QString aMatch;
622
623 // Environment variables
624 //
625 if (d->replace_env && d->envCompletion(url, &aMatch)) {
626 return aMatch;
627 }
628
629 // User directories
630 //
631 if (d->replace_home && d->userCompletion(url, &aMatch)) {
632 return aMatch;
633 }
634
635 // Replace user directories and variables
636 url.filter(d->replace_home, d->replace_env);
637
638 // qDebug() << "Filtered: proto=" << url.scheme()
639 // << ", dir=" << url.dir()
640 // << ", file=" << url.file()
641 // << ", kurl url=" << *url.kurl();
642
643 if (d->mode == ExeCompletion) {
644 // Executables
645 //
646 if (d->exeCompletion(url, &aMatch)) {
647 return aMatch;
648 }
649
650 // KRun can run "man:" and "info:" etc. so why not treat them
651 // as executables...
652
653 if (d->urlCompletion(url, &aMatch)) {
654 return aMatch;
655 }
656 } else {
657 // Local files, directories
658 //
659 if (d->fileCompletion(url, &aMatch)) {
660 return aMatch;
661 }
662
663 // All other...
664 //
665 if (d->urlCompletion(url, &aMatch)) {
666 return aMatch;
667 }
668 }
669
670 d->setListedUrl(CTNone);
671 stop();
672
673 return QString();
674}
675
676/*
677 * finished
678 *
679 * Go on and call KCompletion.
680 * Called when all matches have been added
681 */
682QString KUrlCompletionPrivate::finished()
683{
684 if (last_compl_type == CTInfo) {
685 return q->KCompletion::makeCompletion(compl_text.toLower());
686 } else {
687 return q->KCompletion::makeCompletion(compl_text);
688 }
689}
690
691/*
692 * isRunning
693 *
694 * Return true if either a KIO job or a thread is running
695 */
697{
698 return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished());
699}
700
701/*
702 * stop
703 *
704 * Stop and delete a running KIO job or the DirLister
705 */
707{
708 if (d->list_job) {
709 d->list_job->kill();
710 d->list_job = nullptr;
711 }
712
713 if (d->dirListThread) {
714 d->dirListThread->requestTermination();
715 delete d->dirListThread;
716 d->dirListThread = nullptr;
717 }
718
719 if (d->userListThread) {
720 d->userListThread->requestTermination();
721 delete d->userListThread;
722 d->userListThread = nullptr;
723 }
724}
725
726/*
727 * Keep track of the last listed directory
728 */
729void KUrlCompletionPrivate::setListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden)
730{
731 last_compl_type = complType;
732 last_path_listed = directory;
733 last_file_listed = filter;
734 last_no_hidden = no_hidden;
735 last_prepend = prepend;
736}
737
738bool KUrlCompletionPrivate::isListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden)
739{
740 /* clang-format off */
741 return last_compl_type == complType
742 && (last_path_listed == directory || (directory.isEmpty() && last_path_listed.isEmpty()))
743 && (filter.startsWith(last_file_listed) || (filter.isEmpty() && last_file_listed.isEmpty()))
744 && last_no_hidden == no_hidden
745 && last_prepend == prepend; // e.g. relative path vs absolute
746 /* clang-format on */
747}
748
749/*
750 * isAutoCompletion
751 *
752 * Returns true if completion mode is Auto or Popup
753 */
754bool KUrlCompletionPrivate::isAutoCompletion()
755{
756 /* clang-format off */
761 /* clang-format on */
762}
763//////////////////////////////////////////////////
764//////////////////////////////////////////////////
765// User directories
766//
767
768bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
769{
770 if (url.scheme() != QLatin1String("file") || !url.dir().isEmpty() || !url.file().startsWith(QLatin1Char('~')) || !prepend.isEmpty()) {
771 return false;
772 }
773
774 if (!isListedUrl(CTUser)) {
775 q->stop();
776 q->clear();
777 setListedUrl(CTUser);
778
779 Q_ASSERT(!userListThread); // caller called stop()
780 userListThread = new UserListThread(this);
781 QObject::connect(userListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches) {
782 slotCompletionThreadDone(thread, matches);
783 });
784 userListThread->start();
785
786 // If the thread finishes quickly make sure that the results
787 // are added to the first matching case.
788
789 userListThread->wait(initialWaitDuration());
790 const QStringList l = userListThread->matches();
791 addMatches(l);
792 }
793 *pMatch = finished();
794 return true;
795}
796
797/////////////////////////////////////////////////////
798/////////////////////////////////////////////////////
799// Environment variables
800//
801
802bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
803{
804 if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) {
805 return false;
806 }
807
808 if (!isListedUrl(CTEnv)) {
809 q->stop();
810 q->clear();
811
813 const QStringList keys = env.keys();
814
815 QStringList l;
816 l.reserve(keys.size());
817 for (const QString &key : keys) {
818 l.append(prepend + QLatin1Char('$') + key);
819 }
820
821 addMatches(l);
822 }
823
824 setListedUrl(CTEnv);
825
826 *pMatch = finished();
827 return true;
828}
829
830//////////////////////////////////////////////////
831//////////////////////////////////////////////////
832// Executables
833//
834
835bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
836{
837 if (!url.isLocalFile()) {
838 return false;
839 }
840
841 QString directory = unescape(url.dir()); // remove escapes
842
843 // Find directories to search for completions, either
844 //
845 // 1. complete path given in url
846 // 2. current directory (d->cwd)
847 // 3. $PATH
848 // 4. no directory at all
849
850 QStringList dirList;
851
852 if (!url.file().isEmpty()) {
853 // $PATH
855
856 QStringList::Iterator it = dirList.begin();
857
858 for (; it != dirList.end(); ++it) {
859 it->append(QLatin1Char('/'));
860 }
861 } else if (Utils::isAbsoluteLocalPath(directory)) {
862 // complete path in url
863 dirList.append(directory);
864 } else if (!directory.isEmpty() && !cwd.isEmpty()) {
865 // current directory
866 dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory);
867 }
868
869 // No hidden files unless the user types "."
870 bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
871
872 // List files if needed
873 //
874 if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
875 q->stop();
876 q->clear();
877
878 setListedUrl(CTExe, directory, url.file(), no_hidden_files);
879
880 *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
881 } else {
882 *pMatch = finished();
883 }
884
885 return true;
886}
887
888//////////////////////////////////////////////////
889//////////////////////////////////////////////////
890// Local files
891//
892
893bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
894{
895 if (!url.isLocalFile()) {
896 return false;
897 }
898
899 QString directory = unescape(url.dir());
900
901 if (url.url() == QLatin1String("..")) {
902 *pMatch = QStringLiteral("..");
903 return true;
904 }
905
906 // qDebug() << "fileCompletion" << url << "dir=" << dir;
907
908 // Find directories to search for completions, either
909 //
910 // 1. complete path given in url
911 // 2. current directory (d->cwd)
912 // 3. no directory at all
913
914 QStringList dirList;
915
916 if (Utils::isAbsoluteLocalPath(directory)) {
917 // complete path in url
918 dirList.append(directory);
919 } else if (!cwd.isEmpty()) {
920 // current directory
921 QString dirToAdd = cwd.toLocalFile();
922 if (!directory.isEmpty()) {
923 Utils::appendSlash(dirToAdd);
924 dirToAdd += directory;
925 }
926 dirList.append(dirToAdd);
927 }
928
929 // No hidden files unless the user types "."
930 bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
931
932 // List files if needed
933 //
934 if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
935 q->stop();
936 q->clear();
937
938 setListedUrl(CTFile, directory, QString(), no_hidden_files);
939
940 // Append '/' to directories in Popup mode?
941 bool append_slash =
943
944 bool only_dir = (mode == KUrlCompletion::DirCompletion);
945
946 *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, append_slash);
947 } else {
948 *pMatch = finished();
949 }
950
951 return true;
952}
953
954//////////////////////////////////////////////////
955//////////////////////////////////////////////////
956// URLs not handled elsewhere...
957//
958
959static bool isLocalProtocol(const QString &protocol)
960{
961 return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
962}
963
964bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
965{
966 // qDebug() << *url.kurl();
967 if (onlyLocalProto && isLocalProtocol(url.scheme())) {
968 return false;
969 }
970
971 // Use d->cwd as base url in case url is not absolute
972 QUrl url_dir = url.kurl();
973 if (url_dir.isRelative() && !cwd.isEmpty()) {
974 // Create an URL with the directory to be listed
975 url_dir = cwd.resolved(url_dir);
976 }
977
978 // url is malformed
979 if (!url_dir.isValid() || url.scheme().isEmpty()) {
980 return false;
981 }
982
983 // non local urls
984 if (!isLocalProtocol(url.scheme())) {
985 // url does not specify host
986 if (url_dir.host().isEmpty()) {
987 return false;
988 }
989
990 // url does not specify a valid directory
992 return false;
993 }
994
995 // automatic completion is disabled
996 if (isAutoCompletion() && !url_auto_completion) {
997 return false;
998 }
999 }
1000
1001 // url handler doesn't support listing
1002 if (!KProtocolManager::supportsListing(url_dir)) {
1003 return false;
1004 }
1005
1006 // Remove escapes
1007 const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
1008 url_dir.setPath(directory);
1009
1010 // List files if needed
1011 //
1012 if (!isListedUrl(CTUrl, directory, url.file())) {
1013 q->stop();
1014 q->clear();
1015
1016 setListedUrl(CTUrl, directory, QString());
1017
1018 QList<QUrl> url_list;
1019 url_list.append(url_dir);
1020
1021 listUrls(url_list, QString(), false);
1022
1023 pMatch->clear();
1024 } else if (!q->isRunning()) {
1025 *pMatch = finished();
1026 } else {
1027 pMatch->clear();
1028 }
1029
1030 return true;
1031}
1032
1033//////////////////////////////////////////////////
1034//////////////////////////////////////////////////
1035// Directory and URL listing
1036//
1037
1038/*
1039 * addMatches
1040 *
1041 * Called to add matches to KCompletion
1042 */
1043void KUrlCompletionPrivate::addMatches(const QStringList &matchList)
1044{
1045 q->insertItems(matchList);
1046}
1047
1048/*
1049 * listDirectories
1050 *
1051 * List files starting with 'filter' in the given directories,
1052 * either using DirLister or listURLs()
1053 *
1054 * In either case, addMatches() is called with the listed
1055 * files, and eventually finished() when the listing is done
1056 *
1057 * Returns the match if available, or QString() if
1058 * DirLister timed out or using kio
1059 */
1060QString KUrlCompletionPrivate::listDirectories(const QStringList &dirList,
1061 const QString &filter,
1062 bool only_exe,
1063 bool only_dir,
1064 bool no_hidden,
1065 bool append_slash_to_dir)
1066{
1067 assert(!q->isRunning());
1068
1069 if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) {
1070 qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread";
1071
1072 // Don't use KIO
1073
1074 QStringList dirs;
1075
1077 for (QStringList::ConstIterator it = dirList.constBegin(); it != end; ++it) {
1078 QUrl url = QUrl::fromLocalFile(*it);
1079 if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) {
1080 dirs.append(*it);
1081 }
1082 }
1083
1084 Q_ASSERT(!dirListThread); // caller called stop()
1085 dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, only_exe, only_dir, no_hidden, append_slash_to_dir);
1086 QObject::connect(dirListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches) {
1087 slotCompletionThreadDone(thread, matches);
1088 });
1089 dirListThread->start();
1090 dirListThread->wait(initialWaitDuration());
1091 qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches();
1092 addMatches(dirListThread->matches());
1093
1094 return finished();
1095 }
1096
1097 // Use KIO
1098 // qDebug() << "Listing (listDirectories):" << dirList << "with KIO";
1099
1100 QList<QUrl> url_list;
1101
1104
1105 url_list.reserve(dirList.size());
1106 for (; it != end; ++it) {
1107 url_list.append(QUrl(*it));
1108 }
1109
1110 listUrls(url_list, filter, only_exe, no_hidden);
1111 // Will call addMatches() and finished()
1112
1113 return QString();
1114}
1115
1116/*
1117 * listURLs
1118 *
1119 * Use KIO to list the given urls
1120 *
1121 * addMatches() is called with the listed files
1122 * finished() is called when the listing is done
1123 */
1124void KUrlCompletionPrivate::listUrls(const QList<QUrl> &urls, const QString &filter, bool only_exe, bool no_hidden)
1125{
1126 assert(list_urls.isEmpty());
1127 assert(list_job == nullptr);
1128
1129 list_urls = urls;
1130 list_urls_filter = filter;
1131 list_urls_only_exe = only_exe;
1132 list_urls_no_hidden = no_hidden;
1133
1134 // qDebug() << "Listing URLs:" << *urls[0] << ",...";
1135
1136 // Start it off by calling slotIOFinished
1137 //
1138 // This will start a new list job as long as there
1139 // are urls in d->list_urls
1140 //
1141 slotIOFinished(nullptr);
1142}
1143
1144/*
1145 * slotEntries
1146 *
1147 * Receive files listed by KIO and call addMatches()
1148 */
1149void KUrlCompletionPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &entries)
1150{
1151 QStringList matchList;
1152
1153 const QString filter = list_urls_filter;
1154 const int filter_len = filter.length();
1155
1156 // Iterate over all files
1157 for (const auto &entry : entries) {
1158 const QString udsUrl = entry.stringValue(KIO::UDSEntry::UDS_URL);
1159
1160 QString entry_name;
1161 if (!udsUrl.isEmpty()) {
1162 // qDebug() << "url:" << url;
1163 entry_name = QUrl(udsUrl).fileName();
1164 } else {
1165 entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
1166 }
1167
1168 // This can happen with kdeconnect://deviceId as a completion for kdeconnect:/,
1169 // there's no fileName [and the UDS_NAME is unrelated, can't use that].
1170 // This code doesn't support completing hostnames anyway (see addPathToUrl below).
1171 if (entry_name.isEmpty()) {
1172 continue;
1173 }
1174
1175 if (entry_name.at(0) == QLatin1Char('.')
1176 && (list_urls_no_hidden || entry_name.length() == 1 || (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) {
1177 continue;
1178 }
1179
1180 const bool isDir = entry.isDir();
1181
1182 if (mode == KUrlCompletion::DirCompletion && !isDir) {
1183 continue;
1184 }
1185
1186 if (filter_len != 0 && QStringView(entry_name).left(filter_len) != filter) {
1187 continue;
1188 }
1189
1190 if (!mimeTypeFilters.isEmpty() && !isDir && !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) {
1191 continue;
1192 }
1193
1194 QString toAppend = entry_name;
1195
1196 if (isDir) {
1197 toAppend.append(QLatin1Char('/'));
1198 }
1199
1200 if (!list_urls_only_exe || (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & s_modeExe) // true if executable
1201 ) {
1202 if (complete_url) {
1203 QUrl url(prepend);
1204 addPathToUrl(url, toAppend);
1205 matchList.append(url.toDisplayString());
1206 } else {
1207 matchList.append(prepend + toAppend);
1208 }
1209 }
1210 }
1211
1212 addMatches(matchList);
1213}
1214
1215/*
1216 * slotIOFinished
1217 *
1218 * Called when a KIO job is finished.
1219 *
1220 * Start a new list job if there are still urls in
1221 * list_urls, otherwise call finished()
1222 */
1223void KUrlCompletionPrivate::slotIOFinished(KJob *job)
1224{
1225 assert(job == list_job);
1226 Q_UNUSED(job)
1227
1228 if (list_urls.isEmpty()) {
1229 list_job = nullptr;
1230
1231 finished(); // will call KCompletion::makeCompletion()
1232
1233 } else {
1234 QUrl kurl(list_urls.takeFirst());
1235
1236 // list_urls.removeAll( kurl );
1237
1238 // qDebug() << "Start KIO::listDir" << kurl;
1239
1240 list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
1241 list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true"));
1242
1243 assert(list_job);
1244
1245 q->connect(list_job, &KJob::result, q, [this](KJob *job) {
1246 slotIOFinished(job);
1247 });
1248
1249 q->connect(list_job, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
1250 slotEntries(job, list);
1251 });
1252 }
1253}
1254
1255///////////////////////////////////////////////////
1256///////////////////////////////////////////////////
1257
1258/*
1259 * postProcessMatch, postProcessMatches
1260 *
1261 * Called by KCompletion before emitting match() and matches()
1262 *
1263 * Append '/' to directories for file completion. This is
1264 * done here to avoid stat()'ing a lot of files
1265 */
1266void KUrlCompletion::postProcessMatch(QString *pMatch) const
1267{
1268 // qDebug() << *pMatch;
1269
1270 if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) {
1271 // Add '/' to directories in file completion mode
1272 // unless it has already been done
1273 if (d->last_compl_type == CTFile && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
1274 QString copy = QUrl(*pMatch).toLocalFile();
1275 expandTilde(copy);
1276 expandEnv(copy);
1277 if (!Utils::isAbsoluteLocalPath(copy)) {
1278 copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/'));
1279 }
1280
1281 // qDebug() << "stat'ing" << copy;
1282
1283 QByteArray file = QFile::encodeName(copy);
1284
1285 QT_STATBUF sbuff;
1286 if (QT_STAT(file.constData(), &sbuff) == 0) {
1287 if (Utils::isDirMask(sbuff.st_mode)) {
1288 pMatch->append(QLatin1Char('/'));
1289 }
1290 } else {
1291 // qDebug() << "Could not stat file" << copy;
1292 }
1293 }
1294 }
1295}
1296
1297void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const
1298{
1299 // Maybe '/' should be added to directories here as in
1300 // postProcessMatch() but it would slow things down
1301 // when there are a lot of matches...
1302}
1303
1304void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const
1305{
1306 // Maybe '/' should be added to directories here as in
1307 // postProcessMatch() but it would slow things down
1308 // when there are a lot of matches...
1309}
1310
1311void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches)
1312{
1313 if (thread != userListThread && thread != dirListThread) {
1314 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches";
1315 return;
1316 }
1317
1318 qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread";
1319 q->setItems(matches);
1320
1321 if (userListThread == thread) {
1322 thread->wait();
1323 delete thread;
1324 userListThread = nullptr;
1325 } else if (dirListThread == thread) {
1326 thread->wait();
1327 delete thread;
1328 dirListThread = nullptr;
1329 }
1330 finished(); // will call KCompletion::makeCompletion()
1331}
1332
1333// static
1334QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv)
1335{
1336 if (text.isEmpty()) {
1337 return text;
1338 }
1339
1340 KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd
1341 if (!url.kurl().isLocalFile()) {
1342 return text;
1343 }
1344
1345 url.filter(replaceHome, replaceEnv);
1346 return url.dir() + url.file();
1347}
1348
1350{
1351 return replacedPath(text, d->replace_home, d->replace_env);
1352}
1353
1355{
1356 d->mimeTypeFilters = mimeTypeFilters;
1357}
1358
1360{
1361 return d->mimeTypeFilters;
1362}
1363
1364/////////////////////////////////////////////////////////
1365/////////////////////////////////////////////////////////
1366// Static functions
1367
1368/*
1369 * expandEnv
1370 *
1371 * Expand environment variables in text. Escaped '$' are ignored.
1372 * Return true if expansion was made.
1373 */
1374static bool expandEnv(QString &text)
1375{
1376 // Find all environment variables beginning with '$'
1377 //
1378 int pos = 0;
1379
1380 bool expanded = false;
1381
1382 while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
1383 // Skip escaped '$'
1384 //
1385 if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
1386 pos++;
1387 }
1388 // Variable found => expand
1389 //
1390 else {
1391 // Find the end of the variable = next '/' or ' '
1392 //
1393 int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
1394 int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
1395
1396 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) {
1397 pos2 = pos_tmp;
1398 }
1399
1400 if (pos2 == -1) {
1401 pos2 = text.length();
1402 }
1403
1404 // Replace if the variable is terminated by '/' or ' '
1405 // and defined
1406 //
1407 if (pos2 >= 0) {
1408 const int len = pos2 - pos;
1409 const QStringView key = QStringView(text).mid(pos + 1, len - 1);
1410 const QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit().constData()));
1411
1412 if (!value.isEmpty()) {
1413 expanded = true;
1414 text.replace(pos, len, value);
1415 pos = pos + value.length();
1416 } else {
1417 pos = pos2;
1418 }
1419 }
1420 }
1421 }
1422
1423 return expanded;
1424}
1425
1426/*
1427 * expandTilde
1428 *
1429 * Replace "~user" with the users home directory
1430 * Return true if expansion was made.
1431 */
1432static bool expandTilde(QString &text)
1433{
1434 if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) {
1435 return false;
1436 }
1437
1438 bool expanded = false;
1439
1440 // Find the end of the user name = next '/' or ' '
1441 //
1442 int pos2 = text.indexOf(QLatin1Char(' '), 1);
1443 int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
1444
1445 if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) {
1446 pos2 = pos_tmp;
1447 }
1448
1449 if (pos2 == -1) {
1450 pos2 = text.length();
1451 }
1452
1453 // Replace ~user if the user name is terminated by '/' or ' '
1454 //
1455 if (pos2 >= 0) {
1456 QString userName = text.mid(1, pos2 - 1);
1457 QString dir;
1458
1459 // A single ~ is replaced with $HOME
1460 //
1461 if (userName.isEmpty()) {
1462 dir = QDir::homePath();
1463 }
1464 // ~user is replaced with the dir from passwd
1465 //
1466 else {
1467 KUser user(userName);
1468 dir = user.homeDir();
1469 }
1470
1471 if (!dir.isEmpty()) {
1472 expanded = true;
1473 text.replace(0, pos2, dir);
1474 }
1475 }
1476
1477 return expanded;
1478}
1479
1480/*
1481 * unescape
1482 *
1483 * Remove escapes and return the result in a new string
1484 *
1485 */
1486static QString unescape(const QString &text)
1487{
1488 QString result;
1489 result.reserve(text.size());
1490
1491 for (const QChar ch : text) {
1492 if (ch != QLatin1Char('\\')) {
1493 result.append(ch);
1494 }
1495 }
1496
1497 return result;
1498}
1499
1500#include "kurlcompletion.moc"
1501#include "moc_kurlcompletion.cpp"
void insertItems(const QStringList &items)
virtual void setIgnoreCase(bool ignoreCase)
virtual void clear()
CompletionMode completionMode() const
void setSorterFunction(SorterFunction sortFunc)
void setShouldAutoSuggest(bool shouldAutosuggest)
virtual void setItems(const QStringList &itemList)
The base class for all jobs.
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.
@ UDS_URL
An alternative URL (If different from the caption).
Definition udsentry.h:251
@ UDS_MIME_TYPE
A MIME type; the KIO worker should set it if it's known.
Definition udsentry.h:253
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition udsentry.h:224
@ UDS_ACCESS
Access permissions (part of the mode returned by stat)
Definition udsentry.h:232
void result(KJob *job)
static QString protocolClass(const QString &protocol)
Returns the protocol class for the specified protocol.
static bool supportsListing(const QUrl &url)
Returns whether the protocol can list files/objects.
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
This class does completion of URLs including user directories (~user) and environment variables.
QString makeCompletion(const QString &text) override
Finds completions to the given text.
virtual void setMode(Mode mode)
Changes the completion mode: exe or file completion.
virtual bool replaceEnv() const
Checks whether environment variables are completed and whether they are replaced internally while fin...
virtual void stop()
Stops asynchronous completion.
void setMimeTypeFilters(const QStringList &mimeTypes)
Sets the MIME type filters for the file dialog.
virtual QUrl dir() const
Returns the current directory, as it was given in setDir.
virtual bool replaceHome() const
Returns whether ~username is completed and whether ~username is replaced internally with the user's h...
virtual void setReplaceHome(bool replace)
Enables/disables completion of ~username and replacement (internally) of ~username with the user's ho...
KUrlCompletion()
Constructs a KUrlCompletion object in FileCompletion mode.
virtual void setReplaceEnv(bool replace)
Enables/disables completion and replacement (internally) of environment variables in URLs.
virtual void setDir(const QUrl &dir)
Sets the current directory (used as base for completion).
~KUrlCompletion() override
Destructs the KUrlCompletion object.
Mode
Determines how completion is done.
virtual Mode mode() const
Returns the completion mode: exe or file completion (default FileCompletion).
virtual bool isRunning() const
Check whether asynchronous completion is in progress.
QStringList mimeTypeFilters() const
Returns the MIME type filters for the file dialog.
QString replacedPath(const QString &text) const
Replaces username and/or environment variables, depending on the current settings and returns the fil...
static QStringList allUserNames(uint maxCount=KCOREADDONS_UINT_MAX)
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT ListJob * listDir(const QUrl &url, JobFlags flags=DefaultFlags, ListJob::ListFlags listFlags=ListJob::ListFlag::IncludeHidden)
List the contents of url, which is assumed to be a directory.
Definition listjob.cpp:239
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
KIOCORE_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
const QList< QKeySequence > & end()
bool authorizeUrlAction(const QString &action, const QUrl &baseURL, const QUrl &destURL)
Returns whether a certain URL related action is authorized.
T loadRelaxed() const const
void storeRelaxed(T newValue)
const char * constData() const const
bool isEmpty() const const
int toInt(bool *ok, int base) const const
void setCaseSensitivity(Qt::CaseSensitivity cs)
QString homePath()
QChar listSeparator()
QByteArray encodeName(const QString &fileName)
QString fileName() const const
bool isDir() const const
void append(QList< T > &&value)
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
value_type takeFirst()
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
Q_SIGNALSQ_SIGNALS
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QThread * thread() const const
QStringList keys() const const
QProcessEnvironment systemEnvironment()
QString & append(QChar ch)
const QChar at(qsizetype position) const const
void clear()
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & prepend(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QStringView mid(qsizetype start, qsizetype length) const const
QByteArray toLocal8Bit() const const
CaseSensitive
SkipEmptyParts
QTextStream & left(QTextStream &stream)
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
bool isFinished() const const
void start(Priority priority)
bool wait(QDeadlineTimer deadline)
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
QString fileName(ComponentFormattingOptions options) const const
QUrl fromLocalFile(const QString &localFile)
QString host(ComponentFormattingOptions options) const const
bool isEmpty() const const
bool isLocalFile() const const
bool isRelative() const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
QUrl resolved(const QUrl &relative) const const
QString scheme() const const
void setFragment(const QString &fragment, ParsingMode mode)
void setPath(const QString &path, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
QString toDisplayString(FormattingOptions options) const const
QString toLocalFile() const const
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 13 2024 11:53:34 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.