kpilot Library API Documentation

DOC-converter.cc

00001 /* DOC-converter.cc KPilot 00002 ** 00003 ** Copyright (C) 2002-2003 by Reinhold Kainhofer 00004 ** 00005 ** The doc converter synchronizes text files on the PC with DOC databases on the Palm 00006 */ 00007 00008 /* 00009 ** This program is free software; you can redistribute it and/or modify 00010 ** it under the terms of the GNU General Public License as published by 00011 ** the Free Software Foundation; either version 2 of the License, or 00012 ** (at your option) any later version. 00013 ** 00014 ** This program is distributed in the hope that it will be useful, 00015 ** but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00017 ** GNU General Public License for more details. 00018 ** 00019 ** You should have received a copy of the GNU General Public License 00020 ** along with this program in a file called COPYING; if not, write to 00021 ** the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, 00022 ** MA 02111-1307, USA. 00023 */ 00024 00025 /* 00026 ** Bug reports and questions can be sent to kde-pim@kde.org 00027 */ 00028 00029 00030 #include "options.h" 00031 #include "DOC-converter.moc" 00032 00033 #include <qdir.h> 00034 #include <qfileinfo.h> 00035 #include <qregexp.h> 00036 #include <qsortedlist.h> 00037 00038 #include <pilotDatabase.h> 00039 #include <pilotLocalDatabase.h> 00040 #include <pilotSerialDatabase.h> 00041 00042 #include "pilotDOCHead.h" 00043 #include "pilotDOCEntry.h" 00044 #include "pilotDOCBookmark.h" 00045 00046 00047 00048 // Something to allow us to check what revision 00049 // the modules are that make up a binary distribution. 00050 const char *doc_converter_id = "$Id: DOC-converter.cc,v 1.13 2003/08/12 18:11:51 mueller Exp $"; 00051 00052 #define min(a,b) (a<b)?(a):(b) 00053 00054 00055 00056 /**************************************************************************************************** 00057 * various bookmark classes. Most important is the bmkList findMatches(QString) function, 00058 * which needs to return a list of all bookmarks found for the given bookmark expression. 00059 * A bookmark usually consists of a bookmark text and an offset into the text document. 00060 ****************************************************************************************************/ 00061 00062 00063 bool docBookmark::compare_pos=true; 00064 00065 bool operator< ( const docBookmark &s1, const docBookmark &s2) 00066 { 00067 if (docBookmark::compare_pos) { return s1.position<s2.position;} 00068 else {return s2.bmkName<s2.bmkName;} 00069 } 00070 00071 bool operator== ( const docBookmark &s1, const docBookmark &s2) 00072 { 00073 return (s1.position==s2.position) && (s1.bmkName==s2.bmkName); 00074 } 00075 00076 00077 int docMatchBookmark::findMatches(QString doctext, bmkList &fBookmarks) { 00078 FUNCTIONSETUP; 00079 // bmkList res; 00080 int pos = 0, nr=0, found=0; 00081 DEBUGCONDUIT<<"Finding matches of "<<pattern<<endl; 00082 00083 while (pos >= 0 && found<to) { 00084 pos = doctext.find(pattern, pos); 00085 DEBUGCONDUIT<<"Result of search: pos="<<pos<<endl; 00086 if (pos >= 0) 00087 { 00088 found++; 00089 if (found>=from && found<=to) { 00090 fBookmarks.append(new docBookmark(pattern, pos)); 00091 nr++; 00092 00093 } 00094 pos++; 00095 } 00096 } 00097 return nr; 00098 } 00099 00100 00101 00102 int docRegExpBookmark::findMatches(QString doctext, bmkList &fBookmarks) { 00103 // bmkList res; 00104 QRegExp rx(pattern); 00105 int pos = 0, nr=0, found=0; 00106 00107 while (pos>=0 && found<=to) { 00108 DEBUGCONDUIT<<"Searching for bookmark "<<pattern<<endl; 00109 pos=rx.search(doctext, pos); 00110 if (pos > -1) { 00111 found++; 00112 if (found>=from && found<to) { 00113 if (capSubexpression>=0) { 00114 fBookmarks.append(new docBookmark(/*bmkName.left(16)*/rx.cap(capSubexpression), pos)); 00115 } else { 00116 // TODO: use the subexpressions from the regexp for the bmk name ($1..$9) (given as separate regexp) 00117 QString bmkText(bmkName); 00118 for (int i=0; i<=rx.numCaptures(); i++) { 00119 bmkText.replace(QString("$%1").arg(i), rx.cap(i)); 00120 bmkText.replace(QString("\\%1").arg(i), rx.cap(i)); 00121 } 00122 fBookmarks.append(new docBookmark(bmkText.left(16), pos)); 00123 } 00124 nr++; 00125 } 00126 pos++; 00127 } 00128 } 00129 return nr; 00130 } 00131 00132 00133 00134 00135 00136 00137 00138 00139 /********************************************************************* 00140 C O N S T R U C T O R 00141 *********************************************************************/ 00142 00143 00144 DOCConverter::DOCConverter(QObject *parent, const char *name):QObject(parent,name) { 00145 FUNCTIONSETUP; 00146 docdb=0L; 00147 eSortBookmarks=eSortNone; 00148 fBookmarks.setAutoDelete( TRUE ); 00149 (void) doc_converter_id; 00150 } 00151 00152 00153 00154 DOCConverter::~DOCConverter() { 00155 FUNCTIONSETUP; 00156 } 00157 00158 00159 00160 00161 00162 /********************************************************************* 00163 S Y N C S T R U C T U R E 00164 *********************************************************************/ 00165 00166 00167 00168 void DOCConverter::setTXTpath(QString path, QString file) { 00169 QDir dr(path); 00170 QFileInfo pth(dr, file); 00171 if (!file.isEmpty()) 00172 txtfilename = pth.absFilePath(); 00173 } 00174 00175 00176 00177 void DOCConverter::setTXTpath(QString filename) { 00178 if (!filename.isEmpty()) txtfilename = filename; 00179 } 00180 00181 00182 00183 void DOCConverter::setPDB(PilotDatabase * dbi) { 00184 if (dbi) docdb = dbi; 00185 } 00186 00187 00188 00189 QString DOCConverter::readText() { 00190 FUNCTIONSETUP; 00191 if (txtfilename.isEmpty()) return QString(); 00192 QFile docfile(txtfilename); 00193 if (!docfile.open(IO_ReadOnly)) 00194 { 00195 emit logError(i18n("Unable to open text file %1 for reading.").arg(txtfilename)); 00196 return QString(); 00197 } 00198 00199 QTextStream docstream(&docfile); 00200 00201 QString doc = docstream.read(); 00202 docfile.close(); 00203 return doc; 00204 } 00205 00206 00207 00208 int DOCConverter::findBmkEndtags(QString &text, bmkList&fBmks) { 00209 FUNCTIONSETUP; 00210 // Start from the end of the text 00211 int pos = text.length() - 1, nr=0; 00212 bool doSearch=true; 00213 while (pos >= 0/* && doSearch*/) { 00214 DEBUGCONDUIT<<"Current character is \'"<<text[pos].latin1()<<"\'"<<endl; 00215 // skip whitespace until we reach a > 00216 while (text[pos].isSpace() && pos >= 0) { 00217 DEBUGCONDUIT<<"Skipping whitespaces at the end of the file"<<endl; 00218 pos--; 00219 } 00220 // every other character than a > is assumed to belong to the text, so there are no more bookmarks. 00221 if (pos < 0 || text[pos] != '>') { 00222 DEBUGCONDUIT<<"Current character \'"<<text[pos].latin1()<<"\' at position "<<pos<<" is not and ending >. Finish searching for bookmarks."<<endl; 00223 00224 pos=-1; 00225 break; 00226 } else { 00227 int endpos = pos; 00228 doSearch=true; 00229 DEBUGCONDUIT<<"Found the ending >, now looking for the opening <"<<endl; 00230 00231 // Search for the opening <. There must not be a newline in the bookmark text. 00232 while (doSearch && pos > 0) { 00233 // DEBUGCONDUIT<<"pos="<<pos<<", char="<<text[pos].latin1()<<endl; 00234 pos--; 00235 if (text[pos] == '\n') { 00236 DEBUGCONDUIT<<"Found carriage return at position "<<pos<<" inside the bookmark text, assuming this is not a bookmark, and the text ends in a >"<<endl; 00237 doSearch = false; 00238 pos = -1; 00239 break; 00240 } 00241 if (text[pos] == '<') { 00242 fBmks.append(new docMatchBookmark(text.mid(pos + 1, endpos - pos - 1))); 00243 nr++; 00244 DEBUGCONDUIT<<"Found opening < at position "<<pos<<", bookmarktext ="<<text.mid(pos+1, endpos-pos-1)<<endl; 00245 text.remove(pos, text.length()); 00246 pos--; 00247 doSearch = false; 00248 } 00249 } 00250 } 00251 DEBUGCONDUIT<<"Finished processing the next bookmark, current position: "<<pos<<endl; 00252 } 00253 return nr; 00254 } 00255 00256 int DOCConverter::findBmkInline(QString &text, bmkList &fBmks) { 00257 FUNCTIONSETUP; 00258 // bmkList res; 00259 int nr=0; 00260 QRegExp rx(CSL1("<\\*(.*)\\*>")); 00261 00262 rx.setMinimal(TRUE); 00263 int pos = 0; 00264 while (pos >= 0) { 00265 pos = rx.search(text, pos); 00266 if (pos >= 0) { 00267 fBmks.append(new docBookmark(rx.cap(1), pos+1)); 00268 nr++; 00269 text = text.remove(pos, rx.matchedLength()); 00270 } 00271 } 00272 return nr; 00273 } 00274 00275 int DOCConverter::findBmkFile(QString &, bmkList &fBmks) { 00276 FUNCTIONSETUP; 00277 int nr=0; 00278 00279 QString bmkfilename = txtfilename; 00280 if (bmkfilename.endsWith(CSL1(".txt"))){ 00281 bmkfilename.remove(bmkfilename.length()-4, 4); 00282 } 00283 QString oldbmkfilename=bmkfilename; 00284 bmkfilename+=CSL1(BMK_SUFFIX); 00285 QFile bmkfile(bmkfilename); 00286 if (!bmkfile.open(IO_ReadOnly)) { 00287 bmkfilename=oldbmkfilename+CSL1(PDBBMK_SUFFIX); 00288 bmkfile.setName(bmkfilename); 00289 if (!bmkfile.open(IO_ReadOnly)) { 00290 DEBUGCONDUIT<<"Unable to open bookmarks file "<<bmkfilename<<" for reading the bookmarks of "<<docdb ->dbPathName()<<endl; 00291 return 0; 00292 } 00293 } 00294 00295 DEBUGCONDUIT<<"Bookmark file: "<<bmkfilename<<endl; 00296 00297 QTextStream bmkstream(&bmkfile); 00298 QString line; 00299 while ( !(line=bmkstream.readLine()).isEmpty() ) { 00300 if (!line.isEmpty() && !line.startsWith(CSL1("#")) ) { 00301 QStringList bmkinfo=QStringList::split(CSL1(","), line); 00302 int fieldnr=bmkinfo.count(); 00303 // We use the same syntax for the entries as MakeDocJ bookmark files: 00304 // <bookmark>,<string-to-search>,<bookmark-name-string>,<starting-bookmark>,<ending-bookmark> 00305 // For an explanation see: http://home.kc.rr.com/krzysztow/PalmPilot/MakeDocJ/index.html 00306 if (fieldnr>0){ 00307 DEBUGCONDUIT<<"Working on bookmark \""<<line<<"\""<<endl; 00308 docMatchBookmark*bmk=0L; 00309 QString bookmark=bmkinfo[0]; 00310 bool ok; 00311 int pos=bookmark.toInt(&ok); 00312 if (ok) { 00313 if (fieldnr>1) { 00314 QString name(bmkinfo[1]); 00315 DEBUGCONDUIT<<"Bookmark \""<<name<<"\" set at position "<<pos<<endl; 00316 fBmks.append(new docBookmark(name, pos)); 00317 } 00318 } else if (bookmark==CSL1("-") || bookmark==CSL1("+")) { 00319 if (fieldnr>1) { 00320 QString patt(bmkinfo[1]); 00321 QString name(patt); 00322 if (fieldnr>2) { 00323 int cap=bmkinfo[2].toInt(&ok); 00324 if (ok) { 00325 bmk=new docRegExpBookmark(patt, cap); 00326 } else { 00327 name=bmkinfo[2]; 00328 bmk=new docRegExpBookmark(patt, name); 00329 } 00330 } else{ 00331 bmk=new docRegExpBookmark(patt, name); 00332 } 00333 // The third entry in the line (optional) denotes the index of a capture subexpression (if an integer) or the bookmark text as regexp (if a string) 00334 DEBUGCONDUIT<<"RegExp Bookmark, pattern="<<patt<<", name="<<name<<endl; 00335 if (bmk) { 00336 if (bookmark==CSL1("-")) { 00337 bmk->from=1; 00338 bmk->to=1; 00339 } else { 00340 if (fieldnr>3) { 00341 bool ok; 00342 int tmp=bmkinfo[3].toInt(&ok); 00343 if (ok) bmk->from=tmp; 00344 if (fieldnr>4) { 00345 tmp=bmkinfo[4].toInt(&ok); 00346 if (ok) bmk->to=tmp; 00347 } 00348 } 00349 } 00350 fBmks.append(bmk); 00351 bmk=0L; 00352 } else { 00353 DEBUGCONDUIT<<"Could not allocate bookmark "<<name<<endl; 00354 } 00355 } else { 00356 DEBUGCONDUIT<<"RegExp bookmark found with no other information (no bookmark pattern nor name)"<<endl; 00357 } 00358 } else { 00359 QString pattern(bookmark); 00360 if (fieldnr>1) pattern=bmkinfo[1]; 00361 if (fieldnr>2) bookmark=bmkinfo[2]; 00362 DEBUGCONDUIT<<"RegExp Bookmark, pattern="<<pattern<<", name="<<bookmark<<endl; 00363 bmk=new docRegExpBookmark(pattern, bookmark); 00364 if (bmk) { 00365 bmk->from=1; 00366 bmk->to=1; 00367 fBmks.append(bmk); 00368 } 00369 } 00370 } // fieldnr>0 00371 } // !line.isEmpty() 00372 } // while 00373 return nr; 00374 } 00375 00376 bool DOCConverter::convertTXTtoPDB() { 00377 FUNCTIONSETUP; 00378 00379 if (!docdb) { 00380 emit logError(i18n("Unable to open Database for writing")); 00381 return false; 00382 } 00383 00384 QString text = readText(); 00385 00386 if (fBmkTypes & eBmkEndtags) { 00387 findBmkEndtags(text, fBookmarks); 00388 } // end: EndTag Bookmarks 00389 00390 00391 // Search for all tags <* Bookmark text *> in the text. We have to delete them immediately, otherwise the later bookmarks will be off. 00392 if (fBmkTypes & eBmkInline) { 00393 findBmkInline(text, fBookmarks); 00394 } // end: Inline Bookmarks 00395 00396 00397 // Read in regular expressions and positions from an external file (doc-filename with extension .bmk) 00398 if (fBmkTypes & eBmkFile) 00399 { 00400 findBmkFile(text, fBookmarks); 00401 } 00402 00403 // Process the bookmarks: find the occurrences of the regexps, and sort them if requested: 00404 bmkSortedList pdbBookmarks; 00405 pdbBookmarks.setAutoDelete(TRUE); 00406 docBookmark*bmk; 00407 for (bmk = fBookmarks.first(); bmk; bmk = fBookmarks.next()) 00408 { 00409 bmk->findMatches(text, pdbBookmarks); 00410 } 00411 00412 switch (eSortBookmarks) 00413 { 00414 case eSortName: 00415 docBookmark::compare_pos=false; 00416 // qHeapSort(pdbBookmarks); 00417 pdbBookmarks.sort(); 00418 break; 00419 case eSortPos: 00420 docBookmark::compare_pos=true; 00421 pdbBookmarks.sort(); 00422 break; 00423 case eSortNone: 00424 default: 00425 break; 00426 } 00427 00428 #ifdef DEBUG 00429 DEBUGCONDUIT << "Bookmarks: "<<endl; 00430 for (bmk = pdbBookmarks.first(); bmk; bmk = pdbBookmarks.next()) 00431 { 00432 DEBUGCONDUIT<<bmk->bmkName.left(20)<<" at position "<<bmk->position<<endl; 00433 } 00434 #endif 00435 00436 if (!docdb->isDBOpen()) { 00437 emit logError(i18n("Unable to open palm doc database %1").arg(docdb->dbPathName()) ); 00438 return false; 00439 } 00440 00441 // Clean the whole database, otherwise the records would be just appended! 00442 docdb->deleteRecord(0, true); 00443 00444 // Header record for the doc file format 00445 PilotDOCHead docHead; 00446 docHead.position=0; 00447 docHead.recordSize=4096; 00448 docHead.spare=0; 00449 docHead.storyLen=text.length(); 00450 docHead.version=compress?DOC_COMPRESSED:DOC_UNCOMPRESSED; 00451 docHead.numRecords=(int)( (text.length()-1)/docHead.recordSize)+1; 00452 PilotRecord*rec=docHead.pack(); 00453 docdb->writeRecord(rec); 00454 KPILOT_DELETE(rec); 00455 00456 DEBUGCONDUIT << "Write header record: length="<<text.length()<<", compress="<<compress<<endl; 00457 00458 // First compress the text, then write out the bookmarks and - if existing - also the annotations 00459 int len=text.length(); 00460 int start=0,reclen=0; 00461 int recnum=0; 00462 while (start<len) 00463 { 00464 reclen=min(len-start, PilotDOCEntry::TEXT_SIZE); 00465 DEBUGCONDUIT << "Record #"<<recnum<<", reclen="<<reclen<<", compress="<<compress<<endl; 00466 00467 PilotDOCEntry recText; 00468 // recText.setText(text.mid(start, reclen), reclen); 00469 recText.setText(text.mid(start, reclen)); 00470 // if (compress) 00471 recText.setCompress(compress); 00472 PilotRecord*textRec=recText.pack(); 00473 docdb->writeRecord(textRec); 00474 recnum++; 00475 start+=reclen; 00476 KPILOT_DELETE(textRec); 00477 } 00478 00479 recnum=0; 00480 // Finally, write out the bookmarks 00481 for (bmk = pdbBookmarks.first(); bmk; bmk = pdbBookmarks.next()) 00482 // for (bmkList::const_iterator it=pdbBookmarks.begin(); it!=pdbBookmarks.end(); it++) 00483 { 00484 recnum++; 00485 DEBUGCONDUIT << "Bookmark #"<<recnum<<", Name="<<bmk->bmkName.left(20)<<", Position="<<bmk->position<<endl; 00486 00487 PilotDOCBookmark bmkEntry; 00488 bmkEntry.pos=bmk->position; 00489 strncpy(&bmkEntry.bookmarkName[0], bmk->bmkName.latin1(), 16); 00490 PilotRecord*bmkRecord=bmkEntry.pack(); 00491 docdb->writeRecord(bmkRecord); 00492 KPILOT_DELETE(bmkRecord); 00493 } 00494 00495 pdbBookmarks.clear(); 00496 fBookmarks.clear(); 00497 00498 return true; 00499 } 00500 00501 00502 00503 bool DOCConverter::convertPDBtoTXT() 00504 { 00505 FUNCTIONSETUP; 00506 if (txtfilename.isEmpty()) { 00507 emit logError(i18n("No filename set for the conversion")); 00508 return false; 00509 } 00510 00511 if (!docdb) { 00512 emit logError(i18n("Unable to open Database for reading")); 00513 return false; 00514 } 00515 00516 // The first record of the db is the document header containing information about the doc db 00517 PilotRecord*headerRec = docdb->readRecordByIndex(0); 00518 if (!headerRec) 00519 { 00520 emit logError(i18n("Unable to read database header for database %1.").arg(docdb->dbPathName())); 00521 KPILOT_DELETE(docdb); 00522 return false; 00523 } 00524 PilotDOCHead header(headerRec); 00525 KPILOT_DELETE(headerRec); 00526 00527 DEBUGCONDUIT<<"Database "<<docdb->dbPathName()<<" has "<<header.numRecords<<" text records, "<<endl 00528 <<" total number of records: "<<docdb->recordCount()<<endl 00529 <<" position="<<header.position<<endl 00530 <<" recordSize="<<header.recordSize<<endl 00531 <<" spare="<<header.spare<<endl 00532 <<" storyLen="<<header.storyLen<<endl 00533 // <<" textRecordSize="<<header.textRecordSize<<endl 00534 <<" version="<<header.version<<endl; 00535 00536 // next come the header.numRecords real document records (might be compressed, see the version flag in the header) 00537 QFile docfile(txtfilename); 00538 if (!docfile.open(IO_WriteOnly)) 00539 { 00540 emit logError(i18n("Unable to open output file %1.").arg(txtfilename)); 00541 KPILOT_DELETE(docdb); 00542 return false; 00543 } 00544 QString doctext; 00545 for (int i=1; i<header.numRecords+1; i++) 00546 { 00547 PilotRecord*rec=docdb->readRecordByIndex(i); 00548 if (rec) 00549 { 00550 PilotDOCEntry recText(rec, header.version==DOC_COMPRESSED); 00551 doctext.append(recText.getText()); 00552 DEBUGCONDUIT<<"Record "<<i<<endl; 00553 KPILOT_DELETE(rec); 00554 } else { 00555 emit logMessage(i18n("Could not read text record #%1 from Database %2").arg(i).arg(docdb->dbPathName())); 00556 } 00557 } 00558 00559 // After the document records possibly come a few bookmark records, so read them in and put them in a separate bookmark file. 00560 // for the ztxt conduit there might be annotations after the bookmarks, so the upper bound needs to be adapted. 00561 int upperBmkRec=docdb->recordCount(); 00562 bmkSortedList bmks; 00563 bmks.setAutoDelete(TRUE); 00564 for (int i=header.numRecords+1; i<upperBmkRec; i++) 00565 { 00566 PilotRecord*rec=docdb->readRecordByIndex(i); 00567 if (rec) 00568 { 00569 PilotDOCBookmark bookie(rec); 00570 docBookmark*bmk=new docBookmark(bookie.bookmarkName, bookie.pos); 00571 bmks.append(bmk); 00572 KPILOT_DELETE(rec); 00573 } else { 00574 emit logMessage(i18n("Could not read bookmark record #%1 from Database %2").arg(i).arg(docdb->dbPathName())); 00575 } 00576 } 00577 // TODO: Sort the list of bookmarks according to their position 00578 docBookmark::compare_pos=true; 00579 bmks.sort(); 00580 00581 if ((fBmkTypes & eBmkFile) && (bmks.count()>0)) 00582 { 00583 QString bmkfilename = docfile.name(); 00584 if (bmkfilename.endsWith(CSL1(".txt"))){ 00585 bmkfilename.remove(bmkfilename.length()-4, 4); 00586 } 00587 bmkfilename+=CSL1(PDBBMK_SUFFIX); 00588 QFile bmkfile(bmkfilename); 00589 if (!bmkfile.open(IO_WriteOnly)) 00590 { 00591 emit logError(i18n("Unable to open file %1 for the bookmarks of %2.") 00592 .arg(bmkfilename).arg(docdb ->dbPathName())); 00593 } 00594 else 00595 { 00596 DEBUGCONDUIT<<"Writing "<<upperBmkRec-header.numRecords<< 00597 "("<<upperBmkRec<<") bookmarks to file "<<bmkfilename<<endl; 00598 QTextStream bmkstream(&bmkfile); 00599 for (docBookmark*bmk=bmks.first(); bmk; bmk=bmks.next()) 00600 { 00601 bmkstream<<bmk->position<<", "<<bmk->bmkName<<endl; 00602 } 00603 //bmkstream.close(); 00604 bmkfile.close(); 00605 } 00606 } 00607 if (fBmkTypes & eBmkInline) 00608 { 00609 for (docBookmark*bmk=bmks.last(); bmk; bmk=bmks.prev()) 00610 { 00611 doctext.insert(bmk->position, QString(CSL1("<*") + 00612 bmk->bmkName + 00613 CSL1("*>"))); 00614 } 00615 } 00616 00617 // Finally, write the actual text out to the file. 00618 QTextStream docstream(&docfile); 00619 docstream<<doctext; 00620 //docstream.close(); 00621 docfile.close(); 00622 docdb->cleanup(); 00623 // reset all records to unchanged. I don't know if this is really such a wise idea? 00624 docdb->resetSyncFlags(); 00625 return true; 00626 } 00627 00628
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 Wed Jul 28 23:57:48 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003