00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
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
00052
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
00109 int mTimerID;
00110
00111
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
00129 void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
00130 {
00131 if( fb ) {
00132
00133 for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
00134 delete it;
00135
00136
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
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161 setFreeBusy( 0 );
00162 setShowNoInformation( true );
00163 }
00164
00165
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
00178
00179 mIsOrganizer = false;
00180 mStatusSummaryLabel = new QLabel( this );
00181 mStatusSummaryLabel->setPalette( QToolTip::palette() );
00182 mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
00183 mStatusSummaryLabel->setLineWidth( 1 );
00184 mStatusSummaryLabel->hide();
00185 topLayout->addWidget( mStatusSummaryLabel );
00186
00187
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 );
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
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
00240
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
00248 mGanttView->setShowLegendButton( false );
00249
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
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
00365 return;
00366
00367 if ( item->updateTimerID() != 0 )
00368
00369 killTimer( item->updateTimerID() );
00370
00371
00372
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
00391
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
00464 return true;
00465
00466 QDateTime tryFrom = dtFrom;
00467 QDateTime tryTo = dtTo;
00468
00469
00470
00471 if( tryFrom < QDateTime::currentDateTime() ) {
00472
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
00482 if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00483 break;
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
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
00527
00528
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 ||
00537 (*it).start() >= tryTo )
00538 continue;
00539 else {
00540
00541
00542 int secsDuration = tryFrom.secsTo( tryTo );
00543 tryFrom = (*it).end();
00544 tryTo = tryFrom.addSecs( secsDuration );
00545
00546 tryDate( attendee, tryFrom, tryTo );
00547
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
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"