kpilot Library API Documentation

sysinfo-conduit.cc

00001 /* sysinfo-conduit.cc                           KPilot
00002 **
00003 ** Copyright (C) 2003 by Reinhold Kainhofer
00004 **
00005 */
00006 
00007 /*
00008 ** This program is free software; you can redistribute it and/or modify
00009 ** it under the terms of the GNU General Public License as published by
00010 ** the Free Software Foundation; either version 2 of the License, or
00011 ** (at your option) any later version.
00012 **
00013 ** This program is distributed in the hope that it will be useful,
00014 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00016 ** GNU General Public License for more details.
00017 **
00018 ** You should have received a copy of the GNU General Public License
00019 ** along with this program in a file called COPYING; if not, write to
00020 ** the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
00021 ** MA 02111-1307, USA.
00022 */
00023 
00024 /*
00025 ** Bug reports and questions can be sent to kde-pim@kde.org.
00026 */
00027 
00028 #include "options.h"
00029 
00030 #include <pi-version.h>
00031 
00032 #include <qtimer.h>
00033 #include <qdir.h>
00034 #include <qfileinfo.h>
00035 #include <qregexp.h>
00036 #include <kconfig.h>
00037 #include <kdebug.h>
00038 
00039 #include <pilotSysInfo.h>
00040 #include <pilotUser.h>
00041 #include <pilotCard.h>
00042 #include <kpilotlink.h>
00043 #include <kstandarddirs.h>
00044 #include <pilotSerialDatabase.h>
00045 
00046 #include <sys/utsname.h>
00047 
00048 #include "sysinfo-factory.h"
00049 #include "sysinfo-conduit.moc"
00050 
00051 const QString SysInfoConduit::defaultpage = QString("KPilot System Information Page\n"
00052 "==============================\n"
00053 "(Kpilot was unable to find the correct template file, \n"
00054 "so this simple template was used.)\n\n"
00055 "<!--#ifhardware#\n"
00056 "-) Hardware Information\n"
00057 "     DeviceID:      #deviceid#\n"
00058 "     Device name:   #devicename#\n"
00059 "     Device model:  #devicemodel#\n"
00060 "     Manufacturer:  #manufacturer#\n"
00061 "     Connected via: #devicetype#\n"
00062 "#endifhardware#-->\n"
00063 "\n"
00064 "<!--#ifuser#\n"
00065 "-) User Information\n"
00066 "     Handheld User Name: #username#\n"
00067 "     Handheld Password:  #pw#\n"
00068 "     Handheld User ID:   #uid#\n"
00069 "     Viewer ID:          #viewerid#\n"
00070 "#endifuser#-->\n"
00071 "\n"
00072 "<!--#ifmemory#\n"
00073 "-) Memory Information\n"
00074 "     ROM:       #rom# kB total\n"
00075 "     Total RAM: #totalmem# kB total\n"
00076 "     Free RAM:  #freemem# kB free\n"
00077 "#endifmemory#-->\n"
00078 "\n"
00079 "<!--#ifstorage#\n"
00080 "-) Storage Information\n"
00081 "     Number of cards: #cards#\n"
00082 "     Memory on cards: #storagemem#\n"
00083 "#endifstorage#-->\n"
00084 "\n"
00085 "<!--#ifdblist#\n"
00086 "-) List of Databases on Handheld\n"
00087 "     Available Databases: #dblist(%1,)#\n"
00088 "#endifdblist#-->\n"
00089 "\n"
00090 "<!--#ifrecords#\n"
00091 "-) Number of addresses, todos, events, and memos\n"
00092 "     Addresses: #addresses# entries in Addressbook\n"
00093 "     Events:    #events# entries in Calendar\n"
00094 "     Todos:     #todos# entries in ToDo list\n"
00095 "     Memos:     #memos# memos\n"
00096 "#endifrecords#-->\n"
00097 "\n"
00098 "<!--#ifsync#\n"
00099 "-) Synchronization Information\n"
00100 "     Last sync attempt:      #lastsync#\n"
00101 "     Last successful sync:  #lastsuccsync#\n"
00102 "     Last sync with PC (ID): #lastsyncpc#\n"
00103 "#endifsync#-->\n"
00104 "\n"
00105 "<!--#ifpcversion#\n"
00106 "-) Version Information (Desktop)\n"
00107 "     Operating System:   #os#\n"
00108 "     Hostname:           #hostname#\n"
00109 "     Qt Version:         #qt#\n"
00110 "     KDE Version:        #kde#\n"
00111 "     KPilot Version:     #kpilot#\n"
00112 "     Pilot-Link Version: #pilotlink#\n"
00113 "#endifpcversion#-->\n"
00114 "\n"
00115 "<!--#ifpalmversion#\n"
00116 "-) Version Information (Handheld)\n"
00117 "     PalmOS: #palmos#\n"
00118 "#endifpalmversion#-->\n"
00119 "\n"
00120 "<!--#ifdebug#\n"
00121 "-) Debug Information\n"
00122 "     #debug#\n"
00123 "#endifdebug#-->\n"
00124 "\n"
00125 "------------------------------------------------------------\n"
00126 "Page created <!--#date#--> by the KPilot System Information conduit.\n"
00127 "");
00128 
00129 
00144 // Something to allow us to check what revision
00145 // the modules are that make up a binary distribution.
00146 const char *SysInfo_conduit_id =
00147     "$Id: sysinfo-conduit.cc,v 1.8 2003/08/12 18:11:51 mueller Exp $";
00148 
00149 
00150 
00151 
00152 SysInfoConduit::SysInfoConduit(KPilotDeviceLink * o,
00153     const char *n,
00154     const QStringList & a) :
00155     ConduitAction(o, n, a)
00156 {
00157     FUNCTIONSETUP;
00158 #ifdef DEBUG
00159     DEBUGCONDUIT<<SysInfo_conduit_id<<endl;
00160 #endif
00161     fConduitName=i18n("System Information");
00162 }
00163 
00164 
00165 
00166 SysInfoConduit::~SysInfoConduit()
00167 {
00168     FUNCTIONSETUP;
00169 }
00170 
00171 
00172 
00173 void SysInfoConduit::readConfig()
00174 {
00175     FUNCTIONSETUP;
00176     KConfigGroupSaver g(fConfig, SysInfoConduitFactory::fGroup);
00177     fOutputFile=fConfig->readPathEntry(SysInfoConduitFactory::fOutputFile);
00178     fTemplateFile=fConfig->readPathEntry(SysInfoConduitFactory::fTemplateFile);
00179     fOutputType=(eOutputTypeEnum)(fConfig->readNumEntry(SysInfoConduitFactory::fOutputType, 0));
00180     fHardwareInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fHardwareInfo, true);
00181     fUserInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fUserInfo, true);
00182     fMemoryInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fMemoryInfo, true);
00183     fStorageInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fStorageInfo, true);
00184     fDBList=fConfig->readBoolEntry(SysInfoConduitFactory::fDBList, true);
00185     fRecordNumber=fConfig->readBoolEntry(SysInfoConduitFactory::fRecordNumber, true);
00186     fSyncInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fSyncInfo, true);
00187     fKDEVersion=fConfig->readBoolEntry(SysInfoConduitFactory::fKDEVersion, true);
00188     fPalmOSVersion=fConfig->readBoolEntry(SysInfoConduitFactory::fPalmOSVersion, true);
00189     fDebugInfo=fConfig->readBoolEntry(SysInfoConduitFactory::fDebugInfo, true);
00190 #ifdef DEBUG
00191     DEBUGCONDUIT<<"Output file="<<fOutputFile<<" with type "<<
00192         fOutputType<<" (Template:"<<fTemplateFile<<")"<<endl;
00193     DEBUGCONDUIT<<"HW:"<<fHardwareInfo<<",User:"<<fUserInfo<<
00194         ",Mem:"<<fMemoryInfo<<",Sto:"<<fStorageInfo<<endl;
00195     DEBUGCONDUIT<<"DBL:"<<fDBList<<",Rec:"<<fRecordNumber<<
00196         ",KDE:"<<fKDEVersion<<",PalmOS:"<<fPalmOSVersion<<endl;
00197 #endif
00198 }
00199 
00200 
00201 /* virtual */ bool SysInfoConduit::exec()
00202 {
00203     FUNCTIONSETUP;
00204     DEBUGCONDUIT<<SysInfo_conduit_id<<endl;
00205 
00206     if (!fConfig)
00207     {
00208         kdWarning() << k_funcinfo << ": No config file was set!" << endl;
00209         return false;
00210     }
00211 
00212     readConfig();
00213 
00214     QTimer::singleShot(0, this, SLOT(hardwareInfo()));
00215     return true;
00216 }
00217 
00218 void SysInfoConduit::hardwareInfo()
00219 {
00220     FUNCTIONSETUP;
00221     if (fHardwareInfo) {
00222         /* Retrieve values for
00223         * - #deviceid#
00224         * - #devicename#
00225         * - #devicemodel#
00226         * - #manufactorer#
00227         * - #devicetype#
00228         */
00229         fValues["deviceid"] = QString(fHandle->getSysInfo()->getProductID());
00230         KPilotCard*device = fHandle->getCardInfo();
00231         fValues["devicename"] = QString(device->getCardName());
00232         fValues["devicemodel"] = i18n("unknown");  // TODO
00233         fValues["manufacturer"] = QString(device->getCardManufacturer());
00234         fValues["devicetype"] = QString(
00235             fHandle->deviceTypeString(fHandle->deviceType()));
00236         KPILOT_DELETE(device);
00237         keepParts.append("hardware");
00238     } else removeParts.append("hardware");
00239     QTimer::singleShot(0, this, SLOT(userInfo()));
00240 }
00241 
00242 void SysInfoConduit::userInfo()
00243 {
00244     FUNCTIONSETUP;
00245     if (fUserInfo) {
00246         /* Retrieve values for
00247          * - #username#
00248          * - #uid#
00249          */
00250         KPilotUser*user=fHandle->getPilotUser();
00251         fValues["username"] = user->getUserName();
00252         if (user->getPasswordLength()>0)
00253             fValues["pw"] = i18n("Password set");
00254         else
00255             fValues["pw"] = i18n("No password set");
00256         fValues["uid"] = QString::number(user->getUserID());
00257         fValues["viewerid"] = QString::number(user->getViewerID());
00258         keepParts.append("user");
00259     } else removeParts.append("user");
00260     QTimer::singleShot(0, this, SLOT(memoryInfo()));
00261 }
00262 
00263 void SysInfoConduit::memoryInfo()
00264 {
00265     FUNCTIONSETUP;
00266     if (fMemoryInfo) {
00267         /* Retrieve values for
00268          * - #rom#
00269          * - #totalmem#
00270          * - #freemem#
00271          */
00272         KPilotCard*device = fHandle->getCardInfo();
00273         fValues["rom"] =  QString::number(device->getRomSize()/1024);
00274         fValues["totalmem"] =  QString::number(device->getRamSize()/1024);
00275         fValues["freemem"] =  QString::number(device->getRamFree()/1024);
00276         keepParts.append("memory");
00277     } else removeParts.append("memory");
00278     QTimer::singleShot(0, this, SLOT(storageInfo()));
00279 }
00280 
00281 void SysInfoConduit::storageInfo()
00282 {
00283     FUNCTIONSETUP;
00284     if (fStorageInfo) {
00285         /* Retrieve values for
00286          * - $cards$
00287          */
00288         KPilotCard*device = fHandle->getCardInfo(1);
00289         if (device && device) {
00290             fValues["cards"] = QString("%1 (%2, %3 kB of %3 kB free)")
00291                 .arg(device->getCardName())
00292                 .arg(device->getCardManufacturer())
00293                 .arg(device->getRamFree()/1024)
00294                 .arg(device->getRamSize()/1024);
00295             KPILOT_DELETE(device);
00296         } else {
00297             fValues["cards"] = i18n("No Cards available via pilot-link");
00298         }
00299         keepParts.append("storage");
00300     } else removeParts.append("storage");
00301     QTimer::singleShot(0, this, SLOT(dbListInfo()));
00302 }
00303 
00304 void SysInfoConduit::dbListInfo()
00305 {
00306     FUNCTIONSETUP;
00307     if (fDBList) {
00308         /* Retrieve values for
00309          * - #dblist(structure)#
00310          */
00311         dblist=fHandle->getDBList();
00312         keepParts.append("dblist");
00313     } else removeParts.append("dblist");
00314     QTimer::singleShot(0, this, SLOT(recNumberInfo()));
00315 }
00316 
00317 void SysInfoConduit::recNumberInfo()
00318 {
00319     FUNCTIONSETUP;
00320     if (fRecordNumber) {
00321         /* Retrieve values for
00322          * - #addresses#
00323          * - #events#
00324          * - #todos#
00325          * - #memos#
00326          */
00327         PilotDatabase*fDatabase;
00328         fValues["addresses"] = "ERROR";
00329         fValues["events"] = "ERROR";
00330         fValues["todos"] = "ERROR";
00331         fValues["memos"] = "ERROR";
00332         fDatabase = new PilotSerialDatabase(pilotSocket(), "AddressDB",  this);
00333         if (fDatabase) {
00334             fValues["addresses"] = QString::number(fDatabase->recordCount());
00335             KPILOT_DELETE(fDatabase);
00336         }
00337         fDatabase = new PilotSerialDatabase(pilotSocket(), "DatebookDB",  this);
00338         if (fDatabase) {
00339             fValues["events"] = QString::number(fDatabase->recordCount());
00340             KPILOT_DELETE(fDatabase);
00341         }
00342         fDatabase = new PilotSerialDatabase(pilotSocket(), "ToDoDB",  this);
00343         if (fDatabase) {
00344             fValues["todos"] = QString::number(fDatabase->recordCount());
00345             KPILOT_DELETE(fDatabase);
00346         }
00347         fDatabase = new PilotSerialDatabase(pilotSocket(), "MemoDB",  this);
00348         if (fDatabase) {
00349             fValues["memos"] = QString::number(fDatabase->recordCount());
00350             KPILOT_DELETE(fDatabase);
00351         }
00352         keepParts.append("records");
00353     } else removeParts.append("records");
00354     QTimer::singleShot(0, this, SLOT(syncInfo()));
00355 }
00356 
00357 void SysInfoConduit::syncInfo()
00358 {
00359     FUNCTIONSETUP;
00360     if (fSyncInfo) {
00361         /* Retrieve values for
00362          * - #lastsync#
00363          * - #lastsuccsync#
00364          * - #lastsyncpc#
00365          */
00366         KPilotUser*user=fHandle->getPilotUser();
00367         time_t lastsync = user->getLastSyncDate();
00368         QDateTime qlastsync;
00369         qlastsync.setTime_t(lastsync);
00370         fValues["lastsync"] = qlastsync.toString(Qt::LocalDate);
00371         lastsync = user->getLastSuccessfulSyncDate();
00372         qlastsync.setTime_t(lastsync);
00373         fValues["lastsuccsync"] = qlastsync.toString(Qt::LocalDate);
00374         fValues["lastsyncpc"] = QString::number(user->getLastSyncPC());
00375         keepParts.append("sync");
00376     } else removeParts.append("sync");
00377     QTimer::singleShot(0, this, SLOT(pcVersionInfo()));
00378 }
00379 
00380 void SysInfoConduit::pcVersionInfo()
00381 {
00382     FUNCTIONSETUP;
00383     if (fKDEVersion) {
00384         /* Retrieve values for
00385          * - #os#
00386          * - #qt#
00387          * - #kde#
00388          * - #kpilot#
00389          * - #pilotlink#
00390          */
00391         fValues["kpilot"] = QString::fromLatin1(KPILOT_VERSION);
00392         fValues["kde"] = i18n("unknown");
00393         fValues["qt"] = i18n("unknown");
00394         fValues["os"] = i18n("unknown");
00395         fValues["hostname"] = i18n("unknown");
00396         struct utsname name;
00397         if (uname (&name) >= 0) {
00398             fValues["os"] = QString("%1 %3, %5")
00399                 .arg(name.sysname)
00400                 .arg(name.release)
00401                 .arg(name.machine);
00402             fValues["hostname"] = QString("%2").arg(name.nodename);
00403         }
00404 #ifdef KDE_VERSION_STRING
00405         fValues["kde"] = QString::fromLatin1(KDE_VERSION_STRING);
00406 #endif
00407 #ifdef QT_VERSION_STR
00408         fValues["qt"] = QString::fromLatin1(QT_VERSION_STR);
00409 #endif
00410         fValues["pilotlink"] = CSL1("%1.%2.%3%4")
00411             .arg(PILOT_LINK_VERSION)
00412             .arg(PILOT_LINK_MAJOR)
00413             .arg(PILOT_LINK_MINOR)
00414 #ifdef PILOT_LINK_PATCH
00415             .arg(QString::fromLatin1(PILOT_LINK_PATCH));
00416 #else
00417             .arg(QString());
00418 #endif
00419         keepParts.append("pcversion");
00420     } else removeParts.append("pcversion");
00421     QTimer::singleShot(0, this, SLOT(palmVersionInfo()));
00422 }
00423 
00424 void SysInfoConduit::palmVersionInfo()
00425 {
00426     FUNCTIONSETUP;
00427     if (fPalmOSVersion) {
00428         /* Retrieve values for
00429          * - #palmos#
00430          */
00431 /*      fValues["palmos"] = QString("PalmOSŪ %1.%2 (compat %3.%4)")
00432             .arg(fHandle->getSysInfo()->getMajorVersion())
00433             .arg(fHandle->getSysInfo()->getMinorVersion())
00434             .arg(fHandle->getSysInfo()->getCompatMajorVersion())
00435             .arg(fHandle->getSysInfo()->getCompatMinorVersion());*/
00436         fValues["palmos"] = QString("PalmOSŪ %1.%2").arg(fHandle->majorVersion()).arg(fHandle->minorVersion());
00437 
00438         keepParts.append("palmversion");
00439     } else removeParts.append("palmversion");
00440     QTimer::singleShot(0, this, SLOT(debugInfo()));
00441 }
00442 
00443 void SysInfoConduit::debugInfo()
00444 {
00445     FUNCTIONSETUP;
00446     if (fDebugInfo) {
00447         /* Retrieve values for
00448          * - #debug#
00449          */
00450         fValues["debug"] = i18n("No debug data");
00451         keepParts.append("debug");
00452     } else removeParts.append("debug");
00453     QTimer::singleShot(0, this, SLOT(writeFile()));
00454 }
00455 
00456 void SysInfoConduit::writeFile()
00457 {
00458     FUNCTIONSETUP;
00459 
00460     fValues["date"] = QDateTime::currentDateTime().toString(Qt::LocalDate);
00461 
00462     QString output;
00463     // Open the template file
00464     QString templatefile;
00465     switch(fOutputType)
00466     {
00467         case eOutputText:
00468             templatefile=locate("data", "kpilot/sysinfoconduit/Template.txt");
00469             break;
00470         case eOutputTemplate:
00471             templatefile=fTemplateFile;
00472             break;
00473         case eOutputHTML:
00474         default:
00475             templatefile=locate("data", "kpilot/sysinfoconduit/Template.html");
00476             break;
00477     }
00478 
00479     // Read in the template, close the file
00480     bool loaded=false;
00481     if (!templatefile.isEmpty()){
00482 #ifdef DEBUG
00483         DEBUGCONDUIT<<"Loading template file "<<templatefile<<endl;
00484 #endif
00485         QFile infile(templatefile);
00486         if (infile.open(IO_ReadOnly)) {
00487             QTextStream instream(&infile);
00488             output = instream.read();
00489             infile.close();
00490             loaded=true;
00491         }
00492     }
00493 
00494     if (!loaded) {
00495         kdWarning()<<"Loading template file "<<templatefile<<" failed. Using default template instead."<< endl;
00496         output=defaultpage;
00497     }
00498 
00499     // Remove all parts not extracted
00500     for ( QStringList::Iterator it = removeParts.begin(); it != removeParts.end(); ++it ) {
00501         QRegExp re(QString("<!--#if%1#.*#endif%1#-->").arg(*it).arg(*it));
00502         re.setMinimal(true);
00503         output.remove(re);
00504     }
00505     for ( QStringList::Iterator it = keepParts.begin(); it != keepParts.end(); ++it ) {
00506         QRegExp re(QString("<!--#if%1#(.*)#endif%1#-->").arg(*it).arg(*it));
00507         re.setMinimal(true);
00508         output.replace(re, "\\1");
00509     }
00510 
00511     // Do a loop through all keys in fValues
00512     QMap<QString,QString>::Iterator it;
00513     for ( it = fValues.begin(); it != fValues.end(); ++it ) {
00514         output.replace(QString("#%1#").arg(it.key().latin1()), it.data().latin1());
00515     }
00516 
00517     // Insert the list of databases
00518     QRegExp re("#dblist\\[(.*)\\]#");
00519     re.setMinimal(true);
00520     while (re.search(output)>=0){
00521         QString dbstring;
00522         QString subpatt=re.cap(1);
00523         DBInfo*dbi;
00524         for (dbi=dblist.first(); dbi; dbi=dblist.next() ) {
00525             QString newpatt(subpatt);
00526             char tmpchr[5];
00527             ::memset(&tmpchr[0], 0, 5);
00528             /* Patterns for the dblist argument:
00529              * %0 .. Database name
00530              * %1 .. type
00531              * %2 .. creator
00532              * %3 .. index
00533              * %4 .. flags
00534              * %5 .. miscFlags
00535              * %6 .. version
00536              * %7 .. createDate
00537              * %8 .. modifyDate
00538              * %9 .. backupDate
00539              */
00540             newpatt.replace("%0", QString(dbi->name));
00541             set_long(&tmpchr[0],dbi->type);
00542             newpatt.replace("%1", QString(tmpchr));
00543             set_long(&tmpchr[0],dbi->creator);
00544             newpatt.replace("%2", tmpchr);
00545             newpatt.replace("%3", QString::number(dbi->index));
00546             newpatt.replace("%4", QString::number(dbi->flags));
00547             newpatt.replace("%5", QString::number(dbi->miscFlags));
00548             newpatt.replace("%6", QString::number(dbi->version));
00549             QDateTime tm;
00550             tm.setTime_t(dbi->createDate);
00551             newpatt.replace("%7", tm.toString(Qt::LocalDate));
00552             tm.setTime_t(dbi->modifyDate);
00553             newpatt.replace("%8", tm.toString(Qt::LocalDate));
00554             tm.setTime_t(dbi->backupDate);
00555             newpatt.replace("%9", tm.toString(Qt::LocalDate));
00556 
00557             dbstring.append(newpatt);
00558         }
00559         // Now, just replace the whole found pattern by the string we just constructed.
00560         output.replace(re.cap(0), dbstring);
00561     }
00562 
00563     // Write out the result
00564     QFile outfile(fOutputFile);
00565     if (fOutputFile.isEmpty() || (!outfile.open(IO_WriteOnly)) ) {
00566         QFileInfo fi(QDir::home(), QString("KPilotSysInfo.")+QFileInfo(templatefile).extension() );
00567         fOutputFile=fi.absFilePath();
00568         kdWarning()<<i18n("Unable to open output file, using %1 instead.").arg(fOutputFile).latin1()<<endl;
00569         emit logMessage(i18n("Unable to open output file, using %1 instead.").arg(fOutputFile));
00570         outfile.setName(fOutputFile);
00571         if (!outfile.open(IO_WriteOnly)) {
00572             kdWarning()<<i18n("Unable to open %1").arg(fOutputFile).latin1()<<endl;
00573             emit logError(i18n("Unable to open %1").arg(fOutputFile));
00574             QTimer::singleShot(0, this, SLOT(cleanup()));
00575             return;
00576         }
00577     }
00578 
00579     // Finally, write the actual text out to the file.
00580     QTextStream outstream(&outfile);
00581     outstream<<output;
00582     outfile.close();
00583 
00584     emit logMessage(i18n("Handheld system information written to the file %1").arg(fOutputFile));
00585     QTimer::singleShot(0, this, SLOT(cleanup()));
00586 }
00587 
00588 void SysInfoConduit::cleanup()
00589 {
00590     FUNCTIONSETUP;
00591     // Nothing to clean up so far (Do I have memory leaks somewhere???)
00592     emit syncDone(this);
00593 }
KDE Logo
This file is part of the documentation for kpilot Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:36:49 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003