• Skip to content
  • Skip to link menu
KDE 4.7 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

KIO

kurlcompletion.cpp
Go to the documentation of this file.
00001 /* -*- indent-tabs-mode: t; tab-width: 4; c-basic-offset:4 -*-
00002 
00003    This file is part of the KDE libraries
00004    Copyright (C) 2000 David Smith <dsmith@algonet.se>
00005    Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
00006 
00007    This class was inspired by a previous KUrlCompletion by
00008    Henner Zeller <zeller@think.de>
00009 
00010    This library is free software; you can redistribute it and/or
00011    modify it under the terms of the GNU Library General Public
00012    License as published by the Free Software Foundation; either
00013    version 2 of the License, or (at your option) any later version.
00014 
00015    This library is distributed in the hope that it will be useful,
00016    but WITHOUT ANY WARRANTY; without even the implied warranty of
00017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00018    Library General Public License for more details.
00019 
00020    You should have received a copy of the GNU Library General Public License
00021    along with this library; see the file COPYING.LIB.   If not, write to
00022    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00023    Boston, MA 02110-1301, USA.
00024 */
00025 
00026 #include "kurlcompletion.h"
00027 
00028 #include <config.h>
00029 
00030 #include <stdlib.h>
00031 #include <assert.h>
00032 #include <limits.h>
00033 
00034 #include <QtCore/QCoreApplication>
00035 #include <QtCore/QMutableStringListIterator>
00036 #include <QtCore/QRegExp>
00037 #include <QtCore/QTimer>
00038 #include <QtCore/QDir>
00039 #include <QtCore/QDirIterator>
00040 #include <QtCore/QFile>
00041 #include <QtCore/QTextIStream>
00042 #include <QtCore/QThread>
00043 #include <QtGui/QActionEvent>
00044 
00045 #include <kauthorized.h>
00046 #include <kdebug.h>
00047 #include <kurl.h>
00048 #include <kio/job.h>
00049 #include <kprotocolmanager.h>
00050 #include <kconfig.h>
00051 #include <kglobal.h>
00052 #include <kglobalsettings.h>
00053 #include <kde_file.h>
00054 
00055 #include <sys/types.h>
00056 #include <dirent.h>
00057 #include <unistd.h>
00058 #include <sys/stat.h>
00059 #include <pwd.h>
00060 #include <time.h>
00061 #include <sys/param.h>
00062 #include <kconfiggroup.h>
00063 
00064 #ifdef Q_WS_WIN
00065 #include <kkernel_win.h>
00066 #endif
00067 
00068 static bool expandTilde(QString&);
00069 static bool expandEnv(QString&);
00070 
00071 static QString unescape(const QString& text);
00072 
00073 // Permission mask for files that are executable by
00074 // user, group or other
00075 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
00076 
00077 // Constants for types of completion
00078 enum ComplType {CTNone = 0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
00079 
00080 class CompletionThread;
00081 
00084 // KUrlCompletionPrivate
00085 //
00086 class KUrlCompletionPrivate
00087 {
00088 public:
00089     KUrlCompletionPrivate(KUrlCompletion* parent)
00090         : q(parent),
00091           url_auto_completion(true),
00092           userListThread(0),
00093           dirListThread(0) {
00094     }
00095 
00096     ~KUrlCompletionPrivate();
00097 
00098     void _k_slotEntries(KIO::Job*, const KIO::UDSEntryList&);
00099     void _k_slotIOFinished(KJob*);
00100 
00101     class MyURL;
00102     bool userCompletion(const MyURL& url, QString* match);
00103     bool envCompletion(const MyURL& url, QString* match);
00104     bool exeCompletion(const MyURL& url, QString* match);
00105     bool fileCompletion(const MyURL& url, QString* match);
00106     bool urlCompletion(const MyURL& url, QString* match);
00107 
00108     bool isAutoCompletion();
00109 
00110     // List the next dir in m_dirs
00111     QString listDirectories(const QStringList&,
00112                              const QString&,
00113                              bool only_exe = false,
00114                              bool only_dir = false,
00115                              bool no_hidden = false,
00116                              bool stat_files = true);
00117 
00118     void listUrls(const QList<KUrl> &urls,
00119                    const QString& filter = QString(),
00120                    bool only_exe = false,
00121                    bool no_hidden = false);
00122 
00123     void addMatches(const QStringList&);
00124     QString finished();
00125 
00126     void init();
00127 
00128     void setListedUrl(int compl_type /* enum ComplType */,
00129                        const QString& dir = QString(),
00130                        const QString& filter = QString(),
00131                        bool no_hidden = false);
00132 
00133     bool isListedUrl(int compl_type /* enum ComplType */,
00134                       const QString& dir = QString(),
00135                       const QString& filter = QString(),
00136                       bool no_hidden = false);
00137 
00138     KUrlCompletion* q;
00139     QList<KUrl> list_urls;
00140 
00141     bool onlyLocalProto;
00142 
00143     // urlCompletion() in Auto/Popup mode?
00144     bool url_auto_completion;
00145 
00146     // Append '/' to directories in Popup mode?
00147     // Doing that stat's all files and is slower
00148     bool popup_append_slash;
00149 
00150     // Keep track of currently listed files to avoid reading them again
00151     QString last_path_listed;
00152     QString last_file_listed;
00153     QString last_prepend;
00154     int last_compl_type;
00155     int last_no_hidden;
00156 
00157     QString cwd; // "current directory" = base dir for completion
00158 
00159     KUrlCompletion::Mode mode; // ExeCompletion, FileCompletion, DirCompletion
00160     bool replace_env;
00161     bool replace_home;
00162     bool complete_url; // if true completing a URL (i.e. 'prepend' is a URL), otherwise a path
00163 
00164     KIO::ListJob* list_job; // kio job to list directories
00165 
00166     QString prepend; // text to prepend to listed items
00167     QString compl_text; // text to pass on to KCompletion
00168 
00169     // Filters for files read with  kio
00170     bool list_urls_only_exe; // true = only list executables
00171     bool list_urls_no_hidden;
00172     QString list_urls_filter; // filter for listed files
00173 
00174     CompletionThread* userListThread;
00175     CompletionThread* dirListThread;
00176 };
00177 
00183 class CompletionMatchEvent : public QEvent
00184 {
00185 public:
00186     CompletionMatchEvent(CompletionThread* thread) :
00187         QEvent(uniqueType()),
00188         m_completionThread(thread)
00189     {}
00190 
00191     CompletionThread* completionThread() const {
00192         return m_completionThread;
00193     }
00194     static Type uniqueType() {
00195         return Type(User + 61080);
00196     }
00197 
00198 private:
00199     CompletionThread* m_completionThread;
00200 };
00201 
00202 class CompletionThread : public QThread
00203 {
00204 protected:
00205     CompletionThread(KUrlCompletionPrivate* receiver) :
00206         QThread(),
00207         m_prepend(receiver->prepend),
00208         m_complete_url(receiver->complete_url),
00209         m_receiver(receiver),
00210         m_terminationRequested(false)
00211     {}
00212 
00213 public:
00214     void requestTermination() {
00215         m_terminationRequested = true;
00216     }
00217     QStringList matches() const {
00218         return m_matches;
00219     }
00220 
00221 protected:
00222     void addMatch(const QString& match) {
00223         m_matches.append(match);
00224     }
00225     bool terminationRequested() const {
00226         return m_terminationRequested;
00227     }
00228     void done() {
00229         if (!m_terminationRequested)
00230             qApp->postEvent(m_receiver->q, new CompletionMatchEvent(this));
00231         else
00232             deleteLater();
00233     }
00234 
00235     const QString m_prepend;
00236     const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
00237 
00238 private:
00239     KUrlCompletionPrivate* m_receiver;
00240     QStringList m_matches;
00241     bool m_terminationRequested;
00242 };
00243 
00249 class UserListThread : public CompletionThread
00250 {
00251 public:
00252     UserListThread(KUrlCompletionPrivate* receiver) :
00253         CompletionThread(receiver)
00254     {}
00255 
00256 protected:
00257     virtual void run() {
00258         static const QChar tilde = '~';
00259 
00260         // we don't need to handle prepend here, right? ~user is always at pos 0
00261         assert(m_prepend.isEmpty());
00262         struct passwd* pw;
00263         while ((pw = ::getpwent()) && !terminationRequested())
00264             addMatch(tilde + QString::fromLocal8Bit(pw->pw_name));
00265 
00266         ::endpwent();
00267 
00268         addMatch(QString(tilde));
00269 
00270         done();
00271     }
00272 };
00273 
00274 class DirectoryListThread : public CompletionThread
00275 {
00276 public:
00277     DirectoryListThread(KUrlCompletionPrivate* receiver,
00278                          const QStringList& dirList,
00279                          const QString& filter,
00280                          bool onlyExe,
00281                          bool onlyDir,
00282                          bool noHidden,
00283                          bool appendSlashToDir) :
00284         CompletionThread(receiver),
00285         m_dirList(dirList),
00286         m_filter(filter),
00287         m_onlyExe(onlyExe),
00288         m_onlyDir(onlyDir),
00289         m_noHidden(noHidden),
00290         m_appendSlashToDir(appendSlashToDir)
00291     {}
00292 
00293     virtual void run();
00294 
00295 private:
00296     QStringList m_dirList;
00297     QString m_filter;
00298     bool m_onlyExe;
00299     bool m_onlyDir;
00300     bool m_noHidden;
00301     bool m_appendSlashToDir;
00302 };
00303 
00304 void DirectoryListThread::run()
00305 {
00306     // Thread safety notes:
00307     //
00308     // There very possibly may be thread safety issues here, but I've done a check
00309     // of all of the things that would seem to be problematic.  Here are a few
00310     // things that I have checked to be safe here (some used indirectly):
00311     //
00312     // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
00313     // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
00314     //
00315     // Also see (for POSIX functions):
00316     // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
00317 
00318     // kDebug() << "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();
00319 
00320     QStringList::ConstIterator end = m_dirList.constEnd();
00321     for (QStringList::ConstIterator it = m_dirList.constBegin();
00322             it != end && !terminationRequested();
00323             ++it) {
00324         // kDebug() << "Scanning directory" << *it;
00325 
00326         // A trick from KIO that helps performance by a little bit:
00327         // chdir to the directory so we won't have to deal with full paths
00328         // with stat()
00329 
00330         QString path = QDir::currentPath();
00331         QDir::setCurrent(*it);
00332 
00333         QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
00334 
00335         if (m_onlyExe)
00336             iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
00337         else if (m_onlyDir)
00338             iterator_filter |= QDir::Dirs;
00339         else
00340             iterator_filter |= (QDir::Dirs | QDir::Files);
00341 
00342         QDirIterator current_dir_iterator(*it, iterator_filter);
00343 
00344         while (current_dir_iterator.hasNext()) {
00345             current_dir_iterator.next();
00346 
00347             QFileInfo file_info = current_dir_iterator.fileInfo();
00348             const QString file_name = file_info.fileName();
00349 
00350             //kDebug() << "Found" << file_name;
00351 
00352             if (m_filter.isEmpty() || file_name.startsWith(m_filter)) {
00353 
00354                 QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name;
00355                 // Add '/' to directories
00356                 if (m_appendSlashToDir && file_info.isDir())
00357                     toAppend.append(QLatin1Char('/'));
00358 
00359                 addMatch(m_prepend + toAppend);
00360             }
00361         }
00362 
00363         // chdir to the original directory
00364         QDir::setCurrent(path);
00365     }
00366 
00367     done();
00368 }
00369 
00370 KUrlCompletionPrivate::~KUrlCompletionPrivate()
00371 {
00372     if (userListThread)
00373         userListThread->requestTermination();
00374     if (dirListThread)
00375         dirListThread->requestTermination();
00376 }
00377 
00380 // MyURL - wrapper for KUrl with some different functionality
00381 //
00382 
00383 class KUrlCompletionPrivate::MyURL
00384 {
00385 public:
00386     MyURL(const QString& url, const QString& cwd);
00387     MyURL(const MyURL& url);
00388     ~MyURL();
00389 
00390     KUrl kurl() const {
00391         return m_kurl;
00392     }
00393 
00394     QString protocol() const {
00395         return m_kurl.protocol();
00396     }
00397     // The directory with a trailing '/'
00398     QString dir() const {
00399         return m_kurl.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
00400     }
00401     QString file() const {
00402         return m_kurl.fileName(KUrl::ObeyTrailingSlash);
00403     }
00404 
00405     // The initial, unparsed, url, as a string.
00406     QString url() const {
00407         return m_url;
00408     }
00409 
00410     // Is the initial string a URL, or just a path (whether absolute or relative)
00411     bool isURL() const {
00412         return m_isURL;
00413     }
00414 
00415     void filter(bool replace_user_dir, bool replace_env);
00416 
00417 private:
00418     void init(const QString& url, const QString& cwd);
00419 
00420     KUrl m_kurl;
00421     QString m_url;
00422     bool m_isURL;
00423 };
00424 
00425 KUrlCompletionPrivate::MyURL::MyURL(const QString& _url, const QString& cwd)
00426 {
00427     init(_url, cwd);
00428 }
00429 
00430 KUrlCompletionPrivate::MyURL::MyURL(const MyURL& _url)
00431     : m_kurl(_url.m_kurl)
00432 {
00433     m_url = _url.m_url;
00434     m_isURL = _url.m_isURL;
00435 }
00436 
00437 void KUrlCompletionPrivate::MyURL::init(const QString& _url, const QString& cwd)
00438 {
00439     // Save the original text
00440     m_url = _url;
00441 
00442     // Non-const copy
00443     QString url_copy = _url;
00444 
00445     // Special shortcuts for "man:" and "info:"
00446     if (url_copy.startsWith(QLatin1Char('#'))) {
00447         if (url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#'))
00448             url_copy.replace(0, 2, QLatin1String("info:"));
00449         else
00450             url_copy.replace(0, 1, QLatin1String("man:"));
00451     }
00452 
00453     // Look for a protocol in 'url'
00454     QRegExp protocol_regex = QRegExp("^(?![A-Za-z]:)[^/\\s\\\\]*:");
00455 
00456     // Assume "file:" or whatever is given by 'cwd' if there is
00457     // no protocol.  (KUrl does this only for absolute paths)
00458     if (protocol_regex.indexIn(url_copy) == 0) {
00459         m_kurl = KUrl(url_copy);
00460         m_isURL = true;
00461     } else { // relative path or ~ or $something
00462         m_isURL = false;
00463         if (!QDir::isRelativePath(url_copy) ||
00464                 url_copy.startsWith(QLatin1Char('~')) ||
00465                 url_copy.startsWith(QLatin1Char('$'))) {
00466             m_kurl = KUrl();
00467             m_kurl.setPath(url_copy);
00468         } else {
00469             if (cwd.isEmpty()) {
00470                 m_kurl = KUrl(url_copy);
00471             } else {
00472                 m_kurl = KUrl(cwd);
00473                 m_kurl.addPath(url_copy);
00474             }
00475         }
00476     }
00477 }
00478 
00479 KUrlCompletionPrivate::MyURL::~MyURL()
00480 {
00481 }
00482 
00483 void KUrlCompletionPrivate::MyURL::filter(bool replace_user_dir, bool replace_env)
00484 {
00485     QString d = dir() + file();
00486     if (replace_user_dir) expandTilde(d);
00487     if (replace_env) expandEnv(d);
00488     m_kurl.setPath(d);
00489 }
00490 
00493 // KUrlCompletion
00494 //
00495 
00496 KUrlCompletion::KUrlCompletion() : KCompletion(), d(new KUrlCompletionPrivate(this))
00497 {
00498     d->init();
00499 }
00500 
00501 
00502 KUrlCompletion::KUrlCompletion(Mode _mode)
00503     : KCompletion(),
00504       d(new KUrlCompletionPrivate(this))
00505 {
00506     d->init();
00507     setMode(_mode);
00508 }
00509 
00510 KUrlCompletion::~KUrlCompletion()
00511 {
00512     stop();
00513     delete d;
00514 }
00515 
00516 
00517 void KUrlCompletionPrivate::init()
00518 {
00519     cwd = QDir::homePath();
00520 
00521     replace_home = true;
00522     replace_env = true;
00523     last_no_hidden = false;
00524     last_compl_type = 0;
00525     list_job = 0L;
00526     mode = KUrlCompletion::FileCompletion;
00527 
00528     // Read settings
00529     KConfigGroup cg(KGlobal::config(), "URLCompletion");
00530 
00531     url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
00532     popup_append_slash = cg.readEntry("popupAppendSlash", true);
00533     onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
00534 
00535     q->setIgnoreCase(true);
00536 }
00537 
00538 void KUrlCompletion::setDir(const QString& _dir)
00539 {
00540     d->cwd = _dir;
00541 }
00542 
00543 QString KUrlCompletion::dir() const
00544 {
00545     return d->cwd;
00546 }
00547 
00548 KUrlCompletion::Mode KUrlCompletion::mode() const
00549 {
00550     return d->mode;
00551 }
00552 
00553 void KUrlCompletion::setMode(Mode _mode)
00554 {
00555     d->mode = _mode;
00556 }
00557 
00558 bool KUrlCompletion::replaceEnv() const
00559 {
00560     return d->replace_env;
00561 }
00562 
00563 void KUrlCompletion::setReplaceEnv(bool replace)
00564 {
00565     d->replace_env = replace;
00566 }
00567 
00568 bool KUrlCompletion::replaceHome() const
00569 {
00570     return d->replace_home;
00571 }
00572 
00573 void KUrlCompletion::setReplaceHome(bool replace)
00574 {
00575     d->replace_home = replace;
00576 }
00577 
00578 /*
00579  * makeCompletion()
00580  *
00581  * Entry point for file name completion
00582  */
00583 QString KUrlCompletion::makeCompletion(const QString& text)
00584 {
00585     //kDebug() << text << "d->cwd=" << d->cwd;
00586 
00587     KUrlCompletionPrivate::MyURL url(text, d->cwd);
00588 
00589     d->compl_text = text;
00590 
00591     // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
00592     // This is what gets prepended to the directory-listing matches.
00593     int toRemove = url.file().length() - url.kurl().query().length();
00594     if (url.kurl().hasRef())
00595         toRemove += url.kurl().ref().length() + 1;
00596     d->prepend = text.left(text.length() - toRemove);
00597     d->complete_url = url.isURL();
00598 
00599     QString aMatch;
00600 
00601     // Environment variables
00602     //
00603     if (d->replace_env && d->envCompletion(url, &aMatch))
00604         return aMatch;
00605 
00606     // User directories
00607     //
00608     if (d->replace_home && d->userCompletion(url, &aMatch))
00609         return aMatch;
00610 
00611     // Replace user directories and variables
00612     url.filter(d->replace_home, d->replace_env);
00613 
00614     //kDebug() << "Filtered: proto=" << url.protocol()
00615     //          << ", dir=" << url.dir()
00616     //          << ", file=" << url.file()
00617     //          << ", kurl url=" << *url.kurl();
00618 
00619     if (d->mode == ExeCompletion) {
00620         // Executables
00621         //
00622         if (d->exeCompletion(url, &aMatch))
00623             return aMatch;
00624 
00625         // KRun can run "man:" and "info:" etc. so why not treat them
00626         // as executables...
00627 
00628         if (d->urlCompletion(url, &aMatch))
00629             return aMatch;
00630     } else {
00631         // Local files, directories
00632         //
00633         if (d->fileCompletion(url, &aMatch))
00634             return aMatch;
00635 
00636         // All other...
00637         //
00638         if (d->urlCompletion(url, &aMatch))
00639             return aMatch;
00640     }
00641 
00642     d->setListedUrl(CTNone);
00643     stop();
00644 
00645     return QString();
00646 }
00647 
00648 /*
00649  * finished
00650  *
00651  * Go on and call KCompletion.
00652  * Called when all matches have been added
00653  */
00654 QString KUrlCompletionPrivate::finished()
00655 {
00656     if (last_compl_type == CTInfo)
00657         return q->KCompletion::makeCompletion(compl_text.toLower());
00658     else
00659         return q->KCompletion::makeCompletion(compl_text);
00660 }
00661 
00662 /*
00663  * isRunning
00664  *
00665  * Return true if either a KIO job or the DirLister
00666  * is running
00667  */
00668 bool KUrlCompletion::isRunning() const
00669 {
00670     return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
00671 }
00672 
00673 /*
00674  * stop
00675  *
00676  * Stop and delete a running KIO job or the DirLister
00677  */
00678 void KUrlCompletion::stop()
00679 {
00680     if (d->list_job) {
00681         d->list_job->kill();
00682         d->list_job = 0L;
00683     }
00684 
00685     if (d->dirListThread) {
00686         d->dirListThread->requestTermination();
00687         d->dirListThread = 0;
00688     }
00689 }
00690 
00691 /*
00692  * Keep track of the last listed directory
00693  */
00694 void KUrlCompletionPrivate::setListedUrl(int complType,
00695         const QString& directory,
00696         const QString& filter,
00697         bool no_hidden)
00698 {
00699     last_compl_type = complType;
00700     last_path_listed = directory;
00701     last_file_listed = filter;
00702     last_no_hidden = (int) no_hidden;
00703     last_prepend = prepend;
00704 }
00705 
00706 bool KUrlCompletionPrivate::isListedUrl(int complType,
00707         const QString& directory,
00708         const QString& filter,
00709         bool no_hidden)
00710 {
00711     return  last_compl_type == complType
00712             && (last_path_listed == directory
00713                 || (directory.isEmpty() && last_path_listed.isEmpty()))
00714             && (filter.startsWith (last_file_listed)
00715                 || (filter.isEmpty() && last_file_listed.isEmpty()))
00716             && last_no_hidden == (int) no_hidden
00717             && last_prepend == prepend; // e.g. relative path vs absolute
00718 }
00719 
00720 /*
00721  * isAutoCompletion
00722  *
00723  * Returns true if completion mode is Auto or Popup
00724  */
00725 bool KUrlCompletionPrivate::isAutoCompletion()
00726 {
00727     return q->completionMode() == KGlobalSettings::CompletionAuto
00728            || q->completionMode() == KGlobalSettings::CompletionPopup
00729            || q->completionMode() == KGlobalSettings::CompletionMan
00730            || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
00731 }
00734 // User directories
00735 //
00736 
00737 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00738 {
00739     if (url.protocol() != QLatin1String("file")
00740             || !url.dir().isEmpty()
00741             || !url.file().startsWith(QLatin1Char('~')))
00742         return false;
00743 
00744     if (!isListedUrl(CTUser)) {
00745         q->stop();
00746         q->clear();
00747 
00748         if (!userListThread) {
00749             userListThread = new UserListThread(this);
00750             userListThread->start();
00751 
00752             // If the thread finishes quickly make sure that the results
00753             // are added to the first matching case.
00754 
00755             userListThread->wait(200);
00756             const QStringList l = userListThread->matches();
00757             addMatches(l);
00758         }
00759     }
00760     *pMatch = finished();
00761     return true;
00762 }
00763 
00766 // Environment variables
00767 //
00768 
00769 #ifndef Q_OS_WIN
00770 extern char** environ; // Array of environment variables
00771 #endif
00772 
00773 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00774 {
00775     if (url.file().isEmpty() || url.file().at(0) != QLatin1Char('$'))
00776         return false;
00777 
00778     if (!isListedUrl(CTEnv)) {
00779         q->stop();
00780         q->clear();
00781 
00782         char** env = environ;
00783 
00784         QString dollar = QLatin1String("$");
00785 
00786         QStringList l;
00787 
00788         while (*env) {
00789             QString s = QString::fromLocal8Bit(*env);
00790 
00791             int pos = s.indexOf(QLatin1Char('='));
00792 
00793             if (pos == -1)
00794                 pos = s.length();
00795 
00796             if (pos > 0)
00797                 l.append(prepend + dollar + s.left(pos));
00798 
00799             env++;
00800         }
00801 
00802         addMatches(l);
00803     }
00804 
00805     setListedUrl(CTEnv);
00806 
00807     *pMatch = finished();
00808     return true;
00809 }
00810 
00813 // Executables
00814 //
00815 
00816 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00817 {
00818     if (url.protocol() != QLatin1String("file"))
00819         return false;
00820 
00821     QString directory = unescape(url.dir());  // remove escapes
00822 
00823     // Find directories to search for completions, either
00824     //
00825     // 1. complete path given in url
00826     // 2. current directory (d->cwd)
00827     // 3. $PATH
00828     // 4. no directory at all
00829 
00830     QStringList dirList;
00831 
00832     if (!url.file().isEmpty()) {
00833         // $PATH
00834         dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
00835                       KPATH_SEPARATOR, QString::SkipEmptyParts);
00836 
00837         QStringList::Iterator it = dirList.begin();
00838 
00839         for (; it != dirList.end(); ++it)
00840             it->append(QLatin1Char('/'));
00841     } else if (!QDir::isRelativePath(directory)) {
00842         // complete path in url
00843         dirList.append(directory);
00844     } else if (!directory.isEmpty() && !cwd.isEmpty()) {
00845         // current directory
00846         dirList.append(cwd + QLatin1Char('/') + directory);
00847     }
00848 
00849     // No hidden files unless the user types "."
00850     bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
00851 
00852     // List files if needed
00853     //
00854     if (!isListedUrl(CTExe, directory, url.file(), no_hidden_files)) {
00855         q->stop();
00856         q->clear();
00857 
00858         setListedUrl(CTExe, directory, url.file(), no_hidden_files);
00859 
00860         *pMatch = listDirectories(dirList, url.file(), true, false, no_hidden_files);
00861     } else if (!q->isRunning()) {
00862         *pMatch = finished();
00863     } else {
00864         if (dirListThread)
00865             setListedUrl(CTExe, directory, url.file(), no_hidden_files);
00866         pMatch->clear();
00867     }
00868 
00869     return true;
00870 }
00871 
00874 // Local files
00875 //
00876 
00877 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00878 {
00879     if (url.protocol() != QLatin1String("file"))
00880         return false;
00881 
00882     QString directory = unescape(url.dir());
00883 
00884     if (url.url().length() && url.url().at(0) == QLatin1Char('.')) {
00885         if (url.url().length() == 1) {
00886             *pMatch = (q->completionMode() == KGlobalSettings::CompletionMan) ?
00887                       QLatin1String(".") :
00888                       QLatin1String("..");
00889             return true;
00890         } else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.')) {
00891             *pMatch = QLatin1String("..");
00892             return true;
00893         }
00894     }
00895 
00896     //kDebug() << "fileCompletion" << url << "dir=" << dir;
00897 
00898     // Find directories to search for completions, either
00899     //
00900     // 1. complete path given in url
00901     // 2. current directory (d->cwd)
00902     // 3. no directory at all
00903 
00904     QStringList dirList;
00905 
00906     if (!QDir::isRelativePath(directory)) {
00907         // complete path in url
00908         dirList.append(directory);
00909     } else if (!cwd.isEmpty()) {
00910         // current directory
00911         QString dirToAdd = cwd;
00912         if (!directory.isEmpty()) {
00913             if (!cwd.endsWith('/'))
00914                 dirToAdd.append(QLatin1Char('/'));
00915             dirToAdd.append(directory);
00916         }
00917         dirList.append(dirToAdd);
00918     }
00919 
00920     // No hidden files unless the user types "."
00921     bool no_hidden_files = !url.file().startsWith(QLatin1Char('.'));
00922 
00923     // List files if needed
00924     //
00925     if (!isListedUrl(CTFile, directory, QString(), no_hidden_files)) {
00926         q->stop();
00927         q->clear();
00928 
00929         setListedUrl(CTFile, directory, QString(), no_hidden_files);
00930 
00931         // Append '/' to directories in Popup mode?
00932         bool append_slash = (popup_append_slash
00933                              && (q->completionMode() == KGlobalSettings::CompletionPopup ||
00934                                  q->completionMode() == KGlobalSettings::CompletionPopupAuto));
00935 
00936         bool only_dir = (mode == KUrlCompletion::DirCompletion);
00937 
00938         *pMatch = listDirectories(dirList, QString(), false, only_dir, no_hidden_files,
00939                                    append_slash);
00940     } else if (!q->isRunning()) {
00941         *pMatch = finished();
00942     } else {
00943         pMatch->clear();
00944     }
00945 
00946     return true;
00947 }
00948 
00951 // URLs not handled elsewhere...
00952 //
00953 
00954 static bool isLocalProtocol(const QString& protocol)
00955 {
00956     return (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local"));
00957 }
00958 
00959 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL& url, QString* pMatch)
00960 {
00961     //kDebug() << *url.kurl();
00962     if (onlyLocalProto && isLocalProtocol(url.protocol()))
00963         return false;
00964 
00965     // Use d->cwd as base url in case url is not absolute
00966     KUrl url_dir = url.kurl();
00967     if (url_dir.isRelative() && !cwd.isEmpty()) {
00968         const KUrl url_cwd (cwd);
00969         // Create an URL with the directory to be listed
00970         url_dir = KUrl(url_cwd,  url_dir.url());
00971     }
00972 
00973     // url is malformed
00974     if (!url_dir.isValid())
00975         return false;
00976 
00977     // url handler doesn't support listing
00978     if (!KProtocolManager::supportsListing(url_dir))
00979         return false;
00980 
00981     // non local urls
00982     if (!isLocalProtocol(url.protocol())) {
00983         // url does not specify host
00984         if (url_dir.host().isEmpty())
00985             return false;
00986 
00987         // url does not specify a valid directory
00988         if (url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash).isEmpty())
00989             return false;
00990 
00991         // automatic completion is disabled
00992         if (isAutoCompletion() && !url_auto_completion)
00993             return false;
00994     }
00995 
00996     url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
00997 
00998     // Remove escapes
00999     QString directory = unescape(url_dir.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash));
01000 
01001     url_dir.setPath(directory);
01002 
01003     // List files if needed
01004     //
01005     if (!isListedUrl(CTUrl, url_dir.prettyUrl(), url.file())) {
01006         q->stop();
01007         q->clear();
01008 
01009         setListedUrl(CTUrl, url_dir.prettyUrl(), QString());
01010 
01011         QList<KUrl> url_list;
01012         url_list.append(url_dir);
01013 
01014         listUrls(url_list, QString(), false);
01015 
01016         pMatch->clear();
01017     } else if (!q->isRunning()) {
01018         *pMatch = finished();
01019     } else {
01020         pMatch->clear();
01021     }
01022 
01023     return true;
01024 }
01025 
01028 // Directory and URL listing
01029 //
01030 
01031 /*
01032  * addMatches
01033  *
01034  * Called to add matches to KCompletion
01035  */
01036 void KUrlCompletionPrivate::addMatches(const QStringList& matchList)
01037 {
01038     q->insertItems(matchList);
01039 }
01040 
01041 /*
01042  * listDirectories
01043  *
01044  * List files starting with 'filter' in the given directories,
01045  * either using DirLister or listURLs()
01046  *
01047  * In either case, addMatches() is called with the listed
01048  * files, and eventually finished() when the listing is done
01049  *
01050  * Returns the match if available, or QString() if
01051  * DirLister timed out or using kio
01052  */
01053 QString KUrlCompletionPrivate::listDirectories(
01054     const QStringList& dirList,
01055     const QString& filter,
01056     bool only_exe,
01057     bool only_dir,
01058     bool no_hidden,
01059     bool append_slash_to_dir)
01060 {
01061     assert(!q->isRunning());
01062 
01063     if (qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty()) {
01064 
01065         //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
01066 
01067         // Don't use KIO
01068 
01069         if (dirListThread)
01070             dirListThread->requestTermination();
01071 
01072         QStringList dirs;
01073 
01074         QStringList::ConstIterator end = dirList.constEnd();
01075         for (QStringList::ConstIterator it = dirList.constBegin();
01076                 it != end;
01077                 ++it) {
01078             KUrl url;
01079             url.setPath(*it);
01080             if (KAuthorized::authorizeUrlAction(QLatin1String("list"), KUrl(), url))
01081                 dirs.append(*it);
01082         }
01083 
01084         dirListThread = new DirectoryListThread(this, dirs, filter, only_exe, only_dir,
01085                                                 no_hidden, append_slash_to_dir);
01086         dirListThread->start();
01087         dirListThread->wait(200);
01088         addMatches(dirListThread->matches());
01089 
01090         return finished();
01091     }
01092 
01093     // Use KIO
01094     //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
01095 
01096     QList<KUrl> url_list;
01097 
01098     QStringList::ConstIterator it = dirList.constBegin();
01099     QStringList::ConstIterator end = dirList.constEnd();
01100 
01101     for (; it != end; ++it) {
01102         url_list.append(KUrl(*it));
01103     }
01104 
01105     listUrls(url_list, filter, only_exe, no_hidden);
01106     // Will call addMatches() and finished()
01107 
01108     return QString();
01109 }
01110 
01111 /*
01112  * listURLs
01113  *
01114  * Use KIO to list the given urls
01115  *
01116  * addMatches() is called with the listed files
01117  * finished() is called when the listing is done
01118  */
01119 void KUrlCompletionPrivate::listUrls(
01120     const QList<KUrl> &urls,
01121     const QString& filter,
01122     bool only_exe,
01123     bool no_hidden)
01124 {
01125     assert(list_urls.isEmpty());
01126     assert(list_job == 0L);
01127 
01128     list_urls = urls;
01129     list_urls_filter = filter;
01130     list_urls_only_exe = only_exe;
01131     list_urls_no_hidden = no_hidden;
01132 
01133     //kDebug() << "Listing URLs:" << *urls[0] << ",...";
01134 
01135     // Start it off by calling _k_slotIOFinished
01136     //
01137     // This will start a new list job as long as there
01138     // are urls in d->list_urls
01139     //
01140     _k_slotIOFinished(0);
01141 }
01142 
01143 /*
01144  * _k_slotEntries
01145  *
01146  * Receive files listed by KIO and call addMatches()
01147  */
01148 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01149 {
01150     QStringList matchList;
01151 
01152     KIO::UDSEntryList::ConstIterator it = entries.constBegin();
01153     const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
01154 
01155     QString filter = list_urls_filter;
01156 
01157     int filter_len = filter.length();
01158 
01159     // Iterate over all files
01160     //
01161     for (; it != end; ++it) {
01162         const KIO::UDSEntry& entry = *it;
01163         const QString url = entry.stringValue(KIO::UDSEntry::UDS_URL);
01164 
01165         QString entry_name;
01166         if (!url.isEmpty()) {
01167             // kDebug() << "url:" << url;
01168             entry_name = KUrl(url).fileName();
01169         } else {
01170             entry_name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
01171         }
01172 
01173         // kDebug() << "name:" << name;
01174 
01175         if ((!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
01176                 (list_urls_no_hidden ||
01177                  entry_name.length() == 1 ||
01178                  (entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.'))))
01179             continue;
01180 
01181         const bool isDir = entry.isDir();
01182 
01183         if (mode == KUrlCompletion::DirCompletion && !isDir)
01184             continue;
01185 
01186         if (filter_len == 0 || entry_name.left(filter_len) == filter) {
01187 
01188             QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
01189 
01190             if (isDir)
01191                 toAppend.append(QLatin1Char('/'));
01192 
01193             if (!list_urls_only_exe ||
01194                     (entry.numberValue(KIO::UDSEntry::UDS_ACCESS) & MODE_EXE)  // true if executable
01195                ) {
01196                 matchList.append(prepend + toAppend);
01197             }
01198         }
01199     }
01200 
01201     addMatches(matchList);
01202 }
01203 
01204 /*
01205  * _k_slotIOFinished
01206  *
01207  * Called when a KIO job is finished.
01208  *
01209  * Start a new list job if there are still urls in
01210  * list_urls, otherwise call finished()
01211  */
01212 void KUrlCompletionPrivate::_k_slotIOFinished(KJob* job)
01213 {
01214     assert(job == list_job); Q_UNUSED(job)
01215 
01216     if (list_urls.isEmpty()) {
01217 
01218         list_job = 0L;
01219 
01220         finished(); // will call KCompletion::makeCompletion()
01221 
01222     } else {
01223 
01224         KUrl kurl(list_urls.takeFirst());
01225 
01226 //      list_urls.removeAll( kurl );
01227 
01228 //      kDebug() << "Start KIO::listDir" << kurl;
01229 
01230         list_job = KIO::listDir(kurl, KIO::HideProgressInfo);
01231         list_job->addMetaData("no-auth-prompt", "true");
01232 
01233         assert(list_job);
01234 
01235         q->connect(list_job,
01236                     SIGNAL(result(KJob*)),
01237                     SLOT(_k_slotIOFinished(KJob*)));
01238 
01239         q->connect(list_job,
01240                     SIGNAL(entries(KIO::Job*, const KIO::UDSEntryList&)),
01241                     SLOT(_k_slotEntries(KIO::Job*, const KIO::UDSEntryList&)));
01242     }
01243 }
01244 
01247 
01248 /*
01249  * postProcessMatch, postProcessMatches
01250  *
01251  * Called by KCompletion before emitting match() and matches()
01252  *
01253  * Append '/' to directories for file completion. This is
01254  * done here to avoid stat()'ing a lot of files
01255  */
01256 void KUrlCompletion::postProcessMatch(QString* pMatch) const
01257 {
01258 //  kDebug() << *pMatch;
01259 
01260     if (!pMatch->isEmpty()) {
01261 
01262         // Add '/' to directories in file completion mode
01263         // unless it has already been done
01264         if (d->last_compl_type == CTFile
01265                 && pMatch->at(pMatch->length() - 1) != QLatin1Char('/')) {
01266             QString copy;
01267 
01268             if (pMatch->startsWith(QLatin1String("file:")))
01269                 copy = KUrl(*pMatch).toLocalFile();
01270             else
01271                 copy = *pMatch;
01272 
01273             expandTilde(copy);
01274             expandEnv(copy);
01275 #ifdef Q_WS_WIN
01276             DWORD dwAttr = GetFileAttributesW((LPCWSTR) copy.utf16());
01277             if (dwAttr == INVALID_FILE_ATTRIBUTES) {
01278                 kDebug() << "Could not get file attribs ( "
01279                          << GetLastError()
01280                          << " ) for "
01281                          << copy;
01282             } else if ((dwAttr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)
01283                 pMatch->append(QLatin1Char('/'));
01284 #else
01285             if (QDir::isRelativePath(copy))
01286                 copy.prepend(d->cwd + QLatin1Char('/'));
01287 
01288 //          kDebug() << "stat'ing" << copy;
01289 
01290             KDE_struct_stat sbuff;
01291 
01292             QByteArray file = QFile::encodeName(copy);
01293 
01294             if (KDE_stat(file.data(), &sbuff) == 0) {
01295                 if (S_ISDIR(sbuff.st_mode))
01296                     pMatch->append(QLatin1Char('/'));
01297             } else {
01298                 kDebug() << "Could not stat file" << copy;
01299             }
01300 #endif
01301         }
01302     }
01303 }
01304 
01305 void KUrlCompletion::postProcessMatches(QStringList* /*matches*/) const
01306 {
01307     // Maybe '/' should be added to directories here as in
01308     // postProcessMatch() but it would slow things down
01309     // when there are a lot of matches...
01310 }
01311 
01312 void KUrlCompletion::postProcessMatches(KCompletionMatches* /*matches*/) const
01313 {
01314     // Maybe '/' should be added to directories here as in
01315     // postProcessMatch() but it would slow things down
01316     // when there are a lot of matches...
01317 }
01318 
01319 void KUrlCompletion::customEvent(QEvent* e)
01320 {
01321     if (e->type() == CompletionMatchEvent::uniqueType()) {
01322 
01323         CompletionMatchEvent* matchEvent = static_cast<CompletionMatchEvent*>(e);
01324 
01325         matchEvent->completionThread()->wait();
01326 
01327         if (!d->isListedUrl(CTUser)) {
01328             stop();
01329             clear();
01330             d->addMatches(matchEvent->completionThread()->matches());
01331         } else {
01332             d->setListedUrl(CTUser);
01333         }
01334 
01335         if (d->userListThread == matchEvent->completionThread())
01336             d->userListThread = 0;
01337 
01338         if (d->dirListThread == matchEvent->completionThread())
01339             d->dirListThread = 0;
01340 
01341         delete matchEvent->completionThread();
01342     }
01343 }
01344 
01345 // static
01346 QString KUrlCompletion::replacedPath(const QString& text, bool replaceHome, bool replaceEnv)
01347 {
01348     if (text.isEmpty())
01349         return text;
01350 
01351     KUrlCompletionPrivate::MyURL url(text, QString());  // no need to replace something of our current cwd
01352     if (!url.kurl().isLocalFile())
01353         return text;
01354 
01355     url.filter(replaceHome, replaceEnv);
01356     return url.dir() + url.file();
01357 }
01358 
01359 
01360 QString KUrlCompletion::replacedPath(const QString& text) const
01361 {
01362     return replacedPath(text, d->replace_home, d->replace_env);
01363 }
01364 
01367 // Static functions
01368 
01369 /*
01370  * expandEnv
01371  *
01372  * Expand environment variables in text. Escaped '$' are ignored.
01373  * Return true if expansion was made.
01374  */
01375 static bool expandEnv(QString& text)
01376 {
01377     // Find all environment variables beginning with '$'
01378     //
01379     int pos = 0;
01380 
01381     bool expanded = false;
01382 
01383     while ((pos = text.indexOf(QLatin1Char('$'), pos)) != -1) {
01384 
01385         // Skip escaped '$'
01386         //
01387         if (pos > 0 && text.at(pos - 1) == QLatin1Char('\\')) {
01388             pos++;
01389         }
01390         // Variable found => expand
01391         //
01392         else {
01393             // Find the end of the variable = next '/' or ' '
01394             //
01395             int pos2 = text.indexOf(QLatin1Char(' '), pos + 1);
01396             int pos_tmp = text.indexOf(QLatin1Char('/'), pos + 1);
01397 
01398             if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
01399                 pos2 = pos_tmp;
01400 
01401             if (pos2 == -1)
01402                 pos2 = text.length();
01403 
01404             // Replace if the variable is terminated by '/' or ' '
01405             // and defined
01406             //
01407             if (pos2 >= 0) {
01408                 int len = pos2 - pos;
01409                 QString key = text.mid(pos + 1, len - 1);
01410                 QString value =
01411                     QString::fromLocal8Bit(qgetenv(key.toLocal8Bit()));
01412 
01413                 if (!value.isEmpty()) {
01414                     expanded = true;
01415                     text.replace(pos, len, value);
01416                     pos = pos + value.length();
01417                 } else {
01418                     pos = pos2;
01419                 }
01420             }
01421         }
01422     }
01423 
01424     return expanded;
01425 }
01426 
01427 /*
01428  * expandTilde
01429  *
01430  * Replace "~user" with the users home directory
01431  * Return true if expansion was made.
01432  */
01433 static bool expandTilde(QString& text)
01434 {
01435     if (text.isEmpty() || (text.at(0) != QLatin1Char('~')))
01436         return false;
01437 
01438     bool expanded = false;
01439 
01440     // Find the end of the user name = next '/' or ' '
01441     //
01442     int pos2 = text.indexOf(QLatin1Char(' '), 1);
01443     int pos_tmp = text.indexOf(QLatin1Char('/'), 1);
01444 
01445     if (pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2))
01446         pos2 = pos_tmp;
01447 
01448     if (pos2 == -1)
01449         pos2 = text.length();
01450 
01451     // Replace ~user if the user name is terminated by '/' or ' '
01452     //
01453     if (pos2 >= 0) {
01454 
01455         QString user = text.mid(1, pos2 - 1);
01456         QString dir;
01457 
01458         // A single ~ is replaced with $HOME
01459         //
01460         if (user.isEmpty()) {
01461             dir = QDir::homePath();
01462         }
01463         // ~user is replaced with the dir from passwd
01464         //
01465         else {
01466             struct passwd* pw = ::getpwnam(user.toLocal8Bit());
01467 
01468             if (pw)
01469                 dir = QFile::decodeName(pw->pw_dir);
01470 
01471             ::endpwent();
01472         }
01473 
01474         if (!dir.isEmpty()) {
01475             expanded = true;
01476             text.replace(0, pos2, dir);
01477         }
01478     }
01479 
01480     return expanded;
01481 }
01482 
01483 /*
01484  * unescape
01485  *
01486  * Remove escapes and return the result in a new string
01487  *
01488  */
01489 static QString unescape(const QString& text)
01490 {
01491     QString result;
01492 
01493     for (int pos = 0; pos < text.length(); pos++)
01494         if (text.at(pos) != QLatin1Char('\\'))
01495             result.insert(result.length(), text.at(pos));
01496 
01497     return result;
01498 }
01499 
01500 #include "kurlcompletion.moc"

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal