# kdecore Library API Documentation

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

# kstringhandler.cpp

    00001 /* This file is part of the KDE libraries
    00002    Copyright (C) 1999 Ian Zepp (icszepp@islc.net)
    00003
    00004    This library is free software; you can redistribute it and/or
    00005    modify it under the terms of the GNU Library General Public
    00006    License as published by the Free Software Foundation; either
    00007    version 2 of the License, or (at your option) any later version.
    00008
    00009    This library is distributed in the hope that it will be useful,
    00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
    00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    00012    Library General Public License for more details.
    00013
    00014    You should have received a copy of the GNU Library General Public License
    00015    along with this library; see the file COPYING.LIB.  If not, write to
    00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    00017    Boston, MA 02111-1307, USA.
    00018 */
    00019
    00020 /* AIX needs strings.h for str*casecmp(), and our config.h loads it on AIX
    00021    So we don't need to include strings.h explicitly */
    00022 #include "config.h"
    00023
    00024 #include "kstringhandler.h"
    00025 #include "kglobal.h"
    00026
    00027 static void parsePythonRange( const QCString &range, uint &start, uint &end )
    00028 {
    00029     const int colon = range.find( ':' );
    00030     if ( colon == -1 ) {
    00031         start = range.toUInt();
    00032         end = start;
    00033     } else if ( colon == int( range.length() - 1 ) ) {
    00034         start = range.left( colon ).toUInt();
    00035     } else if ( colon == 0 ) {
    00036         end = range.mid( 1 ).toUInt();
    00037     } else {
    00038         start = range.left( colon ).toInt();
    00039         end = range.mid( colon + 1 ).toInt();
    00040     }
    00041 }
    00042
    00043 QString KStringHandler::word( const QString &text , uint pos )
    00044 {
    00045     return text.section( ' ', pos, pos );
    00046 }
    00047
    00048 QString KStringHandler::word( const QString &text , const char *range )
    00049 {
    00050     // Format in: START:END
    00051     // Note index starts a 0 (zero)
    00052     //
    00053     // 0:        first word to end
    00054     // 1:3        second to fourth words
    00055     QStringList list = QStringList::split( " ", text , true );
    00056     QString tmp = "";
    00057     QString r = range;
    00058
    00059     if ( text.isEmpty() )
    00060         return tmp;
    00061
    00062     uint pos = 0, cnt = list.count();
    00063     parsePythonRange( range, pos, cnt );
    00064
    00065     //
    00066     // Extract words
    00067     //
    00068     int wordsToExtract = cnt-pos+1;
    00069     QStringList::Iterator it = list.at( pos);
    00070
    00071     while ( (it != list.end()) && (wordsToExtract-- > 0))
    00072     {
    00073        tmp += *it;
    00074        tmp += " ";
    00075        it++;
    00076     }
    00077
    00078     return tmp.stripWhiteSpace();
    00079 }
    00080
    00081 //
    00082 // Insertion and removal routines
    00083 //
    00084 QString KStringHandler::insword( const QString &text , const QString &word , uint pos )
    00085 {
    00086     if ( text.isEmpty() )
    00087         return word;
    00088
    00089     if ( word.isEmpty() )
    00090         return text;
    00091
    00092     // Split words and add into list
    00093     QStringList list = QStringList::split( " ", text, true );
    00094
    00095     if ( pos >= list.count() )
    00096         list.append( word );
    00097     else
    00098         list.insert( list.at(pos) , word );
    00099
    00100     // Rejoin
    00101     return list.join( " " );
    00102 }
    00103
    00104 QString KStringHandler::setword( const QString &text , const QString &word , uint pos )
    00105 {
    00106     if ( text.isEmpty() )
    00107         return word;
    00108
    00109     if ( word.isEmpty() )
    00110         return text;
    00111
    00112     // Split words and add into list
    00113     QStringList list = QStringList::split( " ", text, true );
    00114
    00115     if ( pos >= list.count() )
    00116         list.append( word );
    00117     else
    00118     {
    00119         list.insert( list.remove( list.at(pos) ) , word );
    00120     }
    00121
    00122     // Rejoin
    00123     return list.join( " " );
    00124 }
    00125
    00126 QString KStringHandler::remrange( const QString &text , const char *range )
    00127 {
    00128     // Format in: START:END
    00129     // Note index starts a 0 (zero)
    00130     //
    00131     // 0:        first word to end
    00132     // 1:3        second to fourth words
    00133     QStringList list = QStringList::split( " ", text , true );
    00134     QString tmp = "";
    00135     QString r = range;
    00136
    00137     if ( text.isEmpty() )
    00138         return tmp;
    00139
    00140     uint pos = 0, cnt = list.count();
    00141     parsePythonRange( range, pos, cnt );
    00142
    00143     //
    00144     // Remove that range of words
    00145     //
    00146     int wordsToDelete = cnt-pos+1;
    00147     QStringList::Iterator it = list.at( pos);
    00148
    00149     while ( (it != list.end()) && (wordsToDelete-- > 0))
    00150        it = list.remove( it );
    00151
    00152     return list.join( " " );
    00153 }
    00154
    00155 QString KStringHandler::remword( const QString &text , uint pos )
    00156 {
    00157     QString tmp = "";
    00158
    00159     if ( text.isEmpty() )
    00160         return tmp;
    00161
    00162     // Split words and add into list
    00163     QStringList list = QStringList::split( " ", text, true );
    00164
    00165     if ( pos < list.count() )
    00166         list.remove( list.at( pos ) );
    00167
    00168     // Rejoin
    00169     return list.join( " " );
    00170 }
    00171
    00172 QString KStringHandler::remword( const QString &text , const QString &word )
    00173 {
    00174     QString tmp = "";
    00175
    00176     if ( text.isEmpty() )
    00177         return tmp;
    00178
    00179     if ( word.isEmpty() )
    00180         return text;
    00181
    00182     // Split words and add into list
    00183     QStringList list = QStringList::split( " ", text, true );
    00184
    00185     QStringList::Iterator it = list.find(word);
    00186
    00187     if (it != list.end())
    00188        list.remove( it );
    00189
    00190     // Rejoin
    00191     return list.join( " " );
    00192 }
    00193
    00194 //
    00195 // Capitalization routines
    00196 //
    00197 QString KStringHandler::capwords( const QString &text )
    00198 {
    00199     if ( text.isEmpty() ) {
    00200         return text;
    00201     }
    00202
    00203     const QString strippedText = text.stripWhiteSpace();
    00204     const QStringList words = capwords( QStringList::split( ' ', strippedText ) );
    00205
    00206     QString result = text;
    00207     result.replace( strippedText, words.join( " " ) );
    00208     return result;
    00209 }
    00210
    00211 QStringList KStringHandler::capwords( const QStringList &list )
    00212 {
    00213     QStringList tmp = list;
    00214     for ( QStringList::Iterator it = tmp.begin(); it != tmp.end(); ++it ) {
    00215         *it = ( *it )[ 0 ].upper() + ( *it ).mid( 1 );
    00216     }
    00217     return tmp;
    00218 }
    00219
    00220 //
    00221 // Reverse routines
    00222 //
    00223 QString KStringHandler::reverse( const QString &text )
    00224 {
    00225     QString tmp;
    00226
    00227     if ( text.isEmpty() )
    00228         return tmp;
    00229
    00230     QStringList list;
    00231     list = QStringList::split( " ", text, true );
    00232     list = reverse( list );
    00233
    00234     return list.join( " " );
    00235 }
    00236
    00237 QStringList KStringHandler::reverse( const QStringList &list )
    00238 {
    00239     QStringList tmp;
    00240
    00241     if ( list.count() == 0 )
    00242         return tmp;
    00243
    00244     for ( QStringList::ConstIterator it= list.begin();
    00245           it != list.end();
    00246           it++)
    00247         tmp.prepend( *it );
    00248
    00249     return tmp;
    00250 }
    00251
    00252 //
    00253 // Left, Right, Center justification
    00254 //
    00255 QString KStringHandler::ljust( const QString &text , uint width )
    00256 {
    00257     return text.stripWhiteSpace().leftJustify( width );
    00258 }
    00259
    00260 QString KStringHandler::rjust( const QString &text , uint width )
    00261 {
    00262     return text.stripWhiteSpace().rightJustify( width );
    00263 }
    00264
    00265 QString KStringHandler::center( const QString &text , uint width )
    00266 {
    00267     const QString s = text.stripWhiteSpace();
    00268     const unsigned int length = s.length();
    00269     if ( width <= length ) {
    00270         return s;
    00271      }
    00272
    00273     QString result;
    00274     result.fill( ' ', ( width - length ) / 2 );
    00275     result += s;
    00276
    00277     return result.leftJustify( width );
    00278 }
    00279
    00280 QString KStringHandler::lsqueeze( const QString & str, uint maxlen )
    00281 {
    00282   if (str.length() > maxlen) {
    00283     int part = maxlen-3;
    00284     return QString("..." + str.right(part));
    00285   }
    00286   else return str;
    00287 }
    00288
    00289 QString KStringHandler::csqueeze( const QString & str, uint maxlen )
    00290 {
    00291   if (str.length() > maxlen && maxlen > 3) {
    00292     int part = (maxlen-3)/2;
    00293     return QString(str.left(part) + "..." + str.right(part));
    00294   }
    00295   else return str;
    00296 }
    00297
    00298 QString KStringHandler::rsqueeze( const QString & str, uint maxlen )
    00299 {
    00300   if (str.length() > maxlen) {
    00301     int part = maxlen-3;
    00302     return QString(str.left(part) + "...");
    00303   }
    00304   else return str;
    00305 }
    00306
    00307 QString KStringHandler::lEmSqueeze(const QString &name, const QFontMetrics& fontMetrics, uint maxlen)
    00308 {
    00309   return lPixelSqueeze(name, fontMetrics, fontMetrics.maxWidth() * maxlen);
    00310 }
    00311
    00312 QString KStringHandler::lPixelSqueeze(const QString& name, const QFontMetrics& fontMetrics, uint maxPixels)
    00313 {
    00314   uint nameWidth = fontMetrics.width(name);
    00315
    00316   if (maxPixels < nameWidth)
    00317   {
    00318     QString tmp = name;
    00319     const uint em = fontMetrics.maxWidth();
    00320     maxPixels -= fontMetrics.width("...");
    00321
    00322     while (maxPixels < nameWidth && !tmp.isEmpty())
    00323     {
    00324       int delta = (nameWidth - maxPixels) / em;
    00325       delta = kClamp(delta, 1, delta); // no max
    00326
    00327       tmp.remove(0, delta);
    00328       nameWidth = fontMetrics.width(tmp);
    00329     }
    00330
    00331     return ("..." + tmp);
    00332   }
    00333
    00334   return name;
    00335 }
    00336
    00337 QString KStringHandler::cEmSqueeze(const QString& name, const QFontMetrics& fontMetrics, uint maxlen)
    00338 {
    00339   return cPixelSqueeze(name, fontMetrics, fontMetrics.maxWidth() * maxlen);
    00340 }
    00341
    00342 QString KStringHandler::cPixelSqueeze(const QString& s, const QFontMetrics& fm, uint width)
    00343 {
    00344   if ( s.isEmpty() || uint( fm.width( s ) ) <= width ) {
    00345     return s;
    00346   }
    00347
    00348   const unsigned int length = s.length();
    00349   if ( length == 2 ) {
    00350     return s;
    00351   }
    00352
    00353   const int maxWidth = width - fm.width( '.' ) * 3;
    00354   if ( maxWidth <= 0 ) {
    00355     return "...";
    00356   }
    00357
    00358   unsigned int leftIdx = 0, rightIdx = length;
    00359   unsigned int leftWidth = fm.charWidth( s, leftIdx++ );
    00360   unsigned int rightWidth = fm.charWidth( s, --rightIdx );
    00361   while ( leftWidth + rightWidth < uint( maxWidth ) ) {
    00362     while ( leftWidth <= rightWidth && leftWidth + rightWidth < uint( maxWidth ) ) {
    00363       leftWidth += fm.charWidth( s, leftIdx++ );
    00364     }
    00365     while ( rightWidth <= leftWidth && leftWidth + rightWidth < uint( maxWidth ) ) {
    00366       rightWidth += fm.charWidth( s, --rightIdx );
    00367     }
    00368   }
    00369
    00370   if ( leftWidth > rightWidth ) {
    00371     --leftIdx;
    00372   } else {
    00373     ++rightIdx;
    00374   }
    00375
    00376   rightIdx = length - rightIdx;
    00377   if ( leftIdx == 0 && rightIdx == 1 || leftIdx == 1 && rightIdx == 0 ) {
    00378     return "...";
    00379   }
    00380
    00381   return s.left( leftIdx ) + "..." + s.right( rightIdx );
    00382 }
    00383
    00384 QString KStringHandler::rEmSqueeze(const QString& name, const QFontMetrics& fontMetrics, uint maxlen)
    00385 {
    00386   return rPixelSqueeze(name, fontMetrics, fontMetrics.maxWidth() * maxlen);
    00387 }
    00388
    00389 QString KStringHandler::rPixelSqueeze(const QString& name, const QFontMetrics& fontMetrics, uint maxPixels)
    00390 {
    00391   uint nameWidth = fontMetrics.width(name);
    00392
    00393   if (maxPixels < nameWidth)
    00394   {
    00395     QString tmp = name;
    00396     const uint em = fontMetrics.maxWidth();
    00397     maxPixels -= fontMetrics.width("...");
    00398
    00399     while (maxPixels < nameWidth && !tmp.isEmpty())
    00400     {
    00401       int length = tmp.length();
    00402       int delta = em ? (nameWidth - maxPixels) / em : length;
    00403       delta = kClamp(delta, 1, length) ;
    00404
    00405       tmp.remove(length - delta, delta);
    00406       nameWidth = fontMetrics.width(tmp);
    00407     }
    00408
    00409     return (tmp + "...");
    00410   }
    00411
    00412   return name;
    00413 }
    00414
    00416
    00417 bool KStringHandler::matchFileName( const QString& filename, const QString& pattern  )
    00418 {
    00419    int len = filename.length();
    00420    int pattern_len = pattern.length();
    00421
    00422    if (!pattern_len)
    00423       return false;
    00424
    00425    // Patterns like "Makefile*"
    00426    if ( pattern[ pattern_len - 1 ] == '*' && len + 1 >= pattern_len ) {
    00427       if ( pattern[ 0 ] == '*' )
    00428       {
    00429          return filename.find(pattern.mid(1, pattern_len - 2)) != -1;
    00430       }
    00431
    00432       const QChar *c1 = pattern.unicode();
    00433       const QChar *c2 = filename.unicode();
    00434       int cnt = 1;
    00435       while ( cnt < pattern_len && *c1++ == *c2++ )
    00436          ++cnt;
    00437       return cnt == pattern_len;
    00438    }
    00439
    00440    // Patterns like "*~", "*.extension"
    00441    if ( pattern[ 0 ] == '*' && len + 1 >= pattern_len )
    00442    {
    00443      const QChar *c1 = pattern.unicode() + pattern_len - 1;
    00444      const QChar *c2 = filename.unicode() + len - 1;
    00445      int cnt = 1;
    00446      while ( cnt < pattern_len && *c1-- == *c2-- )
    00447         ++cnt;
    00448      return cnt == pattern_len;
    00449   }
    00450
    00451    // Patterns like "Makefile"
    00452    return ( filename == pattern );
    00453 }
    00454
    00455   QStringList
    00456 KStringHandler::perlSplit(const QString & sep, const QString & s, uint max)
    00457 {
    00458   bool ignoreMax = 0 == max;
    00459
    00460   QStringList l;
    00461
    00462   int searchStart = 0;
    00463
    00464   int tokenStart = s.find(sep, searchStart);
    00465
    00466   while (-1 != tokenStart && (ignoreMax || l.count() < max - 1))
    00467   {
    00468     if (!s.mid(searchStart, tokenStart - searchStart).isEmpty())
    00469       l << s.mid(searchStart, tokenStart - searchStart);
    00470
    00471     searchStart = tokenStart + sep.length();
    00472     tokenStart = s.find(sep, searchStart);
    00473   }
    00474
    00475   if (!s.mid(searchStart, s.length() - searchStart).isEmpty())
    00476     l << s.mid(searchStart, s.length() - searchStart);
    00477
    00478   return l;
    00479 }
    00480
    00481   QStringList
    00482 KStringHandler::perlSplit(const QChar & sep, const QString & s, uint max)
    00483 {
    00484   bool ignoreMax = 0 == max;
    00485
    00486   QStringList l;
    00487
    00488   int searchStart = 0;
    00489
    00490   int tokenStart = s.find(sep, searchStart);
    00491
    00492   while (-1 != tokenStart && (ignoreMax || l.count() < max - 1))
    00493   {
    00494     if (!s.mid(searchStart, tokenStart - searchStart).isEmpty())
    00495       l << s.mid(searchStart, tokenStart - searchStart);
    00496
    00497     searchStart = tokenStart + 1;
    00498     tokenStart = s.find(sep, searchStart);
    00499   }
    00500
    00501   if (!s.mid(searchStart, s.length() - searchStart).isEmpty())
    00502     l << s.mid(searchStart, s.length() - searchStart);
    00503
    00504   return l;
    00505 }
    00506
    00507   QStringList
    00508 KStringHandler::perlSplit(const QRegExp & sep, const QString & s, uint max)
    00509 {
    00510   bool ignoreMax = 0 == max;
    00511
    00512   QStringList l;
    00513
    00514   int searchStart = 0;
    00515   int tokenStart = sep.search(s, searchStart);
    00516   int len = sep.matchedLength();
    00517
    00518   while (-1 != tokenStart && (ignoreMax || l.count() < max - 1))
    00519   {
    00520     if (!s.mid(searchStart, tokenStart - searchStart).isEmpty())
    00521       l << s.mid(searchStart, tokenStart - searchStart);
    00522
    00523     searchStart = tokenStart + len;
    00524     tokenStart = sep.search(s, searchStart);
    00525     len = sep.matchedLength();
    00526   }
    00527
    00528   if (!s.mid(searchStart, s.length() - searchStart).isEmpty())
    00529     l << s.mid(searchStart, s.length() - searchStart);
    00530
    00531   return l;
    00532 }
    00533
    00534  QString
    00535 KStringHandler::tagURLs( const QString& text )
    00536 {
    00537     /*static*/ QRegExp urlEx("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%]+[\\d\\w/]");
    00538
    00539     QString richText( text );
    00540     int urlPos = 0, urlLen;
    00541     while ((urlPos = urlEx.search(richText, urlPos)) >= 0)
    00542     {
    00543         urlLen = urlEx.matchedLength();
    00544         QString href = richText.mid( urlPos, urlLen );
    00545         // Qt doesn't support (?<=pattern) so we do it here
    00546         if((urlPos > 0) && richText[urlPos-1].isLetterOrNumber()){
    00547             urlPos++;
    00548             continue;
    00549         }
    00550         // Don't use QString::arg since %01, %20, etc could be in the string
    00551         QString anchor = "<a href=\"" + href + "\">" + href + "</a>";
    00552         richText.replace( urlPos, urlLen, anchor );
    00553
    00554
    00555         urlPos += anchor.length();
    00556     }
    00557     return richText;
    00558 }
    00559
    00560 QString KStringHandler::obscure( const QString &str )
    00561 {
    00562   QString result;
    00563   const QChar *unicode = str.unicode();
    00564   for ( uint i = 0; i < str.length(); ++i )
    00565     result += ( unicode[ i ].unicode() < 0x20 ) ? unicode[ i ] :
    00566         QChar( 0x1001F - unicode[ i ].unicode() );
    00567
    00568   return result;
    00569 }
    00570
    00571 bool KStringHandler::isUtf8(const char *buf)
    00572 {
    00573   int i, n;
    00574   register unsigned char c;
    00575   bool gotone = false;
    00576
    00577   if (!buf)
    00578     return true; // whatever, just don't crash
    00579
    00580 #define F 0   /* character never appears in text */
    00581 #define T 1   /* character appears in plain ASCII text */
    00582 #define I 2   /* character appears in ISO-8859 text */
    00583 #define X 3   /* character appears in non-ISO extended ASCII (Mac, IBM PC) */
    00584
    00585   static const unsigned char text_chars[256] = {
    00586         /*                  BEL BS HT LF    FF CR    */
    00587         F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F,  /* 0x0X */
    00588         /*                              ESC          */
    00589         F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F,  /* 0x1X */
    00590         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x2X */
    00591         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x3X */
    00592         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x4X */
    00593         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x5X */
    00594         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T,  /* 0x6X */
    00595         T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F,  /* 0x7X */
    00596         /*            NEL                            */
    00597         X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X,  /* 0x8X */
    00598         X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,  /* 0x9X */
    00599         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xaX */
    00600         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xbX */
    00601         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xcX */
    00602         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xdX */
    00603         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I,  /* 0xeX */
    00604         I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I   /* 0xfX */
    00605   };
    00606
    00607   /* *ulen = 0; */
    00608   for (i = 0; (c = buf[i]); i++) {
    00609     if ((c & 0x80) == 0) {        /* 0xxxxxxx is plain ASCII */
    00610       /*
    00611        * Even if the whole file is valid UTF-8 sequences,
    00612        * still reject it if it uses weird control characters.
    00613        */
    00614
    00615       if (text_chars[c] != T)
    00616         return false;
    00617
    00618     } else if ((c & 0x40) == 0) { /* 10xxxxxx never 1st byte */
    00619       return false;
    00620     } else {                           /* 11xxxxxx begins UTF-8 */
    00621       int following;
    00622
    00623     if ((c & 0x20) == 0) {             /* 110xxxxx */
    00624       following = 1;
    00625     } else if ((c & 0x10) == 0) {      /* 1110xxxx */
    00626       following = 2;
    00627     } else if ((c & 0x08) == 0) {      /* 11110xxx */
    00628       following = 3;
    00629     } else if ((c & 0x04) == 0) {      /* 111110xx */
    00630       following = 4;
    00631     } else if ((c & 0x02) == 0) {      /* 1111110x */
    00632       following = 5;
    00633     } else
    00634       return false;
    00635
    00636       for (n = 0; n < following; n++) {
    00637         i++;
    00638         if (!(c = buf[i]))
    00639           goto done;
    00640
    00641         if ((c & 0x80) == 0 || (c & 0x40))
    00642           return false;
    00643       }
    00644       gotone = true;
    00645     }
    00646   }
    00647 done:
    00648   return gotone;   /* don't claim it's UTF-8 if it's all 7-bit */
    00649 }
    00650
    00651 #undef F
    00652 #undef T
    00653 #undef I
    00654 #undef X
    00655
    00656 QString KStringHandler::from8Bit( const char *str )
    00657 {
    00658   if (!str)
    00659     return QString::null;
    00660   if (!*str) {
    00661     static const QString &emptyString = KGlobal::staticQString("");
    00662     return emptyString;
    00663   }
    00664   return KStringHandler::isUtf8( str ) ?
    00665              QString::fromUtf8( str ) :
    00666              QString::fromLocal8Bit( str );
    00667 }

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