# kio Library API Documentation

Overview | Class Hierarchy | Classes | Classes (annotated) | Members | Namespaces | Source Files

# kurlcompletion.cpp

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

This file is part of the documentation for kio Library Version 3.4.1.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat Jun 11 20:24:05 2005 by doxygen 1.4.1 written by Dimitri van Heesch, © 1997-2003