diff --git a/autotests/kurlcompletiontest.cpp b/autotests/kurlcompletiontest.cpp index 6e89921d..893ae8e1 100644 --- a/autotests/kurlcompletiontest.cpp +++ b/autotests/kurlcompletiontest.cpp @@ -1,388 +1,446 @@ /* * Copyright (C) 2004 David Faure * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation; * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include class KUrlCompletionTest : public QObject { Q_OBJECT private Q_SLOTS: void test(); public: KUrlCompletionTest() { #ifdef NO_WAIT // kurlcompletiontest-nowait sets this, to test what happens on slower systems (or systems with many dirs or users) qputenv("KURLCOMPLETION_WAIT", "1"); // 1ms, too short for a full listing of /usr/bin, but at least give a chance for a few items in the result #endif } ~KUrlCompletionTest() { teardown(); } void runAllTests(); void setup(); void teardown(); void testLocalRelativePath(); void testLocalAbsolutePath(); void testLocalURL(); void testEmptyCwd(); void testBug346920(); void testUser(); void testCancel(); // remember to register new test methods in runAllTests private: void waitForCompletion(KUrlCompletion *completion); KUrlCompletion *m_completion; + KUrlCompletion *m_completionWithMimeFilter; QTemporaryDir *m_tempDir; QUrl m_dirURL; QString m_dir; KUrlCompletion *m_completionEmptyCwd; }; void KUrlCompletionTest::setup() { qDebug(); m_completion = new KUrlCompletion; + m_completionWithMimeFilter = new KUrlCompletion; + m_completionWithMimeFilter->setMimeTypeFilters({QStringLiteral("text/x-c++src")}); m_tempDir = new QTemporaryDir; m_dir = m_tempDir->path(); m_dir += QLatin1String("/Dir With#Spaces/"); QDir().mkdir(m_dir); qDebug() << "m_dir=" << m_dir; m_completion->setDir(QUrl::fromLocalFile(m_dir)); + m_completionWithMimeFilter->setDir(m_completion->dir()); m_dirURL = QUrl::fromLocalFile(m_dir); QFile f1(m_dir + "/file1"); bool ok = f1.open(QIODevice::WriteOnly); QVERIFY(ok); f1.close(); QFile f2(m_dir + "/file#a"); ok = f2.open(QIODevice::WriteOnly); QVERIFY(ok); f2.close(); QFile f3(m_dir + "/file."); ok = f3.open(QIODevice::WriteOnly); QVERIFY(ok); f3.close(); + QFile f4(m_dir + "/source.cpp"); + ok = f4.open(QIODevice::WriteOnly); + QVERIFY(ok); + f4.close(); + + QFile f5(m_dir + "/source.php"); + ok = f5.open(QIODevice::WriteOnly); + QVERIFY(ok); + f5.close(); + QDir().mkdir(m_dir + "/file_subdir"); QDir().mkdir(m_dir + "/.1_hidden_file_subdir"); QDir().mkdir(m_dir + "/.2_hidden_file_subdir"); m_completionEmptyCwd = new KUrlCompletion; m_completionEmptyCwd->setDir(QUrl()); } void KUrlCompletionTest::teardown() { delete m_completion; m_completion = nullptr; + delete m_completionWithMimeFilter; + m_completionWithMimeFilter = nullptr; delete m_tempDir; m_tempDir = nullptr; delete m_completionEmptyCwd; m_completionEmptyCwd = nullptr; } void KUrlCompletionTest::waitForCompletion(KUrlCompletion *completion) { while (completion->isRunning()) { qDebug() << "waiting for thread..."; QTest::qWait(5); } // The thread emitted a signal, process it. qApp->sendPostedEvents(nullptr, QEvent::MetaCall); } void KUrlCompletionTest::testLocalRelativePath() { qDebug(); // Completion from relative path, with two matches m_completion->makeCompletion(QStringLiteral("f")); waitForCompletion(m_completion); QStringList comp1all = m_completion->allMatches(); qDebug() << comp1all; QCOMPARE(comp1all.count(), 4); QVERIFY(comp1all.contains("file1")); QVERIFY(comp1all.contains("file#a")); QVERIFY(comp1all.contains("file.")); QVERIFY(comp1all.contains("file_subdir/")); QString comp1 = m_completion->replacedPath(QStringLiteral("file1")); // like KUrlRequester does QCOMPARE(comp1, QString("file1")); // Completion from relative path qDebug() << endl << "now completing on 'file#'"; m_completion->makeCompletion(QStringLiteral("file#")); QVERIFY(!m_completion->isRunning()); // last listing reused QStringList compall = m_completion->allMatches(); qDebug() << compall; QCOMPARE(compall.count(), 1); QCOMPARE(compall.first(), QString("file#a")); QString comp2 = m_completion->replacedPath(compall.first()); // like KUrlRequester does QCOMPARE(comp2, QString("file#a")); // Completion with empty string qDebug() << endl << "now completing on ''"; m_completion->makeCompletion(QLatin1String("")); waitForCompletion(m_completion); QStringList compEmpty = m_completion->allMatches(); QCOMPARE(compEmpty.count(), 0); // Completion with '.', should find all hidden folders // This is broken in Qt 5.2 to 5.5 due to aba336c2b4a in qtbase, // fixed in https://codereview.qt-project.org/143134. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) m_completion->makeCompletion("."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(".2_hidden_file_subdir/")); #endif // Completion with '.2', should find only hidden folders starting with '2' m_completion->makeCompletion(".2"); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file m_completion->makeCompletion("file."); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains("file.")); + + // Completion with 'source' should only find the C++ file + m_completionWithMimeFilter->makeCompletion("source"); + waitForCompletion(m_completionWithMimeFilter); + const auto compSourceFile = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compSourceFile.count(), 1); + QVERIFY(compSourceFile.contains("source.cpp")); + + // But it should also be able to find folders + m_completionWithMimeFilter->makeCompletion("file_subdir"); + waitForCompletion(m_completionWithMimeFilter); + const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compMimeFolder.count(), 1); + QVERIFY(compMimeFolder.contains("file_subdir/")); } void KUrlCompletionTest::testLocalAbsolutePath() { // Completion from absolute path qDebug() << m_dir + "file#"; m_completion->makeCompletion(m_dir + "file#"); waitForCompletion(m_completion); QStringList compall = m_completion->allMatches(); qDebug() << compall; QCOMPARE(compall.count(), 1); QString comp = compall.first(); QCOMPARE(comp, QString(m_dir + "file#a")); comp = m_completion->replacedPath(comp); // like KUrlRequester does QCOMPARE(comp, QString(m_dir + "file#a")); // Completion with '.', should find all hidden folders #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) m_completion->makeCompletion(m_dir + "."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(m_dir + ".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(m_dir + ".2_hidden_file_subdir/")); #endif // Completion with '.2', should find only hidden folders starting with '2' m_completion->makeCompletion(m_dir + ".2"); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(m_dir + ".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file m_completion->makeCompletion(m_dir + "file."); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains(m_dir + "file.")); + + // Completion with 'source' should only find the C++ file + m_completionWithMimeFilter->makeCompletion(m_dir + "source"); + waitForCompletion(m_completionWithMimeFilter); + const auto compSourceFile = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compSourceFile.count(), 1); + QVERIFY(compSourceFile.contains(m_dir + "source.cpp")); + + // But it should also be able to find folders + m_completionWithMimeFilter->makeCompletion(m_dir + "file_subdir"); + waitForCompletion(m_completionWithMimeFilter); + const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compMimeFolder.count(), 1); + QVERIFY(compMimeFolder.contains(m_dir + "file_subdir/")); } void KUrlCompletionTest::testLocalURL() { // Completion from URL qDebug(); QUrl url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file"); m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); QStringList comp1all = m_completion->allMatches(); qDebug() << comp1all; QCOMPARE(comp1all.count(), 4); qDebug() << "Looking for" << m_dirURL.toString() + "file1"; QVERIFY(comp1all.contains(m_dirURL.toString() + "file1")); qDebug() << "Looking for" << m_dirURL.toString() + "file."; QVERIFY(comp1all.contains(m_dirURL.toString() + "file.")); QVERIFY(comp1all.contains(m_dirURL.toString() + "file_subdir/")); QString filehash = m_dirURL.toString() + "file%23a"; qDebug() << "Looking for" << filehash; QVERIFY(comp1all.contains(filehash)); QString filehashPath = m_completion->replacedPath(filehash); // note that it returns a path!! qDebug() << filehashPath; QCOMPARE(filehashPath, QString(m_dirURL.toLocalFile() + "file#a")); // Completion from URL with no match url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "foobar"); qDebug() << "makeCompletion(" << url << ")"; QString comp2 = m_completion->makeCompletion(url.toString()); QVERIFY(comp2.isEmpty()); waitForCompletion(m_completion); QVERIFY(m_completion->allMatches().isEmpty()); // Completion from URL with a ref -> no match url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + 'f'); url.setFragment(QStringLiteral("ref")); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); QVERIFY(m_completion->allMatches().isEmpty()); // Completion with '.', should find all hidden folders #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 1) qDebug() << "makeCompletion(" << (m_dirURL.toString() + ".") << ")"; m_completion->makeCompletion(m_dirURL.toString() + "."); waitForCompletion(m_completion); const auto compAllHidden = m_completion->allMatches(); QCOMPARE(compAllHidden.count(), 2); QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".1_hidden_file_subdir/")); QVERIFY(compAllHidden.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); #endif // Completion with '.2', should find only hidden folders starting with '2' url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + ".2"); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); const auto compHiddenStartingWith2 = m_completion->allMatches(); QCOMPARE(compHiddenStartingWith2.count(), 1); QVERIFY(compHiddenStartingWith2.contains(m_dirURL.toString() + ".2_hidden_file_subdir/")); // Completion with 'file.', should only find one file url = QUrl::fromLocalFile(m_dirURL.toLocalFile() + "file."); qDebug() << "makeCompletion(" << url << ")"; m_completion->makeCompletion(url.toString()); waitForCompletion(m_completion); const auto compFileEndingWithDot = m_completion->allMatches(); QCOMPARE(compFileEndingWithDot.count(), 1); QVERIFY(compFileEndingWithDot.contains(m_dirURL.toString() + "file.")); + + // Completion with 'source' should only find the C++ file + m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + "source"); + waitForCompletion(m_completionWithMimeFilter); + const auto compSourceFile = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compSourceFile.count(), 1); + QVERIFY(compSourceFile.contains(m_dirURL.toString() + "source.cpp")); + + // But it should also be able to find folders + m_completionWithMimeFilter->makeCompletion(m_dirURL.toString() + "file_subdir"); + waitForCompletion(m_completionWithMimeFilter); + const auto compMimeFolder = m_completionWithMimeFilter->allMatches(); + QCOMPARE(compMimeFolder.count(), 1); + QVERIFY(compMimeFolder.contains(m_dirURL.toString() + "file_subdir/")); } void KUrlCompletionTest::testEmptyCwd() { qDebug(); // Completion with empty string (with a KUrlCompletion whose cwd is "") qDebug() << endl << "now completing on '' with empty cwd"; m_completionEmptyCwd->makeCompletion(QLatin1String("")); waitForCompletion(m_completionEmptyCwd); QStringList compEmpty = m_completionEmptyCwd->allMatches(); QCOMPARE(compEmpty.count(), 0); } void KUrlCompletionTest::testBug346920() { m_completionEmptyCwd->makeCompletion(QStringLiteral("~/.")); waitForCompletion(m_completionEmptyCwd); m_completionEmptyCwd->allMatches(); // just don't crash } void KUrlCompletionTest::testUser() { m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); waitForCompletion(m_completionEmptyCwd); const auto matches = m_completionEmptyCwd->allMatches(); if (!KUser::allUserNames().isEmpty()) { Q_ASSERT(!matches.isEmpty()); } foreach (const auto &user, KUser::allUserNames()) { QVERIFY2(matches.contains(QLatin1Char('~') + user), qPrintable(matches.join(' '))); } // Check that the same query doesn't re-list m_completionEmptyCwd->makeCompletion(QStringLiteral("~")); QVERIFY(!m_completionEmptyCwd->isRunning()); QCOMPARE(m_completionEmptyCwd->allMatches(), matches); } // Test cancelling a running thread // In a normal run (./kurlcompletiontest) and a reasonable amount of files, we have few chances of making this happen // But in a "nowait" run (./kurlcompletiontest-nowait), this will cancel the thread before it even starts listing the dir. void KUrlCompletionTest::testCancel() { KUrlCompletion comp; comp.setDir(QUrl::fromLocalFile("/usr/bin")); comp.makeCompletion(QStringLiteral("g")); const QStringList matchesG = comp.allMatches(); // We get many matches in a normal run, and usually 0 matches when testing "no wait" (thread is sleeping) -> this is where this method can test cancelling //qDebug() << "got" << matchesG.count() << "matches"; bool done = !comp.isRunning(); // Doing the same search again, should hopefully not restart everything from scratch comp.makeCompletion(QStringLiteral("g")); const QStringList matchesG2 = comp.allMatches(); QVERIFY(matchesG2.count() >= matchesG.count()); if (done) { QVERIFY(!comp.isRunning()); // it had no reason to restart } done = !comp.isRunning(); // Search for something else, should reuse dir listing but not mix up results comp.makeCompletion(QStringLiteral("a")); if (done) { QVERIFY(!comp.isRunning()); // it had no reason to restart } const QStringList matchesA = comp.allMatches(); //qDebug() << "got" << matchesA.count() << "matches"; foreach (const QString &match, matchesA) { QVERIFY2(!match.startsWith('g'), qPrintable(match)); } waitForCompletion(&comp); foreach (const QString &match, comp.allMatches()) { QVERIFY2(!match.startsWith('g'), qPrintable(match)); } } void KUrlCompletionTest::test() { runAllTests(); // Try again, with another QTemporaryDir (to check that the caching doesn't give us wrong results) runAllTests(); } void KUrlCompletionTest::runAllTests() { setup(); testLocalRelativePath(); testLocalAbsolutePath(); testLocalURL(); testEmptyCwd(); testBug346920(); testUser(); testCancel(); teardown(); } QTEST_MAIN(KUrlCompletionTest) #include "kurlcompletiontest.moc" diff --git a/src/widgets/kurlcompletion.cpp b/src/widgets/kurlcompletion.cpp index 207a731d..9121b090 100644 --- a/src/widgets/kurlcompletion.cpp +++ b/src/widgets/kurlcompletion.cpp @@ -1,1537 +1,1571 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith Copyright (C) 2004 Scott Wheeler This class was inspired by a previous KUrlCompletion by Henner Zeller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlcompletion.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include // QT_LSTAT, QT_STAT, QT_STATBUF #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #else #include #endif static bool expandTilde(QString &); static bool expandEnv(QString &); static QString unescape(const QString &text); // Permission mask for files that are executable by // user, group or other #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH) // Constants for types of completion enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo}; class CompletionThread; // Ensure that we don't end up with "//". static QUrl addPathToUrl(const QUrl &url, const QString &relPath) { QString path = url.path(); if (!path.endsWith('/')) { path += '/'; } path += relPath; QUrl u(url); u.setPath(path); return u; } static QBasicAtomicInt s_waitDuration = Q_BASIC_ATOMIC_INITIALIZER(-1); static int initialWaitDuration() { if (s_waitDuration.load() == -1) { const QByteArray envVar = qgetenv("KURLCOMPLETION_WAIT"); if (envVar.isEmpty()) { s_waitDuration = 200; // default: 200 ms } else { s_waitDuration = envVar.toInt(); } } return s_waitDuration; } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletionPrivate // class KUrlCompletionPrivate { public: KUrlCompletionPrivate(KUrlCompletion *parent) : q(parent), url_auto_completion(true), userListThread(nullptr), dirListThread(nullptr) { } ~KUrlCompletionPrivate(); void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &); void _k_slotIOFinished(KJob *); void slotCompletionThreadDone(QThread *thread, const QStringList &matches); class MyURL; bool userCompletion(const MyURL &url, QString *match); bool envCompletion(const MyURL &url, QString *match); bool exeCompletion(const MyURL &url, QString *match); bool fileCompletion(const MyURL &url, QString *match); bool urlCompletion(const MyURL &url, QString *match); bool isAutoCompletion(); // List the next dir in m_dirs QString listDirectories(const QStringList &, const QString &, bool only_exe = false, bool only_dir = false, bool no_hidden = false, bool stat_files = true); void listUrls(const QList &urls, const QString &filter = QString(), bool only_exe = false, bool no_hidden = false); void addMatches(const QStringList &); QString finished(); void init(); void setListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); bool isListedUrl(ComplType compl_type, const QString &dir = QString(), const QString &filter = QString(), bool no_hidden = false); KUrlCompletion *q; QList list_urls; bool onlyLocalProto; // urlCompletion() in Auto/Popup mode? bool url_auto_completion; // Append '/' to directories in Popup mode? // Doing that stat's all files and is slower bool popup_append_slash; // Keep track of currently listed files to avoid reading them again bool last_no_hidden; QString last_path_listed; QString last_file_listed; QString last_prepend; ComplType last_compl_type; QUrl cwd; // "current directory" = base dir for completion KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion bool replace_env; bool replace_home; bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path KIO::ListJob *list_job; // kio job to list directories QString prepend; // text to prepend to listed items QString compl_text; // text to pass on to KCompletion // Filters for files read with kio bool list_urls_only_exe; // true = only list executables bool list_urls_no_hidden; QString list_urls_filter; // filter for listed files CompletionThread *userListThread; CompletionThread *dirListThread; + + QStringList mimeTypeFilters; }; class CompletionThread : public QThread { Q_OBJECT protected: CompletionThread(KUrlCompletionPrivate *receiver) : QThread(), m_prepend(receiver->prepend), m_complete_url(receiver->complete_url), m_terminationRequested(false) {} public: void requestTermination() { if (!isFinished()) { qCDebug(KIO_WIDGETS) << "stopping thread" << this; } m_terminationRequested.store(true); wait(); } QStringList matches() const { QMutexLocker locker(&m_mutex); return m_matches; } Q_SIGNALS: void completionThreadDone(QThread *thread, const QStringList &matches); protected: void addMatch(const QString &match) { QMutexLocker locker(&m_mutex); m_matches.append(match); } bool terminationRequested() const { return m_terminationRequested.load(); } void done() { if (!terminationRequested()) { qCDebug(KIO_WIDGETS) << "done, emitting signal with" << m_matches.count() << "matches"; emit completionThreadDone(this, m_matches); } } const QString m_prepend; const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path private: mutable QMutex m_mutex; // protects m_matches QStringList m_matches; // written by secondary thread, read by the matches() method QAtomicInt m_terminationRequested; // used as a bool }; /** * A simple thread that fetches a list of tilde-completions and returns this * to the caller via the completionThreadDone signal. */ class UserListThread : public CompletionThread { Q_OBJECT public: UserListThread(KUrlCompletionPrivate *receiver) : CompletionThread(receiver) {} protected: void run() Q_DECL_OVERRIDE { static const QChar tilde = '~'; // we don't need to handle prepend here, right? ~user is always at pos 0 assert(m_prepend.isEmpty()); #pragma message("TODO: add KUser::allUserNames() with a std::function shouldTerminate parameter") #ifndef Q_OS_WIN struct passwd *pw; ::setpwent(); while ((pw = ::getpwent()) && !terminationRequested()) { addMatch(tilde + QString::fromLocal8Bit(pw->pw_name)); } ::endpwent(); #else //currently terminationRequested is ignored on Windows QStringList allUsers = KUser::allUserNames(); Q_FOREACH(const QString& s, allUsers) { addMatch(tilde + s); } #endif addMatch(QString(tilde)); done(); } }; class DirectoryListThread : public CompletionThread { Q_OBJECT public: DirectoryListThread(KUrlCompletionPrivate *receiver, const QStringList &dirList, const QString &filter, + const QStringList &mimeTypeFilters, bool onlyExe, bool onlyDir, bool noHidden, bool appendSlashToDir) : CompletionThread(receiver), m_dirList(dirList), m_filter(filter), + m_mimeTypeFilters(mimeTypeFilters), m_onlyExe(onlyExe), m_onlyDir(onlyDir), m_noHidden(noHidden), m_appendSlashToDir(appendSlashToDir) {} void run() Q_DECL_OVERRIDE; private: QStringList m_dirList; QString m_filter; + QStringList m_mimeTypeFilters; bool m_onlyExe; bool m_onlyDir; bool m_noHidden; bool m_appendSlashToDir; }; void DirectoryListThread::run() { //qDebug() << "Entered DirectoryListThread::run(), m_filter=" << m_filter << ", m_onlyExe=" << m_onlyExe << ", m_onlyDir=" << m_onlyDir << ", m_appendSlashToDir=" << m_appendSlashToDir << ", m_dirList.size()=" << m_dirList.size(); QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot; if (m_onlyExe) { iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable); } else if (m_onlyDir) { iterator_filter |= QDir::Dirs; } else { iterator_filter |= (QDir::Dirs | QDir::Files); } + QMimeDatabase mimeTypes; + const QStringList::const_iterator end = m_dirList.constEnd(); for (QStringList::const_iterator it = m_dirList.constBegin(); it != end && !terminationRequested(); ++it) { //qDebug() << "Scanning directory" << *it; QDirIterator current_dir_iterator(*it, iterator_filter); while (current_dir_iterator.hasNext() && !terminationRequested()) { current_dir_iterator.next(); QFileInfo file_info = current_dir_iterator.fileInfo(); const QString file_name = file_info.fileName(); //qDebug() << "Found" << file_name; - if (m_filter.isEmpty() || file_name.startsWith(m_filter)) { + if (!m_filter.isEmpty() && !file_name.startsWith(m_filter)) { + continue; + } - QString toAppend = file_name; - // Add '/' to directories - if (m_appendSlashToDir && file_info.isDir()) { - toAppend.append(QLatin1Char('/')); + if (!m_mimeTypeFilters.isEmpty() && !file_info.isDir()) { + auto mimeType = mimeTypes.mimeTypeForFile(file_info); + if (!m_mimeTypeFilters.contains(mimeType.name())) { + continue; } + } - if (m_complete_url) { - QUrl info(m_prepend); - info = addPathToUrl(info, toAppend); - addMatch(info.toDisplayString()); - } else { - addMatch(m_prepend + toAppend); - } + QString toAppend = file_name; + // Add '/' to directories + if (m_appendSlashToDir && file_info.isDir()) { + toAppend.append(QLatin1Char('/')); + } + + if (m_complete_url) { + QUrl info(m_prepend); + info = addPathToUrl(info, toAppend); + addMatch(info.toDisplayString()); + } else { + addMatch(m_prepend + toAppend); } } } done(); } KUrlCompletionPrivate::~KUrlCompletionPrivate() { } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // MyURL - wrapper for QUrl with some different functionality // class KUrlCompletionPrivate::MyURL { public: MyURL(const QString &url, const QUrl &cwd); MyURL(const MyURL &url); ~MyURL(); QUrl kurl() const { return m_kurl; } bool isLocalFile() const { return m_kurl.isLocalFile(); } QString scheme() const { return m_kurl.scheme(); } // The directory with a trailing '/' QString dir() const { return m_kurl.adjusted(QUrl::RemoveFilename).path(); } QString file() const { return m_kurl.fileName(); } // The initial, unparsed, url, as a string. QString url() const { return m_url; } // Is the initial string a URL, or just a path (whether absolute or relative) bool isURL() const { return m_isURL; } void filter(bool replace_user_dir, bool replace_env); private: void init(const QString &url, const QUrl &cwd); QUrl m_kurl; QString m_url; bool m_isURL; }; KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QUrl &cwd) { init(_url, cwd); } KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url) : m_kurl(_url.m_kurl) { m_url = _url.m_url; m_isURL = _url.m_isURL; } void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QUrl &cwd) { // Save the original text m_url = _url; // Non-const copy QString url_copy = _url; // Special shortcuts for "man:" and "info:" if (url_copy.startsWith(QLatin1Char('#'))) { if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#')) { url_copy.replace(0, 2, QStringLiteral("info:")); } else { url_copy.replace(0, 1, QStringLiteral("man:")); } } // Look for a protocol in 'url' QRegExp protocol_regex = QRegExp(QStringLiteral("^(?![A-Za-z]:)[^/\\s\\\\]*:")); // Assume "file:" or whatever is given by 'cwd' if there is // no protocol. (QUrl does this only for absolute paths) if (protocol_regex.indexIn(url_copy) == 0) { m_kurl = QUrl(url_copy); m_isURL = true; } else { // relative path or ~ or $something m_isURL = false; if (!QDir::isRelativePath(url_copy) || url_copy.startsWith(QLatin1Char('~')) || url_copy.startsWith(QLatin1Char('$'))) { m_kurl = QUrl::fromLocalFile(url_copy); } else { // Relative path if (cwd.isEmpty()) { m_kurl = QUrl(url_copy); } else { m_kurl = cwd; m_kurl.setPath(m_kurl.path() + '/' + url_copy); } } } } KUrlCompletionPrivate::MyURL::~MyURL() { } void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env) { QString d = dir() + file(); if (replace_user_dir) { expandTilde(d); } if (replace_env) { expandEnv(d); } m_kurl.setPath(d); } /////////////////////////////////////////////////////// /////////////////////////////////////////////////////// // KUrlCompletion // KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); } KUrlCompletion::KUrlCompletion(Mode _mode) : KCompletion(), d(new KUrlCompletionPrivate(this)) { d->init(); setMode(_mode); } KUrlCompletion::~KUrlCompletion() { stop(); delete d; } void KUrlCompletionPrivate::init() { cwd = QUrl::fromLocalFile(QDir::homePath()); replace_home = true; replace_env = true; last_no_hidden = false; last_compl_type = CTNone; list_job = nullptr; mode = KUrlCompletion::FileCompletion; // Read settings KConfigGroup cg(KSharedConfig::openConfig(), "URLCompletion"); url_auto_completion = cg.readEntry("alwaysAutoComplete", true); popup_append_slash = cg.readEntry("popupAppendSlash", true); onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false); q->setIgnoreCase(true); } void KUrlCompletion::setDir(const QUrl &dir) { d->cwd = dir; } QUrl KUrlCompletion::dir() const { return d->cwd; } KUrlCompletion::Mode KUrlCompletion::mode() const { return d->mode; } void KUrlCompletion::setMode(Mode _mode) { d->mode = _mode; } bool KUrlCompletion::replaceEnv() const { return d->replace_env; } void KUrlCompletion::setReplaceEnv(bool replace) { d->replace_env = replace; } bool KUrlCompletion::replaceHome() const { return d->replace_home; } void KUrlCompletion::setReplaceHome(bool replace) { d->replace_home = replace; } /* * makeCompletion() * * Entry point for file name completion */ QString KUrlCompletion::makeCompletion(const QString &text) { qCDebug(KIO_WIDGETS) << text << "d->cwd=" << d->cwd; KUrlCompletionPrivate::MyURL url(text, d->cwd); d->compl_text = text; // Set d->prepend to the original URL, with the filename [and ref/query] stripped. // This is what gets prepended to the directory-listing matches. if (url.isURL()) { QUrl directoryUrl(url.kurl()); directoryUrl.setQuery(QString()); directoryUrl.setFragment(QString()); directoryUrl.setPath(url.dir()); d->prepend = directoryUrl.toString(); } else { d->prepend = text.left(text.length() - url.file().length()); } d->complete_url = url.isURL(); QString aMatch; // Environment variables // if (d->replace_env && d->envCompletion(url, &aMatch)) { return aMatch; } // User directories // if (d->replace_home && d->userCompletion(url, &aMatch)) { return aMatch; } // Replace user directories and variables url.filter(d->replace_home, d->replace_env); //qDebug() << "Filtered: proto=" << url.scheme() // << ", dir=" << url.dir() // << ", file=" << url.file() // << ", kurl url=" << *url.kurl(); if (d->mode == ExeCompletion) { // Executables // if (d->exeCompletion(url, &aMatch)) { return aMatch; } // KRun can run "man:" and "info:" etc. so why not treat them // as executables... if (d->urlCompletion(url, &aMatch)) { return aMatch; } } else { // Local files, directories // if (d->fileCompletion(url, &aMatch)) { return aMatch; } // All other... // if (d->urlCompletion(url, &aMatch)) { return aMatch; } } d->setListedUrl(CTNone); stop(); return QString(); } /* * finished * * Go on and call KCompletion. * Called when all matches have been added */ QString KUrlCompletionPrivate::finished() { if (last_compl_type == CTInfo) { return q->KCompletion::makeCompletion(compl_text.toLower()); } else { return q->KCompletion::makeCompletion(compl_text); } } /* * isRunning * * Return true if either a KIO job or a thread is running */ bool KUrlCompletion::isRunning() const { return d->list_job || (d->dirListThread && !d->dirListThread->isFinished()) || (d->userListThread && !d->userListThread->isFinished()); } /* * stop * * Stop and delete a running KIO job or the DirLister */ void KUrlCompletion::stop() { if (d->list_job) { d->list_job->kill(); d->list_job = nullptr; } if (d->dirListThread) { d->dirListThread->requestTermination(); delete d->dirListThread; d->dirListThread = nullptr; } if (d->userListThread) { d->userListThread->requestTermination(); delete d->userListThread; d->userListThread = nullptr; } } /* * Keep track of the last listed directory */ void KUrlCompletionPrivate::setListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { last_compl_type = complType; last_path_listed = directory; last_file_listed = filter; last_no_hidden = no_hidden; last_prepend = prepend; } bool KUrlCompletionPrivate::isListedUrl(ComplType complType, const QString &directory, const QString &filter, bool no_hidden) { return last_compl_type == complType && (last_path_listed == directory || (directory.isEmpty() && last_path_listed.isEmpty())) && (filter.startsWith(last_file_listed) || (filter.isEmpty() && last_file_listed.isEmpty())) && last_no_hidden == no_hidden && last_prepend == prepend; // e.g. relative path vs absolute } /* * isAutoCompletion * * Returns true if completion mode is Auto or Popup */ bool KUrlCompletionPrivate::isAutoCompletion() { return q->completionMode() == KCompletion::CompletionAuto || q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionMan || q->completionMode() == KCompletion::CompletionPopupAuto; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // User directories // bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.scheme() != QLatin1String("file") || !url.dir().isEmpty() || !url.file().startsWith(QLatin1Char('~')) || !prepend.isEmpty()) { return false; } if (!isListedUrl(CTUser)) { q->stop(); q->clear(); setListedUrl(CTUser); Q_ASSERT(!userListThread); // caller called stop() userListThread = new UserListThread(this); QObject::connect(userListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); userListThread->start(); // If the thread finishes quickly make sure that the results // are added to the first matching case. userListThread->wait(initialWaitDuration()); const QStringList l = userListThread->matches(); addMatches(l); } *pMatch = finished(); return true; } ///////////////////////////////////////////////////// ///////////////////////////////////////////////////// // Environment variables // bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$')) { return false; } if (!isListedUrl(CTEnv)) { q->stop(); q->clear(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QStringList keys = env.keys(); QString dollar = QStringLiteral("$"); QStringList l; Q_FOREACH(const QString &key, keys) { l.append(prepend + dollar + key); } addMatches(l); } setListedUrl(CTEnv); *pMatch = finished(); return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Executables // bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); // remove escapes // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. $PATH // 4. no directory at all QStringList dirList; if (!url.file().isEmpty()) { // $PATH // ### maybe Qt should have a QDir::pathSeparator() to avoid ifdefs.. #ifdef Q_OS_WIN #define KPATH_SEPARATOR ';' #else #define KPATH_SEPARATOR ':' #endif dirList = QString::fromLocal8Bit(qgetenv("PATH")).split( KPATH_SEPARATOR, QString::SkipEmptyParts); QStringList::Iterator it = dirList.begin(); for (; it != dirList.end(); ++it) { it->append(QLatin1Char('/')); } } else if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!directory.isEmpty() && !cwd.isEmpty()) { // current directory dirList.append(cwd.toLocalFile() + QLatin1Char('/') + directory); } // No hidden files unless the user types "." bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.'); // List files if needed // if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTExe, directory, url.file(), no_hidden_files); *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Local files // bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { if (!url.isLocalFile()) { return false; } QString directory = unescape(url.dir()); if (url.url() == QLatin1String("..")) { *pMatch = QStringLiteral(".."); return true; } //qDebug() << "fileCompletion" << url << "dir=" << dir; // Find directories to search for completions, either // // 1. complete path given in url // 2. current directory (d->cwd) // 3. no directory at all QStringList dirList; if (!QDir::isRelativePath(directory)) { // complete path in url dirList.append(directory); } else if (!cwd.isEmpty()) { // current directory QString dirToAdd = cwd.toLocalFile(); if (!directory.isEmpty()) { if (!dirToAdd.endsWith('/')) { dirToAdd.append(QLatin1Char('/')); } dirToAdd.append(directory); } dirList.append(dirToAdd); } // No hidden files unless the user types "." bool no_hidden_files = !url.file().startsWith(QLatin1Char('.')); // List files if needed // if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) { q->stop(); q->clear(); setListedUrl(CTFile, directory, QString(), no_hidden_files); // Append '/' to directories in Popup mode? bool append_slash = (popup_append_slash && (q->completionMode() == KCompletion::CompletionPopup || q->completionMode() == KCompletion::CompletionPopupAuto)); bool only_dir = (mode == KUrlCompletion::DirCompletion); *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files, append_slash); } else { *pMatch = finished(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // URLs not handled elsewhere... // static bool isLocalProtocol(const QString &protocol) { return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")); } bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch) { //qDebug() << *url.kurl(); if (onlyLocalProto && isLocalProtocol(url.scheme())) { return false; } // Use d->cwd as base url in case url is not absolute QUrl url_dir = url.kurl(); if (url_dir.isRelative() && !cwd.isEmpty()) { // Create an URL with the directory to be listed url_dir = cwd.resolved(url_dir); } // url is malformed if (!url_dir.isValid()) { return false; } // non local urls if (!isLocalProtocol(url.scheme())) { // url does not specify host if (url_dir.host().isEmpty()) { return false; } // url does not specify a valid directory if (url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty()) { return false; } // automatic completion is disabled if (isAutoCompletion() && !url_auto_completion) { return false; } } // url handler doesn't support listing if (!KProtocolManager::supportsListing(url_dir)) { return false; } // Remove escapes const QString directory = unescape(url_dir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); url_dir.setPath(directory); // List files if needed // if (!isListedUrl(CTUrl, directory, url.file())) { q->stop(); q->clear(); setListedUrl(CTUrl, directory, QString()); QList url_list; url_list.append(url_dir); listUrls(url_list, QString(), false); pMatch->clear(); } else if (!q->isRunning()) { *pMatch = finished(); } else { pMatch->clear(); } return true; } ////////////////////////////////////////////////// ////////////////////////////////////////////////// // Directory and URL listing // /* * addMatches * * Called to add matches to KCompletion */ void KUrlCompletionPrivate::addMatches(const QStringList &matchList) { q->insertItems(matchList); } /* * listDirectories * * List files starting with 'filter' in the given directories, * either using DirLister or listURLs() * * In either case, addMatches() is called with the listed * files, and eventually finished() when the listing is done * * Returns the match if available, or QString() if * DirLister timed out or using kio */ QString KUrlCompletionPrivate::listDirectories( const QStringList &dirList, const QString &filter, bool only_exe, bool only_dir, bool no_hidden, bool append_slash_to_dir) { assert(!q->isRunning()); if (qEnvironmentVariableIsEmpty("KURLCOMPLETION_LOCAL_KIO")) { qCDebug(KIO_WIDGETS) << "Listing directories:" << dirList << "with filter=" << filter << "using thread"; // Don't use KIO QStringList dirs; QStringList::ConstIterator end = dirList.constEnd(); for (QStringList::ConstIterator it = dirList.constBegin(); it != end; ++it) { QUrl url = QUrl::fromLocalFile(*it); if (KUrlAuthorized::authorizeUrlAction(QStringLiteral("list"), QUrl(), url)) { dirs.append(*it); } } Q_ASSERT(!dirListThread); // caller called stop() - dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir, - no_hidden, append_slash_to_dir); + dirListThread = new DirectoryListThread(this, dirs, filter, mimeTypeFilters, + only_exe, only_dir, no_hidden, + append_slash_to_dir); QObject::connect(dirListThread, &CompletionThread::completionThreadDone, q, [this](QThread *thread, const QStringList &matches){ slotCompletionThreadDone(thread, matches); }); dirListThread->start(); dirListThread->wait(initialWaitDuration()); qCDebug(KIO_WIDGETS) << "Adding initial matches:" << dirListThread->matches(); addMatches(dirListThread->matches()); return finished(); } // Use KIO //qDebug() << "Listing (listDirectories):" << dirList << "with KIO"; QList url_list; QStringList::ConstIterator it = dirList.constBegin(); QStringList::ConstIterator end = dirList.constEnd(); for (; it != end; ++it) { url_list.append(QUrl(*it)); } listUrls(url_list, filter, only_exe, no_hidden); // Will call addMatches() and finished() return QString(); } /* * listURLs * * Use KIO to list the given urls * * addMatches() is called with the listed files * finished() is called when the listing is done */ void KUrlCompletionPrivate::listUrls( const QList &urls, const QString &filter, bool only_exe, bool no_hidden) { assert(list_urls.isEmpty()); assert(list_job == nullptr); list_urls = urls; list_urls_filter = filter; list_urls_only_exe = only_exe; list_urls_no_hidden = no_hidden; //qDebug() << "Listing URLs:" << *urls[0] << ",..."; // Start it off by calling _k_slotIOFinished // // This will start a new list job as long as there // are urls in d->list_urls // _k_slotIOFinished(nullptr); } /* * _k_slotEntries * * Receive files listed by KIO and call addMatches() */ void KUrlCompletionPrivate::_k_slotEntries(KIO::Job *, const KIO::UDSEntryList &entries) { QStringList matchList; KIO::UDSEntryList::ConstIterator it = entries.constBegin(); const KIO::UDSEntryList::ConstIterator end = entries.constEnd(); QString filter = list_urls_filter; int filter_len = filter.length(); // Iterate over all files // for (; it != end; ++it) { const KIO::UDSEntry &entry = *it; const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL); QString entry_name; if (!url.isEmpty()) { //qDebug() << "url:" << url; entry_name = QUrl(url).fileName(); } else { entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME); } //qDebug() << "name:" << name; if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) && (list_urls_no_hidden || entry_name.length() == 1 || (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.')))) { continue; } const bool isDir = entry.isDir(); if (mode == KUrlCompletion::DirCompletion && !isDir) { continue; } - if (filter_len == 0 || entry_name.left(filter_len) == filter) { + if (filter_len != 0 && entry_name.left(filter_len) != filter) { + continue; + } + + if (!mimeTypeFilters.isEmpty() && !isDir && + !mimeTypeFilters.contains(entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE))) { + continue; + } - QString toAppend = entry_name; + QString toAppend = entry_name; - if (isDir) { - toAppend.append(QLatin1Char('/')); - } + if (isDir) { + toAppend.append(QLatin1Char('/')); + } - if (!list_urls_only_exe || - (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable - ) { - if (complete_url) { - QUrl url(prepend); - url = addPathToUrl(url, toAppend); - matchList.append(url.toDisplayString()); - } else { - matchList.append(prepend + toAppend); - } + if (!list_urls_only_exe || + (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE) // true if executable + ) { + if (complete_url) { + QUrl url(prepend); + url = addPathToUrl(url, toAppend); + matchList.append(url.toDisplayString()); + } else { + matchList.append(prepend + toAppend); } } + } addMatches(matchList); } /* * _k_slotIOFinished * * Called when a KIO job is finished. * * Start a new list job if there are still urls in * list_urls, otherwise call finished() */ void KUrlCompletionPrivate::_k_slotIOFinished(KJob *job) { assert(job == list_job); Q_UNUSED(job) if (list_urls.isEmpty()) { list_job = nullptr; finished(); // will call KCompletion::makeCompletion() } else { QUrl kurl(list_urls.takeFirst()); // list_urls.removeAll( kurl ); //qDebug() << "Start KIO::listDir" << kurl; list_job = KIO::listDir(kurl, KIO::HideProgressInfo); list_job->addMetaData(QStringLiteral("no-auth-prompt"), QStringLiteral("true")); assert(list_job); q->connect(list_job, SIGNAL(result(KJob*)), SLOT(_k_slotIOFinished(KJob*))); q->connect(list_job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(_k_slotEntries(KIO::Job*,KIO::UDSEntryList))); } } /////////////////////////////////////////////////// /////////////////////////////////////////////////// /* * postProcessMatch, postProcessMatches * * Called by KCompletion before emitting match() and matches() * * Append '/' to directories for file completion. This is * done here to avoid stat()'ing a lot of files */ void KUrlCompletion::postProcessMatch(QString *pMatch) const { //qDebug() << *pMatch; if (!pMatch->isEmpty() && pMatch->startsWith(QLatin1String("file:"))) { // Add '/' to directories in file completion mode // unless it has already been done if (d->last_compl_type == CTFile && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) { QString copy = QUrl(*pMatch).toLocalFile(); expandTilde(copy); expandEnv(copy); if (QDir::isRelativePath(copy)) { copy.prepend(d->cwd.toLocalFile() + QLatin1Char('/')); } //qDebug() << "stat'ing" << copy; QByteArray file = QFile::encodeName(copy); QT_STATBUF sbuff; if (QT_STAT(file.constData(), &sbuff) == 0) { if ((sbuff.st_mode & QT_STAT_MASK) == QT_STAT_DIR) { pMatch->append(QLatin1Char('/')); } } else { //qDebug() << "Could not stat file" << copy; } } } } void KUrlCompletion::postProcessMatches(QStringList * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } void KUrlCompletion::postProcessMatches(KCompletionMatches * /*matches*/) const { // Maybe '/' should be added to directories here as in // postProcessMatch() but it would slow things down // when there are a lot of matches... } // no longer used, KF6 TODO: remove this method void KUrlCompletion::customEvent(QEvent *e) { KCompletion::customEvent(e); } void KUrlCompletionPrivate::slotCompletionThreadDone(QThread *thread, const QStringList &matches) { if (thread != userListThread && thread != dirListThread) { qCDebug(KIO_WIDGETS) << "got" << matches.count() << "outdated matches"; return; } qCDebug(KIO_WIDGETS) << "got" << matches.count() << "matches at end of thread"; q->setItems(matches); if (userListThread == thread) { thread->wait(); delete thread; userListThread = nullptr; } if (dirListThread == thread) { thread->wait(); delete thread; dirListThread = nullptr; } finished(); // will call KCompletion::makeCompletion() } // static QString KUrlCompletion::replacedPath(const QString &text, bool replaceHome, bool replaceEnv) { if (text.isEmpty()) { return text; } KUrlCompletionPrivate::MyURL url(text, QUrl()); // no need to replace something of our current cwd if (!url.kurl().isLocalFile()) { return text; } url.filter(replaceHome, replaceEnv); return url.dir() + url.file(); } QString KUrlCompletion::replacedPath(const QString &text) const { return replacedPath(text, d->replace_home, d->replace_env); } +void KUrlCompletion::setMimeTypeFilters(const QStringList &mimeTypeFilters) +{ + d->mimeTypeFilters = mimeTypeFilters; +} + +QStringList KUrlCompletion::mimeTypeFilters() const +{ + return d->mimeTypeFilters; +} + ///////////////////////////////////////////////////////// ///////////////////////////////////////////////////////// // Static functions /* * expandEnv * * Expand environment variables in text. Escaped '$' are ignored. * Return true if expansion was made. */ static bool expandEnv(QString &text) { // Find all environment variables beginning with '$' // int pos = 0; bool expanded = false; while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) { // Skip escaped '$' // if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) { pos++; } // Variable found => expand // else { // Find the end of the variable = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), pos + 1); int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace if the variable is terminated by '/' or ' ' // and defined // if (pos2 >= 0) { int len = pos2 - pos; QString key = text.mid(pos + 1, len - 1); QString value = QString::fromLocal8Bit(qgetenv(key.toLocal8Bit())); if (!value.isEmpty()) { expanded = true; text.replace(pos, len, value); pos = pos + value.length(); } else { pos = pos2; } } } } return expanded; } /* * expandTilde * * Replace "~user" with the users home directory * Return true if expansion was made. */ static bool expandTilde(QString &text) { if (text.isEmpty() || (text.at(0) != QLatin1Char('~'))) { return false; } bool expanded = false; // Find the end of the user name = next '/' or ' ' // int pos2 = text.indexOf(QLatin1Char(' '), 1); int pos_tmp = text.indexOf(QLatin1Char('/'), 1); if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2)) { pos2 = pos_tmp; } if (pos2 == -1) { pos2 = text.length(); } // Replace ~user if the user name is terminated by '/' or ' ' // if (pos2 >= 0) { QString userName = text.mid(1, pos2 - 1); QString dir; // A single ~ is replaced with $HOME // if (userName.isEmpty()) { dir = QDir::homePath(); } // ~user is replaced with the dir from passwd // else { KUser user(userName); dir = user.homeDir(); } if (!dir.isEmpty()) { expanded = true; text.replace(0, pos2, dir); } } return expanded; } /* * unescape * * Remove escapes and return the result in a new string * */ static QString unescape(const QString &text) { QString result; for (int pos = 0; pos < text.length(); pos++) if (text.at(pos) != QLatin1Char('\\')) { result.insert(result.length(), text.at(pos)); } return result; } #include "moc_kurlcompletion.cpp" #include "kurlcompletion.moc" diff --git a/src/widgets/kurlcompletion.h b/src/widgets/kurlcompletion.h index 54297226..08e60540 100644 --- a/src/widgets/kurlcompletion.h +++ b/src/widgets/kurlcompletion.h @@ -1,187 +1,201 @@ /* This file is part of the KDE libraries Copyright (C) 2000 David Smith This class was inspired by a previous KUrlCompletion by Henner Zeller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KURLCOMPLETION_H #define KURLCOMPLETION_H #include "kiowidgets_export.h" #include #include #include namespace KIO { class Job; } class QStringList; class KUrlCompletionPrivate; /** * @class KUrlCompletion kurlcompletion.h * * This class does completion of URLs including user directories (~user) * and environment variables. Remote URLs are passed to KIO. * * @short Completion of a single URL * @author David Smith */ class KIOWIDGETS_EXPORT KUrlCompletion : public KCompletion { Q_OBJECT public: /** * Determines how completion is done. * @li ExeCompletion - executables in $PATH or with full path. * @li FileCompletion - all files with full path or in dir(), URLs * are listed using KIO. * @li DirCompletion - Same as FileCompletion but only returns directories. */ enum Mode { ExeCompletion = 1, FileCompletion, DirCompletion }; /** * Constructs a KUrlCompletion object in FileCompletion mode. */ KUrlCompletion(); /** * This overloaded constructor allows you to set the Mode to ExeCompletion * or FileCompletion without using setMode. Default is FileCompletion. */ KUrlCompletion(Mode); /** * Destructs the KUrlCompletion object. */ virtual ~KUrlCompletion(); /** * Finds completions to the given text. * * Remote URLs are listed with KIO. For performance reasons, local files * are listed with KIO only if KURLCOMPLETION_LOCAL_KIO is set. * The completion is done asyncronously if KIO is used. * * Returns the first match for user, environment, and local dir completion * and QString() for asynchronous completion (KIO or threaded). * * @param text the text to complete * @return the first match, or QString() if not found */ QString makeCompletion(const QString &text) Q_DECL_OVERRIDE; /** * Sets the current directory (used as base for completion). * Default = $HOME. * @param dir the current directory, as a URL (use QUrl::fromLocalFile for local paths) */ virtual void setDir(const QUrl &dir); /** * Returns the current directory, as it was given in setDir * @return the current directory, as a URL (use QUrl::toLocalFile for local paths) */ virtual QUrl dir() const; /** * Check whether asynchronous completion is in progress. * @return true if asynchronous completion is in progress */ virtual bool isRunning() const; /** * Stops asynchronous completion. */ virtual void stop(); /** * Returns the completion mode: exe or file completion (default FileCompletion). * @return the completion mode */ virtual Mode mode() const; /** * Changes the completion mode: exe or file completion * @param mode the new completion mode */ virtual void setMode(Mode mode); /** * Checks whether environment variables are completed and * whether they are replaced internally while finding completions. * Default is enabled. * @return true if environment vvariables will be replaced */ virtual bool replaceEnv() const; /** * Enables/disables completion and replacement (internally) of * environment variables in URLs. Default is enabled. * @param replace true to replace environment variables */ virtual void setReplaceEnv(bool replace); /** * Returns whether ~username is completed and whether ~username * is replaced internally with the user's home directory while * finding completions. Default is enabled. * @return true to replace tilde with the home directory */ virtual bool replaceHome() const; /** * Enables/disables completion of ~username and replacement * (internally) of ~username with the user's home directory. * Default is enabled. * @param replace true to replace tilde with the home directory */ virtual void setReplaceHome(bool replace); /** * Replaces username and/or environment variables, depending on the * current settings and returns the filtered url. Only works with * local files, i.e. returns back the original string for non-local * urls. * @param text the text to process * @return the path or URL resulting from this operation. If you * want to convert it to a QUrl, use QUrl::fromUserInput. */ QString replacedPath(const QString &text) const; /** * @internal I'll let ossi add a real one to KShell :) */ static QString replacedPath(const QString &text, bool replaceHome, bool replaceEnv = true); + /** + * Sets the mimetype filters for the file dialog. + * @see QFileDialog::setMimeTypeFilters() + * @since 5.38 + */ + void setMimeTypeFilters(const QStringList &mimeTypes); + + /** + * Returns the mimetype filters for the file dialog. + * @see QFileDialog::mimeTypeFilters() + * @since 5.38 + */ + QStringList mimeTypeFilters() const; + protected: // Called by KCompletion, adds '/' to directories void postProcessMatch(QString *match) const Q_DECL_OVERRIDE; void postProcessMatches(QStringList *matches) const Q_DECL_OVERRIDE; void postProcessMatches(KCompletionMatches *matches) const Q_DECL_OVERRIDE; void customEvent(QEvent *e) Q_DECL_OVERRIDE; // KF6 TODO: remove private: KUrlCompletionPrivate *const d; Q_PRIVATE_SLOT(d, void _k_slotEntries(KIO::Job *, const KIO::UDSEntryList &)) Q_PRIVATE_SLOT(d, void _k_slotIOFinished(KJob *)) }; #endif // KURLCOMPLETION_H diff --git a/src/widgets/kurlrequester.cpp b/src/widgets/kurlrequester.cpp index c72b71cc..515b7d88 100644 --- a/src/widgets/kurlrequester.cpp +++ b/src/widgets/kurlrequester.cpp @@ -1,664 +1,665 @@ /* This file is part of the KDE libraries Copyright (C) 1999,2000,2001 Carsten Pfeiffer Copyright (C) 2013 Teo Mrnjavac This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2, as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kurlrequester.h" #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KUrlDragPushButton : public QPushButton { Q_OBJECT public: KUrlDragPushButton(QWidget *parent) : QPushButton(parent) { new DragDecorator(this); } ~KUrlDragPushButton() {} void setURL(const QUrl &url) { m_urls.clear(); m_urls.append(url); } private: class DragDecorator : public KDragWidgetDecoratorBase { public: DragDecorator(KUrlDragPushButton *button) : KDragWidgetDecoratorBase(button), m_button(button) {} protected: QDrag *dragObject() Q_DECL_OVERRIDE { if (m_button->m_urls.isEmpty()) { return nullptr; } QDrag *drag = new QDrag(m_button); QMimeData *mimeData = new QMimeData; mimeData->setUrls(m_button->m_urls); drag->setMimeData(mimeData); return drag; } private: KUrlDragPushButton *m_button; }; QList m_urls; }; class KUrlRequester::KUrlRequesterPrivate { public: KUrlRequesterPrivate(KUrlRequester *parent) : m_parent(parent), edit(nullptr), combo(nullptr), fileDialogMode(KFile::File | KFile::ExistingOnly | KFile::LocalOnly), fileDialogAcceptMode(QFileDialog::AcceptOpen) { } ~KUrlRequesterPrivate() { delete myCompletion; delete myFileDialog; } void init(); void setText(const QString &text) { if (combo) { if (combo->isEditable()) { combo->setEditText(text); } else { int i = combo->findText(text); if (i == -1) { combo->addItem(text); combo->setCurrentIndex(combo->count() - 1); } else { combo->setCurrentIndex(i); } } } else { edit->setText(text); } } void connectSignals(KUrlRequester *receiver) { QLineEdit *sender; if (combo) { sender = combo->lineEdit(); } else { sender = edit; } if (sender) { connect(sender, &QLineEdit::textChanged, receiver, &KUrlRequester::textChanged); connect(sender, &QLineEdit::textEdited, receiver, &KUrlRequester::textEdited); connect(sender, SIGNAL(returnPressed()), receiver, SIGNAL(returnPressed())); connect(sender, SIGNAL(returnPressed(QString)), receiver, SIGNAL(returnPressed(QString))); } } void setCompletionObject(KCompletion *comp) { if (combo) { combo->setCompletionObject(comp); } else { edit->setCompletionObject(comp); } } void updateCompletionStartDir(const QUrl &newStartDir) { myCompletion->setDir(newStartDir); } QString text() const { return combo ? combo->currentText() : edit->text(); } /** * replaces ~user or $FOO, if necessary * if text() is a relative path, make it absolute using startDir() */ QUrl url() const { const QString txt = text(); KUrlCompletion *comp; if (combo) { comp = qobject_cast(combo->completionObject()); } else { comp = qobject_cast(edit->completionObject()); } QString enteredPath; if (comp) enteredPath = comp->replacedPath(txt); else enteredPath = txt; if (QDir::isAbsolutePath(enteredPath)) { return QUrl::fromLocalFile(enteredPath); } const QUrl enteredUrl = QUrl(enteredPath); // absolute or relative if (enteredUrl.isRelative() && !txt.isEmpty()) { QUrl finalUrl(m_startDir); finalUrl.setPath(finalUrl.path() + '/' + enteredPath); return finalUrl; } else { return enteredUrl; } } static void applyFileMode(QFileDialog *dlg, KFile::Modes m, QFileDialog::AcceptMode acceptMode) { QFileDialog::FileMode fileMode; if (m & KFile::Directory) { fileMode = QFileDialog::Directory; if ((m & KFile::File) == 0 && (m & KFile::Files) == 0) { dlg->setOption(QFileDialog::ShowDirsOnly, true); } } else if (m & KFile::Files && m & KFile::ExistingOnly) { fileMode = QFileDialog::ExistingFiles; } else if (m & KFile::File && m & KFile::ExistingOnly) { fileMode = QFileDialog::ExistingFile; } else { fileMode = QFileDialog::AnyFile; } dlg->setFileMode(fileMode); dlg->setAcceptMode(acceptMode); } // Converts from "*.foo *.bar|Comment" to "Comment (*.foo *.bar)" QStringList kToQFilters(const QString &filters) const { QStringList qFilters = filters.split('\n', QString::SkipEmptyParts); for (QStringList::iterator it = qFilters.begin(); it != qFilters.end(); ++it) { int sep = it->indexOf('|'); QString globs = it->left(sep); QString desc = it->mid(sep + 1); *it = QStringLiteral("%1 (%2)").arg(desc, globs); } return qFilters; } QUrl getDirFromFileDialog(const QUrl &openUrl) const { return QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly); } // slots void _k_slotUpdateUrl(); void _k_slotOpenDialog(); void _k_slotFileDialogAccepted(); QUrl m_startDir; bool m_startDirCustomized; KUrlRequester *m_parent; // TODO: rename to 'q' KLineEdit *edit; KComboBox *combo; KFile::Modes fileDialogMode; QFileDialog::AcceptMode fileDialogAcceptMode; QString fileDialogFilter; QStringList mimeTypeFilters; KEditListWidget::CustomEditor editor; KUrlDragPushButton *myButton; QFileDialog *myFileDialog; KUrlCompletion *myCompletion; Qt::WindowModality fileDialogModality; }; KUrlRequester::KUrlRequester(QWidget *editWidget, QWidget *parent) : QWidget(parent), d(new KUrlRequesterPrivate(this)) { // must have this as parent editWidget->setParent(this); d->combo = qobject_cast(editWidget); d->edit = qobject_cast(editWidget); if (d->edit) { d->edit->setClearButtonShown(true); } d->init(); } KUrlRequester::KUrlRequester(QWidget *parent) : QWidget(parent), d(new KUrlRequesterPrivate(this)) { d->init(); } KUrlRequester::KUrlRequester(const QUrl &url, QWidget *parent) : QWidget(parent), d(new KUrlRequesterPrivate(this)) { d->init(); setUrl(url); } KUrlRequester::~KUrlRequester() { delete d; } void KUrlRequester::KUrlRequesterPrivate::init() { myFileDialog = nullptr; fileDialogModality = Qt::ApplicationModal; if (!combo && !edit) { edit = new KLineEdit(m_parent); edit->setClearButtonShown(true); } QWidget *widget = combo ? static_cast(combo) : static_cast(edit); QHBoxLayout *topLayout = new QHBoxLayout(m_parent); topLayout->setMargin(0); topLayout->setSpacing(-1); // use default spacing topLayout->addWidget(widget); myButton = new KUrlDragPushButton(m_parent); myButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); int buttonSize = myButton->sizeHint().expandedTo(widget->sizeHint()).height(); myButton->setFixedSize(buttonSize, buttonSize); myButton->setToolTip(i18n("Open file dialog")); connect(myButton, SIGNAL(pressed()), m_parent, SLOT(_k_slotUpdateUrl())); widget->installEventFilter(m_parent); m_parent->setFocusProxy(widget); m_parent->setFocusPolicy(Qt::StrongFocus); topLayout->addWidget(myButton); connectSignals(m_parent); connect(myButton, SIGNAL(clicked()), m_parent, SLOT(_k_slotOpenDialog())); m_startDir = QUrl::fromLocalFile(QDir::currentPath()); m_startDirCustomized = false; myCompletion = new KUrlCompletion(); updateCompletionStartDir(m_startDir); setCompletionObject(myCompletion); QAction *openAction = new QAction(m_parent); openAction->setShortcut(QKeySequence::Open); m_parent->connect(openAction, SIGNAL(triggered(bool)), SLOT(_k_slotOpenDialog())); } void KUrlRequester::setUrl(const QUrl &url) { d->setText(url.toDisplayString(QUrl::PreferLocalFile)); } #ifndef KIOWIDGETS_NO_DEPRECATED void KUrlRequester::setPath(const QString &path) { d->setText(path); } #endif void KUrlRequester::setText(const QString &text) { d->setText(text); } void KUrlRequester::setStartDir(const QUrl &startDir) { d->m_startDir = startDir; d->m_startDirCustomized = true; d->updateCompletionStartDir(startDir); } void KUrlRequester::changeEvent(QEvent *e) { if (e->type() == QEvent::WindowTitleChange) { if (d->myFileDialog) { d->myFileDialog->setWindowTitle(windowTitle()); } } QWidget::changeEvent(e); } QUrl KUrlRequester::url() const { return d->url(); } QUrl KUrlRequester::startDir() const { return d->m_startDir; } QString KUrlRequester::text() const { return d->text(); } void KUrlRequester::KUrlRequesterPrivate::_k_slotOpenDialog() { if (myFileDialog) if (myFileDialog->isVisible()) { //The file dialog is already being shown, raise it and exit myFileDialog->raise(); myFileDialog->activateWindow(); return; } if (((fileDialogMode & KFile::Directory) && !(fileDialogMode & KFile::File)) || /* catch possible fileDialog()->setMode( KFile::Directory ) changes */ (myFileDialog && (myFileDialog->fileMode() == QFileDialog::Directory && myFileDialog->testOption(QFileDialog::ShowDirsOnly)))) { const QUrl openUrl = (!m_parent->url().isEmpty() && !m_parent->url().isRelative()) ? m_parent->url() : m_startDir; /* FIXME We need a new abstract interface for using KDirSelectDialog in a non-modal way */ QUrl newUrl; if (fileDialogMode & KFile::LocalOnly) { newUrl = QFileDialog::getExistingDirectoryUrl(m_parent, QString(), openUrl, QFileDialog::ShowDirsOnly, QStringList() << QStringLiteral("file")); } else { newUrl = getDirFromFileDialog(openUrl); } if (newUrl.isValid()) { m_parent->setUrl(newUrl); emit m_parent->urlSelected(url()); } } else { emit m_parent->openFileDialog(m_parent); //Creates the fileDialog if it doesn't exist yet QFileDialog *dlg = m_parent->fileDialog(); if (!url().isEmpty() && !url().isRelative()) { QUrl u(url()); // If we won't be able to list it (e.g. http), then don't try :) if (KProtocolManager::supportsListing(u)) { dlg->selectUrl(u); } } else { dlg->setDirectoryUrl(m_startDir); } dlg->setAcceptMode(fileDialogAcceptMode); //Update the file dialog window modality if (dlg->windowModality() != fileDialogModality) { dlg->setWindowModality(fileDialogModality); } if (fileDialogModality == Qt::NonModal) { dlg->show(); } else { dlg->exec(); } } } void KUrlRequester::KUrlRequesterPrivate::_k_slotFileDialogAccepted() { if (!myFileDialog) { return; } QUrl newUrl = myFileDialog->selectedUrls().first(); if (newUrl.isValid()) { m_parent->setUrl(newUrl); emit m_parent->urlSelected(url()); // remember url as defaultStartDir and update startdir for autocompletion if (newUrl.isLocalFile() && !m_startDirCustomized) { m_startDir = newUrl.adjusted(QUrl::RemoveFilename); updateCompletionStartDir(m_startDir); } } } void KUrlRequester::setMode(KFile::Modes mode) { Q_ASSERT((mode & KFile::Files) == 0); d->fileDialogMode = mode; if ((mode & KFile::Directory) && !(mode & KFile::File)) { d->myCompletion->setMode(KUrlCompletion::DirCompletion); } if (d->myFileDialog) { d->applyFileMode(d->myFileDialog, mode, d->fileDialogAcceptMode); } } KFile::Modes KUrlRequester::mode() const { return d->fileDialogMode; } void KUrlRequester::setAcceptMode(QFileDialog::AcceptMode mode) { d->fileDialogAcceptMode = mode; if (d->myFileDialog) { d->applyFileMode(d->myFileDialog, d->fileDialogMode, mode); } } QFileDialog::AcceptMode KUrlRequester::acceptMode() const { return d->fileDialogAcceptMode; } void KUrlRequester::setFilter(const QString &filter) { d->fileDialogFilter = filter; if (d->myFileDialog) { d->myFileDialog->setNameFilters(d->kToQFilters(d->fileDialogFilter)); } } QString KUrlRequester::filter() const { return d->fileDialogFilter; } void KUrlRequester::setMimeTypeFilters(const QStringList &mimeTypes) { d->mimeTypeFilters = mimeTypes; if (d->myFileDialog) { d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters); } + d->myCompletion->setMimeTypeFilters(d->mimeTypeFilters); } QStringList KUrlRequester::mimeTypeFilters() const { return d->mimeTypeFilters; } #ifndef KIOWIDGETS_NO_DEPRECATED QFileDialog *KUrlRequester::fileDialog() const { if (!d->myFileDialog) { d->myFileDialog = new QFileDialog(window(), windowTitle()); if (!d->mimeTypeFilters.isEmpty()) { d->myFileDialog->setMimeTypeFilters(d->mimeTypeFilters); } else { d->myFileDialog->setNameFilters(d->kToQFilters(d->fileDialogFilter)); } d->applyFileMode(d->myFileDialog, d->fileDialogMode, d->fileDialogAcceptMode); d->myFileDialog->setWindowModality(d->fileDialogModality); connect(d->myFileDialog, SIGNAL(accepted()), SLOT(_k_slotFileDialogAccepted())); } return d->myFileDialog; } #endif void KUrlRequester::clear() { d->setText(QString()); } KLineEdit *KUrlRequester::lineEdit() const { return d->edit; } KComboBox *KUrlRequester::comboBox() const { return d->combo; } void KUrlRequester::KUrlRequesterPrivate::_k_slotUpdateUrl() { const QUrl visibleUrl = url(); QUrl u = visibleUrl; if (visibleUrl.isRelative()) { u = QUrl::fromLocalFile(QDir::currentPath() + '/').resolved(visibleUrl); } myButton->setURL(u); } bool KUrlRequester::eventFilter(QObject *obj, QEvent *ev) { if ((d->edit == obj) || (d->combo == obj)) { if ((ev->type() == QEvent::FocusIn) || (ev->type() == QEvent::FocusOut)) // Forward focusin/focusout events to the urlrequester; needed by file form element in khtml { QApplication::sendEvent(this, ev); } } return QWidget::eventFilter(obj, ev); } QPushButton *KUrlRequester::button() const { return d->myButton; } KUrlCompletion *KUrlRequester::completionObject() const { return d->myCompletion; } #ifndef KIOWIDGETS_NO_DEPRECATED void KUrlRequester::setClickMessage(const QString &msg) { setPlaceholderText(msg); } #endif void KUrlRequester::setPlaceholderText(const QString &msg) { if (d->edit) { d->edit->setPlaceholderText(msg); } } #ifndef KIOWIDGETS_NO_DEPRECATED QString KUrlRequester::clickMessage() const { return placeholderText(); } #endif QString KUrlRequester::placeholderText() const { if (d->edit) { return d->edit->placeholderText(); } else { return QString(); } } Qt::WindowModality KUrlRequester::fileDialogModality() const { return d->fileDialogModality; } void KUrlRequester::setFileDialogModality(Qt::WindowModality modality) { d->fileDialogModality = modality; } const KEditListWidget::CustomEditor &KUrlRequester::customEditor() { setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); KLineEdit *edit = d->edit; if (!edit && d->combo) { edit = qobject_cast(d->combo->lineEdit()); } #ifndef NDEBUG if (!edit) { qCWarning(KIO_WIDGETS) << "KUrlRequester's lineedit is not a KLineEdit!??\n"; } #endif d->editor.setRepresentationWidget(this); d->editor.setLineEdit(edit); return d->editor; } KUrlComboRequester::KUrlComboRequester(QWidget *parent) : KUrlRequester(new KComboBox(false), parent), d(nullptr) { } #include "moc_kurlrequester.cpp" #include "kurlrequester.moc" diff --git a/tests/kurlrequestertest_gui.cpp b/tests/kurlrequestertest_gui.cpp index 215a9bbb..357034a7 100644 --- a/tests/kurlrequestertest_gui.cpp +++ b/tests/kurlrequestertest_gui.cpp @@ -1,37 +1,42 @@ /* * * This file is part of the KDE project. * Copyright (C) 2001 Carsten Pfeiffer * * You can Freely distribute this program under the GNU Library General Public * License. See the file "COPYING" for the exact licensing terms. */ #include #include #include #include int main(int argc, char **argv) { QApplication app(argc, argv); app.setQuitOnLastWindowClosed(false); QUrl url = KUrlRequesterDialog::getUrl(QUrl(QStringLiteral("ftp://ftp.kde.org"))); qDebug() << "Selected url:" << url; KUrlRequester *req = new KUrlRequester(); KEditListWidget *el = new KEditListWidget(req->customEditor()); el->setWindowTitle(QStringLiteral("Test")); el->show(); KUrlRequester *req1 = new KUrlRequester(); req1->setWindowTitle(QStringLiteral("AAAAAAAAAAAA")); req1->show(); KUrlComboRequester *comboReq = new KUrlComboRequester(); comboReq->setWindowTitle(QStringLiteral("KUrlComboRequester")); comboReq->show(); + auto *mimeFilterReq = new KUrlRequester(); + mimeFilterReq->setMimeTypeFilters({QStringLiteral("text/x-c++src")}); + mimeFilterReq->setWindowTitle(QStringLiteral("MimeFilter")); + mimeFilterReq->show(); + return app.exec(); }