korganizer Library API Documentation

koeditorfreebusy.cpp

00001 /*
00002     This file is part of KOrganizer.
00003 
00004     Copyright (c) 2001,2004 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 <qtooltip.h>
00026 #include <qlayout.h>
00027 #include <qlabel.h>
00028 #include <qcombobox.h>
00029 #include <qpushbutton.h>
00030 
00031 #include <kdebug.h>
00032 #include <klocale.h>
00033 #include <kiconloader.h>
00034 #include <kmessagebox.h>
00035 
00036 #include <libkcal/event.h>
00037 #include <libkcal/freebusy.h>
00038 
00039 #include <kdgantt/KDGanttView.h>
00040 #include <kdgantt/KDGanttViewTaskItem.h>
00041 
00042 #include "koprefs.h"
00043 #include "koglobals.h"
00044 #include "kogroupware.h"
00045 #include "freebusymanager.h"
00046 #include "freebusyurldialog.h"
00047 
00048 #include "koeditorfreebusy.h"
00049 
00050 
00051 // We can't use the CustomListViewItem base class, since we need a
00052 // different inheritance hierarchy for supporting the Gantt view.
00053 class FreeBusyItem : public KDGanttViewTaskItem
00054 {
00055   public:
00056     FreeBusyItem( Attendee *attendee, KDGanttView *parent ) :
00057       KDGanttViewTaskItem( parent ), mAttendee( attendee ), mTimerID( 0 ),
00058       mIsDownloading( false )
00059     {
00060       Q_ASSERT( attendee );
00061       updateItem();
00062       setFreeBusyPeriods( 0 );
00063       setDisplaySubitemsAsGroup( true );
00064       if ( listView () )
00065           listView ()->setRootIsDecorated( false );
00066     }
00067     ~FreeBusyItem() {}
00068 
00069     void updateItem();
00070 
00071     Attendee *attendee() const { return mAttendee; }
00072     void setFreeBusy( KCal::FreeBusy *fb ) { mFreeBusy = fb; }
00073     KCal::FreeBusy* freeBusy() const { return mFreeBusy; }
00074 
00075     void setFreeBusyPeriods( FreeBusy *fb );
00076 
00077     QString key( int column, bool ) const
00078     {
00079       QMap<int,QString>::ConstIterator it = mKeyMap.find( column );
00080       if ( it == mKeyMap.end() ) return listViewText( column );
00081       else return *it;
00082     }
00083 
00084     void setSortKey( int column, const QString &key )
00085     {
00086       mKeyMap.insert( column, key );
00087     }
00088 
00089     QString email() const { return mAttendee->email(); }
00090 
00091     void setUpdateTimerID( int id ) { mTimerID = id; }
00092     int updateTimerID() const { return mTimerID; }
00093 
00094     void startDownload() {
00095       mIsDownloading = true;
00096       FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00097       m->retrieveFreeBusy( attendee()->email() );
00098     }
00099     void setIsDownloading( bool d ) { mIsDownloading = d; }
00100     bool isDownloading() const { return mIsDownloading; }
00101 
00102   private:
00103     Attendee *mAttendee;
00104     KCal::FreeBusy *mFreeBusy;
00105 
00106     QMap<int,QString> mKeyMap;
00107 
00108     // This is used for the update timer
00109     int mTimerID;
00110 
00111     // Only run one download job at a time
00112     bool mIsDownloading;
00113 };
00114 
00115 void FreeBusyItem::updateItem()
00116 {
00117   setListViewText( 0, mAttendee->name() );
00118   setListViewText( 1, mAttendee->email() );
00119   setListViewText( 2, mAttendee->roleStr() );
00120   setListViewText( 3, mAttendee->statusStr() );
00121   if ( mAttendee->RSVP() && !mAttendee->email().isEmpty() )
00122     setPixmap( 4, KOGlobals::self()->smallIcon( "mailappt" ) );
00123   else
00124     setPixmap( 4, KOGlobals::self()->smallIcon( "nomailappt" ) );
00125 }
00126 
00127 
00128 // Set the free/busy periods for this attendee
00129 void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
00130 {
00131   if( fb ) {
00132     // Clean out the old entries
00133     for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
00134       delete it;
00135 
00136     // Evaluate free/busy information
00137     QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00138     for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00139      it != busyPeriods.end(); ++it ) {
00140       KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00141       newSubItem->setStartTime( (*it).start() );
00142       newSubItem->setEndTime( (*it).end() );
00143       newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00144     }
00145     setFreeBusy( fb );
00146     setShowNoInformation( false );
00147   } else {
00148       // No free/busy information
00149       //debug only start
00150       //   int ii ;
00151       //       QDateTime cur = QDateTime::currentDateTime();
00152       //       for( ii = 0; ii < 10 ;++ii ) {
00153       //           KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00154       //           cur = cur.addSecs( 7200 );
00155       //           newSubItem->setStartTime( cur );
00156       //           cur = cur.addSecs( 7200 );
00157       //           newSubItem->setEndTime( cur );
00158       //           newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00159       //       }
00160       //debug only end
00161       setFreeBusy( 0 );
00162       setShowNoInformation( true );
00163   }
00164 
00165   // We are no longer downloading
00166   mIsDownloading = false;
00167 }
00168 
00169 
00170 KOEditorFreeBusy::KOEditorFreeBusy( int spacing, QWidget *parent,
00171                                     const char *name )
00172   : QWidget( parent, name )
00173 {
00174   QVBoxLayout *topLayout = new QVBoxLayout( this );
00175   topLayout->setSpacing( spacing );
00176 
00177   // Label for status summary information
00178   // Uses the tooltip palette to highlight it
00179   mIsOrganizer = false; // Will be set later. This is just valgrind silencing
00180   mStatusSummaryLabel = new QLabel( this );
00181   mStatusSummaryLabel->setPalette( QToolTip::palette() );
00182   mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
00183   mStatusSummaryLabel->setLineWidth( 1 );
00184   mStatusSummaryLabel->hide(); // Will be unhidden later if you are organizer
00185   topLayout->addWidget( mStatusSummaryLabel );
00186 
00187   // The control panel for the gantt widget
00188   QBoxLayout *controlLayout = new QHBoxLayout( topLayout );
00189 
00190   QLabel *label = new QLabel( i18n( "Scale: " ), this );
00191   controlLayout->addWidget( label );
00192 
00193   scaleCombo = new QComboBox( this );
00194   scaleCombo->insertItem( i18n( "Hour" ) );
00195   scaleCombo->insertItem( i18n( "Day" ) );
00196   scaleCombo->insertItem( i18n( "Week" ) );
00197   scaleCombo->insertItem( i18n( "Month" ) );
00198   scaleCombo->insertItem( i18n( "Automatic" ) );
00199   scaleCombo->setCurrentItem( 0 ); // start with "hour"
00200   connect( scaleCombo, SIGNAL( activated( int ) ),
00201            SLOT( slotScaleChanged( int ) ) );
00202   controlLayout->addWidget( scaleCombo );
00203 
00204   QPushButton *button = new QPushButton( i18n( "Center on Start" ), this );
00205   connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) );
00206   controlLayout->addWidget( button );
00207 
00208   button = new QPushButton( i18n( "Zoom to Fit" ), this );
00209   connect( button, SIGNAL( clicked() ), SLOT( slotZoomToTime() ) );
00210   controlLayout->addWidget( button );
00211 
00212   controlLayout->addStretch( 1 );
00213 
00214   button = new QPushButton( i18n( "Pick Date" ), this );
00215   connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
00216   controlLayout->addWidget( button );
00217 
00218   controlLayout->addStretch( 1 );
00219 
00220   button = new QPushButton( i18n("Reload"), this );
00221   controlLayout->addWidget( button );
00222   connect( button, SIGNAL( clicked() ), SLOT( reload() ) );
00223 
00224   mGanttView = new KDGanttView( this, "mGanttView" );
00225   topLayout->addWidget( mGanttView );
00226   // Remove the predefined "Task Name" column
00227   mGanttView->removeColumn( 0 );
00228   mGanttView->addColumn( i18n("Name"), 180 );
00229   mGanttView->addColumn( i18n("Email"), 180 );
00230   mGanttView->addColumn( i18n("Role"), 60 );
00231   mGanttView->addColumn( i18n("Status"), 100 );
00232   mGanttView->addColumn( i18n("RSVP"), 35 );
00233   if ( KOPrefs::instance()->mCompactDialogs ) {
00234     mGanttView->setFixedHeight( 78 );
00235   }
00236   mGanttView->setHeaderVisible( true );
00237   mGanttView->setScale( KDGanttView::Hour );
00238   mGanttView->setShowHeaderPopupMenu( true, true, true, false, false, true );
00239   // Initially, show 15 days back and forth
00240   // set start to even hours, i.e. to 12:AM 0 Min 0 Sec
00241   QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
00242                            .addDays( -15 ).date() );
00243   QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
00244   mGanttView->setHorizonStart( horizonStart );
00245   mGanttView->setHorizonEnd( horizonEnd );
00246   mGanttView->setCalendarMode( true );
00247   //mGanttView->setDisplaySubitemsAsGroup( true );
00248   mGanttView->setShowLegendButton( false );
00249   // Initially, center to current date
00250   mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
00251   if ( KGlobal::locale()->use12Clock() )
00252     mGanttView->setHourFormat( KDGanttView::Hour_12 );
00253   else
00254     mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
00255   connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
00256                                                       const QDateTime & ) ),
00257            mGanttView, SLOT( zoomToSelection( const QDateTime &,
00258                                               const  QDateTime & ) ) );
00259   connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
00260            SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
00261 
00262   FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00263   connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
00264            SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
00265 
00266   connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( reload() ) );
00267 }
00268 
00269 KOEditorFreeBusy::~KOEditorFreeBusy()
00270 {
00271 }
00272 
00273 void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
00274 {
00275   FreeBusyItem *anItem =
00276       static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00277   while( anItem ) {
00278     if( anItem->attendee() == attendee ) {
00279       if ( anItem->updateTimerID() != 0 )
00280         killTimer( anItem->updateTimerID() );
00281       delete anItem;
00282       updateStatusSummary();
00283       break;
00284     }
00285     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00286   }
00287 }
00288 
00289 void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
00290 {
00291   FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
00292   if ( readFBList )
00293     updateFreeBusyData( item );
00294   updateStatusSummary();
00295 }
00296 
00297 void KOEditorFreeBusy::updateAttendee( Attendee *attendee )
00298 {
00299   FreeBusyItem *anItem =
00300       static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00301   while( anItem ) {
00302     if( anItem->attendee() == attendee ) {
00303       anItem->updateItem();
00304       updateFreeBusyData( anItem );
00305       updateStatusSummary();
00306       break;
00307     }
00308     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00309   }
00310 }
00311 
00312 void KOEditorFreeBusy::clearAttendees()
00313 {
00314   mGanttView->clear();
00315 }
00316 
00317 
00318 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
00319 {
00320   mGanttView->setUpdateEnabled( enabled );
00321 }
00322 
00323 bool KOEditorFreeBusy::updateEnabled() const
00324 {
00325   return mGanttView->getUpdateEnabled();
00326 }
00327 
00328 
00329 void KOEditorFreeBusy::readEvent( Event *event )
00330 {
00331   setDateTimes( event->dtStart(), event->dtEnd() );
00332   mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
00333   updateStatusSummary();
00334 }
00335 
00336 
00337 void KOEditorFreeBusy::setDateTimes( QDateTime start, QDateTime end )
00338 {
00339 
00340   slotUpdateGanttView( start, end );
00341 }
00342 
00343 void KOEditorFreeBusy::slotScaleChanged( int newScale )
00344 {
00345   // The +1 is for the Minute scale which we don't offer in the combo box.
00346   KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
00347   mGanttView->setScale( scale );
00348   slotCenterOnStart();
00349 }
00350 
00351 void KOEditorFreeBusy::slotCenterOnStart()
00352 {
00353   mGanttView->centerTimeline( mDtStart );
00354 }
00355 
00356 void KOEditorFreeBusy::slotZoomToTime()
00357 {
00358   mGanttView->zoomToFit();
00359 }
00360 
00361 void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
00362 {
00363   if ( item->isDownloading() )
00364     // This item is already in the process of fetching the FB list
00365     return;
00366 
00367   if ( item->updateTimerID() != 0 )
00368     // An update timer is already running. Reset it
00369     killTimer( item->updateTimerID() );
00370 
00371   // This item does not have a download running, and no timer is set
00372   // Do the download in five seconds
00373   item->setUpdateTimerID( startTimer( 5000 ) );
00374 }
00375 
00376 void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
00377 {
00378   killTimer( event->timerId() );
00379   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00380   while( item ) {
00381     if( item->updateTimerID() == event->timerId() ) {
00382       item->setUpdateTimerID( 0 );
00383       item->startDownload();
00384       return;
00385     }
00386     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00387   }
00388 }
00389 
00390 // Set the Free Busy list for everyone having this email address
00391 // If fb == 0, this disabled the free busy list for them
00392 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
00393                                            const QString &email )
00394 {
00395   kdDebug() << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
00396 
00397   if( fb )
00398     fb->sortList();
00399   bool block = mGanttView->getUpdateEnabled();
00400   mGanttView->setUpdateEnabled( false );
00401   for( KDGanttViewItem *it = mGanttView->firstChild(); it;
00402        it = it->nextSibling() ) {
00403     FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
00404     if( item->email() == email )
00405       item->setFreeBusyPeriods( fb );
00406   }
00407   mGanttView->setUpdateEnabled( block );
00408 }
00409 
00410 
00415 void KOEditorFreeBusy::slotUpdateGanttView( QDateTime dtFrom, QDateTime dtTo )
00416 {
00417   mDtStart = dtFrom;
00418   mDtEnd = dtTo;
00419   bool block = mGanttView->getUpdateEnabled( );
00420   mGanttView->setUpdateEnabled( false );
00421   QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
00422   mGanttView->setHorizonStart( horizonStart  );
00423   mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
00424   mGanttView->clearBackgroundColor();
00425   mGanttView->setIntervalBackgroundColor( dtFrom, dtTo, Qt::magenta );
00426   mGanttView->setUpdateEnabled( block );
00427   mGanttView->centerTimelineAfterShow( dtFrom );
00428 }
00429 
00430 
00434 void KOEditorFreeBusy::slotPickDate()
00435 {
00436   QDateTime start = mDtStart;
00437   QDateTime end = mDtEnd;
00438   bool success = findFreeSlot( start, end );
00439 
00440   if( success ) {
00441     if ( start == mDtStart && end == mDtEnd ) {
00442       KMessageBox::information( this,
00443           i18n( "The meeting has already suitable start/end times." ));
00444     } else {
00445       emit dateTimesChanged( start, end );
00446       slotUpdateGanttView( start, end );
00447       KMessageBox::information( this,
00448           i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
00449           .arg( start.toString() ).arg( end.toString() ) );
00450     }
00451   } else
00452     KMessageBox::sorry( this, i18n( "No suitable date found." ) );
00453 }
00454 
00455 
00460 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
00461 {
00462   if( tryDate( dtFrom, dtTo ) )
00463     // Current time is acceptable
00464     return true;
00465 
00466   QDateTime tryFrom = dtFrom;
00467   QDateTime tryTo = dtTo;
00468 
00469   // Make sure that we never suggest a date in the past, even if the
00470   // user originally scheduled the meeting to be in the past.
00471   if( tryFrom < QDateTime::currentDateTime() ) {
00472     // The slot to look for is at least partially in the past.
00473     int secs = tryFrom.secsTo( tryTo );
00474     tryFrom = QDateTime::currentDateTime();
00475     tryTo = tryFrom.addSecs( secs );
00476   }
00477 
00478   bool found = false;
00479   while( !found ) {
00480     found = tryDate( tryFrom, tryTo );
00481     // PENDING(kalle) Make the interval configurable
00482     if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00483       break; // don't look more than one year in the future
00484   }
00485 
00486   dtFrom = tryFrom;
00487   dtTo = tryTo;
00488 
00489   return found;
00490 }
00491 
00492 
00501 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
00502 {
00503   FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
00504   while( currentItem ) {
00505     if( !tryDate( currentItem, tryFrom, tryTo ) ) {
00506       // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl;
00507       return false;
00508     }
00509 
00510     currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
00511   }
00512 
00513   return true;
00514 }
00515 
00523 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
00524                                 QDateTime &tryFrom, QDateTime &tryTo )
00525 {
00526   // If we don't have any free/busy information, assume the
00527   // participant is free. Otherwise a participant without available
00528   // information would block the whole allocation.
00529   KCal::FreeBusy *fb = attendee->freeBusy();
00530   if( !fb )
00531     return true;
00532 
00533   QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00534   for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00535        it != busyPeriods.end(); ++it ) {
00536     if( (*it).end() <= tryFrom || // busy period ends before try period
00537     (*it).start() >= tryTo )  // busy period starts after try period
00538       continue;
00539     else {
00540       // the current busy period blocks the try period, try
00541       // after the end of the current busy period
00542       int secsDuration = tryFrom.secsTo( tryTo );
00543       tryFrom = (*it).end();
00544       tryTo = tryFrom.addSecs( secsDuration );
00545       // try again with the new try period
00546       tryDate( attendee, tryFrom, tryTo );
00547       // we had to change the date at least once
00548       return false;
00549     }
00550   }
00551 
00552   return true;
00553 }
00554 
00555 void KOEditorFreeBusy::updateStatusSummary()
00556 {
00557   FreeBusyItem *aItem =
00558     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00559   int total = 0;
00560   int accepted = 0;
00561   int tentative = 0;
00562   int declined = 0;
00563   while( aItem ) {
00564     ++total;
00565     switch( aItem->attendee()->status() ) {
00566     case Attendee::Accepted:
00567       ++accepted;
00568       break;
00569     case Attendee::Tentative:
00570       ++tentative;
00571       break;
00572     case Attendee::Declined:
00573       ++declined;
00574       break;
00575     case Attendee::NeedsAction:
00576     case Attendee::Delegated:
00577     case Attendee::Completed:
00578     case Attendee::InProcess:
00579       /* just to shut up the compiler */
00580       break;
00581     }
00582     aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00583   }
00584   if( total > 1 && mIsOrganizer ) {
00585     mStatusSummaryLabel->show();
00586     mStatusSummaryLabel->setText(
00587         i18n( "Of the %1 participants, %2 have accepted, %3"
00588               " have tentatively accepted, and %4 have declined.")
00589         .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00590   } else {
00591     mStatusSummaryLabel->hide();
00592   }
00593   mStatusSummaryLabel->adjustSize();
00594 }
00595 
00596 void KOEditorFreeBusy::triggerReload()
00597 {
00598   mReloadTimer.start( 1000, true );
00599 }
00600 
00601 void KOEditorFreeBusy::cancelReload()
00602 {
00603   mReloadTimer.stop();
00604 }
00605 
00606 void KOEditorFreeBusy::reload()
00607 {
00608   kdDebug() << "KOEditorFreeBusy::reload()" << endl;
00609 
00610   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00611   while( item ) {
00612     updateFreeBusyData( item );
00613     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00614   }
00615 }
00616 
00617 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00618 {
00619   FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00620   if ( !item ) return;
00621 
00622   Attendee *attendee = item->attendee();
00623 
00624   FreeBusyUrlDialog dialog( attendee, this );
00625   dialog.exec();
00626 }
00627 
00628 #include "koeditorfreebusy.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