korganizer Library API Documentation

koeditordetails.cpp

00001 /*
00002     This file is part of KOrganizer.
00003 
00004     Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
00005 
00006     This program is free software; you can redistribute it and/or modify
00007     it under the terms of the GNU General Public License as published by
00008     the Free Software Foundation; either version 2 of the License, or
00009     (at your option) any later version.
00010 
00011     This program 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
00014     GNU General Public License for more details.
00015 
00016     You should have received a copy of the GNU General Public License
00017     along with this program; if not, write to the Free Software
00018     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00019 
00020     As a special exception, permission is given to link this program
00021     with any edition of Qt, and distribute the resulting executable,
00022     without including the source code for Qt in the source distribution.
00023 */
00024 
00025 #include "koeditordetails.h"
00026 
00027 #include <qtooltip.h>
00028 #include <qfiledialog.h>
00029 #include <qlayout.h>
00030 #include <qvbox.h>
00031 #include <qbuttongroup.h>
00032 #include <qvgroupbox.h>
00033 #include <qwidgetstack.h>
00034 #include <qdatetime.h>
00035 #include <qdragobject.h>
00036 #include <qcombobox.h>
00037 #include <qlineedit.h>
00038 #include <qlabel.h>
00039 #include <qcheckbox.h>
00040 #include <qpushbutton.h>
00041 #include <qgroupbox.h>
00042 #include <qradiobutton.h>
00043 #include <qregexp.h>
00044 
00045 #include <kdebug.h>
00046 #include <klocale.h>
00047 #include <kiconloader.h>
00048 #include <kmessagebox.h>
00049 #ifndef KORG_NOKABC
00050 #include <kabc/addresseedialog.h>
00051 #include <kabc/vcardconverter.h>
00052 #include <libkdepim/addressesdialog.h>
00053 #include <libkdepim/addresseelineedit.h>
00054 #include <kabc/distributionlist.h>
00055 #include <kabc/stdaddressbook.h>
00056 #endif
00057 #include <libkdepim/kvcarddrag.h>
00058 #include <libkdepim/email.h>
00059 
00060 #include <libkcal/incidence.h>
00061 
00062 #include "koprefs.h"
00063 #include "koglobals.h"
00064 
00065 #include "koeditorfreebusy.h"
00066 
00067 #include "kocore.h"
00068 
00069 template <>
00070 CustomListViewItem<KCal::Attendee *>::~CustomListViewItem()
00071 {
00072   delete mData;
00073 }
00074 
00075 template <>
00076 void CustomListViewItem<KCal::Attendee *>::updateItem()
00077 {
00078   setText(0,mData->name());
00079   setText(1,mData->email());
00080   setText(2,mData->roleStr());
00081   setText(3,mData->statusStr());
00082   if (mData->RSVP() && !mData->email().isEmpty())
00083     setPixmap(4,KOGlobals::self()->smallIcon("mailappt"));
00084   else
00085     setPixmap(4,KOGlobals::self()->smallIcon("nomailappt"));
00086 }
00087 
00088 KOAttendeeListView::KOAttendeeListView ( QWidget *parent, const char *name )
00089   : KListView(parent, name)
00090 {
00091   setAcceptDrops( true );
00092   setAllColumnsShowFocus( true );
00093   setSorting( -1 );
00094 }
00095 
00101 KOAttendeeListView::~KOAttendeeListView()
00102 {
00103 }
00104 
00105 void KOAttendeeListView::contentsDragEnterEvent( QDragEnterEvent *e )
00106 {
00107   dragEnterEvent(e);
00108 }
00109 
00110 void KOAttendeeListView::contentsDragMoveEvent( QDragMoveEvent *e )
00111 {
00112 #ifndef KORG_NODND
00113   if ( KVCardDrag::canDecode( e ) || QTextDrag::canDecode( e ) ) {
00114     e->accept();
00115   } else {
00116     e->ignore();
00117   }
00118 #endif
00119 }
00120 
00121 void KOAttendeeListView::dragEnterEvent( QDragEnterEvent *e )
00122 {
00123 #ifndef KORG_NODND
00124   if ( KVCardDrag::canDecode( e ) || QTextDrag::canDecode( e ) ) {
00125     e->accept();
00126   } else {
00127     e->ignore();
00128   }
00129 #endif
00130 }
00131 
00132 void KOAttendeeListView::addAttendee( const QString &newAttendee )
00133 {
00134   kdDebug(5850) << " Email: " << newAttendee << endl;
00135   QString name;
00136   QString email;
00137   KPIM::getNameAndMail( newAttendee, name, email );
00138   emit dropped( new Attendee( name, email, true ) );
00139 }
00140 
00141 void KOAttendeeListView::contentsDropEvent( QDropEvent *e )
00142 {
00143   dropEvent(e);
00144 }
00145 
00146 void KOAttendeeListView::dropEvent( QDropEvent *e )
00147 {
00148 #ifndef KORG_NODND
00149   QString text;
00150   QString vcards;
00151 
00152 #ifndef KORG_NOKABC
00153   if ( KVCardDrag::decode( e, vcards ) ) {
00154     KABC::VCardConverter converter;
00155 
00156     KABC::Addressee::List list = converter.parseVCards( vcards );
00157     KABC::Addressee::List::Iterator it;
00158     for ( it = list.begin(); it != list.end(); ++it ) {
00159       QString em( (*it).fullEmail() );
00160       if (em.isEmpty()) {
00161         em=(*it).realName();
00162       }
00163       addAttendee( em );
00164     }
00165   } else
00166 #endif // KORG_NOKABC
00167   if (QTextDrag::decode(e,text)) {
00168     kdDebug(5850) << "Dropped : " << text << endl;
00169     QStringList emails = QStringList::split(",",text);
00170     for(QStringList::ConstIterator it = emails.begin();it!=emails.end();++it) {
00171       addAttendee(*it);
00172     }
00173   }
00174 #endif //KORG_NODND
00175 }
00176 
00177 
00178 KOEditorDetails::KOEditorDetails( int spacing, QWidget *parent,
00179                                   const char *name )
00180   : QWidget( parent, name), mDisableItemUpdate( false ), mFreeBusy( 0 )
00181 {
00182   QGridLayout *topLayout = new QGridLayout( this );
00183   topLayout->setSpacing( spacing );
00184 
00185   mOrganizerHBox = new QHBox( this );
00186   // If creating a new event, then the user is the organizer -> show the
00187   // identity combo
00188   // readEvent will delete it and set another label text instead, if the user
00189   // isn't the organizer.
00190   // Note that the i18n text below is duplicated in readEvent
00191   mOrganizerLabel = new QLabel( i18n( "Identity as organizer:" ),
00192                                 mOrganizerHBox );
00193   mOrganizerCombo = new QComboBox( mOrganizerHBox );
00194   fillOrganizerCombo();
00195   mOrganizerHBox->setStretchFactor( mOrganizerCombo, 100 );
00196 
00197   mListView = new KOAttendeeListView( this, "mListView" );
00198   mListView->addColumn( i18n("Name"), 200 );
00199   mListView->addColumn( i18n("Email"), 200 );
00200   mListView->addColumn( i18n("Role"), 60 );
00201   mListView->addColumn( i18n("Status"), 100 );
00202   mListView->addColumn( i18n("RSVP"), 35 );
00203   mListView->setResizeMode( QListView::LastColumn );
00204   if ( KOPrefs::instance()->mCompactDialogs ) {
00205     mListView->setFixedHeight( 78 );
00206   }
00207 
00208   connect( mListView, SIGNAL( selectionChanged( QListViewItem * ) ),
00209            SLOT( updateAttendeeInput() ) );
00210 #ifndef KORG_NODND
00211   connect( mListView, SIGNAL( dropped( Attendee * ) ),
00212            SLOT( insertAttendee( Attendee * ) ) );
00213 #endif
00214 
00215   QLabel *attendeeLabel = new QLabel( this );
00216   attendeeLabel->setText( i18n("Na&me:") );
00217 
00218   mNameEdit = new KPIM::AddresseeLineEdit( this );
00219   mNameEdit->setClickMessage( i18n("Click to add a new attendee") );
00220   attendeeLabel->setBuddy( mNameEdit );
00221   mNameEdit->installEventFilter( this );
00222   connect( mNameEdit, SIGNAL( textChanged( const QString & ) ),
00223            SLOT( updateAttendeeItem() ) );
00224 
00225   mUidEdit = new QLineEdit( 0 );
00226   mUidEdit->setText( "" );
00227 
00228   QLabel *attendeeRoleLabel = new QLabel( this );
00229   attendeeRoleLabel->setText( i18n("Ro&le:") );
00230 
00231   mRoleCombo = new QComboBox( false, this );
00232   mRoleCombo->insertStringList( Attendee::roleList() );
00233   attendeeRoleLabel->setBuddy( mRoleCombo );
00234   connect( mRoleCombo, SIGNAL( activated( int ) ),
00235            SLOT( updateAttendeeItem() ) );
00236 
00237   QLabel *statusLabel = new QLabel( this );
00238   statusLabel->setText( i18n("Stat&us:") );
00239 
00240   mStatusCombo = new QComboBox( false, this );
00241   mStatusCombo->insertStringList( Attendee::statusList() );
00242   statusLabel->setBuddy( mStatusCombo );
00243   connect( mStatusCombo, SIGNAL( activated( int ) ),
00244            SLOT( updateAttendeeItem() ) );
00245 
00246   mRsvpButton = new QCheckBox( this );
00247   mRsvpButton->setText( i18n("Re&quest response") );
00248   connect( mRsvpButton, SIGNAL( clicked() ), SLOT( updateAttendeeItem() ) );
00249 
00250   QWidget *buttonBox = new QWidget( this );
00251   QVBoxLayout *buttonLayout = new QVBoxLayout( buttonBox );
00252 
00253   QPushButton *newButton = new QPushButton( i18n("&New"), buttonBox );
00254   buttonLayout->addWidget( newButton );
00255   connect( newButton, SIGNAL( clicked() ), SLOT( addNewAttendee() ) );
00256 
00257   mRemoveButton = new QPushButton( i18n("&Remove"), buttonBox );
00258   buttonLayout->addWidget( mRemoveButton );
00259   connect( mRemoveButton, SIGNAL( clicked() ), SLOT( removeAttendee() ) );
00260 
00261   mAddressBookButton = new QPushButton( i18n("Select Addressee..."),
00262                                         buttonBox );
00263   buttonLayout->addWidget( mAddressBookButton );
00264   connect( mAddressBookButton, SIGNAL( clicked() ), SLOT( openAddressBook() ) );
00265 
00266   topLayout->addMultiCellWidget( mOrganizerHBox, 0, 0, 0, 5 );
00267   topLayout->addMultiCellWidget( mListView, 1, 1, 0, 5 );
00268   topLayout->addWidget( attendeeLabel, 2, 0 );
00269   topLayout->addMultiCellWidget( mNameEdit, 2, 2, 1, 1 );
00270 //  topLayout->addWidget( emailLabel, 3, 0 );
00271   topLayout->addWidget( attendeeRoleLabel, 3, 0 );
00272   topLayout->addWidget( mRoleCombo, 3, 1 );
00273 #if 0
00274   topLayout->setColStretch( 2, 1 );
00275   topLayout->addWidget( statusLabel, 3, 3 );
00276   topLayout->addWidget( mStatusCombo, 3, 4 );
00277 #else
00278   topLayout->addWidget( statusLabel, 4, 0 );
00279   topLayout->addWidget( mStatusCombo, 4, 1 );
00280 #endif
00281   topLayout->addMultiCellWidget( mRsvpButton, 5, 5, 0, 1 );
00282   topLayout->addMultiCellWidget( buttonBox, 2, 4, 5, 5 );
00283 
00284 #ifdef KORG_NOKABC
00285   mAddressBookButton->hide();
00286 #endif
00287 
00288   updateAttendeeInput();
00289 }
00290 
00291 KOEditorDetails::~KOEditorDetails()
00292 {
00293 }
00294 
00295 bool KOEditorDetails::eventFilter( QObject *watched, QEvent *ev)
00296 {
00297   if ( watched && watched == mNameEdit && ev->type() == QEvent::FocusIn &&
00298        mListView->childCount() == 0 ) {
00299     addNewAttendee();
00300   }
00301 
00302   return QWidget::eventFilter( watched, ev );
00303 }
00304 
00305 void KOEditorDetails::removeAttendee()
00306 {
00307   AttendeeListItem *aItem =
00308       static_cast<AttendeeListItem *>( mListView->selectedItem() );
00309   if ( !aItem ) return;
00310 
00311   Attendee *delA = new Attendee( aItem->data()->name(), aItem->data()->email(),
00312                                  aItem->data()->RSVP(), aItem->data()->status(),
00313                                  aItem->data()->role(), aItem->data()->uid() );
00314   mdelAttendees.append( delA );
00315 
00316   if ( mFreeBusy ) mFreeBusy->removeAttendee( aItem->data() );
00317   delete aItem;
00318 
00319   updateAttendeeInput();
00320 }
00321 
00322 
00323 void KOEditorDetails::openAddressBook()
00324 {
00325 #ifndef KORG_NOKABC
00326   KPIM::AddressesDialog *dia = new KPIM::AddressesDialog( this, "adddialog" );
00327   dia->setShowCC( false );
00328   dia->setShowBCC( false );
00329   if ( dia->exec() ) {
00330     KABC::Addressee::List aList = dia->allToAddressesNoDuplicates();
00331     for ( KABC::Addressee::List::iterator itr = aList.begin();
00332           itr != aList.end(); ++itr ) {
00333       KABC::Addressee a = (*itr);
00334       bool myself = KOPrefs::instance()->thatIsMe( a.preferredEmail() );
00335       bool sameAsOrganizer = mOrganizerCombo &&
00336         KPIM::compareEmail( a.preferredEmail(), mOrganizerCombo->currentText(), false );
00337       KCal::Attendee::PartStat partStat;
00338       if ( myself && sameAsOrganizer )
00339         partStat = KCal::Attendee::Accepted;
00340       else
00341         partStat = KCal::Attendee::NeedsAction;
00342       insertAttendee( new Attendee( a.realName(), a.preferredEmail(),
00343                                     !myself, partStat,
00344                                     KCal::Attendee::ReqParticipant, a.uid() ),
00345                       true );
00346     }
00347   }
00348   delete dia;
00349   return;
00350 #if 0
00351     // old code
00352     KABC::Addressee a = KABC::AddresseeDialog::getAddressee(this);
00353     if (!a.isEmpty()) {
00354         // If this is myself, I don't want to get a response but instead
00355         // assume I will be available
00356         bool myself = KOPrefs::instance()->thatIsMe( a.preferredEmail() );
00357         KCal::Attendee::PartStat partStat =
00358             myself ? KCal::Attendee::Accepted : KCal::Attendee::NeedsAction;
00359         insertAttendee( new Attendee( a.realName(), a.preferredEmail(),
00360                                       !myself, partStat,
00361                                       KCal::Attendee::ReqParticipant, a.uid() ) );
00362     }
00363 #endif
00364 #endif
00365 }
00366 
00367 
00368 void KOEditorDetails::addNewAttendee()
00369 {
00370   Attendee *a = new Attendee( i18n("Firstname Lastname"),
00371                               i18n("name@domain.com"), true );
00372   insertAttendee( a, false );
00373   // We don't want the hint again
00374   mNameEdit->setClickMessage( "" );
00375   mNameEdit->setFocus();
00376   QTimer::singleShot( 0, mNameEdit, SLOT( selectAll() ) );
00377 }
00378 
00379 
00380 void KOEditorDetails::insertAttendee( Attendee *a )
00381 {
00382   insertAttendee( a, true );
00383 }
00384 
00385 void KOEditorDetails::insertAttendee( Attendee *a, bool goodEmailAddress )
00386 {
00387   // lastItem() is O(n), but for n very small that should be fine
00388   AttendeeListItem *item = new AttendeeListItem( a, mListView,
00389       static_cast<KListViewItem*>( mListView->lastItem() ) );
00390   mListView->setSelected( item, true );
00391   if( mFreeBusy ) mFreeBusy->insertAttendee( a, goodEmailAddress );
00392 }
00393 
00394 void KOEditorDetails::setDefaults()
00395 {
00396   mRsvpButton->setChecked( true );
00397 }
00398 
00399 void KOEditorDetails::readEvent( Incidence *event )
00400 {
00401   // Stop flickering in the free/busy view (not sure if this is necessary)
00402   bool block = false;
00403   if( mFreeBusy ) {
00404     block = mFreeBusy->updateEnabled();
00405     mFreeBusy->setUpdateEnabled( false );
00406     mFreeBusy->clearAttendees();
00407   }
00408 
00409   mListView->clear();
00410   mdelAttendees.clear();
00411   Attendee::List al = event->attendees();
00412   Attendee::List::ConstIterator it;
00413   for( it = al.begin(); it != al.end(); ++it )
00414     insertAttendee( new Attendee( **it ), true );
00415 
00416   mListView->setSelected( mListView->firstChild(), true );
00417 
00418   if ( KOPrefs::instance()->thatIsMe( event->organizer().email() ) ) {
00419     if ( !mOrganizerCombo ) {
00420       mOrganizerCombo = new QComboBox( mOrganizerHBox );
00421       fillOrganizerCombo();
00422     }
00423     mOrganizerLabel->setText( i18n( "Identity as organizer:" ) );
00424 
00425     // This might not be enough, if the combo as a full name too, hence the loop below
00426     // mOrganizerCombo->setCurrentText( event->organizer().fullName() );
00427     for ( int i = 0 ; i < mOrganizerCombo->count(); ++i ) {
00428       QString itemTxt = KPIM::getEmailAddr( mOrganizerCombo->text( i ) );
00429       if ( KPIM::compareEmail( event->organizer().email(), itemTxt, false ) ) {
00430         // Make sure we match the organizer setting completely
00431         mOrganizerCombo->changeItem( event->organizer().fullName(), i );
00432         mOrganizerCombo->setCurrentItem( i );
00433         break;
00434       }
00435     }
00436   } else { // someone else is the organizer
00437     if ( mOrganizerCombo ) {
00438       delete mOrganizerCombo;
00439       mOrganizerCombo = 0;
00440     }
00441     mOrganizerLabel->setText( i18n( "Organizer: %1" ).arg( event->organizer().fullName() ) );
00442   }
00443 
00444   // Reinstate free/busy view updates
00445   if( mFreeBusy ) mFreeBusy->setUpdateEnabled( block );
00446 }
00447 
00448 void KOEditorDetails::writeEvent(Incidence *event)
00449 {
00450   event->clearAttendees();
00451   QListViewItem *item;
00452   AttendeeListItem *a;
00453   for (item = mListView->firstChild(); item;
00454        item = item->nextSibling()) {
00455     a = (AttendeeListItem *)item;
00456     event->addAttendee(new Attendee(*(a->data())));
00457   }
00458   if ( mOrganizerCombo ) {
00459     // TODO: Don't take a string and split it up... Is there a better way?
00460     event->setOrganizer( mOrganizerCombo->currentText() );
00461   }
00462 }
00463 
00464 void KOEditorDetails::cancelAttendeeEvent(Incidence *event)
00465 {
00466   event->clearAttendees();
00467   Attendee * att;
00468   for (att=mdelAttendees.first();att;att=mdelAttendees.next()) {
00469     event->addAttendee(new Attendee(*att));
00470   }
00471   mdelAttendees.clear();
00472 }
00473 
00474 bool KOEditorDetails::validateInput()
00475 {
00476   return true;
00477 }
00478 
00479 void KOEditorDetails::updateAttendeeInput()
00480 {
00481 
00482   setEnableAttendeeInput(!mNameEdit->text().isEmpty());
00483   QListViewItem *item = mListView->selectedItem();
00484   AttendeeListItem *aItem = static_cast<AttendeeListItem *>( item );
00485   if (aItem) {
00486     fillAttendeeInput( aItem );
00487   } else {
00488     clearAttendeeInput();
00489   }
00490 }
00491 
00492 void KOEditorDetails::clearAttendeeInput()
00493 {
00494   mNameEdit->setText("");
00495   mUidEdit->setText("");
00496   mRoleCombo->setCurrentItem(0);
00497   mStatusCombo->setCurrentItem(0);
00498   mRsvpButton->setChecked(true);
00499   setEnableAttendeeInput( false );
00500 }
00501 
00502 void KOEditorDetails::fillAttendeeInput( AttendeeListItem *aItem )
00503 {
00504   Attendee *a = aItem->data();
00505   mDisableItemUpdate = true;
00506   QString name = a->name();
00507   if (!a->email().isEmpty()) {
00508     // Taken from KABC::Addressee::fullEmail
00509     QRegExp needQuotes( "[^ 0-9A-Za-z\\x0080-\\xFFFF]" );
00510     if ( name.find( needQuotes ) != -1 )
00511       name = "\"" + name + "\" <" + a->email() + ">";
00512     else
00513       name += " <" + a->email() + ">";
00514   }
00515   mNameEdit->setText(name);
00516   mUidEdit->setText(a->uid());
00517   mRoleCombo->setCurrentItem(a->role());
00518   mStatusCombo->setCurrentItem(a->status());
00519   mRsvpButton->setChecked(a->RSVP());
00520 
00521   mDisableItemUpdate = false;
00522 
00523   setEnableAttendeeInput( true );
00524 }
00525 
00526 void KOEditorDetails::setEnableAttendeeInput( bool enabled )
00527 {
00528   //mNameEdit->setEnabled( enabled );
00529   mRoleCombo->setEnabled( enabled );
00530   mStatusCombo->setEnabled( enabled );
00531   mRsvpButton->setEnabled( enabled );
00532 
00533   mRemoveButton->setEnabled( enabled );
00534 }
00535 
00536 void KOEditorDetails::updateAttendeeItem()
00537 {
00538   if (mDisableItemUpdate) return;
00539 
00540   QListViewItem *item = mListView->selectedItem();
00541   AttendeeListItem *aItem = static_cast<AttendeeListItem *>( item );
00542   if ( !aItem ) return;
00543 
00544   Attendee *a = aItem->data();
00545   QString name;
00546   QString email;
00547   KPIM::getNameAndMail(mNameEdit->text(), name, email);
00548 
00549   bool iAmTheOrganizer = mOrganizerCombo &&
00550     KOPrefs::instance()->thatIsMe( mOrganizerCombo->currentText() );
00551   if ( iAmTheOrganizer ) {
00552     bool myself =
00553       KPIM::compareEmail( email, mOrganizerCombo->currentText(), false );
00554     bool wasMyself =
00555       KPIM::compareEmail( a->email(), mOrganizerCombo->currentText(), false );
00556     if ( myself ) {
00557       mStatusCombo->setCurrentItem( KCal::Attendee::Accepted );
00558       mRsvpButton->setChecked( false );
00559       mRsvpButton->setEnabled( false );
00560     } else if ( wasMyself ) {
00561       // this was me, but is no longer, reset
00562       mStatusCombo->setCurrentItem( KCal::Attendee::NeedsAction );
00563       mRsvpButton->setChecked( true );
00564       mRsvpButton->setEnabled( true );
00565     }
00566   }
00567   a->setName( name );
00568   a->setUid( mUidEdit->text() );
00569   a->setEmail( email );
00570   a->setRole( Attendee::Role( mRoleCombo->currentItem() ) );
00571   a->setStatus( Attendee::PartStat( mStatusCombo->currentItem() ) );
00572   a->setRSVP( mRsvpButton->isChecked() );
00573   aItem->updateItem();
00574   if ( mFreeBusy ) mFreeBusy->updateAttendee( a );
00575 }
00576 
00577 void KOEditorDetails::setFreeBusyWidget( KOEditorFreeBusy *v )
00578 {
00579   mFreeBusy = v;
00580 }
00581 
00582 void KOEditorDetails::fillOrganizerCombo()
00583 {
00584   Q_ASSERT( mOrganizerCombo );
00585   // Get all emails from KOPrefs (coming from various places),
00586   // and insert them - removing duplicates
00587   const QStringList lst = KOPrefs::instance()->fullEmails();
00588   QStringList uniqueList;
00589   for( QStringList::ConstIterator it = lst.begin(); it != lst.end(); ++it ) {
00590     if ( uniqueList.find( *it ) == uniqueList.end() )
00591       uniqueList << *it;
00592   }
00593   mOrganizerCombo->insertStringList( uniqueList );
00594 }
00595 
00596 #include "koeditordetails.moc"
KDE Logo
This file is part of the documentation for korganizer Library Version 3.3.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Mar 23 22:45:24 2005 by doxygen 1.3.9.1 written by Dimitri van Heesch, © 1997-2003