• Skip to content
  • Skip to link menu
KDE 4.3 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • 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/QMutableStringListIterator>
00035 #include <QtCore/QRegExp>
00036 #include <QtCore/QTimer>
00037 #include <QtCore/QDir>
00038 #include <QtCore/QDirIterator>
00039 #include <QtCore/QFile>
00040 #include <QtCore/QTextIStream>
00041 #include <QtCore/QThread>
00042 #include <QtGui/QActionEvent>
00043 
00044 #include <kapplication.h>
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 <kde_file.h>
00053 
00054 #include <sys/types.h>
00055 #include <dirent.h>
00056 #include <unistd.h>
00057 #include <sys/stat.h>
00058 #include <pwd.h>
00059 #include <time.h>
00060 #include <sys/param.h>
00061 #include <kconfiggroup.h>
00062 
00063 #ifdef Q_WS_WIN
00064 #include <kkernel_win.h>
00065 #endif
00066 
00067 static bool expandTilde(QString &);
00068 static bool expandEnv(QString &);
00069 
00070 static QString unescape(const QString &text);
00071 
00072 // Permission mask for files that are executable by
00073 // user, group or other
00074 #define MODE_EXE (S_IXUSR | S_IXGRP | S_IXOTH)
00075 
00076 // Constants for types of completion
00077 enum ComplType {CTNone=0, CTEnv, CTUser, CTMan, CTExe, CTFile, CTUrl, CTInfo};
00078 
00079 class CompletionThread;
00080 
00083 // KUrlCompletionPrivate
00084 //
00085 class KUrlCompletionPrivate
00086 {
00087 public:
00088     KUrlCompletionPrivate(KUrlCompletion *parent)
00089     : q(parent),
00090       url_auto_completion(true),
00091       userListThread(0),
00092       dirListThread(0)
00093   {
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 { return m_completionThread; }
00192     static Type uniqueType() { return Type(User + 61080); }
00193 
00194 private:
00195     CompletionThread *m_completionThread;
00196 };
00197 
00198 class CompletionThread : public QThread
00199 {
00200 protected:
00201     CompletionThread( KUrlCompletionPrivate *receiver ) :
00202         QThread(),
00203         m_prepend( receiver->prepend ),
00204         m_complete_url( receiver->complete_url ),
00205         m_receiver( receiver ),
00206         m_terminationRequested( false )
00207     {}
00208 
00209 public:
00210     void requestTermination() { m_terminationRequested = true; }
00211     QStringList matches() const { return m_matches; }
00212 
00213 protected:
00214     void addMatch( const QString &match ) { m_matches.append( match ); }
00215     bool terminationRequested() const { return m_terminationRequested; }
00216     void done()
00217     {
00218         if ( !m_terminationRequested )
00219             qApp->postEvent( m_receiver->q, new CompletionMatchEvent( this ) );
00220         else
00221             deleteLater();
00222     }
00223 
00224     const QString m_prepend;
00225     const bool m_complete_url; // if true completing a URL (i.e. 'm_prepend' is a URL), otherwise a path
00226 
00227 private:
00228     KUrlCompletionPrivate *m_receiver;
00229     QStringList m_matches;
00230     bool m_terminationRequested;
00231 };
00232 
00238 class UserListThread : public CompletionThread
00239 {
00240 public:
00241     UserListThread( KUrlCompletionPrivate *receiver ) :
00242         CompletionThread( receiver )
00243     {}
00244 
00245 protected:
00246     virtual void run()
00247     {
00248         static const QChar tilde = '~';
00249 
00250         // we don't need to handle prepend here, right? ~user is always at pos 0
00251         assert(m_prepend.isEmpty());
00252         struct passwd *pw;
00253         while ( ( pw = ::getpwent() ) && !terminationRequested() )
00254             addMatch( tilde + QString::fromLocal8Bit( pw->pw_name ) );
00255 
00256         ::endpwent();
00257 
00258         addMatch( QString( tilde ) );
00259 
00260         done();
00261     }
00262 };
00263 
00264 class DirectoryListThread : public CompletionThread
00265 {
00266 public:
00267     DirectoryListThread( KUrlCompletionPrivate *receiver,
00268                          const QStringList &dirList,
00269                          const QString &filter,
00270                          bool onlyExe,
00271                          bool onlyDir,
00272                          bool noHidden,
00273                          bool appendSlashToDir ) :
00274         CompletionThread( receiver ),
00275         m_dirList( dirList ),
00276         m_filter(  filter  ),
00277         m_onlyExe( onlyExe ),
00278         m_onlyDir( onlyDir ),
00279         m_noHidden( noHidden ),
00280         m_appendSlashToDir( appendSlashToDir )
00281     {}
00282 
00283     virtual void run();
00284 
00285 private:
00286     QStringList m_dirList;
00287     QString m_filter;
00288     bool m_onlyExe;
00289     bool m_onlyDir;
00290     bool m_noHidden;
00291     bool m_appendSlashToDir;
00292 };
00293 
00294 void DirectoryListThread::run()
00295 {
00296     // Thread safety notes:
00297     //
00298     // There very possibly may be thread safety issues here, but I've done a check
00299     // of all of the things that would seem to be problematic.  Here are a few
00300     // things that I have checked to be safe here (some used indirectly):
00301     //
00302     // QDir::currentPath(), QDir::setCurrent(), QFile::decodeName(), QFile::encodeName()
00303     // QString::fromLocal8Bit(), QString::toLocal8Bit(), QTextCodec::codecForLocale()
00304     //
00305     // Also see (for POSIX functions):
00306     // http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_09.html
00307 
00308     // 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();
00309 
00310     QStringList::ConstIterator end = m_dirList.constEnd();
00311     for ( QStringList::ConstIterator it = m_dirList.constBegin();
00312           it != end && !terminationRequested();
00313           ++it )
00314     {
00315         // kDebug() << "Scanning directory" << *it;
00316 
00317         // A trick from KIO that helps performance by a little bit:
00318         // chdir to the directory so we won't have to deal with full paths
00319         // with stat()
00320 
00321         QString path = QDir::currentPath();
00322         QDir::setCurrent( *it );
00323 
00324         QDir::Filters iterator_filter = (m_noHidden ? QDir::Filter(0) : QDir::Hidden) | QDir::Readable | QDir::NoDotAndDotDot;
00325 
00326         if ( m_onlyExe )
00327             iterator_filter |= (QDir::Dirs | QDir::Files | QDir::Executable);
00328         else if ( m_onlyDir )
00329             iterator_filter |= QDir::Dirs;
00330         else
00331             iterator_filter |= (QDir::Dirs | QDir::Files);
00332 
00333         QDirIterator current_dir_iterator( *it, iterator_filter);
00334 
00335         while (current_dir_iterator.hasNext()) {
00336             current_dir_iterator.next();
00337 
00338             QFileInfo file_info = current_dir_iterator.fileInfo();
00339             const QString file_name = file_info.fileName();
00340 
00341             //kDebug() << "Found" << file_name;
00342 
00343             if ( m_filter.isEmpty() || file_name.startsWith( m_filter ) ) {
00344 
00345                 QString toAppend = m_complete_url ? QUrl::toPercentEncoding(file_name) : file_name;
00346                 // Add '/' to directories
00347                 if ( m_appendSlashToDir && file_info.isDir() )
00348                     toAppend.append( QLatin1Char( '/' ) );
00349 
00350                 addMatch( m_prepend + toAppend );
00351             }
00352         }
00353 
00354         // chdir to the original directory
00355         QDir::setCurrent( path );
00356     }
00357 
00358     done();
00359 }
00360 
00361 KUrlCompletionPrivate::~KUrlCompletionPrivate()
00362 {
00363     if ( userListThread )
00364         userListThread->requestTermination();
00365     if ( dirListThread )
00366         dirListThread->requestTermination();
00367 }
00368 
00371 // MyURL - wrapper for KUrl with some different functionality
00372 //
00373 
00374 class KUrlCompletionPrivate::MyURL
00375 {
00376 public:
00377     MyURL(const QString &url, const QString &cwd);
00378     MyURL(const MyURL &url);
00379     ~MyURL();
00380 
00381     KUrl *kurl() const { return m_kurl; }
00382 
00383     QString protocol() const { return m_kurl->protocol(); }
00384     // The directory with a trailing '/'
00385         QString dir() const { return m_kurl->directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash); }
00386     QString file() const { return m_kurl->fileName(KUrl::ObeyTrailingSlash); }
00387 
00388     // The initial, unparsed, url, as a string.
00389     QString url() const { return m_url; }
00390 
00391     // Is the initial string a URL, or just a path (whether absolute or relative)
00392     bool isURL() const { return m_isURL; }
00393 
00394     void filter( bool replace_user_dir, bool replace_env );
00395 
00396 private:
00397     void init(const QString &url, const QString &cwd);
00398 
00399     KUrl *m_kurl;
00400     QString m_url;
00401     bool m_isURL;
00402 };
00403 
00404 KUrlCompletionPrivate::MyURL::MyURL(const QString &_url, const QString &cwd)
00405 {
00406     init(_url, cwd);
00407 }
00408 
00409 KUrlCompletionPrivate::MyURL::MyURL(const MyURL &_url)
00410 {
00411     m_kurl = new KUrl( *(_url.m_kurl) );
00412     m_url = _url.m_url;
00413     m_isURL = _url.m_isURL;
00414 }
00415 
00416 void KUrlCompletionPrivate::MyURL::init(const QString &_url, const QString &cwd)
00417 {
00418     // Save the original text
00419     m_url = _url;
00420 
00421     // Non-const copy
00422     QString url_copy = _url;
00423 
00424     // Special shortcuts for "man:" and "info:"
00425     if ( url_copy.startsWith( QLatin1Char('#') ) ) {
00426         if ( url_copy.length() > 1 && url_copy.at(1) == QLatin1Char('#') )
00427             url_copy.replace( 0, 2, QLatin1String("info:") );
00428         else
00429             url_copy.replace( 0, 1, QLatin1String("man:") );
00430     }
00431 
00432     // Look for a protocol in 'url'
00433     QRegExp protocol_regex = QRegExp( "^(?![A-Za-z]:)[^/\\s\\\\]*:" );
00434 
00435     // Assume "file:" or whatever is given by 'cwd' if there is
00436     // no protocol.  (KUrl does this only for absolute paths)
00437     if ( protocol_regex.indexIn( url_copy ) == 0 )
00438     {
00439         m_kurl = new KUrl( url_copy );
00440         m_isURL = true;
00441     }
00442     else // relative path or ~ or $something
00443     {
00444         m_isURL = false;
00445         if ( !QDir::isRelativePath(url_copy) ||
00446              url_copy.startsWith( QLatin1Char('~') ) ||
00447              url_copy.startsWith( QLatin1Char('$') ))
00448         {
00449             m_kurl = new KUrl;
00450             m_kurl->setPath( url_copy );
00451         }
00452         else
00453         {
00454             if ( cwd.isEmpty() ) {
00455                 m_kurl = new KUrl( url_copy );
00456             } else {
00457                 m_kurl = new KUrl( cwd );
00458                 m_kurl->addPath( url_copy );
00459             }
00460         }
00461     }
00462 }
00463 
00464 KUrlCompletionPrivate::MyURL::~MyURL()
00465 {
00466     delete m_kurl;
00467 }
00468 
00469 void KUrlCompletionPrivate::MyURL::filter( bool replace_user_dir, bool replace_env )
00470 {
00471     QString d = dir() + file();
00472     if ( replace_user_dir ) expandTilde( d );
00473     if ( replace_env ) expandEnv( d );
00474     m_kurl->setPath( d );
00475 }
00476 
00479 // KUrlCompletion
00480 //
00481 
00482 KUrlCompletion::KUrlCompletion() : KCompletion(),d(new KUrlCompletionPrivate(this))
00483 {
00484     d->init();
00485 }
00486 
00487 
00488 KUrlCompletion::KUrlCompletion( Mode _mode ) : KCompletion(),d(new KUrlCompletionPrivate(this))
00489 {
00490     d->init();
00491     setMode ( _mode );
00492 }
00493 
00494 KUrlCompletion::~KUrlCompletion()
00495 {
00496     stop();
00497     delete d;
00498 }
00499 
00500 
00501 void KUrlCompletionPrivate::init()
00502 {
00503     cwd = QDir::homePath();
00504 
00505     replace_home = true;
00506     replace_env = true;
00507     last_no_hidden = false;
00508     last_compl_type = 0;
00509     list_job = 0L;
00510     mode = KUrlCompletion::FileCompletion;
00511 
00512     // Read settings
00513     KConfigGroup cg( KGlobal::config(), "URLCompletion" );
00514 
00515     url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
00516     popup_append_slash = cg.readEntry("popupAppendSlash", true);
00517     onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
00518 
00519     q->setIgnoreCase(true);
00520 }
00521 
00522 void KUrlCompletion::setDir(const QString &_dir)
00523 {
00524     d->cwd = _dir;
00525 }
00526 
00527 QString KUrlCompletion::dir() const
00528 {
00529     return d->cwd;
00530 }
00531 
00532 KUrlCompletion::Mode KUrlCompletion::mode() const
00533 {
00534     return d->mode;
00535 }
00536 
00537 void KUrlCompletion::setMode( Mode _mode )
00538 {
00539     d->mode = _mode;
00540 }
00541 
00542 bool KUrlCompletion::replaceEnv() const
00543 {
00544     return d->replace_env;
00545 }
00546 
00547 void KUrlCompletion::setReplaceEnv( bool replace )
00548 {
00549     d->replace_env = replace;
00550 }
00551 
00552 bool KUrlCompletion::replaceHome() const
00553 {
00554     return d->replace_home;
00555 }
00556 
00557 void KUrlCompletion::setReplaceHome( bool replace )
00558 {
00559     d->replace_home = replace;
00560 }
00561 
00562 /*
00563  * makeCompletion()
00564  *
00565  * Entry point for file name completion
00566  */
00567 QString KUrlCompletion::makeCompletion(const QString &text)
00568 {
00569     //kDebug() << text << "d->cwd=" << d->cwd;
00570 
00571   KUrlCompletionPrivate::MyURL url(text, d->cwd);
00572 
00573     d->compl_text = text;
00574 
00575     // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
00576     // This is what gets prepended to the directory-listing matches.
00577     int toRemove = url.file().length() - url.kurl()->query().length();
00578     if ( url.kurl()->hasRef() )
00579         toRemove += url.kurl()->ref().length() + 1;
00580     d->prepend = text.left( text.length() - toRemove );
00581     d->complete_url = url.isURL();
00582 
00583     QString aMatch;
00584 
00585     // Environment variables
00586     //
00587     if ( d->replace_env && d->envCompletion( url, &aMatch ) )
00588         return aMatch;
00589 
00590     // User directories
00591     //
00592     if ( d->replace_home && d->userCompletion( url, &aMatch ) )
00593         return aMatch;
00594 
00595     // Replace user directories and variables
00596     url.filter( d->replace_home, d->replace_env );
00597 
00598     //kDebug() << "Filtered: proto=" << url.protocol()
00599     //          << ", dir=" << url.dir()
00600     //          << ", file=" << url.file()
00601     //          << ", kurl url=" << *url.kurl();
00602 
00603     if ( d->mode == ExeCompletion ) {
00604         // Executables
00605         //
00606         if ( d->exeCompletion( url, &aMatch ) )
00607             return aMatch;
00608 
00609         // KRun can run "man:" and "info:" etc. so why not treat them
00610         // as executables...
00611 
00612         if ( d->urlCompletion( url, &aMatch ) )
00613             return aMatch;
00614     }
00615     else {
00616         // Local files, directories
00617         //
00618         if ( d->fileCompletion( url, &aMatch ) )
00619             return aMatch;
00620 
00621         // All other...
00622         //
00623         if ( d->urlCompletion( url, &aMatch ) )
00624             return aMatch;
00625     }
00626 
00627     d->setListedUrl( CTNone );
00628     stop();
00629 
00630     return QString();
00631 }
00632 
00633 /*
00634  * finished
00635  *
00636  * Go on and call KCompletion.
00637  * Called when all matches have been added
00638  */
00639 QString KUrlCompletionPrivate::finished()
00640 {
00641     if ( last_compl_type == CTInfo )
00642         return q->KCompletion::makeCompletion( compl_text.toLower() );
00643     else
00644         return q->KCompletion::makeCompletion( compl_text );
00645 }
00646 
00647 /*
00648  * isRunning
00649  *
00650  * Return true if either a KIO job or the DirLister
00651  * is running
00652  */
00653 bool KUrlCompletion::isRunning() const
00654 {
00655     return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
00656 }
00657 
00658 /*
00659  * stop
00660  *
00661  * Stop and delete a running KIO job or the DirLister
00662  */
00663 void KUrlCompletion::stop()
00664 {
00665     if ( d->list_job ) {
00666         d->list_job->kill();
00667         d->list_job = 0L;
00668     }
00669 
00670     while ( !d->list_urls.isEmpty() ) {
00671         delete d->list_urls.takeFirst();
00672     }
00673 
00674     if ( d->dirListThread ) {
00675         d->dirListThread->requestTermination();
00676         d->dirListThread = 0;
00677     }
00678 }
00679 
00680 /*
00681  * Keep track of the last listed directory
00682  */
00683 void KUrlCompletionPrivate::setListedUrl( int complType,
00684                                    const QString& directory,
00685                                    const QString& filter,
00686                                    bool no_hidden )
00687 {
00688     last_compl_type = complType;
00689     last_path_listed = directory;
00690     last_file_listed = filter;
00691     last_no_hidden = (int)no_hidden;
00692     last_prepend = prepend;
00693 }
00694 
00695 bool KUrlCompletionPrivate::isListedUrl( int complType,
00696                                   const QString& directory,
00697                                   const QString& filter,
00698                                   bool no_hidden )
00699 {
00700     return  last_compl_type == complType
00701             && ( last_path_listed == directory
00702                     || (directory.isEmpty() && last_path_listed.isEmpty()) )
00703             && ( filter.startsWith(last_file_listed)
00704                     || (filter.isEmpty() && last_file_listed.isEmpty()) )
00705             && last_no_hidden == (int)no_hidden
00706             && last_prepend == prepend; // e.g. relative path vs absolute
00707 }
00708 
00709 /*
00710  * isAutoCompletion
00711  *
00712  * Returns true if completion mode is Auto or Popup
00713  */
00714 bool KUrlCompletionPrivate::isAutoCompletion()
00715 {
00716     return q->completionMode() == KGlobalSettings::CompletionAuto
00717            || q->completionMode() == KGlobalSettings::CompletionPopup
00718            || q->completionMode() == KGlobalSettings::CompletionMan
00719            || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
00720 }
00723 // User directories
00724 //
00725 
00726 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00727 {
00728     if ( url.protocol() != QLatin1String("file")
00729           || !url.dir().isEmpty()
00730           || !url.file().startsWith( QLatin1Char('~') ) )
00731         return false;
00732 
00733     if ( !isListedUrl( CTUser ) ) {
00734         q->stop();
00735         q->clear();
00736 
00737         if ( !userListThread ) {
00738             userListThread = new UserListThread( this );
00739             userListThread->start();
00740 
00741             // If the thread finishes quickly make sure that the results
00742             // are added to the first matching case.
00743 
00744             userListThread->wait( 200 );
00745             const QStringList l = userListThread->matches();
00746             addMatches( l );
00747         }
00748     }
00749     *pMatch = finished();
00750     return true;
00751 }
00752 
00755 // Environment variables
00756 //
00757 
00758 #ifndef Q_OS_WIN
00759 extern char **environ; // Array of environment variables
00760 #endif
00761 
00762 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00763 {
00764     if ( url.file().isEmpty() || url.file().at(0) != QLatin1Char('$') )
00765         return false;
00766 
00767     if ( !isListedUrl( CTEnv ) ) {
00768         q->stop();
00769         q->clear();
00770 
00771         char **env = environ;
00772 
00773         QString dollar = QLatin1String("$");
00774 
00775         QStringList l;
00776 
00777         while ( *env ) {
00778             QString s = QString::fromLocal8Bit( *env );
00779 
00780             int pos = s.indexOf(QLatin1Char('='));
00781 
00782             if ( pos == -1 )
00783                 pos = s.length();
00784 
00785             if ( pos > 0 )
00786                 l.append( prepend + dollar + s.left(pos) );
00787 
00788             env++;
00789         }
00790 
00791         addMatches( l );
00792     }
00793 
00794     setListedUrl( CTEnv );
00795 
00796     *pMatch = finished();
00797     return true;
00798 }
00799 
00802 // Executables
00803 //
00804 
00805 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00806 {
00807     if ( url.protocol() != QLatin1String("file") )
00808         return false;
00809 
00810     QString directory = unescape( url.dir() ); // remove escapes
00811 
00812     // Find directories to search for completions, either
00813     //
00814     // 1. complete path given in url
00815     // 2. current directory (d->cwd)
00816     // 3. $PATH
00817     // 4. no directory at all
00818 
00819     QStringList dirList;
00820 
00821     if ( !QDir::isRelativePath(directory) ) {
00822         // complete path in url
00823         dirList.append( directory );
00824     }
00825     else if ( !directory.isEmpty() && !cwd.isEmpty() ) {
00826         // current directory
00827         dirList.append( cwd + QLatin1Char('/') + directory );
00828     }
00829     else if ( !url.file().isEmpty() ) {
00830         // $PATH
00831         dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
00832                 KPATH_SEPARATOR,QString::SkipEmptyParts);
00833 
00834         QStringList::Iterator it = dirList.begin();
00835 
00836         for ( ; it != dirList.end(); ++it )
00837             it->append(QLatin1Char('/'));
00838     }
00839 
00840     // No hidden files unless the user types "."
00841     bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
00842 
00843     // List files if needed
00844     //
00845     if ( !isListedUrl( CTExe, directory, url.file(), no_hidden_files ) )
00846     {
00847         q->stop();
00848         q->clear();
00849 
00850         setListedUrl( CTExe, directory, url.file(), no_hidden_files );
00851 
00852         *pMatch = listDirectories( dirList, url.file(), true, false, no_hidden_files );
00853     }
00854     else if ( !q->isRunning() ) {
00855         *pMatch = finished();
00856     }
00857     else {
00858         if ( dirListThread )
00859             setListedUrl( CTExe, directory, url.file(), no_hidden_files );
00860         pMatch->clear();
00861     }
00862 
00863     return true;
00864 }
00865 
00868 // Local files
00869 //
00870 
00871 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00872 {
00873     if ( url.protocol() != QLatin1String("file") )
00874         return false;
00875 
00876     QString directory = unescape( url.dir() );
00877 
00878     if (url.url().length() && url.url().at(0) == QLatin1Char('.'))
00879     {
00880         if (url.url().length() == 1)
00881         {
00882             *pMatch = ( q->completionMode() == KGlobalSettings::CompletionMan )?
00883                     QLatin1String(".") :
00884                     QLatin1String("..");
00885             return true;
00886         }
00887         else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.'))
00888         {
00889             *pMatch = QLatin1String("..");
00890             return true;
00891         }
00892     }
00893 
00894     //kDebug() << "fileCompletion" << url << "dir=" << dir;
00895 
00896     // Find directories to search for completions, either
00897     //
00898     // 1. complete path given in url
00899     // 2. current directory (d->cwd)
00900     // 3. no directory at all
00901 
00902     QStringList dirList;
00903 
00904     if ( !QDir::isRelativePath(directory) ) {
00905         // complete path in url
00906         dirList.append( directory );
00907     }
00908     else if ( !cwd.isEmpty() ) {
00909         // current directory
00910         QString dirToAdd = cwd;
00911         if ( !directory.isEmpty() ) {
00912             if ( !cwd.endsWith('/') )
00913                 dirToAdd.append( QLatin1Char('/') );
00914             dirToAdd.append( directory );
00915         }
00916         dirList.append( dirToAdd );
00917     }
00918 
00919     // No hidden files unless the user types "."
00920     bool no_hidden_files = !url.file().startsWith( QLatin1Char('.') );
00921 
00922     // List files if needed
00923     //
00924     if ( !isListedUrl( CTFile, directory, QString(), no_hidden_files ) )
00925     {
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     }
00941     else if ( !q->isRunning() ) {
00942         *pMatch = finished();
00943     }
00944     else {
00945         pMatch->clear();
00946     }
00947 
00948     return true;
00949 }
00950 
00953 // URLs not handled elsewhere...
00954 //
00955 
00956 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00957 {
00958     //kDebug() << *url.kurl();
00959     if (onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != QLatin1String(":local"))
00960         return false;
00961 
00962     // Use d->cwd as base url in case url is not absolute
00963     KUrl url_cwd( cwd );
00964 
00965     // Create an URL with the directory to be listed
00966     KUrl url_dir( url_cwd, url.kurl()->url() );
00967 
00968     // Don't try url completion if
00969     // 1. malformed url
00970     // 2. protocol that doesn't have listDir()
00971     // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything)
00972     // 4. auto or popup completion mode depending on settings
00973 
00974     bool man_or_info = ( url_dir.protocol() == QLatin1String("man")
00975                          || url_dir.protocol() == QLatin1String("info") );
00976 
00977     if ( !url_dir.isValid()
00978          || !KProtocolManager::supportsListing( url_dir )
00979          || ( !man_or_info
00980                      && ( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash).isEmpty()
00981                    || ( isAutoCompletion()
00982                         && !url_auto_completion ) ) ) ) {
00983                 return false;
00984         }
00985 
00986     url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
00987 
00988     // Remove escapes
00989     QString directory = unescape( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash) );
00990 
00991     url_dir.setPath( directory );
00992 
00993     // List files if needed
00994     //
00995     if ( !isListedUrl( CTUrl, url_dir.prettyUrl(), url.file() ) )
00996     {
00997         q->stop();
00998         q->clear();
00999 
01000         setListedUrl( CTUrl, url_dir.prettyUrl(), QString() );
01001 
01002         QList<KUrl*> url_list;
01003         url_list.append( new KUrl( url_dir ) );
01004 
01005         listUrls( url_list, QString(), false );
01006 
01007         pMatch->clear();
01008     }
01009     else if ( !q->isRunning() ) {
01010         *pMatch = finished();
01011     }
01012     else {
01013         pMatch->clear();
01014     }
01015 
01016     return true;
01017 }
01018 
01021 // Directory and URL listing
01022 //
01023 
01024 /*
01025  * addMatches
01026  *
01027  * Called to add matches to KCompletion
01028  */
01029 void KUrlCompletionPrivate::addMatches( const QStringList &matchList )
01030 {
01031     q->insertItems(matchList);
01032 }
01033 
01034 /*
01035  * listDirectories
01036  *
01037  * List files starting with 'filter' in the given directories,
01038  * either using DirLister or listURLs()
01039  *
01040  * In either case, addMatches() is called with the listed
01041  * files, and eventually finished() when the listing is done
01042  *
01043  * Returns the match if available, or QString() if
01044  * DirLister timed out or using kio
01045  */
01046 QString KUrlCompletionPrivate::listDirectories(
01047         const QStringList &dirList,
01048         const QString &filter,
01049         bool only_exe,
01050         bool only_dir,
01051         bool no_hidden,
01052         bool append_slash_to_dir)
01053 {
01054     assert( !q->isRunning() );
01055 
01056     if ( qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty() ) {
01057 
01058         //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
01059 
01060         // Don't use KIO
01061 
01062         if ( dirListThread )
01063             dirListThread->requestTermination();
01064 
01065         QStringList dirs;
01066 
01067                 QStringList::ConstIterator end = dirList.constEnd();
01068         for ( QStringList::ConstIterator it = dirList.constBegin();
01069               it != end;
01070               ++it )
01071         {
01072             KUrl url;
01073             url.setPath(*it);
01074             if ( KAuthorized::authorizeUrlAction( QLatin1String("list"), KUrl(), url ) )
01075                 dirs.append( *it );
01076         }
01077 
01078         dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir,
01079                                                  no_hidden, append_slash_to_dir );
01080         dirListThread->start();
01081         dirListThread->wait( 200 );
01082         addMatches( dirListThread->matches() );
01083 
01084         return finished();
01085     }
01086 
01087     // Use KIO
01088     //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
01089 
01090     QList<KUrl*> url_list;
01091 
01092     QStringList::ConstIterator it = dirList.constBegin();
01093     QStringList::ConstIterator end = dirList.constEnd();
01094 
01095     for ( ; it != end; ++it ) {
01096         url_list.append( new KUrl( *it ) );
01097     }
01098 
01099     listUrls( url_list, filter, only_exe, no_hidden );
01100     // Will call addMatches() and finished()
01101 
01102     return QString();
01103 }
01104 
01105 /*
01106  * listURLs
01107  *
01108  * Use KIO to list the given urls
01109  *
01110  * addMatches() is called with the listed files
01111  * finished() is called when the listing is done
01112  */
01113 void KUrlCompletionPrivate::listUrls(
01114         const QList<KUrl *> &urls,
01115         const QString &filter,
01116         bool only_exe,
01117         bool no_hidden )
01118 {
01119     assert( list_urls.isEmpty() );
01120     assert( list_job == 0L );
01121 
01122     list_urls = urls;
01123     list_urls_filter = filter;
01124     list_urls_only_exe = only_exe;
01125     list_urls_no_hidden = no_hidden;
01126 
01127     //kDebug() << "Listing URLs:" << *urls[0] << ",...";
01128 
01129     // Start it off by calling _k_slotIOFinished
01130     //
01131     // This will start a new list job as long as there
01132     // are urls in d->list_urls
01133     //
01134     _k_slotIOFinished(0);
01135 }
01136 
01137 /*
01138  * _k_slotEntries
01139  *
01140  * Receive files listed by KIO and call addMatches()
01141  */
01142 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01143 {
01144     QStringList matchList;
01145 
01146     KIO::UDSEntryList::ConstIterator it = entries.constBegin();
01147     const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
01148 
01149     QString filter = list_urls_filter;
01150 
01151     int filter_len = filter.length();
01152 
01153     // Iterate over all files
01154     //
01155     for (; it != end; ++it) {
01156         const KIO::UDSEntry& entry = *it;
01157         const QString url = entry.stringValue( KIO::UDSEntry::UDS_URL );
01158 
01159         QString entry_name;
01160         if (!url.isEmpty()) {
01161             // kDebug() << "url:" << url;
01162             entry_name = KUrl(url).fileName();
01163         } else {
01164             entry_name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
01165         }
01166 
01167         // kDebug() << "name:" << name;
01168 
01169         if ( (!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
01170              ( list_urls_no_hidden ||
01171                 entry_name.length() == 1 ||
01172                   ( entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.') ) ) )
01173             continue;
01174 
01175         const bool isDir = entry.isDir();
01176 
01177         if ( mode == KUrlCompletion::DirCompletion && !isDir )
01178             continue;
01179 
01180         if ( filter_len == 0 || entry_name.left(filter_len) == filter ) {
01181 
01182             QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
01183 
01184             if (isDir)
01185                 toAppend.append( QLatin1Char( '/' ) );
01186 
01187             if ( !list_urls_only_exe ||
01188                  (entry.numberValue( KIO::UDSEntry::UDS_ACCESS ) & MODE_EXE) // true if executable
01189                 ) {
01190                 matchList.append( prepend + toAppend );
01191             }
01192         }
01193     }
01194 
01195     addMatches( matchList );
01196 }
01197 
01198 /*
01199  * _k_slotIOFinished
01200  *
01201  * Called when a KIO job is finished.
01202  *
01203  * Start a new list job if there are still urls in
01204  * list_urls, otherwise call finished()
01205  */
01206 void KUrlCompletionPrivate::_k_slotIOFinished( KJob * job )
01207 {
01208     assert( job == list_job );
01209 
01210     if ( list_urls.isEmpty() ) {
01211 
01212         list_job = 0L;
01213 
01214         finished(); // will call KCompletion::makeCompletion()
01215 
01216     }
01217     else {
01218 
01219         KUrl *kurl = list_urls.takeFirst();
01220 
01221 //      list_urls.removeAll( kurl );
01222 
01223 //      kDebug() << "Start KIO::listDir" << *kurl;
01224 
01225         list_job = KIO::listDir( *kurl, KIO::HideProgressInfo );
01226         list_job->addMetaData("no-auth-prompt", "true");
01227 
01228         assert( list_job );
01229 
01230         q->connect( list_job,
01231                 SIGNAL(result(KJob*)),
01232                 SLOT(_k_slotIOFinished(KJob*)) );
01233 
01234         q->connect( list_job,
01235                 SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList&)),
01236                 SLOT( _k_slotEntries( KIO::Job*, const KIO::UDSEntryList&)) );
01237 
01238         delete kurl;
01239     }
01240 }
01241 
01244 
01245 /*
01246  * postProcessMatch, postProcessMatches
01247  *
01248  * Called by KCompletion before emitting match() and matches()
01249  *
01250  * Append '/' to directories for file completion. This is
01251  * done here to avoid stat()'ing a lot of files
01252  */
01253 void KUrlCompletion::postProcessMatch( QString *pMatch ) const
01254 {
01255 //  kDebug() << *pMatch;
01256 
01257     if ( !pMatch->isEmpty() ) {
01258 
01259         // Add '/' to directories in file completion mode
01260         // unless it has already been done
01261         if ( d->last_compl_type == CTFile
01262                && pMatch->at( pMatch->length()-1 ) != QLatin1Char('/') )
01263         {
01264             QString copy;
01265 
01266             if ( pMatch->startsWith( QLatin1String("file:") ) )
01267                 copy = KUrl(*pMatch).toLocalFile();
01268             else
01269                 copy = *pMatch;
01270 
01271             expandTilde( copy );
01272             expandEnv( copy );
01273 #ifdef Q_WS_WIN
01274             DWORD dwAttr = GetFileAttributesW( (LPCWSTR) copy.utf16() );
01275             if ( dwAttr == INVALID_FILE_ATTRIBUTES ) {
01276                 kDebug() << "Could not get file attribs ( "
01277                     << GetLastError()
01278                     << " ) for "
01279                     << copy;
01280                         } else
01281             if ( ( dwAttr & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
01282                 pMatch->append( QLatin1Char( '/' ) );
01283 #else
01284             if ( QDir::isRelativePath(copy) )
01285                 copy.prepend( d->cwd + QLatin1Char('/') );
01286 
01287 //          kDebug() << "stat'ing" << copy;
01288 
01289             KDE_struct_stat sbuff;
01290 
01291             QByteArray file = QFile::encodeName( copy );
01292 
01293             if ( KDE_stat( file.data(), &sbuff ) == 0 ) {
01294                 if ( S_ISDIR ( sbuff.st_mode ) )
01295                     pMatch->append( QLatin1Char( '/' ) );
01296             }
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         }
01332 
01333         d->setListedUrl( CTUser );
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                 }
01418                 else {
01419                     pos = pos2;
01420                 }
01421             }
01422         }
01423     }
01424 
01425     return expanded;
01426 }
01427 
01428 /*
01429  * expandTilde
01430  *
01431  * Replace "~user" with the users home directory
01432  * Return true if expansion was made.
01433  */
01434 static bool expandTilde(QString &text)
01435 {
01436     if ( text.isEmpty() || ( text.at(0) != QLatin1Char('~') ))
01437         return false;
01438 
01439     bool expanded = false;
01440 
01441     // Find the end of the user name = next '/' or ' '
01442     //
01443     int pos2 = text.indexOf( QLatin1Char(' '), 1 );
01444     int pos_tmp = text.indexOf( QLatin1Char('/'), 1 );
01445 
01446     if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01447         pos2 = pos_tmp;
01448 
01449     if ( pos2 == -1 )
01450         pos2 = text.length();
01451 
01452     // Replace ~user if the user name is terminated by '/' or ' '
01453     //
01454     if ( pos2 >= 0 ) {
01455 
01456         QString user = text.mid( 1, pos2-1 );
01457         QString dir;
01458 
01459         // A single ~ is replaced with $HOME
01460         //
01461         if ( user.isEmpty() ) {
01462             dir = QDir::homePath();
01463         }
01464         // ~user is replaced with the dir from passwd
01465         //
01466         else {
01467             struct passwd *pw = ::getpwnam( user.toLocal8Bit() );
01468 
01469             if ( pw )
01470                 dir = QFile::decodeName( pw->pw_dir );
01471 
01472             ::endpwent();
01473         }
01474 
01475         if ( !dir.isEmpty() ) {
01476             expanded = true;
01477             text.replace(0, pos2, dir);
01478         }
01479     }
01480 
01481     return expanded;
01482 }
01483 
01484 /*
01485  * unescape
01486  *
01487  * Remove escapes and return the result in a new string
01488  *
01489  */
01490 static QString unescape(const QString &text)
01491 {
01492     QString result;
01493 
01494     for (int pos = 0; pos < text.length(); pos++)
01495         if ( text.at(pos) != QLatin1Char('\\') )
01496             result.insert( result.length(), text.at(pos) );
01497 
01498     return result;
01499 }
01500 
01501 #include "kurlcompletion.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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