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 #include <qwhatsthis.h>
00031
00032 #include <kdebug.h>
00033 #include <klocale.h>
00034 #include <kiconloader.h>
00035 #include <kmessagebox.h>
00036
00037 #include <libkcal/event.h>
00038 #include <libkcal/freebusy.h>
00039
00040 #include <kdgantt/KDGanttView.h>
00041 #include <kdgantt/KDGanttViewTaskItem.h>
00042
00043 #include "koprefs.h"
00044 #include "koglobals.h"
00045 #include "kogroupware.h"
00046 #include "freebusymanager.h"
00047 #include "freebusyurldialog.h"
00048
00049 #include "koeditorfreebusy.h"
00050
00051
00052
00053
00054 class FreeBusyItem : public KDGanttViewTaskItem
00055 {
00056 public:
00057 FreeBusyItem( Attendee *attendee, KDGanttView *parent ) :
00058 KDGanttViewTaskItem( parent ), mAttendee( attendee ), mTimerID( 0 ),
00059 mIsDownloading( false )
00060 {
00061 Q_ASSERT( attendee );
00062 updateItem();
00063 setFreeBusyPeriods( 0 );
00064 setDisplaySubitemsAsGroup( true );
00065 if ( listView () )
00066 listView ()->setRootIsDecorated( false );
00067 }
00068 ~FreeBusyItem() {}
00069
00070 void updateItem();
00071
00072 Attendee *attendee() const { return mAttendee; }
00073 void setFreeBusy( KCal::FreeBusy *fb ) { mFreeBusy = fb; }
00074 KCal::FreeBusy* freeBusy() const { return mFreeBusy; }
00075
00076 void setFreeBusyPeriods( FreeBusy *fb );
00077
00078 QString key( int column, bool ) const
00079 {
00080 QMap<int,QString>::ConstIterator it = mKeyMap.find( column );
00081 if ( it == mKeyMap.end() ) return listViewText( column );
00082 else return *it;
00083 }
00084
00085 void setSortKey( int column, const QString &key )
00086 {
00087 mKeyMap.insert( column, key );
00088 }
00089
00090 QString email() const { return mAttendee->email(); }
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
00134 for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
00135 delete it;
00136
00137
00138 QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00139 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00140 it != busyPeriods.end(); ++it ) {
00141 KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00142 newSubItem->setStartTime( (*it).start() );
00143 newSubItem->setEndTime( (*it).end() );
00144 newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00145 }
00146 setFreeBusy( fb );
00147 setShowNoInformation( false );
00148 } else {
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162 setFreeBusy( 0 );
00163 setShowNoInformation( true );
00164 }
00165
00166
00167 mIsDownloading = false;
00168 }
00169
00170
00171 KOEditorFreeBusy::KOEditorFreeBusy( int spacing, QWidget *parent,
00172 const char *name )
00173 : QWidget( parent, name )
00174 {
00175 QVBoxLayout *topLayout = new QVBoxLayout( this );
00176 topLayout->setSpacing( spacing );
00177
00178
00179
00180 mIsOrganizer = false;
00181 mStatusSummaryLabel = new QLabel( this );
00182 mStatusSummaryLabel->setPalette( QToolTip::palette() );
00183 mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
00184 mStatusSummaryLabel->setLineWidth( 1 );
00185 mStatusSummaryLabel->hide();
00186 topLayout->addWidget( mStatusSummaryLabel );
00187
00188
00189 QBoxLayout *controlLayout = new QHBoxLayout( topLayout );
00190
00191 QString whatsThis = i18n("Sets the zoom level on the Gantt chart. "
00192 "'Hour' shows a range of several hours, "
00193 "'Day' shows a range of a few days, "
00194 "'Week' shows a range of a few months, "
00195 "and 'Month' shows a range of a few years, "
00196 "while 'Automatic' selects the range most "
00197 "appropriate for the current event or to-do.");
00198 QLabel *label = new QLabel( i18n( "Scale: " ), this );
00199 QWhatsThis::add( label, whatsThis );
00200 controlLayout->addWidget( label );
00201
00202 scaleCombo = new QComboBox( this );
00203 QWhatsThis::add( scaleCombo, whatsThis );
00204 scaleCombo->insertItem( i18n( "Hour" ) );
00205 scaleCombo->insertItem( i18n( "Day" ) );
00206 scaleCombo->insertItem( i18n( "Week" ) );
00207 scaleCombo->insertItem( i18n( "Month" ) );
00208 scaleCombo->insertItem( i18n( "Automatic" ) );
00209 scaleCombo->setCurrentItem( 0 );
00210 connect( scaleCombo, SIGNAL( activated( int ) ),
00211 SLOT( slotScaleChanged( int ) ) );
00212 controlLayout->addWidget( scaleCombo );
00213
00214 QPushButton *button = new QPushButton( i18n( "Center on Start" ), this );
00215 QWhatsThis::add( button,
00216 i18n("Centers the Gantt chart on the start time "
00217 "and day of this event.") );
00218 connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) );
00219 controlLayout->addWidget( button );
00220
00221 button = new QPushButton( i18n( "Zoom to Fit" ), this );
00222 QWhatsThis::add( button,
00223 i18n("Zooms the Gantt chart so that you can see the "
00224 "entire duration of the event on it.") );
00225 connect( button, SIGNAL( clicked() ), SLOT( slotZoomToTime() ) );
00226 controlLayout->addWidget( button );
00227
00228 controlLayout->addStretch( 1 );
00229
00230 button = new QPushButton( i18n( "Pick Date" ), this );
00231 QWhatsThis::add( button,
00232 i18n("Moves the event to a date and time when all the "
00233 "attendees are free.") );
00234 connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
00235 controlLayout->addWidget( button );
00236
00237 controlLayout->addStretch( 1 );
00238
00239 button = new QPushButton( i18n("Reload"), this );
00240 QWhatsThis::add( button,
00241 i18n("Reloads Free/Busy data for all attendees from "
00242 "the corresponding servers.") );
00243 controlLayout->addWidget( button );
00244 connect( button, SIGNAL( clicked() ), SLOT( reload() ) );
00245
00246 mGanttView = new KDGanttView( this, "mGanttView" );
00247 QWhatsThis::add( mGanttView,
00248 i18n("Shows the free/busy status of all attendees. "
00249 "Double-clicking on an attendees entry in the "
00250 "list will allow you to enter the location of their "
00251 "Free/Busy Information.") );
00252 topLayout->addWidget( mGanttView );
00253
00254 mGanttView->removeColumn( 0 );
00255 mGanttView->addColumn( i18n("Name"), 180 );
00256 mGanttView->addColumn( i18n("Email"), 180 );
00257 mGanttView->addColumn( i18n("Role"), 60 );
00258 mGanttView->addColumn( i18n("Status"), 100 );
00259 mGanttView->addColumn( i18n("RSVP"), 35 );
00260 if ( KOPrefs::instance()->mCompactDialogs ) {
00261 mGanttView->setFixedHeight( 78 );
00262 }
00263 mGanttView->setHeaderVisible( true );
00264 mGanttView->setScale( KDGanttView::Hour );
00265 mGanttView->setShowHeaderPopupMenu( true, true, true, false, false, true );
00266
00267
00268 QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
00269 .addDays( -15 ).date() );
00270 QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
00271 mGanttView->setHorizonStart( horizonStart );
00272 mGanttView->setHorizonEnd( horizonEnd );
00273 mGanttView->setCalendarMode( true );
00274
00275 mGanttView->setShowLegendButton( false );
00276
00277 mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
00278 if ( KGlobal::locale()->use12Clock() )
00279 mGanttView->setHourFormat( KDGanttView::Hour_12 );
00280 else
00281 mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
00282 connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
00283 const QDateTime & ) ),
00284 mGanttView, SLOT( zoomToSelection( const QDateTime &,
00285 const QDateTime & ) ) );
00286 connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
00287 SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
00288
00289 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00290 connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
00291 SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
00292
00293 connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( reload() ) );
00294 }
00295
00296 KOEditorFreeBusy::~KOEditorFreeBusy()
00297 {
00298 }
00299
00300 void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
00301 {
00302 FreeBusyItem *anItem =
00303 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00304 while( anItem ) {
00305 if( anItem->attendee() == attendee ) {
00306 if ( anItem->updateTimerID() != 0 )
00307 killTimer( anItem->updateTimerID() );
00308 delete anItem;
00309 updateStatusSummary();
00310 break;
00311 }
00312 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00313 }
00314 }
00315
00316 void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
00317 {
00318 FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
00319 if ( readFBList )
00320 updateFreeBusyData( item );
00321 updateStatusSummary();
00322 }
00323
00324 void KOEditorFreeBusy::updateAttendee( Attendee *attendee )
00325 {
00326 FreeBusyItem *anItem =
00327 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00328 while( anItem ) {
00329 if( anItem->attendee() == attendee ) {
00330 anItem->updateItem();
00331 updateFreeBusyData( anItem );
00332 updateStatusSummary();
00333 break;
00334 }
00335 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00336 }
00337 }
00338
00339 void KOEditorFreeBusy::clearAttendees()
00340 {
00341 mGanttView->clear();
00342 }
00343
00344
00345 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
00346 {
00347 mGanttView->setUpdateEnabled( enabled );
00348 }
00349
00350 bool KOEditorFreeBusy::updateEnabled() const
00351 {
00352 return mGanttView->getUpdateEnabled();
00353 }
00354
00355
00356 void KOEditorFreeBusy::readEvent( Event *event )
00357 {
00358 setDateTimes( event->dtStart(), event->dtEnd() );
00359 mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
00360 updateStatusSummary();
00361 }
00362
00363
00364 void KOEditorFreeBusy::setDateTimes( const QDateTime &start, const QDateTime &end )
00365 {
00366 slotUpdateGanttView( start, end );
00367 }
00368
00369 void KOEditorFreeBusy::slotScaleChanged( int newScale )
00370 {
00371
00372 KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
00373 mGanttView->setScale( scale );
00374 slotCenterOnStart();
00375 }
00376
00377 void KOEditorFreeBusy::slotCenterOnStart()
00378 {
00379 mGanttView->centerTimeline( mDtStart );
00380 }
00381
00382 void KOEditorFreeBusy::slotZoomToTime()
00383 {
00384 mGanttView->zoomToFit();
00385 }
00386
00387 void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
00388 {
00389 if ( item->isDownloading() )
00390
00391 return;
00392
00393 if ( item->updateTimerID() != 0 )
00394
00395 killTimer( item->updateTimerID() );
00396
00397
00398
00399 item->setUpdateTimerID( startTimer( 5000 ) );
00400 }
00401
00402 void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
00403 {
00404 killTimer( event->timerId() );
00405 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00406 while( item ) {
00407 if( item->updateTimerID() == event->timerId() ) {
00408 item->setUpdateTimerID( 0 );
00409 item->startDownload();
00410 return;
00411 }
00412 item = static_cast<FreeBusyItem *>( item->nextSibling() );
00413 }
00414 }
00415
00416
00417
00418 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
00419 const QString &email )
00420 {
00421 kdDebug(5850) << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
00422
00423 if( fb )
00424 fb->sortList();
00425 bool block = mGanttView->getUpdateEnabled();
00426 mGanttView->setUpdateEnabled( false );
00427 for( KDGanttViewItem *it = mGanttView->firstChild(); it;
00428 it = it->nextSibling() ) {
00429 FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
00430 if( item->email() == email )
00431 item->setFreeBusyPeriods( fb );
00432 }
00433 mGanttView->setUpdateEnabled( block );
00434 }
00435
00436
00441 void KOEditorFreeBusy::slotUpdateGanttView( const QDateTime &dtFrom, const QDateTime &dtTo )
00442 {
00443 mDtStart = dtFrom;
00444 mDtEnd = dtTo;
00445 bool block = mGanttView->getUpdateEnabled( );
00446 mGanttView->setUpdateEnabled( false );
00447 QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
00448 mGanttView->setHorizonStart( horizonStart );
00449 mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
00450 mGanttView->clearBackgroundColor();
00451 mGanttView->setIntervalBackgroundColor( dtFrom, dtTo, Qt::magenta );
00452 mGanttView->setUpdateEnabled( block );
00453 mGanttView->centerTimelineAfterShow( dtFrom );
00454 }
00455
00456
00460 void KOEditorFreeBusy::slotPickDate()
00461 {
00462 QDateTime start = mDtStart;
00463 QDateTime end = mDtEnd;
00464 bool success = findFreeSlot( start, end );
00465
00466 if( success ) {
00467 if ( start == mDtStart && end == mDtEnd ) {
00468 KMessageBox::information( this,
00469 i18n( "The meeting already has suitable start/end times." ), QString::null,
00470 "MeetingTimeOKFreeBusy" );
00471 } else {
00472 emit dateTimesChanged( start, end );
00473 slotUpdateGanttView( start, end );
00474 KMessageBox::information( this,
00475 i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
00476 .arg( start.toString() ).arg( end.toString() ), QString::null,
00477 "MeetingMovedFreeBusy" );
00478 }
00479 } else
00480 KMessageBox::sorry( this, i18n( "No suitable date found." ) );
00481 }
00482
00483
00488 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
00489 {
00490 if( tryDate( dtFrom, dtTo ) )
00491
00492 return true;
00493
00494 QDateTime tryFrom = dtFrom;
00495 QDateTime tryTo = dtTo;
00496
00497
00498
00499 if( tryFrom < QDateTime::currentDateTime() ) {
00500
00501 int secs = tryFrom.secsTo( tryTo );
00502 tryFrom = QDateTime::currentDateTime();
00503 tryTo = tryFrom.addSecs( secs );
00504 }
00505
00506 bool found = false;
00507 while( !found ) {
00508 found = tryDate( tryFrom, tryTo );
00509
00510 if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00511 break;
00512 }
00513
00514 dtFrom = tryFrom;
00515 dtTo = tryTo;
00516
00517 return found;
00518 }
00519
00520
00529 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
00530 {
00531 FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
00532 while( currentItem ) {
00533 if( !tryDate( currentItem, tryFrom, tryTo ) ) {
00534
00535 return false;
00536 }
00537
00538 currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
00539 }
00540
00541 return true;
00542 }
00543
00551 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
00552 QDateTime &tryFrom, QDateTime &tryTo )
00553 {
00554
00555
00556
00557 KCal::FreeBusy *fb = attendee->freeBusy();
00558 if( !fb )
00559 return true;
00560
00561 QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00562 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00563 it != busyPeriods.end(); ++it ) {
00564 if( (*it).end() <= tryFrom ||
00565 (*it).start() >= tryTo )
00566 continue;
00567 else {
00568
00569
00570 int secsDuration = tryFrom.secsTo( tryTo );
00571 tryFrom = (*it).end();
00572 tryTo = tryFrom.addSecs( secsDuration );
00573
00574 tryDate( attendee, tryFrom, tryTo );
00575
00576 return false;
00577 }
00578 }
00579
00580 return true;
00581 }
00582
00583 void KOEditorFreeBusy::updateStatusSummary()
00584 {
00585 FreeBusyItem *aItem =
00586 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00587 int total = 0;
00588 int accepted = 0;
00589 int tentative = 0;
00590 int declined = 0;
00591 while( aItem ) {
00592 ++total;
00593 switch( aItem->attendee()->status() ) {
00594 case Attendee::Accepted:
00595 ++accepted;
00596 break;
00597 case Attendee::Tentative:
00598 ++tentative;
00599 break;
00600 case Attendee::Declined:
00601 ++declined;
00602 break;
00603 case Attendee::NeedsAction:
00604 case Attendee::Delegated:
00605 case Attendee::Completed:
00606 case Attendee::InProcess:
00607
00608 break;
00609 }
00610 aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00611 }
00612 if( total > 1 && mIsOrganizer ) {
00613 mStatusSummaryLabel->show();
00614 mStatusSummaryLabel->setText(
00615 i18n( "Of the %1 participants, %2 have accepted, %3"
00616 " have tentatively accepted, and %4 have declined.")
00617 .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00618 } else {
00619 mStatusSummaryLabel->hide();
00620 }
00621 mStatusSummaryLabel->adjustSize();
00622 }
00623
00624 void KOEditorFreeBusy::triggerReload()
00625 {
00626 mReloadTimer.start( 1000, true );
00627 }
00628
00629 void KOEditorFreeBusy::cancelReload()
00630 {
00631 mReloadTimer.stop();
00632 }
00633
00634 void KOEditorFreeBusy::reload()
00635 {
00636 kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
00637
00638 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00639 while( item ) {
00640 updateFreeBusyData( item );
00641 item = static_cast<FreeBusyItem *>( item->nextSibling() );
00642 }
00643 }
00644
00645 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00646 {
00647 FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00648 if ( !item ) return;
00649
00650 Attendee *attendee = item->attendee();
00651
00652 FreeBusyUrlDialog dialog( attendee, this );
00653 dialog.exec();
00654 }
00655
00656 #include "koeditorfreebusy.moc"