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 QString organizer = KOPrefs::instance()->email(); 00157 mOrganizerLabel = new QLabel( i18n("Organizer: %1").arg( organizer ), this ); 00158 mIsOrganizer = true; // Will be set later. This is just valgrind silencing 00159 topLayout->addWidget( mOrganizerLabel ); 00160 00161 // Label for status summary information 00162 // Uses the tooltip palette to highlight it 00163 mStatusSummaryLabel = new QLabel( this ); 00164 mStatusSummaryLabel->setPalette( QToolTip::palette() ); 00165 mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box ); 00166 mStatusSummaryLabel->setLineWidth( 1 ); 00167 topLayout->addWidget( mStatusSummaryLabel ); 00168 00169 // The control panel for the gantt widget 00170 QBoxLayout *controlLayout = new QHBoxLayout( topLayout ); 00171 00172 QLabel *label = new QLabel( i18n( "Scale: " ), this ); 00173 controlLayout->addWidget( label ); 00174 00175 scaleCombo = new QComboBox( this ); 00176 scaleCombo->insertItem( i18n( "Hour" ) ); 00177 scaleCombo->insertItem( i18n( "Day" ) ); 00178 scaleCombo->insertItem( i18n( "Week" ) ); 00179 scaleCombo->insertItem( i18n( "Month" ) ); 00180 scaleCombo->insertItem( i18n( "Automatic" ) ); 00181 scaleCombo->setCurrentItem( 0 ); // start with "hour" 00182 connect( scaleCombo, SIGNAL( activated( int ) ), 00183 SLOT( slotScaleChanged( int ) ) ); 00184 controlLayout->addWidget( scaleCombo ); 00185 00186 QPushButton *button = new QPushButton( i18n( "Center on Start" ), this ); 00187 connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) ); 00188 controlLayout->addWidget( button ); 00189 00190 button = new QPushButton( i18n( "Zoom to Fit" ), this ); 00191 connect( button, SIGNAL( clicked() ), SLOT( slotZoomToTime() ) ); 00192 controlLayout->addWidget( button ); 00193 00194 controlLayout->addStretch( 1 ); 00195 00196 button = new QPushButton( i18n( "Pick Date" ), this ); 00197 connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) ); 00198 controlLayout->addWidget( button ); 00199 00200 controlLayout->addStretch( 1 ); 00201 00202 button = new QPushButton( i18n("Reload"), this ); 00203 controlLayout->addWidget( button ); 00204 connect( button, SIGNAL( clicked() ), SLOT( reload() ) ); 00205 00206 mGanttView = new KDGanttView( this, "mGanttView" ); 00207 topLayout->addWidget( mGanttView ); 00208 // Remove the predefined "Task Name" column 00209 mGanttView->removeColumn( 0 ); 00210 mGanttView->addColumn( i18n("Name"), 180 ); 00211 mGanttView->addColumn( i18n("Email"), 180 ); 00212 mGanttView->addColumn( i18n("Role"), 60 ); 00213 mGanttView->addColumn( i18n("Status"), 100 ); 00214 mGanttView->addColumn( i18n("RSVP"), 35 ); 00215 if ( KOPrefs::instance()->mCompactDialogs ) { 00216 mGanttView->setFixedHeight( 78 ); 00217 } 00218 mGanttView->setHeaderVisible( true ); 00219 mGanttView->setScale( KDGanttView::Hour ); 00220 mGanttView->setShowHeaderPopupMenu( true, true, true, false, false, true ); 00221 // Initially, show 15 days back and forth 00222 // set start to even hours, i.e. to 12:AM 0 Min 0 Sec 00223 QDateTime horizonStart = QDateTime( QDateTime::currentDateTime() 00224 .addDays( -15 ).date() ); 00225 QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 ); 00226 mGanttView->setHorizonStart( horizonStart ); 00227 mGanttView->setHorizonEnd( horizonEnd ); 00228 mGanttView->setCalendarMode( true ); 00229 //mGanttView->setDisplaySubitemsAsGroup( true ); 00230 mGanttView->setShowLegendButton( false ); 00231 // Initially, center to current date 00232 mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() ); 00233 if ( KGlobal::locale()->use12Clock() ) 00234 mGanttView->setHourFormat( KDGanttView::Hour_12 ); 00235 else 00236 mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit ); 00237 connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &, 00238 const QDateTime & ) ), 00239 mGanttView, SLOT( zoomToSelection( const QDateTime &, 00240 const QDateTime & ) ) ); 00241 // connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ), 00242 // SLOT( updateFreeBusyData( KDGanttViewItem * ) ) ); 00243 connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ), 00244 SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) ); 00245 00246 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager(); 00247 connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ), 00248 SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) ); 00249 00250 connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( reload() ) ); 00251 } 00252 00253 KOEditorFreeBusy::~KOEditorFreeBusy() 00254 { 00255 } 00256 00257 void KOEditorFreeBusy::removeAttendee( Attendee *attendee ) 00258 { 00259 FreeBusyItem *anItem = 00260 static_cast<FreeBusyItem *>( mGanttView->firstChild() ); 00261 while( anItem ) { 00262 if( anItem->attendee() == attendee ) { 00263 delete anItem; 00264 updateStatusSummary(); 00265 break; 00266 } 00267 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() ); 00268 } 00269 } 00270 00271 void KOEditorFreeBusy::insertAttendee( Attendee *attendee ) 00272 { 00273 (void)new FreeBusyItem( attendee, mGanttView ); 00274 #if 0 00275 updateFreeBusyData( attendee ); 00276 #endif 00277 updateStatusSummary(); 00278 } 00279 00280 void KOEditorFreeBusy::updateAttendee( Attendee *attendee ) 00281 { 00282 FreeBusyItem *anItem = 00283 static_cast<FreeBusyItem *>( mGanttView->firstChild() ); 00284 while( anItem ) { 00285 if( anItem->attendee() == attendee ) { 00286 anItem->updateItem(); 00287 updateFreeBusyData( attendee ); 00288 updateStatusSummary(); 00289 break; 00290 } 00291 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() ); 00292 } 00293 } 00294 00295 void KOEditorFreeBusy::clearAttendees() 00296 { 00297 mGanttView->clear(); 00298 } 00299 00300 00301 void KOEditorFreeBusy::setUpdateEnabled( bool enabled ) 00302 { 00303 mGanttView->setUpdateEnabled( enabled ); 00304 } 00305 00306 bool KOEditorFreeBusy::updateEnabled() const 00307 { 00308 return mGanttView->getUpdateEnabled(); 00309 } 00310 00311 00312 void KOEditorFreeBusy::readEvent( Event *event ) 00313 { 00314 setDateTimes( event->dtStart(), event->dtEnd() ); 00315 } 00316 00317 00318 void KOEditorFreeBusy::setDateTimes( QDateTime start, QDateTime end ) 00319 { 00320 mDtStart = start; 00321 mDtEnd = end; 00322 00323 mGanttView->centerTimelineAfterShow( start ); 00324 mGanttView->clearBackgroundColor(); 00325 mGanttView->setIntervalBackgroundColor( start, end, Qt::magenta ); 00326 } 00327 00328 void KOEditorFreeBusy::slotScaleChanged( int newScale ) 00329 { 00330 // The +1 is for the Minute scale which we don't offer in the combo box. 00331 KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 ); 00332 mGanttView->setScale( scale ); 00333 slotCenterOnStart(); 00334 } 00335 00336 void KOEditorFreeBusy::slotCenterOnStart() 00337 { 00338 mGanttView->centerTimeline( mDtStart ); 00339 } 00340 00341 void KOEditorFreeBusy::slotZoomToTime() 00342 { 00343 mGanttView->zoomToFit(); 00344 } 00345 00346 void KOEditorFreeBusy::updateFreeBusyData( KDGanttViewItem *item ) 00347 { 00348 FreeBusyItem *g = static_cast<FreeBusyItem *>( item ); 00349 updateFreeBusyData( g->attendee() ); 00350 } 00351 00352 void KOEditorFreeBusy::updateFreeBusyData( Attendee *attendee ) 00353 { 00354 if( KOGroupware::instance() && attendee->name() != "(EmptyName)" ) { 00355 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager(); 00356 m->retrieveFreeBusy( attendee->email() ); 00357 } 00358 } 00359 00360 // Set the Free Busy list for everyone having this email address 00361 // If fb == 0, this disabled the free busy list for them 00362 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb, 00363 const QString &email ) 00364 { 00365 kdDebug() << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl; 00366 00367 if( fb ) 00368 fb->sortList(); 00369 bool block = mGanttView->getUpdateEnabled(); 00370 mGanttView->setUpdateEnabled( false ); 00371 for( KDGanttViewItem *it = mGanttView->firstChild(); it; 00372 it = it->nextSibling() ) { 00373 FreeBusyItem *item = static_cast<FreeBusyItem *>( it ); 00374 if( item->email() == email ) 00375 item->setFreeBusyPeriods( fb ); 00376 } 00377 mGanttView->setUpdateEnabled( block ); 00378 } 00379 00380 00385 void KOEditorFreeBusy::slotUpdateGanttView( QDateTime dtFrom, QDateTime dtTo ) 00386 { 00387 bool block = mGanttView->getUpdateEnabled( ); 00388 mGanttView->setUpdateEnabled( false ); 00389 QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() ); 00390 mGanttView->setHorizonStart( horizonStart ); 00391 mGanttView->setHorizonEnd( dtTo.addDays( 15 ) ); 00392 mGanttView->clearBackgroundColor(); 00393 mGanttView->setIntervalBackgroundColor( dtFrom, dtTo, Qt::magenta ); 00394 mGanttView->setUpdateEnabled( block ); 00395 mGanttView->centerTimelineAfterShow( dtFrom ); 00396 } 00397 00398 00402 void KOEditorFreeBusy::slotPickDate() 00403 { 00404 QDateTime start = mDtStart; 00405 QDateTime end = mDtEnd; 00406 bool success = findFreeSlot( start, end ); 00407 00408 if( success ) { 00409 if ( start == mDtStart && end == mDtEnd ) { 00410 KMessageBox::information( this, 00411 i18n( "The meeting has already suitable start/end times." )); 00412 } else { 00413 emit dateTimesChanged( start, end ); 00414 slotUpdateGanttView( start, end ); 00415 KMessageBox::information( this, 00416 i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." ) 00417 .arg( start.toString() ).arg( end.toString() ) ); 00418 } 00419 } else 00420 KMessageBox::sorry( this, i18n( "No suitable date found." ) ); 00421 } 00422 00423 00428 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo ) 00429 { 00430 if( tryDate( dtFrom, dtTo ) ) 00431 // Current time is acceptable 00432 return true; 00433 00434 QDateTime tryFrom = dtFrom; 00435 QDateTime tryTo = dtTo; 00436 00437 // Make sure that we never suggest a date in the past, even if the 00438 // user originally scheduled the meeting to be in the past. 00439 if( tryFrom < QDateTime::currentDateTime() ) { 00440 // The slot to look for is at least partially in the past. 00441 int secs = tryFrom.secsTo( tryTo ); 00442 tryFrom = QDateTime::currentDateTime(); 00443 tryTo = tryFrom.addSecs( secs ); 00444 } 00445 00446 bool found = false; 00447 while( !found ) { 00448 found = tryDate( tryFrom, tryTo ); 00449 // PENDING(kalle) Make the interval configurable 00450 if( !found && dtFrom.daysTo( tryFrom ) > 365 ) 00451 break; // don't look more than one year in the future 00452 } 00453 00454 dtFrom = tryFrom; 00455 dtTo = tryTo; 00456 00457 return found; 00458 } 00459 00460 00469 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo ) 00470 { 00471 FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() ); 00472 while( currentItem ) { 00473 if( !tryDate( currentItem, tryFrom, tryTo ) ) { 00474 // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl; 00475 return false; 00476 } 00477 00478 currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() ); 00479 } 00480 00481 return true; 00482 } 00483 00491 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee, 00492 QDateTime &tryFrom, QDateTime &tryTo ) 00493 { 00494 // If we don't have any free/busy information, assume the 00495 // participant is free. Otherwise a participant without available 00496 // information would block the whole allocation. 00497 KCal::FreeBusy *fb = attendee->freeBusy(); 00498 if( !fb ) 00499 return true; 00500 00501 QValueList<KCal::Period> busyPeriods = fb->busyPeriods(); 00502 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin(); 00503 it != busyPeriods.end(); ++it ) { 00504 if( (*it).end() <= tryFrom || // busy period ends before try period 00505 (*it).start() >= tryTo ) // busy period starts after try period 00506 continue; 00507 else { 00508 // the current busy period blocks the try period, try 00509 // after the end of the current busy period 00510 int secsDuration = tryFrom.secsTo( tryTo ); 00511 tryFrom = (*it).end(); 00512 tryTo = tryFrom.addSecs( secsDuration ); 00513 // try again with the new try period 00514 tryDate( attendee, tryFrom, tryTo ); 00515 // we had to change the date at least once 00516 return false; 00517 } 00518 } 00519 00520 return true; 00521 } 00522 00523 void KOEditorFreeBusy::updateStatusSummary() 00524 { 00525 FreeBusyItem *aItem = 00526 static_cast<FreeBusyItem *>( mGanttView->firstChild() ); 00527 int total = 0; 00528 int accepted = 0; 00529 int tentative = 0; 00530 int declined = 0; 00531 while( aItem ) { 00532 ++total; 00533 switch( aItem->attendee()->status() ) { 00534 case Attendee::Accepted: 00535 ++accepted; 00536 break; 00537 case Attendee::Tentative: 00538 ++tentative; 00539 break; 00540 case Attendee::Declined: 00541 ++declined; 00542 break; 00543 case Attendee::NeedsAction: 00544 case Attendee::Delegated: 00545 case Attendee::Completed: 00546 case Attendee::InProcess: 00547 /* just to shut up the compiler */ 00548 break; 00549 } 00550 aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() ); 00551 } 00552 if( total > 1 && mIsOrganizer ) { 00553 mStatusSummaryLabel->show(); 00554 mStatusSummaryLabel->setText( 00555 i18n( "Of the %1 participants, %2 have accepted, %3" 00556 " have tentatively accepted, and %4 have declined.") 00557 .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) ); 00558 } else { 00559 mStatusSummaryLabel->hide(); 00560 mStatusSummaryLabel->setText( "" ); 00561 } 00562 mStatusSummaryLabel->adjustSize(); 00563 } 00564 00565 void KOEditorFreeBusy::triggerReload() 00566 { 00567 mReloadTimer.start( 1000, true ); 00568 } 00569 00570 void KOEditorFreeBusy::cancelReload() 00571 { 00572 mReloadTimer.stop(); 00573 } 00574 00575 void KOEditorFreeBusy::reload() 00576 { 00577 kdDebug() << "KOEditorFreeBusy::reload()" << endl; 00578 00579 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); 00580 while( item ) { 00581 updateFreeBusyData( item->attendee() ); 00582 item = static_cast<FreeBusyItem *>( item->nextSibling() ); 00583 } 00584 } 00585 00586 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i ) 00587 { 00588 FreeBusyItem *item = static_cast<FreeBusyItem *>( i ); 00589 if ( !item ) return; 00590 00591 Attendee *attendee = item->attendee(); 00592 00593 FreeBusyUrlDialog dialog( attendee, this ); 00594 dialog.exec(); 00595 } 00596 00597 #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 Fri Aug 27 12:53:22 2004 by doxygen 1.3.8 written by Dimitri van Heesch, © 1997-2003