korganizer Library API Documentation

koeditorgantt.cpp

00001 /* 00002 This file is part of KOrganizer. 00003 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> 00004 00005 This program is free software; you can redistribute it and/or modify 00006 it under the terms of the GNU General Public License as published by 00007 the Free Software Foundation; either version 2 of the License, or 00008 (at your option) any later version. 00009 00010 This program is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00013 GNU General Public License for more details. 00014 00015 You should have received a copy of the GNU General Public License 00016 along with this program; if not, write to the Free Software 00017 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 00018 00019 As a special exception, permission is given to link this program 00020 with any edition of Qt, and distribute the resulting executable, 00021 without including the source code for Qt in the source distribution. 00022 */ 00023 00024 #include <qtooltip.h> 00025 #include <qlayout.h> 00026 #include <qlabel.h> 00027 #include <qcombobox.h> 00028 #include <qpushbutton.h> 00029 00030 #include <kdebug.h> 00031 #include <klocale.h> 00032 #include <kiconloader.h> 00033 #include <kmessagebox.h> 00034 00035 #include <libkcal/event.h> 00036 #include <libkcal/freebusy.h> 00037 00038 #include <kdgantt/KDGanttView.h> 00039 #include <kdgantt/KDGanttViewTaskItem.h> 00040 00041 #include "koprefs.h" 00042 #include "koglobals.h" 00043 #include "kogroupware.h" 00044 00045 #include "koeditorgantt.h" 00046 00047 00048 // We can't use the CustomListViewItem base class, since we need a 00049 // different inheritance hierarchy for supporting the Gantt view. 00050 class GanttItem : public KDGanttViewTaskItem 00051 { 00052 public: 00053 GanttItem( Attendee* data, KDGanttView *parent ) : 00054 KDGanttViewTaskItem( parent ), mData( data ) 00055 { 00056 Q_ASSERT( data ); 00057 updateItem(); 00058 setFreeBusyPeriods( 0 ); 00059 } 00060 ~GanttItem(); 00061 00062 void updateItem(); 00063 00064 Attendee* data() const { return mData; } 00065 void setFreeBusy( KCal::FreeBusy* fb ) { mFreeBusy = fb; } 00066 KCal::FreeBusy* freeBusy() const { return mFreeBusy; } 00067 00068 void setFreeBusyPeriods( FreeBusy* fb ); 00069 00070 QString key(int column, bool) const 00071 { 00072 QMap<int,QString>::ConstIterator it = mKeyMap.find(column); 00073 if (it == mKeyMap.end()) return listViewText(column); 00074 else return *it; 00075 } 00076 00077 void setSortKey(int column,const QString &key) 00078 { 00079 mKeyMap.insert(column,key); 00080 } 00081 00082 QString email() const { return mData->email(); } 00083 00084 private: 00085 Attendee* mData; 00086 KCal::FreeBusy* mFreeBusy; 00087 00088 QMap<int,QString> mKeyMap; 00089 }; 00090 00091 00092 00093 GanttItem::~GanttItem() 00094 { 00095 } 00096 00097 void GanttItem::updateItem() 00098 { 00099 setListViewText(0,mData->name()); 00100 setListViewText(1,mData->email()); 00101 setListViewText(2,mData->roleStr()); 00102 setListViewText(3,mData->statusStr()); 00103 if (mData->RSVP() && !mData->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 GanttItem::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 setFreeBusy( 0 ); 00132 setShowNoInformation( true ); 00133 } 00134 } 00135 00136 00137 KOEditorGantt::KOEditorGantt( int spacing, QWidget* parent, const char* name ) 00138 : QWidget( parent, name ) 00139 { 00140 QVBoxLayout* topLayout = new QVBoxLayout( this ); 00141 topLayout->setSpacing( spacing ); 00142 00143 QString organizer = KOPrefs::instance()->email(); 00144 mOrganizerLabel = new QLabel( i18n("Organizer: %1").arg( organizer ), this ); 00145 mIsOrganizer = true; // Will be set later. This is just valgrind silencing 00146 topLayout->addWidget( mOrganizerLabel ); 00147 00148 // Label for status summary information 00149 // Uses the tooltip palette to highlight it 00150 mStatusSummaryLabel = new QLabel( this ); 00151 mStatusSummaryLabel->setPalette( QToolTip::palette() ); 00152 mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box ); 00153 mStatusSummaryLabel->setLineWidth( 1 ); 00154 topLayout->addWidget( mStatusSummaryLabel ); 00155 00156 // The control panel for the gantt widget 00157 QHBox* scaleHB = new QHBox( this ); 00158 topLayout->addWidget( scaleHB ); 00159 QLabel* scaleLA = new QLabel( i18n( "Scale: " ), scaleHB ); 00160 scaleLA->setAlignment( AlignRight | AlignVCenter ); 00161 scaleCombo = new QComboBox( scaleHB ); 00162 scaleCombo->insertItem( i18n( "Hour" ) ); 00163 scaleCombo->insertItem( i18n( "Day" ) ); 00164 scaleCombo->insertItem( i18n( "Week" ) ); 00165 scaleCombo->insertItem( i18n( "Month" ) ); 00166 scaleCombo->insertItem( i18n( "Automatic" ) ); 00167 scaleCombo->setCurrentItem( 0 ); // start with "hour" 00168 QLabel* dummy = new QLabel( scaleHB ); 00169 scaleHB->setStretchFactor( dummy, 2 ); 00170 QLabel* hFormatLA = new QLabel( i18n( "Hour format:" ), scaleHB ); 00171 hFormatLA->setAlignment( AlignRight | AlignVCenter ); 00172 dummy = new QLabel( scaleHB ); 00173 scaleHB->setStretchFactor( dummy, 2 ); 00174 QPushButton* centerPB = new QPushButton( i18n( "Center on Start" ), scaleHB ); 00175 connect( centerPB, SIGNAL( clicked() ), this, SLOT( slotCenterOnStart() ) ); 00176 dummy = new QLabel( scaleHB ); 00177 scaleHB->setStretchFactor( dummy, 2 ); 00178 QPushButton* zoomPB = new QPushButton( i18n( "Zoom to Fit" ), scaleHB ); 00179 connect( zoomPB, SIGNAL( clicked() ), this, SLOT( slotZoomToTime() ) ); 00180 dummy = new QLabel( scaleHB ); 00181 scaleHB->setStretchFactor( dummy, 2 ); 00182 QPushButton* pickPB = new QPushButton( i18n( "Pick Date" ), scaleHB ); 00183 connect( pickPB, SIGNAL( clicked() ), this, SLOT( slotPickDate() ) ); 00184 connect( scaleCombo, SIGNAL( activated( int ) ), 00185 this, SLOT( slotScaleChanged( int ) ) ); 00186 00187 mGanttView = new KDGanttView(this,"mGanttView"); 00188 topLayout->addWidget( mGanttView ); 00189 // Remove the predefined "Task Name" column 00190 mGanttView->removeColumn( 0 ); 00191 mGanttView->addColumn(i18n("Name"),180); 00192 mGanttView->addColumn(i18n("Email"),180); 00193 mGanttView->addColumn(i18n("Role"),60); 00194 mGanttView->addColumn(i18n("Status"),100); 00195 mGanttView->addColumn(i18n("RSVP"),35); 00196 if ( KOPrefs::instance()->mCompactDialogs ) { 00197 mGanttView->setFixedHeight(78); 00198 } 00199 mGanttView->setHeaderVisible( true ); 00200 mGanttView->setScale( KDGanttView::Hour ); 00201 #if 0 00202 // TODO: Do something about this ugly kroupware_branch hack 00203 if ( !strcmp(QTextCodec::codecForLocale()->locale(),"de_DE@euro") ) { 00204 mGanttView->setHourFormat( KDGanttView::Hour_24 ); 00205 hFormatCombo->setCurrentItem( 0 ); 00206 } 00207 #endif 00208 // Initially, show 15 days back and forth 00209 // set start to even hours, i.e. to 12:AM 0 Min 0 Sec 00210 QDateTime horizonStart = QDateTime( QDateTime::currentDateTime().addDays( -15 ).date() ); 00211 QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 ); 00212 mGanttView->setHorizonStart( horizonStart ); 00213 mGanttView->setHorizonEnd( horizonEnd ); 00214 mGanttView->setCalendarMode( true ); 00215 mGanttView->setDisplaySubitemsAsGroup( true ); 00216 mGanttView->setShowLegendButton( false ); 00217 // Initially, center to current date 00218 mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() ); 00219 if ( KGlobal::locale()->use12Clock() ) 00220 mGanttView->setHourFormat( KDGanttView::Hour_12 ); 00221 else 00222 mGanttView->setHourFormat( KDGanttView::Hour_24 ); 00223 00224 connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem* ) ), 00225 SLOT( updateFreeBusyData() ) ); 00226 } 00227 00228 KOEditorGantt::~KOEditorGantt() 00229 { 00230 } 00231 00232 void KOEditorGantt::removeAttendee( Attendee* attendee ) 00233 { 00234 GanttItem *anItem = 00235 static_cast<GanttItem*>( mGanttView->firstChild() ); 00236 while( anItem ) { 00237 if( anItem->data() == attendee ) { 00238 delete anItem; 00239 updateStatusSummary(); 00240 break; 00241 } 00242 anItem = static_cast<GanttItem*>( anItem->nextSibling() ); 00243 } 00244 } 00245 00246 void KOEditorGantt::insertAttendee( Attendee* attendee ) 00247 { 00248 (void)new GanttItem( attendee, mGanttView ); 00249 updateFreeBusyData( attendee ); 00250 updateStatusSummary(); 00251 } 00252 00253 void KOEditorGantt::updateAttendee( Attendee* attendee ) 00254 { 00255 GanttItem *anItem = 00256 static_cast<GanttItem*>( mGanttView->firstChild() ); 00257 while( anItem ) { 00258 if( anItem->data() == attendee ) { 00259 anItem->updateItem(); 00260 updateFreeBusyData( attendee ); 00261 updateStatusSummary(); 00262 break; 00263 } 00264 anItem = static_cast<GanttItem*>( anItem->nextSibling() ); 00265 } 00266 } 00267 00268 void KOEditorGantt::clearAttendees() 00269 { 00270 mGanttView->clear(); 00271 } 00272 00273 00274 void KOEditorGantt::setUpdateEnabled( bool enabled ) 00275 { 00276 mGanttView->setUpdateEnabled( enabled ); 00277 } 00278 00279 bool KOEditorGantt::updateEnabled() const 00280 { 00281 return mGanttView->getUpdateEnabled(); 00282 } 00283 00284 00285 void KOEditorGantt::readEvent( Event* event ) 00286 { 00287 setDateTimes( event->dtStart(), event->dtEnd() ); 00288 } 00289 00290 00291 void KOEditorGantt::setDateTimes( QDateTime start, QDateTime end ) 00292 { 00293 mDtStart = start; 00294 mDtEnd = end; 00295 00296 mGanttView->centerTimelineAfterShow( start ); 00297 mGanttView->clearBackgroundColor(); 00298 mGanttView->setIntervalBackgroundColor( start, end, Qt::magenta ); 00299 } 00300 00301 void KOEditorGantt::slotScaleChanged( int newScale ) 00302 { 00303 // The +1 is for the Minute scale which we don't offer in the combo box. 00304 KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 ); 00305 mGanttView->setScale( scale ); 00306 slotCenterOnStart(); 00307 } 00308 00309 void KOEditorGantt::slotCenterOnStart() 00310 { 00311 mGanttView->centerTimeline( mDtStart ); 00312 } 00313 00314 void KOEditorGantt::slotZoomToTime() 00315 { 00316 bool block = mGanttView->getUpdateEnabled(); 00317 mGanttView->setUpdateEnabled( false ); 00318 if ( scaleCombo->currentItem() != 4 ) { 00319 scaleCombo->setCurrentItem( 4 );// auto 00320 slotScaleChanged( 4 );// auto 00321 } 00322 mGanttView->setUpdateEnabled( block ); 00323 mGanttView->zoomToSelection( mDtStart, mDtEnd ); 00324 } 00325 00326 00332 void KOEditorGantt::updateFreeBusyData( Attendee* attendee ) 00333 { 00334 if( KOGroupware::instance() && attendee->name() != "(EmptyName)" ) { 00335 if( attendee->email() == KOPrefs::instance()->email() ) { 00336 // Don't download our own free-busy list from the net 00337 QCString fbText = KOGroupware::instance()->getFreeBusyString().utf8(); 00338 slotInsertFreeBusy( attendee->email(), 00339 KOGroupware::instance()->parseFreeBusy( fbText ) ); 00340 } else 00341 KOGroupware::instance()->downloadFreeBusyData( attendee->email(), this, 00342 SLOT( slotInsertFreeBusy( const QString&, FreeBusy* ) ) ); 00343 } 00344 } 00345 00346 // Set the Free Busy list for everyone having this email address 00347 // If fb == 0, this disabled the free busy list for them 00348 void KOEditorGantt::slotInsertFreeBusy( const QString& email, FreeBusy* fb ) 00349 { 00350 if( fb ) 00351 fb->sortList(); 00352 bool block = mGanttView->getUpdateEnabled(); 00353 mGanttView->setUpdateEnabled(false); 00354 for( KDGanttViewItem* it = mGanttView->firstChild(); it; 00355 it = it->nextSibling() ) { 00356 GanttItem* item = static_cast<GanttItem*>( it ); 00357 if( item->email() == email ) 00358 item->setFreeBusyPeriods( fb ); 00359 } 00360 mGanttView->setUpdateEnabled(block); 00361 } 00362 00363 00368 void KOEditorGantt::slotUpdateGanttView( QDateTime dtFrom, QDateTime dtTo ) 00369 { 00370 bool block = mGanttView->getUpdateEnabled( ); 00371 mGanttView->setUpdateEnabled( false ); 00372 QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() ); 00373 mGanttView->setHorizonStart( horizonStart ); 00374 mGanttView->setHorizonEnd( dtTo.addDays( 15 ) ); 00375 mGanttView->clearBackgroundColor(); 00376 mGanttView->setIntervalBackgroundColor( dtFrom, dtTo, Qt::magenta ); 00377 mGanttView->setUpdateEnabled( block ); 00378 mGanttView->centerTimelineAfterShow( dtFrom ); 00379 } 00380 00381 00385 void KOEditorGantt::slotPickDate() 00386 { 00387 QDateTime start = mDtStart; 00388 QDateTime end = mDtEnd; 00389 bool success = findFreeSlot( start, end ); 00390 00391 if( success ) { 00392 if ( start == mDtStart && end == mDtEnd ) { 00393 KMessageBox::information( this, i18n( "The meeting has already suitable start/end times." )); 00394 } else { 00395 emit dateTimesChanged( start, end ); 00396 slotUpdateGanttView( start, end ); 00397 KMessageBox::information( this, i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." ).arg( start.toString() ).arg( end.toString() ) ); 00398 } 00399 } else 00400 KMessageBox::sorry( this, i18n( "No suitable date found." ) ); 00401 } 00402 00403 00408 bool KOEditorGantt::findFreeSlot( QDateTime& dtFrom, QDateTime& dtTo ) 00409 { 00410 if( tryDate( dtFrom, dtTo ) ) 00411 // Current time is acceptable 00412 return true; 00413 00414 QDateTime tryFrom = dtFrom; 00415 QDateTime tryTo = dtTo; 00416 00417 // Make sure that we never suggest a date in the past, even if the 00418 // user originally scheduled the meeting to be in the past. 00419 if( tryFrom < QDateTime::currentDateTime() ) { 00420 // The slot to look for is at least partially in the past. 00421 int secs = tryFrom.secsTo( tryTo ); 00422 tryFrom = QDateTime::currentDateTime(); 00423 tryTo = tryFrom.addSecs( secs ); 00424 } 00425 00426 bool found = false; 00427 while( !found ) { 00428 found = tryDate( tryFrom, tryTo ); 00429 // PENDING(kalle) Make the interval configurable 00430 if( !found && dtFrom.daysTo( tryFrom ) > 365 ) 00431 break; // don't look more than one year in the future 00432 } 00433 00434 dtFrom = tryFrom; 00435 dtTo = tryTo; 00436 00437 return found; 00438 } 00439 00440 00449 bool KOEditorGantt::tryDate( QDateTime& tryFrom, QDateTime& tryTo ) 00450 { 00451 GanttItem* currentItem = static_cast<GanttItem*>( mGanttView->firstChild() ); 00452 while( currentItem ) { 00453 if( !tryDate( currentItem, tryFrom, tryTo ) ) { 00454 // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl; 00455 return false; 00456 } 00457 00458 currentItem = static_cast<GanttItem*>( currentItem->nextSibling() ); 00459 } 00460 00461 return true; 00462 } 00463 00471 bool KOEditorGantt::tryDate( GanttItem* attendee, 00472 QDateTime& tryFrom, QDateTime& tryTo ) 00473 { 00474 // If we don't have any free/busy information, assume the 00475 // participant is free. Otherwise a participant without available 00476 // information would block the whole allocation. 00477 KCal::FreeBusy* fb = attendee->freeBusy(); 00478 if( !fb ) 00479 return true; 00480 00481 QValueList<KCal::Period> busyPeriods = fb->busyPeriods(); 00482 for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin(); 00483 it != busyPeriods.end(); ++it ) { 00484 if( (*it).end() <= tryFrom || // busy period ends before try period 00485 (*it).start() >= tryTo ) // busy period starts after try period 00486 continue; 00487 else { 00488 // the current busy period blocks the try period, try 00489 // after the end of the current busy period 00490 int secsDuration = tryFrom.secsTo( tryTo ); 00491 tryFrom = (*it).end(); 00492 tryTo = tryFrom.addSecs( secsDuration ); 00493 // try again with the new try period 00494 tryDate( attendee, tryFrom, tryTo ); 00495 // we had to change the date at least once 00496 return false; 00497 } 00498 } 00499 00500 return true; 00501 } 00502 00503 void KOEditorGantt::updateStatusSummary() 00504 { 00505 GanttItem *aItem = 00506 static_cast<GanttItem*>(mGanttView->firstChild()); 00507 int total = 0; 00508 int accepted = 0; 00509 int tentative = 0; 00510 int declined = 0; 00511 while( aItem ) { 00512 ++total; 00513 switch( aItem->data()->status() ) { 00514 case Attendee::Accepted: 00515 ++accepted; 00516 break; 00517 case Attendee::Tentative: 00518 ++tentative; 00519 break; 00520 case Attendee::Declined: 00521 ++declined; 00522 break; 00523 case Attendee::NeedsAction: 00524 case Attendee::Delegated: 00525 case Attendee::Completed: 00526 case Attendee::InProcess: 00527 /* just to shut up the compiler */ 00528 break; 00529 } 00530 aItem = static_cast<GanttItem*>(aItem->nextSibling()); 00531 } 00532 if( total > 1 && mIsOrganizer ) { 00533 mStatusSummaryLabel->show(); 00534 mStatusSummaryLabel->setText( i18n( "Of the %1 participants, %2 have accepted, %3" 00535 " have tentatively accepted, and %4 have declined.") 00536 .arg(total).arg(accepted).arg(tentative).arg(declined)); 00537 } else { 00538 mStatusSummaryLabel->hide(); 00539 mStatusSummaryLabel->setText(""); 00540 } 00541 mStatusSummaryLabel->adjustSize(); 00542 } 00543 00544 #include "koeditorgantt.moc"
KDE Logo
This file is part of the documentation for korganizer Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Wed Jul 28 23:58:13 2004 by doxygen 1.3.7 written by Dimitri van Heesch, © 1997-2003