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 Sat May 1 11:38:29 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003