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 if ( !m->retrieveFreeBusy( attendee()->email() ) )
00098 mIsDownloading = false;
00099 }
00100 void setIsDownloading( bool d ) { mIsDownloading = d; }
00101 bool isDownloading() const { return mIsDownloading; }
00102
00103 private:
00104 Attendee *mAttendee;
00105 KCal::FreeBusy *mFreeBusy;
00106
00107 QMap<int,QString> mKeyMap;
00108
00109
00110 int mTimerID;
00111
00112
00113 bool mIsDownloading;
00114 };
00115
00116 void FreeBusyItem::updateItem()
00117 {
00118 setListViewText( 0, mAttendee->name() );
00119 setListViewText( 1, mAttendee->email() );
00120 setListViewText( 2, mAttendee->roleStr() );
00121 setListViewText( 3, mAttendee->statusStr() );
00122 if ( mAttendee->RSVP() && !mAttendee->email().isEmpty() )
00123 setPixmap( 4, KOGlobals::self()->smallIcon( "mailappt" ) );
00124 else
00125 setPixmap( 4, KOGlobals::self()->smallIcon( "nomailappt" ) );
00126 }
00127
00128
00129
00130 void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
00131 {
00132 if( fb ) {
00133 setStartTime( fb->dtStart() );
00134 setEndTime( fb->dtEnd() );
00135
00136 for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
00137 delete it;
00138
00139
00140 QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00141 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00142 it != busyPeriods.end(); ++it ) {
00143 KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00144 newSubItem->setStartTime( (*it).start() );
00145 newSubItem->setEndTime( (*it).end() );
00146 newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00147 }
00148 setFreeBusy( fb );
00149 setShowNoInformation( false );
00150 setShowNoInformationBeforeAndAfter( true );
00151 } else {
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165 setFreeBusy( 0 );
00166 setShowNoInformation( true );
00167 }
00168
00169
00170 mIsDownloading = false;
00171 }
00172
00173
00174 KOEditorFreeBusy::KOEditorFreeBusy( int spacing, QWidget *parent,
00175 const char *name )
00176 : QWidget( parent, name )
00177 {
00178 QVBoxLayout *topLayout = new QVBoxLayout( this );
00179 topLayout->setSpacing( spacing );
00180
00181
00182
00183 mIsOrganizer = false;
00184 mStatusSummaryLabel = new QLabel( this );
00185 mStatusSummaryLabel->setPalette( QToolTip::palette() );
00186 mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
00187 mStatusSummaryLabel->setLineWidth( 1 );
00188 mStatusSummaryLabel->hide();
00189 topLayout->addWidget( mStatusSummaryLabel );
00190
00191
00192 QBoxLayout *controlLayout = new QHBoxLayout( topLayout );
00193
00194 QLabel *label = new QLabel( i18n( "Scale: " ), this );
00195 controlLayout->addWidget( label );
00196
00197 scaleCombo = new QComboBox( this );
00198 scaleCombo->insertItem( i18n( "Hour" ) );
00199 scaleCombo->insertItem( i18n( "Day" ) );
00200 scaleCombo->insertItem( i18n( "Week" ) );
00201 scaleCombo->insertItem( i18n( "Month" ) );
00202 scaleCombo->insertItem( i18n( "Automatic" ) );
00203 scaleCombo->setCurrentItem( 0 );
00204 connect( scaleCombo, SIGNAL( activated( int ) ),
00205 SLOT( slotScaleChanged( int ) ) );
00206 controlLayout->addWidget( scaleCombo );
00207
00208 QPushButton *button = new QPushButton( i18n( "Center on Start" ), this );
00209 connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) );
00210 controlLayout->addWidget( button );
00211
00212 button = new QPushButton( i18n( "Zoom to Fit" ), this );
00213 connect( button, SIGNAL( clicked() ), SLOT( slotZoomToTime() ) );
00214 controlLayout->addWidget( button );
00215
00216 controlLayout->addStretch( 1 );
00217
00218 button = new QPushButton( i18n( "Pick Date" ), this );
00219 connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
00220 controlLayout->addWidget( button );
00221
00222 controlLayout->addStretch( 1 );
00223
00224 button = new QPushButton( i18n("Reload"), this );
00225 controlLayout->addWidget( button );
00226 connect( button, SIGNAL( clicked() ), SLOT( reload() ) );
00227
00228 mGanttView = new KDGanttView( this, "mGanttView" );
00229 topLayout->addWidget( mGanttView );
00230
00231 mGanttView->removeColumn( 0 );
00232 mGanttView->addColumn( i18n("Name"), 180 );
00233 mGanttView->addColumn( i18n("Email"), 180 );
00234 mGanttView->addColumn( i18n("Role"), 60 );
00235 mGanttView->addColumn( i18n("Status"), 100 );
00236 mGanttView->addColumn( i18n("RSVP"), 35 );
00237 if ( KOPrefs::instance()->mCompactDialogs ) {
00238 mGanttView->setFixedHeight( 78 );
00239 }
00240 mGanttView->setHeaderVisible( true );
00241 mGanttView->setScale( KDGanttView::Hour );
00242 mGanttView->setShowHeaderPopupMenu( true, true, true, false, false, true );
00243
00244
00245 QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
00246 .addDays( -15 ).date() );
00247 QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
00248 mGanttView->setHorizonStart( horizonStart );
00249 mGanttView->setHorizonEnd( horizonEnd );
00250 mGanttView->setCalendarMode( true );
00251
00252 mGanttView->setShowLegendButton( false );
00253
00254 mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
00255 if ( KGlobal::locale()->use12Clock() )
00256 mGanttView->setHourFormat( KDGanttView::Hour_12 );
00257 else
00258 mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
00259 connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
00260 const QDateTime & ) ),
00261 mGanttView, SLOT( zoomToSelection( const QDateTime &,
00262 const QDateTime & ) ) );
00263 connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
00264 SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
00265
00266 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00267 connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
00268 SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
00269
00270 connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( reload() ) );
00271 }
00272
00273 KOEditorFreeBusy::~KOEditorFreeBusy()
00274 {
00275 }
00276
00277 void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
00278 {
00279 FreeBusyItem *anItem =
00280 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00281 while( anItem ) {
00282 if( anItem->attendee() == attendee ) {
00283 if ( anItem->updateTimerID() != 0 )
00284 killTimer( anItem->updateTimerID() );
00285 delete anItem;
00286 updateStatusSummary();
00287 break;
00288 }
00289 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00290 }
00291 }
00292
00293 void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
00294 {
00295 FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
00296 if ( readFBList )
00297 updateFreeBusyData( item );
00298 updateStatusSummary();
00299 }
00300
00301 void KOEditorFreeBusy::updateAttendee( Attendee *attendee )
00302 {
00303 FreeBusyItem *anItem =
00304 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00305 while( anItem ) {
00306 if( anItem->attendee() == attendee ) {
00307 anItem->updateItem();
00308 updateFreeBusyData( anItem );
00309 updateStatusSummary();
00310 break;
00311 }
00312 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00313 }
00314 }
00315
00316 void KOEditorFreeBusy::clearAttendees()
00317 {
00318 mGanttView->clear();
00319 }
00320
00321
00322 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
00323 {
00324 mGanttView->setUpdateEnabled( enabled );
00325 }
00326
00327 bool KOEditorFreeBusy::updateEnabled() const
00328 {
00329 return mGanttView->getUpdateEnabled();
00330 }
00331
00332
00333 void KOEditorFreeBusy::readEvent( Event *event )
00334 {
00335 setDateTimes( event->dtStart(), event->dtEnd() );
00336 mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
00337 updateStatusSummary();
00338 }
00339
00340
00341 void KOEditorFreeBusy::setDateTimes( QDateTime start, QDateTime end )
00342 {
00343
00344 slotUpdateGanttView( start, end );
00345 }
00346
00347 void KOEditorFreeBusy::slotScaleChanged( int newScale )
00348 {
00349
00350 KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
00351 mGanttView->setScale( scale );
00352 slotCenterOnStart();
00353 }
00354
00355 void KOEditorFreeBusy::slotCenterOnStart()
00356 {
00357 mGanttView->centerTimeline( mDtStart );
00358 }
00359
00360 void KOEditorFreeBusy::slotZoomToTime()
00361 {
00362 mGanttView->zoomToFit();
00363 }
00364
00365 void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
00366 {
00367 if ( item->isDownloading() )
00368
00369 return;
00370
00371 if ( item->updateTimerID() != 0 )
00372
00373 killTimer( item->updateTimerID() );
00374
00375
00376
00377 item->setUpdateTimerID( startTimer( 5000 ) );
00378 }
00379
00380 void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
00381 {
00382 killTimer( event->timerId() );
00383 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00384 while( item ) {
00385 if( item->updateTimerID() == event->timerId() ) {
00386 item->setUpdateTimerID( 0 );
00387 item->startDownload();
00388 return;
00389 }
00390 item = static_cast<FreeBusyItem *>( item->nextSibling() );
00391 }
00392 }
00393
00394
00395
00396 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
00397 const QString &email )
00398 {
00399 kdDebug() << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
00400
00401 if( fb ) {
00402 fb->sortList();
00403
00404 if ( fb->dtEnd().toTime_t() == 0 ) return;
00405 }
00406 bool block = mGanttView->getUpdateEnabled();
00407 mGanttView->setUpdateEnabled( false );
00408 for( KDGanttViewItem *it = mGanttView->firstChild(); it;
00409 it = it->nextSibling() ) {
00410 FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
00411 if( item->email() == email )
00412 item->setFreeBusyPeriods( fb );
00413 }
00414 mGanttView->setUpdateEnabled( block );
00415 }
00416
00417
00422 void KOEditorFreeBusy::slotUpdateGanttView( QDateTime dtFrom, QDateTime dtTo )
00423 {
00424 mDtStart = dtFrom;
00425 mDtEnd = dtTo;
00426 bool block = mGanttView->getUpdateEnabled( );
00427 mGanttView->setUpdateEnabled( false );
00428 QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
00429 mGanttView->setHorizonStart( horizonStart );
00430 mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
00431 mGanttView->clearBackgroundColor();
00432 mGanttView->setIntervalBackgroundColor( dtFrom, dtTo, Qt::magenta );
00433 mGanttView->setUpdateEnabled( block );
00434 mGanttView->centerTimelineAfterShow( dtFrom );
00435 }
00436
00437
00441 void KOEditorFreeBusy::slotPickDate()
00442 {
00443 QDateTime start = mDtStart;
00444 QDateTime end = mDtEnd;
00445 bool success = findFreeSlot( start, end );
00446
00447 if( success ) {
00448 if ( start == mDtStart && end == mDtEnd ) {
00449 KMessageBox::information( this,
00450 i18n( "The meeting has already suitable start/end times." ));
00451 } else {
00452 emit dateTimesChanged( start, end );
00453 slotUpdateGanttView( start, end );
00454 KMessageBox::information( this,
00455 i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
00456 .arg( start.toString() ).arg( end.toString() ) );
00457 }
00458 } else
00459 KMessageBox::sorry( this, i18n( "No suitable date found." ) );
00460 }
00461
00462
00467 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
00468 {
00469 if( tryDate( dtFrom, dtTo ) )
00470
00471 return true;
00472
00473 QDateTime tryFrom = dtFrom;
00474 QDateTime tryTo = dtTo;
00475
00476
00477
00478 if( tryFrom < QDateTime::currentDateTime() ) {
00479
00480 int secs = tryFrom.secsTo( tryTo );
00481 tryFrom = QDateTime::currentDateTime();
00482 tryTo = tryFrom.addSecs( secs );
00483 }
00484
00485 bool found = false;
00486 while( !found ) {
00487 found = tryDate( tryFrom, tryTo );
00488
00489 if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00490 break;
00491 }
00492
00493 dtFrom = tryFrom;
00494 dtTo = tryTo;
00495
00496 return found;
00497 }
00498
00499
00508 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
00509 {
00510 FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
00511 while( currentItem ) {
00512 if( !tryDate( currentItem, tryFrom, tryTo ) ) {
00513
00514 return false;
00515 }
00516
00517 currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
00518 }
00519
00520 return true;
00521 }
00522
00530 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
00531 QDateTime &tryFrom, QDateTime &tryTo )
00532 {
00533
00534
00535
00536 KCal::FreeBusy *fb = attendee->freeBusy();
00537 if( !fb )
00538 return true;
00539
00540 QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00541 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00542 it != busyPeriods.end(); ++it ) {
00543 if( (*it).end() <= tryFrom ||
00544 (*it).start() >= tryTo )
00545 continue;
00546 else {
00547
00548
00549 int secsDuration = tryFrom.secsTo( tryTo );
00550 tryFrom = (*it).end();
00551 tryTo = tryFrom.addSecs( secsDuration );
00552
00553 tryDate( attendee, tryFrom, tryTo );
00554
00555 return false;
00556 }
00557 }
00558
00559 return true;
00560 }
00561
00562 void KOEditorFreeBusy::updateStatusSummary()
00563 {
00564 FreeBusyItem *aItem =
00565 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00566 int total = 0;
00567 int accepted = 0;
00568 int tentative = 0;
00569 int declined = 0;
00570 while( aItem ) {
00571 ++total;
00572 switch( aItem->attendee()->status() ) {
00573 case Attendee::Accepted:
00574 ++accepted;
00575 break;
00576 case Attendee::Tentative:
00577 ++tentative;
00578 break;
00579 case Attendee::Declined:
00580 ++declined;
00581 break;
00582 case Attendee::NeedsAction:
00583 case Attendee::Delegated:
00584 case Attendee::Completed:
00585 case Attendee::InProcess:
00586
00587 break;
00588 }
00589 aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00590 }
00591 if( total > 1 && mIsOrganizer ) {
00592 mStatusSummaryLabel->show();
00593 mStatusSummaryLabel->setText(
00594 i18n( "Of the %1 participants, %2 have accepted, %3"
00595 " have tentatively accepted, and %4 have declined.")
00596 .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00597 } else {
00598 mStatusSummaryLabel->hide();
00599 }
00600 mStatusSummaryLabel->adjustSize();
00601 }
00602
00603 void KOEditorFreeBusy::triggerReload()
00604 {
00605 mReloadTimer.start( 1000, true );
00606 }
00607
00608 void KOEditorFreeBusy::cancelReload()
00609 {
00610 mReloadTimer.stop();
00611 }
00612
00613 void KOEditorFreeBusy::reload()
00614 {
00615 kdDebug() << "KOEditorFreeBusy::reload()" << endl;
00616
00617 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00618 while( item ) {
00619 updateFreeBusyData( item );
00620 item = static_cast<FreeBusyItem *>( item->nextSibling() );
00621 }
00622 }
00623
00624 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00625 {
00626 FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00627 if ( !item ) return;
00628
00629 Attendee *attendee = item->attendee();
00630
00631 FreeBusyUrlDialog dialog( attendee, this );
00632 dialog.exec();
00633 }
00634
00635 #include "koeditorfreebusy.moc"