libkcal Library API Documentation

htmlexport.cpp

00001 /* 00002 This file is part of libkcal. 00003 00004 Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org> 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License as published by the Free Software Foundation; either 00009 version 2 of the License, or (at your option) any later version. 00010 00011 This library is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00014 Library General Public License for more details. 00015 00016 You should have received a copy of the GNU Library General Public License 00017 along with this library; see the file COPYING.LIB. If not, write to 00018 the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 00019 Boston, MA 02111-1307, USA. 00020 */ 00021 00022 #include <qapplication.h> 00023 #include <qfile.h> 00024 #include <qtextstream.h> 00025 #include <qtextcodec.h> 00026 #include <qregexp.h> 00027 00028 #include <kglobal.h> 00029 #include <klocale.h> 00030 #include <kdebug.h> 00031 #include <kcalendarsystem.h> 00032 00033 #include <libkcal/calendar.h> 00034 #include <libkcal/event.h> 00035 #include <libkcal/todo.h> 00036 00037 #ifndef KORG_NOKABC 00038 #include <kabc/stdaddressbook.h> 00039 #endif 00040 #include "htmlexport.h" 00041 00042 using namespace KCal; 00043 00044 HtmlExport::HtmlExport( Calendar *calendar ) : 00045 mCalendar( calendar ), 00046 mMonthViewEnabled( true ), mEventsEnabled( false ), mTodosEnabled( true ), 00047 mCategoriesTodoEnabled( false ), mAttendeesTodoEnabled( false ), 00048 mCategoriesEventEnabled( false ), mAttendeesEventEnabled( false ), 00049 mDueDateEnabled( false ), 00050 mExcludePrivateTodoEnabled( false ), 00051 mExcludeConfidentialTodoEnabled( false ), 00052 mExcludePrivateEventEnabled( false ), 00053 mExcludeConfidentialEventEnabled( false ) 00054 { 00055 mTitle = I18N_NOOP("Calendar"); 00056 mTitleTodo = I18N_NOOP("To-Do List"); 00057 mCreditName = ""; 00058 mCreditURL = ""; 00059 } 00060 00061 bool HtmlExport::save(const QString &fileName) 00062 { 00063 QFile f(fileName); 00064 if (!f.open(IO_WriteOnly)) { 00065 return false; 00066 } 00067 QTextStream ts(&f); 00068 bool success = save(&ts); 00069 f.close(); 00070 return success; 00071 } 00072 00073 bool HtmlExport::save(QTextStream *ts) 00074 { 00075 ts->setEncoding(QTextStream::UnicodeUTF8); 00076 00077 // Write HTML header 00078 *ts << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "; 00079 *ts << "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 00080 00081 *ts << "<html><head>" << endl; 00082 *ts << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset="; 00083 *ts << "UTF-8\" />\n"; 00084 if (!mTitle.isEmpty()) 00085 *ts << " <title>" << mTitle << "</title>\n"; 00086 *ts << " <style type=\"text/css\">\n"; 00087 *ts << styleSheet(); 00088 *ts << " </style>\n"; 00089 *ts << "</head><body>\n"; 00090 00091 // TO DO: Write header 00092 // (Heading, Calendar-Owner, Calendar-Date, ...) 00093 00094 if (eventsEnabled() || monthViewEnabled()) { 00095 if (!mTitle.isEmpty()) 00096 *ts << "<h1>" << mTitle << "</h1>\n"; 00097 } 00098 00099 // Write Month View 00100 if (monthViewEnabled()) { 00101 createHtmlMonthView(ts); 00102 } 00103 00104 // Write Event List 00105 if (eventsEnabled()) { 00106 // Write HTML page content 00107 createHtmlEventList(ts); 00108 } 00109 00110 // Write Todo List 00111 if (todosEnabled()) { 00112 if (!mTitleTodo.isEmpty()) 00113 *ts << "<h1>" << mTitleTodo << "</h1>\n"; 00114 00115 // Write HTML page content 00116 createHtmlTodoList(ts); 00117 } 00118 00119 // Write trailer 00120 QString trailer = i18n("This page was created "); 00121 00122 if (!mEmail.isEmpty()) { 00123 if (!mName.isEmpty()) 00124 trailer += i18n("by <a href=\"mailto:%1\">%2</a> ").arg( mEmail ).arg( mName ); 00125 else 00126 trailer += i18n("by <a href=\"mailto:%1\">%2</a> ").arg( mEmail ).arg( mEmail ); 00127 } else { 00128 if (!mName.isEmpty()) 00129 trailer += i18n("by %1 ").arg( mName ); 00130 } 00131 if (!mCreditName.isEmpty()) { 00132 if (!mCreditURL.isEmpty()) 00133 trailer += i18n("with <a href=\"%1\">%2</a>").arg( mCreditURL ).arg( mCreditName ); 00134 else 00135 trailer += i18n("with %1").arg( mCreditName ); 00136 } 00137 *ts << "<p>" << trailer << "</p>\n"; 00138 00139 // Write HTML trailer 00140 *ts << "</body></html>\n"; 00141 00142 return true; 00143 } 00144 00145 void HtmlExport::createHtmlMonthView(QTextStream *ts) 00146 { 00147 QDate start = fromDate(); 00148 start.setYMD(start.year(),start.month(),1); // go back to first day in month 00149 00150 QDate end(start.year(),start.month(),start.daysInMonth()); 00151 00152 int startmonth = start.month(); 00153 int startyear = start.year(); 00154 00155 while ( start < toDate() ) { 00156 // Write header 00157 *ts << "<h2>" << (i18n("month_year","%1 %2").arg(KGlobal::locale()->calendar()->monthName(start)) 00158 .arg(start.year())) << "</h2>\n"; 00159 if ( KGlobal::locale()->weekStartDay() == 1 ) { 00160 start = start.addDays(1 - start.dayOfWeek()); 00161 } else { 00162 if (start.dayOfWeek() != 7) { 00163 start = start.addDays(-start.dayOfWeek()); 00164 } 00165 } 00166 *ts << "<table border=\"1\">\n"; 00167 00168 // Write table header 00169 *ts << " <tr>"; 00170 for(int i=0; i<7; ++i) { 00171 *ts << "<th>" << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) << "</th>"; 00172 } 00173 *ts << "</tr>\n"; 00174 00175 // Write days 00176 while (start <= end) { 00177 *ts << " <tr>\n"; 00178 for(int i=0;i<7;++i) { 00179 *ts << " <td valign=\"top\"><table border=\"0\">"; 00180 00181 *ts << "<tr><td "; 00182 if (mHolidayMap.contains(start) || start.dayOfWeek() == 7) { 00183 *ts << "class=\"dateholiday\""; 00184 } else { 00185 *ts << "class=\"date\""; 00186 } 00187 *ts << ">" << QString::number(start.day()); 00188 00189 if (mHolidayMap.contains(start)) { 00190 *ts << " <em>" << mHolidayMap[start] << "</em>"; 00191 } 00192 00193 *ts << "</td></tr><tr><td valign=\"top\">"; 00194 00195 Event::List events = mCalendar->events(start,true); 00196 if (events.count()) { 00197 *ts << "<table>"; 00198 Event::List::ConstIterator it; 00199 for( it = events.begin(); it != events.end(); ++it ) { 00200 if ( checkSecrecy( *it ) ) { 00201 createHtmlEvent( ts, *it, start, false ); 00202 } 00203 } 00204 *ts << "</table>"; 00205 } else { 00206 *ts << "&nbsp;"; 00207 } 00208 00209 *ts << "</td></tr></table></td>\n"; 00210 start = start.addDays(1); 00211 } 00212 *ts << " </tr>\n"; 00213 } 00214 *ts << "</table>\n"; 00215 startmonth += 1; 00216 if ( startmonth > 12 ) { 00217 startyear += 1; 00218 startmonth = 1; 00219 } 00220 start.setYMD( startyear, startmonth, 1 ); 00221 end.setYMD(start.year(),start.month(),start.daysInMonth()); 00222 } 00223 } 00224 00225 void HtmlExport::createHtmlEventList (QTextStream *ts) 00226 { 00227 *ts << "<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">\n"; 00228 *ts << " <tr>\n"; 00229 *ts << " <th class=\"sum\">" << i18n("Start Time") << "</th>\n"; 00230 *ts << " <th>" << i18n("End Time") << "</th>\n"; 00231 *ts << " <th>" << i18n("Event") << "</th>\n"; 00232 if (categoriesEventEnabled()) { 00233 *ts << " <th>" << i18n("Categories") << "</th>\n"; 00234 } 00235 if (attendeesEventEnabled()) { 00236 *ts << " <th>" << i18n("Attendees") << "</th>\n"; 00237 } 00238 00239 *ts << " </tr>\n"; 00240 00241 int columns = 3; 00242 if (categoriesEventEnabled()) ++columns; 00243 if (attendeesEventEnabled()) ++columns; 00244 00245 for (QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1)) { 00246 kdDebug(5850) << "Getting events for " << dt.toString() << endl; 00247 Event::List events = mCalendar->events(dt,true); 00248 if (events.count()) { 00249 *ts << " <tr><td colspan=\"" << QString::number(columns) 00250 << "\" class=\"datehead\"><i>" 00251 << KGlobal::locale()->formatDate(dt) 00252 << "</i></td></tr>\n"; 00253 00254 Event::List::ConstIterator it; 00255 for( it = events.begin(); it != events.end(); ++it ) { 00256 if ( checkSecrecy( *it ) ) { 00257 createHtmlEvent( ts, *it, dt ); 00258 } 00259 } 00260 } 00261 } 00262 00263 *ts << "</table>\n"; 00264 } 00265 00266 void HtmlExport::createHtmlEvent (QTextStream *ts, Event *event, 00267 QDate date,bool withDescription) 00268 { 00269 kdDebug(5850) << "HtmlExport::createHtmlEvent(): " << event->summary() << endl; 00270 *ts << " <tr>\n"; 00271 00272 if (!event->doesFloat()) { 00273 if (event->isMultiDay() && (event->dtStart().date() != date)) { 00274 *ts << " <td>&nbsp;</td>\n"; 00275 } else { 00276 *ts << " <td valign=\"top\">" << event->dtStartTimeStr() << "</td>\n"; 00277 } 00278 if (event->isMultiDay() && (event->dtEnd().date() != date)) { 00279 *ts << " <td>&nbsp;</td>\n"; 00280 } else { 00281 *ts << " <td valign=\"top\">" << event->dtEndTimeStr() << "</td>\n"; 00282 } 00283 } else { 00284 *ts << " <td>&nbsp;</td><td>&nbsp;</td>\n"; 00285 } 00286 00287 *ts << " <td class=\"sum\">\n"; 00288 *ts << " <b>" << cleanChars(event->summary()) << "</b>\n"; 00289 if (withDescription && !event->description().isEmpty()) { 00290 *ts << " <p>" << breakString(cleanChars(event->description())) << "</p>\n"; 00291 } 00292 *ts << " </td>\n"; 00293 00294 if (categoriesEventEnabled()) { 00295 *ts << " <td>\n"; 00296 formatHtmlCategories(ts,event); 00297 *ts << " </td>\n"; 00298 } 00299 00300 if (attendeesEventEnabled()) { 00301 *ts << " <td>\n"; 00302 formatHtmlAttendees(ts,event); 00303 *ts << " </td>\n"; 00304 } 00305 00306 *ts << " </tr>\n"; 00307 } 00308 00309 void HtmlExport::createHtmlTodoList ( QTextStream *ts ) 00310 { 00311 Todo::List rawTodoList = mCalendar->todos(); 00312 00313 Todo::List::Iterator it = rawTodoList.begin(); 00314 while ( it != rawTodoList.end() ) { 00315 Todo *ev = *it; 00316 Todo *subev = ev; 00317 if ( ev->relatedTo() ) { 00318 if ( ev->relatedTo()->type()=="Todo" ) { 00319 if ( rawTodoList.find( static_cast<Todo *>( ev->relatedTo() ) ) == 00320 rawTodoList.end() ) { 00321 rawTodoList.append( static_cast<Todo *>( ev->relatedTo() ) ); 00322 } 00323 } 00324 } 00325 it = rawTodoList.find( subev ); 00326 ++it; 00327 } 00328 00329 // Sort list by priorities. This is brute force and should be 00330 // replaced by a real sorting algorithm. 00331 Todo::List todoList; 00332 for ( int i = 1; i <= 5; ++i ) { 00333 for( it = rawTodoList.begin(); it != rawTodoList.end(); ++it ) { 00334 if ( (*it)->priority() == i && checkSecrecy( *it ) ) { 00335 todoList.append( *it ); 00336 } 00337 } 00338 } 00339 00340 *ts << "<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">\n"; 00341 *ts << " <tr>\n"; 00342 *ts << " <th class=\"sum\">" << i18n("Task") << "</th>\n"; 00343 *ts << " <th>" << i18n("Priority") << "</th>\n"; 00344 *ts << " <th>" << i18n("Completed") << "</th>\n"; 00345 if (dueDateEnabled()) { 00346 *ts << " <th>" << i18n("Due Date") << "</th>\n"; 00347 } 00348 if (categoriesTodoEnabled()) { 00349 *ts << " <th>" << i18n("Categories") << "</th>\n"; 00350 } 00351 if (attendeesTodoEnabled()) { 00352 *ts << " <th>" << i18n("Attendees") << "</th>\n"; 00353 } 00354 *ts << " </tr>\n"; 00355 00356 // Create top-level list. 00357 for( it = todoList.begin(); it != todoList.end(); ++it ) { 00358 if ( !(*it)->relatedTo() ) createHtmlTodo( ts, *it ); 00359 } 00360 00361 // Create sub-level lists 00362 for( it = todoList.begin(); it != todoList.end(); ++it ) { 00363 Incidence::List relations = (*it)->relations(); 00364 if (relations.count()) { 00365 // Generate sub-task list of event ev 00366 *ts << " <tr>\n"; 00367 *ts << " <td class=\"subhead\" colspan="; 00368 int columns = 3; 00369 if (dueDateEnabled()) ++columns; 00370 if (categoriesTodoEnabled()) ++columns; 00371 if (attendeesTodoEnabled()) ++columns; 00372 *ts << "\"" << QString::number(columns) << "\""; 00373 *ts << "><a name=\"sub" << (*it)->uid() << "\"></a>" 00374 << i18n("Sub-Tasks of: ") << "<a href=\"#" 00375 << (*it)->uid() << "\"><b>" << cleanChars( (*it)->summary()) 00376 << "</b></a></td>\n"; 00377 *ts << " </tr>\n"; 00378 00379 Todo::List sortedList; 00380 // Sort list by priorities. This is brute force and should be 00381 // replaced by a real sorting algorithm. 00382 for ( int i = 1; i <= 5; ++i ) { 00383 Incidence::List::ConstIterator it2; 00384 for( it2 = relations.begin(); it2 != relations.end(); ++it2 ) { 00385 Todo *ev3 = dynamic_cast<Todo *>( *it2 ); 00386 if ( ev3 && ev3->priority() == i ) sortedList.append( ev3 ); 00387 } 00388 } 00389 00390 Todo::List::ConstIterator it3; 00391 for( it3 = sortedList.begin(); it3 != sortedList.end(); ++it3 ) { 00392 createHtmlTodo( ts, *it3 ); 00393 } 00394 } 00395 } 00396 00397 *ts << "</table>\n"; 00398 } 00399 00400 void HtmlExport::createHtmlTodo (QTextStream *ts,Todo *todo) 00401 { 00402 kdDebug(5850) << "HtmlExport::createHtmlTodo()" << endl; 00403 00404 bool completed = todo->isCompleted(); 00405 Incidence::List relations = todo->relations(); 00406 00407 *ts << "<tr>\n"; 00408 00409 *ts << " <td class=\"sum\""; 00410 if (completed) *ts << "done"; 00411 *ts << ">\n"; 00412 *ts << " <a name=\"" << todo->uid() << "\"></a>\n"; 00413 *ts << " <b>" << cleanChars(todo->summary()) << "</b>\n"; 00414 if (!todo->description().isEmpty()) { 00415 *ts << " <p>" << breakString(cleanChars(todo->description())) << "</p>\n"; 00416 } 00417 if (relations.count()) { 00418 *ts << " <div align=\"right\"><a href=\"#sub" << todo->uid() 00419 << "\">" << i18n("Sub-Tasks") << "</a></div>\n"; 00420 } 00421 00422 *ts << " </td"; 00423 if (completed) *ts << " class=\"done\""; 00424 *ts << ">\n"; 00425 00426 *ts << " <td"; 00427 if (completed) *ts << " class=\"done\""; 00428 *ts << ">\n"; 00429 *ts << " " << todo->priority() << "\n"; 00430 *ts << " </td>\n"; 00431 00432 *ts << " <td"; 00433 if (completed) *ts << " class=\"done\""; 00434 *ts << ">\n"; 00435 *ts << " " << i18n("%1 %").arg(todo->percentComplete()) << "\n"; 00436 *ts << " </td>\n"; 00437 00438 if (dueDateEnabled()) { 00439 *ts << " <td"; 00440 if (completed) *ts << " class=\"done\""; 00441 *ts << ">\n"; 00442 if (todo->hasDueDate()) { 00443 *ts << " " << todo->dtDueDateStr() << "\n"; 00444 } else { 00445 *ts << " &nbsp;\n"; 00446 } 00447 *ts << " </td>\n"; 00448 } 00449 00450 if (categoriesTodoEnabled()) { 00451 *ts << " <td"; 00452 if (completed) *ts << " class=\"done\""; 00453 *ts << ">\n"; 00454 formatHtmlCategories(ts,todo); 00455 *ts << " </td>\n"; 00456 } 00457 00458 if (attendeesTodoEnabled()) { 00459 *ts << " <td"; 00460 if (completed) *ts << " class=\"done\""; 00461 *ts << ">\n"; 00462 formatHtmlAttendees(ts,todo); 00463 *ts << " </td>\n"; 00464 } 00465 00466 *ts << "</tr>\n"; 00467 } 00468 00469 bool HtmlExport::checkSecrecy( Incidence *incidence ) 00470 { 00471 int secrecy = incidence->secrecy(); 00472 if ( secrecy == Incidence::SecrecyPublic ) { 00473 return true; 00474 } 00475 if ( secrecy == Incidence::SecrecyPrivate && !excludePrivateEventEnabled() ) { 00476 return true; 00477 } 00478 if ( secrecy == Incidence::SecrecyConfidential && 00479 !excludeConfidentialEventEnabled() ) { 00480 return true; 00481 } 00482 return false; 00483 } 00484 00485 void HtmlExport::formatHtmlCategories (QTextStream *ts,Incidence *event) 00486 { 00487 if (!event->categoriesStr().isEmpty()) { 00488 *ts << " " << cleanChars(event->categoriesStr()) << "\n"; 00489 } else { 00490 *ts << " &nbsp;\n"; 00491 } 00492 } 00493 00494 void HtmlExport::formatHtmlAttendees (QTextStream *ts,Incidence *event) 00495 { 00496 Attendee::List attendees = event->attendees(); 00497 if (attendees.count()) { 00498 *ts << "<em>"; 00499 #ifndef KORG_NOKABC 00500 KABC::AddressBook *add_book = KABC::StdAddressBook::self(); 00501 KABC::Addressee::List addressList; 00502 addressList = add_book->findByEmail(event->organizer()); 00503 KABC::Addressee o = addressList.first(); 00504 if (!o.isEmpty() && addressList.size()<2) { 00505 *ts << "<a href=\"mailto:" << event->organizer() << "\">"; 00506 *ts << cleanChars(o.formattedName()) << "</a>\n"; 00507 } 00508 else *ts << event->organizer(); 00509 #else 00510 *ts << event->organizer(); 00511 #endif 00512 *ts << "</em><br />"; 00513 Attendee::List::ConstIterator it; 00514 for( it = attendees.begin(); it != attendees.end(); ++it ) { 00515 Attendee *a = *it; 00516 if (!a->email().isEmpty()) { 00517 *ts << "<a href=\"mailto:" << a->email(); 00518 *ts << "\">" << cleanChars(a->name()) << "</a>"; 00519 } 00520 else { 00521 *ts << " " << cleanChars(a->name()); 00522 } 00523 *ts << "<br />" << "\n"; 00524 } 00525 } else { 00526 *ts << " &nbsp;\n"; 00527 } 00528 } 00529 00530 QString HtmlExport::breakString(const QString &text) 00531 { 00532 int number = text.contains("\n"); 00533 if(number < 0) { 00534 return text; 00535 } else { 00536 QString out; 00537 QString tmpText = text; 00538 int pos = 0; 00539 QString tmp; 00540 for(int i=0;i<=number;i++) { 00541 pos = tmpText.find("\n"); 00542 tmp = tmpText.left(pos); 00543 tmpText = tmpText.right(tmpText.length() - pos - 1); 00544 out += tmp + "<br />"; 00545 } 00546 return out; 00547 } 00548 } 00549 00550 QString HtmlExport::cleanChars(const QString &text) 00551 { 00552 QString txt = text; 00553 txt = txt.replace( "&", "&amp;" ); 00554 txt = txt.replace( "<", "&lt;" ); 00555 txt = txt.replace( ">", "&gt;" ); 00556 txt = txt.replace( "\"", "&quot;" ); 00557 txt = txt.replace( "ä", "&auml;" ); 00558 txt = txt.replace( "Ä", "&Auml;" ); 00559 txt = txt.replace( "ö", "&ouml;" ); 00560 txt = txt.replace( "Ö", "&Ouml;" ); 00561 txt = txt.replace( "ü", "&uuml;" ); 00562 txt = txt.replace( "Ü", "&Uuml;" ); 00563 txt = txt.replace( "ß", "&szlig;" ); 00564 txt = txt.replace( "¤", "&euro;" ); 00565 txt = txt.replace( "é", "&eacute;" ); 00566 00567 return txt; 00568 } 00569 00570 void HtmlExport::setStyleSheet( const QString &styleSheet ) 00571 { 00572 mStyleSheet = styleSheet; 00573 } 00574 00575 QString HtmlExport::styleSheet() 00576 { 00577 if ( !mStyleSheet.isEmpty() ) return mStyleSheet; 00578 00579 QString css; 00580 00581 if ( QApplication::reverseLayout() ) { 00582 css += " body { background-color:white; color:black; direction: rtl }\n"; 00583 css += " td { text-align:center; background-color:#eee }\n"; 00584 css += " th { text-align:center; background-color:#228; color:white }\n"; 00585 css += " td.sumdone { background-color:#ccc }\n"; 00586 css += " td.done { background-color:#ccc }\n"; 00587 css += " td.subhead { text-align:center; background-color:#ccf }\n"; 00588 css += " td.datehead { text-align:center; background-color:#ccf }\n"; 00589 css += " td.space { background-color:white }\n"; 00590 css += " td.dateholiday { color:red }\n"; 00591 } else { 00592 css += " body { background-color:white; color:black }\n"; 00593 css += " td { text-align:center; background-color:#eee }\n"; 00594 css += " th { text-align:center; background-color:#228; color:white }\n"; 00595 css += " td.sum { text-align:left }\n"; 00596 css += " td.sumdone { text-align:left; background-color:#ccc }\n"; 00597 css += " td.done { background-color:#ccc }\n"; 00598 css += " td.subhead { text-align:center; background-color:#ccf }\n"; 00599 css += " td.datehead { text-align:center; background-color:#ccf }\n"; 00600 css += " td.space { background-color:white }\n"; 00601 css += " td.date { text-align:left }\n"; 00602 css += " td.dateholiday { text-align:left; color:red }\n"; 00603 } 00604 00605 return css; 00606 } 00607 00608 00609 void HtmlExport::addHoliday( QDate date, QString name) 00610 { 00611 mHolidayMap[date] = name; 00612 } 00613
KDE Logo
This file is part of the documentation for libkcal Library Version 3.3.0.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 21 19:46:19 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003