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