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