• Skip to content
  • Skip to link menu
KDE 4.6 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/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     : m_kurl(_url.m_kurl)
00411 {
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 = 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 = KUrl();
00450                         m_kurl.setPath( url_copy );
00451         }
00452         else
00453         {
00454             if ( cwd.isEmpty() ) {
00455                                 m_kurl = KUrl( url_copy );
00456             } else {
00457                                 m_kurl = KUrl( cwd );
00458                                 m_kurl.addPath( url_copy );
00459             }
00460         }
00461     }
00462 }
00463 
00464 KUrlCompletionPrivate::MyURL::~MyURL()
00465 {
00466 }
00467 
00468 void KUrlCompletionPrivate::MyURL::filter( bool replace_user_dir, bool replace_env )
00469 {
00470     QString d = dir() + file();
00471     if ( replace_user_dir ) expandTilde( d );
00472     if ( replace_env ) expandEnv( d );
00473         m_kurl.setPath( d );
00474 }
00475 
00478 // KUrlCompletion
00479 //
00480 
00481 KUrlCompletion::KUrlCompletion() : KCompletion(),d(new KUrlCompletionPrivate(this))
00482 {
00483     d->init();
00484 }
00485 
00486 
00487 KUrlCompletion::KUrlCompletion( Mode _mode )
00488     : KCompletion(),
00489     d(new KUrlCompletionPrivate(this))
00490 {
00491     d->init();
00492     setMode ( _mode );
00493 }
00494 
00495 KUrlCompletion::~KUrlCompletion()
00496 {
00497     stop();
00498         delete d;
00499 }
00500 
00501 
00502 void KUrlCompletionPrivate::init()
00503 {
00504     cwd = QDir::homePath();
00505 
00506     replace_home = true;
00507     replace_env = true;
00508     last_no_hidden = false;
00509     last_compl_type = 0;
00510     list_job = 0L;
00511     mode = KUrlCompletion::FileCompletion;
00512 
00513     // Read settings
00514     KConfigGroup cg( KGlobal::config(), "URLCompletion" );
00515 
00516     url_auto_completion = cg.readEntry("alwaysAutoComplete", true);
00517     popup_append_slash = cg.readEntry("popupAppendSlash", true);
00518     onlyLocalProto = cg.readEntry("LocalProtocolsOnly", false);
00519 
00520     q->setIgnoreCase(true);
00521 }
00522 
00523 void KUrlCompletion::setDir(const QString &_dir)
00524 {
00525     d->cwd = _dir;
00526 }
00527 
00528 QString KUrlCompletion::dir() const
00529 {
00530     return d->cwd;
00531 }
00532 
00533 KUrlCompletion::Mode KUrlCompletion::mode() const
00534 {
00535     return d->mode;
00536 }
00537 
00538 void KUrlCompletion::setMode( Mode _mode )
00539 {
00540     d->mode = _mode;
00541 }
00542 
00543 bool KUrlCompletion::replaceEnv() const
00544 {
00545     return d->replace_env;
00546 }
00547 
00548 void KUrlCompletion::setReplaceEnv( bool replace )
00549 {
00550     d->replace_env = replace;
00551 }
00552 
00553 bool KUrlCompletion::replaceHome() const
00554 {
00555     return d->replace_home;
00556 }
00557 
00558 void KUrlCompletion::setReplaceHome( bool replace )
00559 {
00560     d->replace_home = replace;
00561 }
00562 
00563 /*
00564  * makeCompletion()
00565  *
00566  * Entry point for file name completion
00567  */
00568 QString KUrlCompletion::makeCompletion(const QString &text)
00569 {
00570     //kDebug() << text << "d->cwd=" << d->cwd;
00571 
00572   KUrlCompletionPrivate::MyURL url(text, d->cwd);
00573 
00574     d->compl_text = text;
00575 
00576     // Set d->prepend to the original URL, with the filename [and ref/query] stripped.
00577     // This is what gets prepended to the directory-listing matches.
00578         int toRemove = url.file().length() - url.kurl().query().length();
00579         if ( url.kurl().hasRef() )
00580                 toRemove += url.kurl().ref().length() + 1;
00581     d->prepend = text.left( text.length() - toRemove );
00582     d->complete_url = url.isURL();
00583 
00584     QString aMatch;
00585 
00586     // Environment variables
00587     //
00588     if ( d->replace_env && d->envCompletion( url, &aMatch ) )
00589         return aMatch;
00590 
00591     // User directories
00592     //
00593     if ( d->replace_home && d->userCompletion( url, &aMatch ) )
00594         return aMatch;
00595 
00596     // Replace user directories and variables
00597     url.filter( d->replace_home, d->replace_env );
00598 
00599     //kDebug() << "Filtered: proto=" << url.protocol()
00600     //          << ", dir=" << url.dir()
00601     //          << ", file=" << url.file()
00602     //          << ", kurl url=" << *url.kurl();
00603 
00604     if ( d->mode == ExeCompletion ) {
00605         // Executables
00606         //
00607         if ( d->exeCompletion( url, &aMatch ) )
00608             return aMatch;
00609 
00610         // KRun can run "man:" and "info:" etc. so why not treat them
00611         // as executables...
00612 
00613         if ( d->urlCompletion( url, &aMatch ) )
00614             return aMatch;
00615     }
00616     else {
00617         // Local files, directories
00618         //
00619         if ( d->fileCompletion( url, &aMatch ) )
00620             return aMatch;
00621 
00622         // All other...
00623         //
00624         if ( d->urlCompletion( url, &aMatch ) )
00625             return aMatch;
00626     }
00627 
00628     d->setListedUrl( CTNone );
00629     stop();
00630 
00631     return QString();
00632 }
00633 
00634 /*
00635  * finished
00636  *
00637  * Go on and call KCompletion.
00638  * Called when all matches have been added
00639  */
00640 QString KUrlCompletionPrivate::finished()
00641 {
00642     if ( last_compl_type == CTInfo )
00643         return q->KCompletion::makeCompletion( compl_text.toLower() );
00644     else
00645         return q->KCompletion::makeCompletion( compl_text );
00646 }
00647 
00648 /*
00649  * isRunning
00650  *
00651  * Return true if either a KIO job or the DirLister
00652  * is running
00653  */
00654 bool KUrlCompletion::isRunning() const
00655 {
00656     return d->list_job || (d->dirListThread && !d->dirListThread->isFinished());
00657 }
00658 
00659 /*
00660  * stop
00661  *
00662  * Stop and delete a running KIO job or the DirLister
00663  */
00664 void KUrlCompletion::stop()
00665 {
00666     if ( d->list_job ) {
00667         d->list_job->kill();
00668         d->list_job = 0L;
00669         }
00670 
00671     if ( d->dirListThread ) {
00672         d->dirListThread->requestTermination();
00673         d->dirListThread = 0;
00674     }
00675 }
00676 
00677 /*
00678  * Keep track of the last listed directory
00679  */
00680 void KUrlCompletionPrivate::setListedUrl( int complType,
00681                                    const QString& directory,
00682                                    const QString& filter,
00683                                    bool no_hidden )
00684 {
00685     last_compl_type = complType;
00686     last_path_listed = directory;
00687     last_file_listed = filter;
00688     last_no_hidden = (int)no_hidden;
00689     last_prepend = prepend;
00690 }
00691 
00692 bool KUrlCompletionPrivate::isListedUrl( int complType,
00693                                   const QString& directory,
00694                                   const QString& filter,
00695                                   bool no_hidden )
00696 {
00697     return  last_compl_type == complType
00698             && ( last_path_listed == directory
00699                     || (directory.isEmpty() && last_path_listed.isEmpty()) )
00700             && ( filter.startsWith(last_file_listed)
00701                     || (filter.isEmpty() && last_file_listed.isEmpty()) )
00702             && last_no_hidden == (int)no_hidden
00703             && last_prepend == prepend; // e.g. relative path vs absolute
00704 }
00705 
00706 /*
00707  * isAutoCompletion
00708  *
00709  * Returns true if completion mode is Auto or Popup
00710  */
00711 bool KUrlCompletionPrivate::isAutoCompletion()
00712 {
00713     return q->completionMode() == KGlobalSettings::CompletionAuto
00714            || q->completionMode() == KGlobalSettings::CompletionPopup
00715            || q->completionMode() == KGlobalSettings::CompletionMan
00716            || q->completionMode() == KGlobalSettings::CompletionPopupAuto;
00717 }
00720 // User directories
00721 //
00722 
00723 bool KUrlCompletionPrivate::userCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00724 {
00725     if ( url.protocol() != QLatin1String("file")
00726           || !url.dir().isEmpty()
00727           || !url.file().startsWith( QLatin1Char('~') ) )
00728         return false;
00729 
00730     if ( !isListedUrl( CTUser ) ) {
00731         q->stop();
00732         q->clear();
00733 
00734         if ( !userListThread ) {
00735             userListThread = new UserListThread( this );
00736             userListThread->start();
00737 
00738             // If the thread finishes quickly make sure that the results
00739             // are added to the first matching case.
00740 
00741             userListThread->wait( 200 );
00742             const QStringList l = userListThread->matches();
00743             addMatches( l );
00744         }
00745     }
00746     *pMatch = finished();
00747     return true;
00748 }
00749 
00752 // Environment variables
00753 //
00754 
00755 #ifndef Q_OS_WIN
00756 extern char **environ; // Array of environment variables
00757 #endif
00758 
00759 bool KUrlCompletionPrivate::envCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00760 {
00761     if ( url.file().isEmpty() || url.file().at(0) != QLatin1Char('$') )
00762         return false;
00763 
00764     if ( !isListedUrl( CTEnv ) ) {
00765         q->stop();
00766         q->clear();
00767 
00768         char **env = environ;
00769 
00770         QString dollar = QLatin1String("$");
00771 
00772         QStringList l;
00773 
00774         while ( *env ) {
00775             QString s = QString::fromLocal8Bit( *env );
00776 
00777             int pos = s.indexOf(QLatin1Char('='));
00778 
00779             if ( pos == -1 )
00780                 pos = s.length();
00781 
00782             if ( pos > 0 )
00783                 l.append( prepend + dollar + s.left(pos) );
00784 
00785             env++;
00786         }
00787 
00788         addMatches( l );
00789     }
00790 
00791     setListedUrl( CTEnv );
00792 
00793     *pMatch = finished();
00794     return true;
00795 }
00796 
00799 // Executables
00800 //
00801 
00802 bool KUrlCompletionPrivate::exeCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00803 {
00804     if ( url.protocol() != QLatin1String("file") )
00805         return false;
00806 
00807     QString directory = unescape( url.dir() ); // remove escapes
00808 
00809     // Find directories to search for completions, either
00810     //
00811     // 1. complete path given in url
00812     // 2. current directory (d->cwd)
00813     // 3. $PATH
00814     // 4. no directory at all
00815 
00816     QStringList dirList;
00817 
00818     if ( !QDir::isRelativePath(directory) ) {
00819         // complete path in url
00820         dirList.append( directory );
00821     }
00822     else if ( !directory.isEmpty() && !cwd.isEmpty() ) {
00823         // current directory
00824         dirList.append( cwd + QLatin1Char('/') + directory );
00825     }
00826     else if ( !url.file().isEmpty() ) {
00827         // $PATH
00828         dirList = QString::fromLocal8Bit(qgetenv("PATH")).split(
00829                 KPATH_SEPARATOR,QString::SkipEmptyParts);
00830 
00831         QStringList::Iterator it = dirList.begin();
00832 
00833         for ( ; it != dirList.end(); ++it )
00834             it->append(QLatin1Char('/'));
00835     }
00836 
00837     // No hidden files unless the user types "."
00838     bool no_hidden_files = url.file().isEmpty() || url.file().at(0) != QLatin1Char('.');
00839 
00840     // List files if needed
00841     //
00842     if ( !isListedUrl( CTExe, directory, url.file(), no_hidden_files ) )
00843     {
00844         q->stop();
00845         q->clear();
00846 
00847         setListedUrl( CTExe, directory, url.file(), no_hidden_files );
00848 
00849         *pMatch = listDirectories( dirList, url.file(), true, false, no_hidden_files );
00850     }
00851     else if ( !q->isRunning() ) {
00852         *pMatch = finished();
00853     }
00854     else {
00855         if ( dirListThread )
00856             setListedUrl( CTExe, directory, url.file(), no_hidden_files );
00857         pMatch->clear();
00858     }
00859 
00860     return true;
00861 }
00862 
00865 // Local files
00866 //
00867 
00868 bool KUrlCompletionPrivate::fileCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00869 {
00870     if ( url.protocol() != QLatin1String("file") )
00871         return false;
00872 
00873     QString directory = unescape( url.dir() );
00874 
00875     if (url.url().length() && url.url().at(0) == QLatin1Char('.'))
00876     {
00877         if (url.url().length() == 1)
00878         {
00879             *pMatch = ( q->completionMode() == KGlobalSettings::CompletionMan )?
00880                     QLatin1String(".") :
00881                     QLatin1String("..");
00882             return true;
00883         }
00884         else if (url.url().length() == 2 && url.url().at(1) == QLatin1Char('.'))
00885         {
00886             *pMatch = QLatin1String("..");
00887             return true;
00888         }
00889     }
00890 
00891     //kDebug() << "fileCompletion" << url << "dir=" << dir;
00892 
00893     // Find directories to search for completions, either
00894     //
00895     // 1. complete path given in url
00896     // 2. current directory (d->cwd)
00897     // 3. no directory at all
00898 
00899     QStringList dirList;
00900 
00901     if ( !QDir::isRelativePath(directory) ) {
00902         // complete path in url
00903         dirList.append( directory );
00904     }
00905     else if ( !cwd.isEmpty() ) {
00906         // current directory
00907         QString dirToAdd = cwd;
00908         if ( !directory.isEmpty() ) {
00909             if ( !cwd.endsWith('/') )
00910                 dirToAdd.append( QLatin1Char('/') );
00911             dirToAdd.append( directory );
00912         }
00913         dirList.append( dirToAdd );
00914     }
00915 
00916     // No hidden files unless the user types "."
00917     bool no_hidden_files = !url.file().startsWith( QLatin1Char('.') );
00918 
00919     // List files if needed
00920     //
00921     if ( !isListedUrl( CTFile, directory, QString(), no_hidden_files ) )
00922     {
00923         q->stop();
00924         q->clear();
00925 
00926         setListedUrl( CTFile, directory, QString(), no_hidden_files );
00927 
00928         // Append '/' to directories in Popup mode?
00929         bool append_slash = ( popup_append_slash
00930             && (q->completionMode() == KGlobalSettings::CompletionPopup ||
00931             q->completionMode() == KGlobalSettings::CompletionPopupAuto ) );
00932 
00933         bool only_dir = ( mode == KUrlCompletion::DirCompletion );
00934 
00935         *pMatch = listDirectories( dirList, QString(), false, only_dir, no_hidden_files,
00936                                   append_slash );
00937     }
00938     else if ( !q->isRunning() ) {
00939         *pMatch = finished();
00940     }
00941     else {
00942         pMatch->clear();
00943     }
00944 
00945     return true;
00946 }
00947 
00950 // URLs not handled elsewhere...
00951 //
00952 
00953 bool KUrlCompletionPrivate::urlCompletion(const KUrlCompletionPrivate::MyURL &url, QString *pMatch)
00954 {
00955     //kDebug() << *url.kurl();
00956     if (onlyLocalProto && KProtocolInfo::protocolClass(url.protocol()) != QLatin1String(":local"))
00957         return false;
00958 
00959     // Use d->cwd as base url in case url is not absolute
00960     KUrl url_cwd( cwd );
00961 
00962     // Create an URL with the directory to be listed
00963         KUrl url_dir( url_cwd, url.kurl().url() );
00964 
00965     // Don't try url completion if
00966     // 1. malformed url
00967     // 2. protocol that doesn't have listDir()
00968     // 3. there is no directory (e.g. "ftp://ftp.kd" shouldn't do anything)
00969     // 4. auto or popup completion mode depending on settings
00970 
00971     bool man_or_info = ( url_dir.protocol() == QLatin1String("man")
00972                          || url_dir.protocol() == QLatin1String("info") );
00973 
00974     if ( !url_dir.isValid()
00975          || !KProtocolManager::supportsListing( url_dir )
00976          || ( !man_or_info
00977                      && ( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash).isEmpty()
00978                    || ( isAutoCompletion()
00979                         && !url_auto_completion ) ) ) ) {
00980                 return false;
00981         }
00982 
00983     url_dir.setFileName(QString()); // not really nesseccary, but clear the filename anyway...
00984 
00985     // Remove escapes
00986     QString directory = unescape( url_dir.directory(KUrl::AppendTrailingSlash|KUrl::ObeyTrailingSlash) );
00987 
00988     url_dir.setPath( directory );
00989 
00990     // List files if needed
00991     //
00992     if ( !isListedUrl( CTUrl, url_dir.prettyUrl(), url.file() ) )
00993     {
00994         q->stop();
00995         q->clear();
00996 
00997         setListedUrl( CTUrl, url_dir.prettyUrl(), QString() );
00998 
00999                 QList<KUrl> url_list;
01000                 url_list.append( url_dir );
01001 
01002         listUrls( url_list, QString(), false );
01003 
01004         pMatch->clear();
01005     }
01006     else if ( !q->isRunning() ) {
01007         *pMatch = finished();
01008     }
01009     else {
01010         pMatch->clear();
01011     }
01012 
01013     return true;
01014 }
01015 
01018 // Directory and URL listing
01019 //
01020 
01021 /*
01022  * addMatches
01023  *
01024  * Called to add matches to KCompletion
01025  */
01026 void KUrlCompletionPrivate::addMatches( const QStringList &matchList )
01027 {
01028     q->insertItems(matchList);
01029 }
01030 
01031 /*
01032  * listDirectories
01033  *
01034  * List files starting with 'filter' in the given directories,
01035  * either using DirLister or listURLs()
01036  *
01037  * In either case, addMatches() is called with the listed
01038  * files, and eventually finished() when the listing is done
01039  *
01040  * Returns the match if available, or QString() if
01041  * DirLister timed out or using kio
01042  */
01043 QString KUrlCompletionPrivate::listDirectories(
01044         const QStringList &dirList,
01045         const QString &filter,
01046         bool only_exe,
01047         bool only_dir,
01048         bool no_hidden,
01049         bool append_slash_to_dir)
01050 {
01051     assert( !q->isRunning() );
01052 
01053     if ( qgetenv("KURLCOMPLETION_LOCAL_KIO").isEmpty() ) {
01054 
01055         //kDebug() << "Listing (listDirectories):" << dirList << "filter=" << filter << "without KIO";
01056 
01057         // Don't use KIO
01058 
01059         if ( dirListThread )
01060             dirListThread->requestTermination();
01061 
01062         QStringList dirs;
01063 
01064                 QStringList::ConstIterator end = dirList.constEnd();
01065         for ( QStringList::ConstIterator it = dirList.constBegin();
01066               it != end;
01067               ++it )
01068         {
01069             KUrl url;
01070             url.setPath(*it);
01071             if ( KAuthorized::authorizeUrlAction( QLatin1String("list"), KUrl(), url ) )
01072                 dirs.append( *it );
01073         }
01074 
01075         dirListThread = new DirectoryListThread( this, dirs, filter, only_exe, only_dir,
01076                                                  no_hidden, append_slash_to_dir );
01077         dirListThread->start();
01078         dirListThread->wait( 200 );
01079         addMatches( dirListThread->matches() );
01080 
01081         return finished();
01082     }
01083 
01084     // Use KIO
01085     //kDebug() << "Listing (listDirectories):" << dirList << "with KIO";
01086 
01087         QList<KUrl> url_list;
01088 
01089     QStringList::ConstIterator it = dirList.constBegin();
01090     QStringList::ConstIterator end = dirList.constEnd();
01091 
01092     for ( ; it != end; ++it ) {
01093                 url_list.append( KUrl( *it ) );
01094     }
01095 
01096     listUrls( url_list, filter, only_exe, no_hidden );
01097     // Will call addMatches() and finished()
01098 
01099     return QString();
01100 }
01101 
01102 /*
01103  * listURLs
01104  *
01105  * Use KIO to list the given urls
01106  *
01107  * addMatches() is called with the listed files
01108  * finished() is called when the listing is done
01109  */
01110 void KUrlCompletionPrivate::listUrls(
01111                 const QList<KUrl> &urls,
01112         const QString &filter,
01113         bool only_exe,
01114         bool no_hidden )
01115 {
01116     assert( list_urls.isEmpty() );
01117     assert( list_job == 0L );
01118 
01119     list_urls = urls;
01120     list_urls_filter = filter;
01121     list_urls_only_exe = only_exe;
01122     list_urls_no_hidden = no_hidden;
01123 
01124     //kDebug() << "Listing URLs:" << *urls[0] << ",...";
01125 
01126     // Start it off by calling _k_slotIOFinished
01127     //
01128     // This will start a new list job as long as there
01129     // are urls in d->list_urls
01130     //
01131     _k_slotIOFinished(0);
01132 }
01133 
01134 /*
01135  * _k_slotEntries
01136  *
01137  * Receive files listed by KIO and call addMatches()
01138  */
01139 void KUrlCompletionPrivate::_k_slotEntries(KIO::Job*, const KIO::UDSEntryList& entries)
01140 {
01141     QStringList matchList;
01142 
01143     KIO::UDSEntryList::ConstIterator it = entries.constBegin();
01144     const KIO::UDSEntryList::ConstIterator end = entries.constEnd();
01145 
01146     QString filter = list_urls_filter;
01147 
01148     int filter_len = filter.length();
01149 
01150     // Iterate over all files
01151     //
01152     for (; it != end; ++it) {
01153         const KIO::UDSEntry& entry = *it;
01154         const QString url = entry.stringValue( KIO::UDSEntry::UDS_URL );
01155 
01156         QString entry_name;
01157         if (!url.isEmpty()) {
01158             // kDebug() << "url:" << url;
01159             entry_name = KUrl(url).fileName();
01160         } else {
01161             entry_name = entry.stringValue( KIO::UDSEntry::UDS_NAME );
01162         }
01163 
01164         // kDebug() << "name:" << name;
01165 
01166         if ( (!entry_name.isEmpty() && entry_name.at(0) == QLatin1Char('.')) &&
01167              ( list_urls_no_hidden ||
01168                 entry_name.length() == 1 ||
01169                   ( entry_name.length() == 2 && entry_name.at(1) == QLatin1Char('.') ) ) )
01170             continue;
01171 
01172         const bool isDir = entry.isDir();
01173 
01174         if ( mode == KUrlCompletion::DirCompletion && !isDir )
01175             continue;
01176 
01177         if ( filter_len == 0 || entry_name.left(filter_len) == filter ) {
01178 
01179             QString toAppend = complete_url ? QUrl::toPercentEncoding(entry_name) : entry_name;
01180 
01181             if (isDir)
01182                 toAppend.append( QLatin1Char( '/' ) );
01183 
01184             if ( !list_urls_only_exe ||
01185                  (entry.numberValue( KIO::UDSEntry::UDS_ACCESS ) & MODE_EXE) // true if executable
01186                 ) {
01187                 matchList.append( prepend + toAppend );
01188             }
01189         }
01190     }
01191 
01192     addMatches( matchList );
01193 }
01194 
01195 /*
01196  * _k_slotIOFinished
01197  *
01198  * Called when a KIO job is finished.
01199  *
01200  * Start a new list job if there are still urls in
01201  * list_urls, otherwise call finished()
01202  */
01203 void KUrlCompletionPrivate::_k_slotIOFinished( KJob * job )
01204 {
01205     assert( job == list_job ); Q_UNUSED(job)
01206 
01207     if ( list_urls.isEmpty() ) {
01208 
01209         list_job = 0L;
01210 
01211         finished(); // will call KCompletion::makeCompletion()
01212 
01213     }
01214     else {
01215 
01216                 KUrl kurl(list_urls.takeFirst());
01217 
01218 //      list_urls.removeAll( kurl );
01219 
01220 //      kDebug() << "Start KIO::listDir" << kurl;
01221 
01222                 list_job = KIO::listDir( kurl, KIO::HideProgressInfo );
01223         list_job->addMetaData("no-auth-prompt", "true");
01224 
01225         assert( list_job );
01226 
01227         q->connect( list_job,
01228                 SIGNAL(result(KJob*)),
01229                 SLOT(_k_slotIOFinished(KJob*)) );
01230 
01231         q->connect( list_job,
01232                 SIGNAL( entries( KIO::Job*, const KIO::UDSEntryList&)),
01233                 SLOT( _k_slotEntries( KIO::Job*, const KIO::UDSEntryList&)) );
01234     }
01235 }
01236 
01239 
01240 /*
01241  * postProcessMatch, postProcessMatches
01242  *
01243  * Called by KCompletion before emitting match() and matches()
01244  *
01245  * Append '/' to directories for file completion. This is
01246  * done here to avoid stat()'ing a lot of files
01247  */
01248 void KUrlCompletion::postProcessMatch( QString *pMatch ) const
01249 {
01250 //  kDebug() << *pMatch;
01251 
01252     if ( !pMatch->isEmpty() ) {
01253 
01254         // Add '/' to directories in file completion mode
01255         // unless it has already been done
01256         if ( d->last_compl_type == CTFile
01257                && pMatch->at( pMatch->length()-1 ) != QLatin1Char('/') )
01258         {
01259             QString copy;
01260 
01261             if ( pMatch->startsWith( QLatin1String("file:") ) )
01262                 copy = KUrl(*pMatch).toLocalFile();
01263             else
01264                 copy = *pMatch;
01265 
01266             expandTilde( copy );
01267             expandEnv( copy );
01268 #ifdef Q_WS_WIN
01269             DWORD dwAttr = GetFileAttributesW( (LPCWSTR) copy.utf16() );
01270             if ( dwAttr == INVALID_FILE_ATTRIBUTES ) {
01271                 kDebug() << "Could not get file attribs ( "
01272                     << GetLastError()
01273                     << " ) for "
01274                     << copy;
01275                         } else
01276             if ( ( dwAttr & FILE_ATTRIBUTE_DIRECTORY ) == FILE_ATTRIBUTE_DIRECTORY )
01277                 pMatch->append( QLatin1Char( '/' ) );
01278 #else
01279             if ( QDir::isRelativePath(copy) )
01280                 copy.prepend( d->cwd + QLatin1Char('/') );
01281 
01282 //          kDebug() << "stat'ing" << copy;
01283 
01284             KDE_struct_stat sbuff;
01285 
01286             QByteArray file = QFile::encodeName( copy );
01287 
01288             if ( KDE_stat( file.data(), &sbuff ) == 0 ) {
01289                 if ( S_ISDIR ( sbuff.st_mode ) )
01290                     pMatch->append( QLatin1Char( '/' ) );
01291             }
01292             else {
01293                 kDebug() << "Could not stat file" << copy;
01294             }
01295 #endif
01296         }
01297     }
01298 }
01299 
01300 void KUrlCompletion::postProcessMatches( QStringList * /*matches*/ ) const
01301 {
01302     // Maybe '/' should be added to directories here as in
01303     // postProcessMatch() but it would slow things down
01304     // when there are a lot of matches...
01305 }
01306 
01307 void KUrlCompletion::postProcessMatches( KCompletionMatches * /*matches*/ ) const
01308 {
01309     // Maybe '/' should be added to directories here as in
01310     // postProcessMatch() but it would slow things down
01311     // when there are a lot of matches...
01312 }
01313 
01314 void KUrlCompletion::customEvent(QEvent *e)
01315 {
01316     if ( e->type() == CompletionMatchEvent::uniqueType() ) {
01317 
01318         CompletionMatchEvent *matchEvent = static_cast<CompletionMatchEvent *>( e );
01319 
01320         matchEvent->completionThread()->wait();
01321 
01322         if ( !d->isListedUrl( CTUser ) ) {
01323             stop();
01324             clear();
01325             d->addMatches( matchEvent->completionThread()->matches() );
01326         }
01327 
01328         d->setListedUrl( CTUser );
01329 
01330         if ( d->userListThread == matchEvent->completionThread() )
01331             d->userListThread = 0;
01332 
01333         if ( d->dirListThread == matchEvent->completionThread() )
01334             d->dirListThread = 0;
01335 
01336         delete matchEvent->completionThread();
01337     }
01338 }
01339 
01340 // static
01341 QString KUrlCompletion::replacedPath( const QString& text, bool replaceHome, bool replaceEnv )
01342 {
01343     if ( text.isEmpty() )
01344         return text;
01345 
01346     KUrlCompletionPrivate::MyURL url( text, QString() ); // no need to replace something of our current cwd
01347         if ( !url.kurl().isLocalFile() )
01348         return text;
01349 
01350     url.filter( replaceHome, replaceEnv );
01351     return url.dir() + url.file();
01352 }
01353 
01354 
01355 QString KUrlCompletion::replacedPath( const QString& text ) const
01356 {
01357     return replacedPath( text, d->replace_home, d->replace_env );
01358 }
01359 
01362 // Static functions
01363 
01364 /*
01365  * expandEnv
01366  *
01367  * Expand environment variables in text. Escaped '$' are ignored.
01368  * Return true if expansion was made.
01369  */
01370 static bool expandEnv( QString &text )
01371 {
01372     // Find all environment variables beginning with '$'
01373     //
01374     int pos = 0;
01375 
01376     bool expanded = false;
01377 
01378     while ( (pos = text.indexOf(QLatin1Char('$'), pos)) != -1 ) {
01379 
01380         // Skip escaped '$'
01381         //
01382         if ( pos > 0 && text.at(pos-1) == QLatin1Char('\\') ) {
01383             pos++;
01384         }
01385         // Variable found => expand
01386         //
01387         else {
01388             // Find the end of the variable = next '/' or ' '
01389             //
01390             int pos2 = text.indexOf( QLatin1Char(' '), pos+1 );
01391             int pos_tmp = text.indexOf( QLatin1Char('/'), pos+1 );
01392 
01393             if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01394                 pos2 = pos_tmp;
01395 
01396             if ( pos2 == -1 )
01397                 pos2 = text.length();
01398 
01399             // Replace if the variable is terminated by '/' or ' '
01400             // and defined
01401             //
01402             if ( pos2 >= 0 ) {
01403                 int len = pos2 - pos;
01404                 QString key = text.mid( pos+1, len-1);
01405                 QString value =
01406                     QString::fromLocal8Bit( qgetenv(key.toLocal8Bit()) );
01407 
01408                 if ( !value.isEmpty() ) {
01409                     expanded = true;
01410                     text.replace( pos, len, value );
01411                     pos = pos + value.length();
01412                 }
01413                 else {
01414                     pos = pos2;
01415                 }
01416             }
01417         }
01418     }
01419 
01420     return expanded;
01421 }
01422 
01423 /*
01424  * expandTilde
01425  *
01426  * Replace "~user" with the users home directory
01427  * Return true if expansion was made.
01428  */
01429 static bool expandTilde(QString &text)
01430 {
01431     if ( text.isEmpty() || ( text.at(0) != QLatin1Char('~') ))
01432         return false;
01433 
01434     bool expanded = false;
01435 
01436     // Find the end of the user name = next '/' or ' '
01437     //
01438     int pos2 = text.indexOf( QLatin1Char(' '), 1 );
01439     int pos_tmp = text.indexOf( QLatin1Char('/'), 1 );
01440 
01441     if ( pos2 == -1 || (pos_tmp != -1 && pos_tmp < pos2) )
01442         pos2 = pos_tmp;
01443 
01444     if ( pos2 == -1 )
01445         pos2 = text.length();
01446 
01447     // Replace ~user if the user name is terminated by '/' or ' '
01448     //
01449     if ( pos2 >= 0 ) {
01450 
01451         QString user = text.mid( 1, pos2-1 );
01452         QString dir;
01453 
01454         // A single ~ is replaced with $HOME
01455         //
01456         if ( user.isEmpty() ) {
01457             dir = QDir::homePath();
01458         }
01459         // ~user is replaced with the dir from passwd
01460         //
01461         else {
01462             struct passwd *pw = ::getpwnam( user.toLocal8Bit() );
01463 
01464             if ( pw )
01465                 dir = QFile::decodeName( pw->pw_dir );
01466 
01467             ::endpwent();
01468         }
01469 
01470         if ( !dir.isEmpty() ) {
01471             expanded = true;
01472             text.replace(0, pos2, dir);
01473         }
01474     }
01475 
01476     return expanded;
01477 }
01478 
01479 /*
01480  * unescape
01481  *
01482  * Remove escapes and return the result in a new string
01483  *
01484  */
01485 static QString unescape(const QString &text)
01486 {
01487     QString result;
01488 
01489     for (int pos = 0; pos < text.length(); pos++)
01490         if ( text.at(pos) != QLatin1Char('\\') )
01491             result.insert( result.length(), text.at(pos) );
01492 
01493     return result;
01494 }
01495 
01496 #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
  • 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.3
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