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