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().email());
00503     KABC::Addressee o = addressList.first();
00504     if (!o.isEmpty() && addressList.size()<2) {
00505       *ts << "<a href=\"mailto:" << event->organizer().email() << "\">";
00506       *ts << cleanChars(o.formattedName()) << "</a>\n";
00507     }
00508         else *ts << event->organizer().fullName();
00509 #else
00510       *ts << event->organizer().fullName();
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.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Thu Oct 4 14:39:37 2007 by doxygen 1.4.2 written by Dimitri van Heesch, © 1997-2003