korganizer

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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 #include <qvaluevector.h>
00031 #include <qwhatsthis.h>
00032 
00033 #include <kdebug.h>
00034 #include <klocale.h>
00035 #include <kiconloader.h>
00036 #include <kmessagebox.h>
00037 
00038 #ifndef KORG_NOKABC
00039 #include <kabc/addresseedialog.h>
00040 #include <kabc/vcardconverter.h>
00041 #include <libkdepim/addressesdialog.h>
00042 #include <libkdepim/addresseelineedit.h>
00043 #include <libkdepim/distributionlist.h>
00044 #include <kabc/stdaddressbook.h>
00045 #endif
00046 
00047 #include <libkcal/event.h>
00048 #include <libkcal/freebusy.h>
00049 
00050 #include <libemailfunctions/email.h>
00051 
00052 #include <kdgantt/KDGanttView.h>
00053 #include <kdgantt/KDGanttViewTaskItem.h>
00054 #include <kdgantt/KDGanttViewSubwidgets.h>
00055 
00056 #include "koprefs.h"
00057 #include "koglobals.h"
00058 #include "kogroupware.h"
00059 #include "freebusymanager.h"
00060 #include "freebusyurldialog.h"
00061 
00062 #include "koeditorfreebusy.h"
00063 
00064 // The FreeBusyItem is the whole line for a given attendee.
00065 // Individual "busy" periods are created as sub-items of this item.
00066 //
00067 // We can't use the CustomListViewItem base class, since we need a
00068 // different inheritance hierarchy for supporting the Gantt view.
00069 class FreeBusyItem : public KDGanttViewTaskItem
00070 {
00071   public:
00072     FreeBusyItem( Attendee *attendee, KDGanttView *parent ) :
00073       KDGanttViewTaskItem( parent, parent->lastItem() ), mAttendee( attendee ), mTimerID( 0 ),
00074       mIsDownloading( false )
00075     {
00076       Q_ASSERT( attendee );
00077       updateItem();
00078       setFreeBusyPeriods( 0 );
00079       setDisplaySubitemsAsGroup( true );
00080       if ( listView () )
00081           listView ()->setRootIsDecorated( false );
00082     }
00083     ~FreeBusyItem() {}
00084 
00085     void updateItem();
00086 
00087     Attendee *attendee() const { return mAttendee; }
00088     void setFreeBusy( KCal::FreeBusy *fb ) { mFreeBusy = fb; }
00089     KCal::FreeBusy* freeBusy() const { return mFreeBusy; }
00090 
00091     void setFreeBusyPeriods( FreeBusy *fb );
00092 
00093     QString key( int column, bool ) const
00094     {
00095       QMap<int,QString>::ConstIterator it = mKeyMap.find( column );
00096       if ( it == mKeyMap.end() ) return listViewText( column );
00097       else return *it;
00098     }
00099 
00100     void setSortKey( int column, const QString &key )
00101     {
00102       mKeyMap.insert( column, key );
00103     }
00104 
00105     QString email() const { return mAttendee->email(); }
00106     void setUpdateTimerID( int id ) { mTimerID = id; }
00107     int updateTimerID() const { return mTimerID; }
00108 
00109     void startDownload( bool forceDownload ) {
00110       mIsDownloading = true;
00111       FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00112       if ( !m->retrieveFreeBusy( attendee()->email(), forceDownload ) )
00113         mIsDownloading = false;
00114     }
00115     void setIsDownloading( bool d ) { mIsDownloading = d; }
00116     bool isDownloading() const { return mIsDownloading; }
00117 
00118   private:
00119     Attendee *mAttendee;
00120     KCal::FreeBusy *mFreeBusy;
00121 
00122     QMap<int,QString> mKeyMap;
00123 
00124     // This is used for the update timer
00125     int mTimerID;
00126 
00127     // Only run one download job at a time
00128     bool mIsDownloading;
00129 };
00130 
00131 void FreeBusyItem::updateItem()
00132 {
00133   setListViewText( 0, mAttendee->fullName() );
00134   switch ( mAttendee->status() ) {
00135     case Attendee::Accepted:
00136       setPixmap( 0, KOGlobals::self()->smallIcon( "ok" ) );
00137       break;
00138     case Attendee::Declined:
00139       setPixmap( 0, KOGlobals::self()->smallIcon( "no" ) );
00140       break;
00141     case Attendee::NeedsAction:
00142     case Attendee::InProcess:
00143       setPixmap( 0, KOGlobals::self()->smallIcon( "help" ) );
00144       break;
00145     case Attendee::Tentative:
00146       setPixmap( 0, KOGlobals::self()->smallIcon( "apply" ) );
00147       break;
00148     case Attendee::Delegated:
00149       setPixmap( 0, KOGlobals::self()->smallIcon( "mail_forward" ) );
00150       break;
00151     default:
00152       setPixmap( 0, QPixmap() );
00153   }
00154 }
00155 
00156 
00157 // Set the free/busy periods for this attendee
00158 void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
00159 {
00160   if( fb ) {
00161     // Clean out the old entries
00162     for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
00163       delete it;
00164 
00165     // Evaluate free/busy information
00166     QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00167     for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00168      it != busyPeriods.end(); ++it ) {
00169       KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00170       newSubItem->setStartTime( (*it).start() );
00171       newSubItem->setEndTime( (*it).end() );
00172       newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00173       QString toolTip;
00174       if ( !(*it).summary().isEmpty() )
00175         toolTip += "<b>" + (*it).summary() + "</b><br/>";
00176       if ( !(*it).location().isEmpty() )
00177         toolTip += i18n( "Location: %1" ).arg( (*it).location() );
00178       if ( !toolTip.isEmpty() )
00179         newSubItem->setTooltipText( toolTip );
00180     }
00181     setFreeBusy( fb );
00182     setShowNoInformation( false );
00183   } else {
00184       // No free/busy information
00185       //debug only start
00186       //   int ii ;
00187       //       QDateTime cur = QDateTime::currentDateTime();
00188       //       for( ii = 0; ii < 10 ;++ii ) {
00189       //           KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
00190       //           cur = cur.addSecs( 7200 );
00191       //           newSubItem->setStartTime( cur );
00192       //           cur = cur.addSecs( 7200 );
00193       //           newSubItem->setEndTime( cur );
00194       //           newSubItem->setColors( Qt::red, Qt::red, Qt::red );
00195       //       }
00196       //debug only end
00197       setFreeBusy( 0 );
00198       setShowNoInformation( true );
00199   }
00200 
00201   // We are no longer downloading
00202   mIsDownloading = false;
00203 }
00204 
00206 
00207 KOEditorFreeBusy::KOEditorFreeBusy( int spacing, QWidget *parent,
00208                                     const char *name )
00209   : KOAttendeeEditor( parent, name )
00210 {
00211   QVBoxLayout *topLayout = new QVBoxLayout( this );
00212   topLayout->setSpacing( spacing );
00213 
00214   initOrganizerWidgets( this, topLayout );
00215 
00216   // Label for status summary information
00217   // Uses the tooltip palette to highlight it
00218   mIsOrganizer = false; // Will be set later. This is just valgrind silencing
00219   mStatusSummaryLabel = new QLabel( this );
00220   mStatusSummaryLabel->setPalette( QToolTip::palette() );
00221   mStatusSummaryLabel->setFrameStyle( QFrame::Plain | QFrame::Box );
00222   mStatusSummaryLabel->setLineWidth( 1 );
00223   mStatusSummaryLabel->hide(); // Will be unhidden later if you are organizer
00224   topLayout->addWidget( mStatusSummaryLabel );
00225 
00226   // The control panel for the gantt widget
00227   QBoxLayout *controlLayout = new QHBoxLayout( topLayout );
00228 
00229   QString whatsThis = i18n("Sets the zoom level on the Gantt chart. "
00230                "'Hour' shows a range of several hours, "
00231                "'Day' shows a range of a few days, "
00232                "'Week' shows a range of a few months, "
00233                "and 'Month' shows a range of a few years, "
00234                "while 'Automatic' selects the range most "
00235                "appropriate for the current event or to-do.");
00236   QLabel *label = new QLabel( i18n( "Scale: " ), this );
00237   QWhatsThis::add( label, whatsThis );
00238   controlLayout->addWidget( label );
00239 
00240   scaleCombo = new QComboBox( this );
00241   QWhatsThis::add( scaleCombo, whatsThis );
00242   scaleCombo->insertItem( i18n( "Hour" ) );
00243   scaleCombo->insertItem( i18n( "Day" ) );
00244   scaleCombo->insertItem( i18n( "Week" ) );
00245   scaleCombo->insertItem( i18n( "Month" ) );
00246   scaleCombo->insertItem( i18n( "Automatic" ) );
00247   scaleCombo->setCurrentItem( 0 ); // start with "hour"
00248   connect( scaleCombo, SIGNAL( activated( int ) ),
00249            SLOT( slotScaleChanged( int ) ) );
00250   controlLayout->addWidget( scaleCombo );
00251 
00252   QPushButton *button = new QPushButton( i18n( "Center on Start" ), this );
00253   QWhatsThis::add( button,
00254            i18n("Centers the Gantt chart on the start time "
00255                 "and day of this event.") );
00256   connect( button, SIGNAL( clicked() ), SLOT( slotCenterOnStart() ) );
00257   controlLayout->addWidget( button );
00258 
00259   controlLayout->addStretch( 1 );
00260 
00261   button = new QPushButton( i18n( "Pick Date" ), this );
00262   QWhatsThis::add( button,
00263            i18n("Moves the event to a date and time when all the "
00264             "attendees are free.") );
00265   connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
00266   controlLayout->addWidget( button );
00267 
00268   controlLayout->addStretch( 1 );
00269 
00270   button = new QPushButton( i18n("Reload"), this );
00271   QWhatsThis::add( button,
00272            i18n("Reloads Free/Busy data for all attendees from "
00273             "the corresponding servers.") );
00274   controlLayout->addWidget( button );
00275   connect( button, SIGNAL( clicked() ), SLOT( manualReload() ) );
00276 
00277   mGanttView = new KDGanttView( this, "mGanttView" );
00278   QWhatsThis::add( mGanttView,
00279            i18n("Shows the free/busy status of all attendees. "
00280             "Double-clicking on an attendees entry in the "
00281             "list will allow you to enter the location of their "
00282             "Free/Busy Information.") );
00283   topLayout->addWidget( mGanttView );
00284   // Remove the predefined "Task Name" column
00285   mGanttView->removeColumn( 0 );
00286   mGanttView->addColumn( i18n("Attendee") );
00287   if ( KOPrefs::instance()->mCompactDialogs ) {
00288     mGanttView->setFixedHeight( 78 );
00289   }
00290   mGanttView->setHeaderVisible( true );
00291   mGanttView->setScale( KDGanttView::Hour );
00292   mGanttView->setShowHeaderPopupMenu( false, false, false, false, false, false );
00293   // Initially, show 15 days back and forth
00294   // set start to even hours, i.e. to 12:AM 0 Min 0 Sec
00295   QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
00296                            .addDays( -15 ).date() );
00297   QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
00298   mGanttView->setHorizonStart( horizonStart );
00299   mGanttView->setHorizonEnd( horizonEnd );
00300   mGanttView->setCalendarMode( true );
00301   //mGanttView->setDisplaySubitemsAsGroup( true );
00302   mGanttView->setShowLegendButton( false );
00303   // Initially, center to current date
00304   mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
00305   if ( KGlobal::locale()->use12Clock() )
00306     mGanttView->setHourFormat( KDGanttView::Hour_12 );
00307   else
00308     mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
00309 
00310   // mEventRectangle is the colored rectangle representing the event being modified
00311   mEventRectangle = new KDIntervalColorRectangle( mGanttView );
00312   mEventRectangle->setColor( Qt::magenta );
00313   mGanttView->addIntervalBackgroundColor( mEventRectangle );
00314 
00315   connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
00316                                                       const QDateTime & ) ),
00317            mGanttView, SLOT( zoomToSelection( const QDateTime &,
00318                                               const  QDateTime & ) ) );
00319   connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
00320            SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
00321   connect( mGanttView, SIGNAL( intervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ),
00322            this, SLOT( slotIntervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ) );
00323 
00324   connect( mGanttView, SIGNAL(lvSelectionChanged(KDGanttViewItem*)),
00325           this, SLOT(updateAttendeeInput()) );
00326   connect( mGanttView, SIGNAL(lvItemLeftClicked(KDGanttViewItem*)),
00327            this, SLOT(showAttendeeStatusMenu()) );
00328   connect( mGanttView, SIGNAL(lvItemRightClicked(KDGanttViewItem*)),
00329            this, SLOT(showAttendeeStatusMenu()) );
00330   connect( mGanttView, SIGNAL(lvMouseButtonClicked(int, KDGanttViewItem*, const QPoint&, int)),
00331            this, SLOT(listViewClicked(int, KDGanttViewItem*)) );
00332 
00333   FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00334   connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
00335            SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
00336 
00337   connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( autoReload() ) );
00338 
00339   initEditWidgets( this, topLayout );
00340   connect( mRemoveButton, SIGNAL(clicked()),
00341            SLOT(removeAttendee()) );
00342 
00343   slotOrganizerChanged( mOrganizerCombo->currentText() );
00344   connect( mOrganizerCombo, SIGNAL( activated(const QString&) ),
00345            this, SLOT( slotOrganizerChanged(const QString&) ) );
00346 
00347   //suppress the buggy consequences of clicks on the time header widget
00348   mGanttView->timeHeaderWidget()->installEventFilter( this );
00349 }
00350 
00351 KOEditorFreeBusy::~KOEditorFreeBusy()
00352 {
00353 }
00354 
00355 void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
00356 {
00357   FreeBusyItem *anItem =
00358       static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00359   while( anItem ) {
00360     if( anItem->attendee() == attendee ) {
00361       if ( anItem->updateTimerID() != 0 )
00362         killTimer( anItem->updateTimerID() );
00363       delete anItem;
00364       updateStatusSummary();
00365       break;
00366     }
00367     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00368   }
00369 }
00370 
00371 void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
00372 {
00373   FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
00374   if ( readFBList )
00375     updateFreeBusyData( item );
00376   else {
00377     clearSelection();
00378     mGanttView->setSelected( item, true );
00379   }
00380   updateStatusSummary();
00381   emit updateAttendeeSummary( mGanttView->childCount() );
00382 }
00383 
00384 void KOEditorFreeBusy::clearAttendees()
00385 {
00386   mGanttView->clear();
00387 }
00388 
00389 
00390 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
00391 {
00392   mGanttView->setUpdateEnabled( enabled );
00393 }
00394 
00395 bool KOEditorFreeBusy::updateEnabled() const
00396 {
00397   return mGanttView->getUpdateEnabled();
00398 }
00399 
00400 
00401 void KOEditorFreeBusy::readEvent( Event *event )
00402 {
00403   bool block = updateEnabled();
00404   setUpdateEnabled( false );
00405   clearAttendees();
00406 
00407   setDateTimes( event->dtStart(), event->dtEnd() );
00408   mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
00409   updateStatusSummary();
00410   clearSelection();
00411   KOAttendeeEditor::readEvent( event );
00412 
00413   setUpdateEnabled( block );
00414   emit updateAttendeeSummary( mGanttView->childCount() );
00415 }
00416 
00417 void KOEditorFreeBusy::slotIntervalColorRectangleMoved( const QDateTime& start, const QDateTime& end )
00418 {
00419   kdDebug() << k_funcinfo << "slotIntervalColorRectangleMoved " << start << "," << end << endl;
00420   mDtStart = start;
00421   mDtEnd = end;
00422   emit dateTimesChanged( start, end );
00423 }
00424 
00425 void KOEditorFreeBusy::setDateTimes( const QDateTime &start, const QDateTime &end )
00426 {
00427   slotUpdateGanttView( start, end );
00428 }
00429 
00430 void KOEditorFreeBusy::slotScaleChanged( int newScale )
00431 {
00432   // The +1 is for the Minute scale which we don't offer in the combo box.
00433   KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
00434   mGanttView->setScale( scale );
00435   slotCenterOnStart();
00436 }
00437 
00438 void KOEditorFreeBusy::slotCenterOnStart()
00439 {
00440   mGanttView->centerTimeline( mDtStart );
00441 }
00442 
00443 void KOEditorFreeBusy::slotZoomToTime()
00444 {
00445   mGanttView->zoomToFit();
00446 }
00447 
00448 void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
00449 {
00450   if ( item->isDownloading() )
00451     // This item is already in the process of fetching the FB list
00452     return;
00453 
00454   if ( item->updateTimerID() != 0 )
00455     // An update timer is already running. Reset it
00456     killTimer( item->updateTimerID() );
00457 
00458   // This item does not have a download running, and no timer is set
00459   // Do the download in five seconds
00460   item->setUpdateTimerID( startTimer( 5000 ) );
00461 }
00462 
00463 void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
00464 {
00465   killTimer( event->timerId() );
00466   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00467   while( item ) {
00468     if( item->updateTimerID() == event->timerId() ) {
00469       item->setUpdateTimerID( 0 );
00470       item->startDownload( mForceDownload );
00471       return;
00472     }
00473     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00474   }
00475 }
00476 
00477 // Set the Free Busy list for everyone having this email address
00478 // If fb == 0, this disabled the free busy list for them
00479 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
00480                                            const QString &email )
00481 {
00482   kdDebug(5850) << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
00483 
00484   if( fb )
00485     fb->sortList();
00486   bool block = mGanttView->getUpdateEnabled();
00487   mGanttView->setUpdateEnabled( false );
00488   for( KDGanttViewItem *it = mGanttView->firstChild(); it;
00489        it = it->nextSibling() ) {
00490     FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
00491     if( item->email() == email )
00492       item->setFreeBusyPeriods( fb );
00493   }
00494   mGanttView->setUpdateEnabled( block );
00495 }
00496 
00497 
00502 void KOEditorFreeBusy::slotUpdateGanttView( const QDateTime &dtFrom, const QDateTime &dtTo )
00503 {
00504   mDtStart = dtFrom;
00505   mDtEnd = dtTo;
00506   bool block = mGanttView->getUpdateEnabled( );
00507   mGanttView->setUpdateEnabled( false );
00508   QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
00509   mGanttView->setHorizonStart( horizonStart  );
00510   mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
00511   mEventRectangle->setDateTimes( dtFrom, dtTo );
00512   mGanttView->setUpdateEnabled( block );
00513   mGanttView->centerTimelineAfterShow( dtFrom );
00514 }
00515 
00516 
00520 void KOEditorFreeBusy::slotPickDate()
00521 {
00522   QDateTime start = mDtStart;
00523   QDateTime end = mDtEnd;
00524   bool success = findFreeSlot( start, end );
00525 
00526   if( success ) {
00527     if ( start == mDtStart && end == mDtEnd ) {
00528       KMessageBox::information( this,
00529           i18n( "The meeting already has suitable start/end times." ), QString::null,
00530           "MeetingTimeOKFreeBusy" );
00531     } else {
00532       emit dateTimesChanged( start, end );
00533       slotUpdateGanttView( start, end );
00534       KMessageBox::information( this,
00535           i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
00536           .arg( start.toString() ).arg( end.toString() ), QString::null,
00537           "MeetingMovedFreeBusy" );
00538     }
00539   } else
00540     KMessageBox::sorry( this, i18n( "No suitable date found." ) );
00541 }
00542 
00543 
00548 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
00549 {
00550   if( tryDate( dtFrom, dtTo ) )
00551     // Current time is acceptable
00552     return true;
00553 
00554   QDateTime tryFrom = dtFrom;
00555   QDateTime tryTo = dtTo;
00556 
00557   // Make sure that we never suggest a date in the past, even if the
00558   // user originally scheduled the meeting to be in the past.
00559   if( tryFrom < QDateTime::currentDateTime() ) {
00560     // The slot to look for is at least partially in the past.
00561     int secs = tryFrom.secsTo( tryTo );
00562     tryFrom = QDateTime::currentDateTime();
00563     tryTo = tryFrom.addSecs( secs );
00564   }
00565 
00566   bool found = false;
00567   while( !found ) {
00568     found = tryDate( tryFrom, tryTo );
00569     // PENDING(kalle) Make the interval configurable
00570     if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00571       break; // don't look more than one year in the future
00572   }
00573 
00574   dtFrom = tryFrom;
00575   dtTo = tryTo;
00576 
00577   return found;
00578 }
00579 
00580 
00589 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
00590 {
00591   FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
00592   while( currentItem ) {
00593     if( !tryDate( currentItem, tryFrom, tryTo ) ) {
00594       // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl;
00595       return false;
00596     }
00597 
00598     currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
00599   }
00600 
00601   return true;
00602 }
00603 
00611 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
00612                                 QDateTime &tryFrom, QDateTime &tryTo )
00613 {
00614   // If we don't have any free/busy information, assume the
00615   // participant is free. Otherwise a participant without available
00616   // information would block the whole allocation.
00617   KCal::FreeBusy *fb = attendee->freeBusy();
00618   if( !fb )
00619     return true;
00620 
00621   QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00622   for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00623        it != busyPeriods.end(); ++it ) {
00624     if( (*it).end() <= tryFrom || // busy period ends before try period
00625     (*it).start() >= tryTo )  // busy period starts after try period
00626       continue;
00627     else {
00628       // the current busy period blocks the try period, try
00629       // after the end of the current busy period
00630       int secsDuration = tryFrom.secsTo( tryTo );
00631       tryFrom = (*it).end();
00632       tryTo = tryFrom.addSecs( secsDuration );
00633       // try again with the new try period
00634       tryDate( attendee, tryFrom, tryTo );
00635       // we had to change the date at least once
00636       return false;
00637     }
00638   }
00639 
00640   return true;
00641 }
00642 
00643 void KOEditorFreeBusy::updateStatusSummary()
00644 {
00645   FreeBusyItem *aItem =
00646     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00647   int total = 0;
00648   int accepted = 0;
00649   int tentative = 0;
00650   int declined = 0;
00651   while( aItem ) {
00652     ++total;
00653     switch( aItem->attendee()->status() ) {
00654     case Attendee::Accepted:
00655       ++accepted;
00656       break;
00657     case Attendee::Tentative:
00658       ++tentative;
00659       break;
00660     case Attendee::Declined:
00661       ++declined;
00662       break;
00663     case Attendee::NeedsAction:
00664     case Attendee::Delegated:
00665     case Attendee::Completed:
00666     case Attendee::InProcess:
00667     case Attendee::None:
00668       /* just to shut up the compiler */
00669       break;
00670     }
00671     aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00672   }
00673   if( total > 1 && mIsOrganizer ) {
00674     mStatusSummaryLabel->show();
00675     mStatusSummaryLabel->setText(
00676         i18n( "Of the %1 participants, %2 have accepted, %3"
00677               " have tentatively accepted, and %4 have declined.")
00678         .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00679   } else {
00680     mStatusSummaryLabel->hide();
00681   }
00682   mStatusSummaryLabel->adjustSize();
00683 }
00684 
00685 void KOEditorFreeBusy::triggerReload()
00686 {
00687   mReloadTimer.start( 1000, true );
00688 }
00689 
00690 void KOEditorFreeBusy::cancelReload()
00691 {
00692   mReloadTimer.stop();
00693 }
00694 
00695 void KOEditorFreeBusy::manualReload()
00696 {
00697   mForceDownload = true;
00698   reload();
00699 }
00700 
00701 void KOEditorFreeBusy::autoReload()
00702 {
00703   mForceDownload = false;
00704   reload();
00705 }
00706 
00707 void KOEditorFreeBusy::reload()
00708 {
00709   kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
00710 
00711   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00712   while( item ) {
00713     if (  mForceDownload )
00714       item->startDownload( mForceDownload );
00715     else
00716       updateFreeBusyData( item );
00717 
00718     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00719   }
00720 }
00721 
00722 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00723 {
00724   FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00725   if ( !item ) return;
00726 
00727   Attendee *attendee = item->attendee();
00728 
00729   FreeBusyUrlDialog dialog( attendee, this );
00730   dialog.exec();
00731 }
00732 
00733 void KOEditorFreeBusy::writeEvent(KCal::Event * event)
00734 {
00735   event->clearAttendees();
00736   QValueVector<FreeBusyItem*> toBeDeleted;
00737   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00738         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00739   {
00740     Attendee *attendee = item->attendee();
00741     Q_ASSERT( attendee );
00742     /* Check if the attendee is a distribution list and expand it */
00743     if ( attendee->email().isEmpty() ) {
00744       KPIM::DistributionList list =
00745         KPIM::DistributionList::findByName( KABC::StdAddressBook::self(), attendee->name() );
00746       if ( !list.isEmpty() ) {
00747         toBeDeleted.push_back( item ); // remove it once we are done expanding
00748         KPIM::DistributionList::Entry::List entries = list.entries( KABC::StdAddressBook::self() );
00749         KPIM::DistributionList::Entry::List::Iterator it( entries.begin() );
00750         while ( it != entries.end() ) {
00751           KPIM::DistributionList::Entry &e = ( *it );
00752           ++it;
00753           // this calls insertAttendee, which appends
00754           insertAttendeeFromAddressee( e.addressee, attendee );
00755           // TODO: duplicate check, in case it was already added manually
00756         }
00757       }
00758     } else {
00759       bool skip = false;
00760       if ( attendee->email().endsWith( "example.net" ) ) {
00761         if ( KMessageBox::warningYesNo( this, i18n("%1 does not look like a valid email address. "
00762                 "Are you sure you want to invite this participant?").arg( attendee->email() ),
00763               i18n("Invalid email address") ) != KMessageBox::Yes ) {
00764           skip = true;
00765         }
00766       }
00767       if ( !skip ) {
00768         event->addAttendee( new Attendee( *attendee ) );
00769       }
00770     }
00771   }
00772 
00773   KOAttendeeEditor::writeEvent( event );
00774 
00775   // cleanup
00776   QValueVector<FreeBusyItem*>::iterator it;
00777   for( it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it ) {
00778     delete *it;
00779   }
00780 }
00781 
00782 KCal::Attendee * KOEditorFreeBusy::currentAttendee() const
00783 {
00784   KDGanttViewItem *item = mGanttView->selectedItem();
00785   FreeBusyItem *aItem = static_cast<FreeBusyItem*>( item );
00786   if ( !aItem )
00787     return 0;
00788   return aItem->attendee();
00789 }
00790 
00791 void KOEditorFreeBusy::updateCurrentItem()
00792 {
00793   FreeBusyItem* item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00794   if ( item ) {
00795     item->updateItem();
00796     updateFreeBusyData( item );
00797     updateStatusSummary();
00798   }
00799 }
00800 
00801 void KOEditorFreeBusy::removeAttendee()
00802 {
00803   FreeBusyItem *item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00804   if ( !item )
00805     return;
00806 
00807   FreeBusyItem *nextSelectedItem = static_cast<FreeBusyItem*>( item->nextSibling() );
00808   if( mGanttView->childCount() == 1 )
00809       nextSelectedItem = 0;
00810   if( mGanttView->childCount() > 1 && item == mGanttView->lastItem() )
00811       nextSelectedItem = static_cast<FreeBusyItem*>(  mGanttView->firstChild() );
00812 
00813   Attendee *delA = new Attendee( item->attendee()->name(), item->attendee()->email(),
00814                                  item->attendee()->RSVP(), item->attendee()->status(),
00815                                  item->attendee()->role(), item->attendee()->uid() );
00816   mdelAttendees.append( delA );
00817   delete item;
00818 
00819   updateStatusSummary();
00820   if( nextSelectedItem )
00821       mGanttView->setSelected( nextSelectedItem, true );
00822   updateAttendeeInput();
00823   emit updateAttendeeSummary( mGanttView->childCount() );
00824 }
00825 
00826 void KOEditorFreeBusy::clearSelection() const
00827 {
00828   KDGanttViewItem *item = mGanttView->selectedItem();
00829   if ( item )
00830     mGanttView->setSelected( item, false );
00831   mGanttView->repaint();
00832   item->repaint();
00833 }
00834 
00835 void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
00836 {
00837   const QStringList myEmails = KOPrefs::instance()->allEmails();
00838   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00839         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00840   {
00841     for ( QStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
00842       if ( item->attendee()->email() == *it2 ) {
00843         item->attendee()->setStatus( status );
00844         item->updateItem();
00845       }
00846     }
00847   }
00848 }
00849 
00850 void KOEditorFreeBusy::showAttendeeStatusMenu()
00851 {
00852   if ( mGanttView->mapFromGlobal( QCursor::pos() ).x() > 22 )
00853     return;
00854   QPopupMenu popup;
00855   popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
00856   popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
00857   popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
00858   popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
00859   popup.insertItem( KOGlobals::self()->smallIcon( "mail_forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
00860   popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
00861   popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
00862   popup.setItemChecked( currentAttendee()->status(), true );
00863   int status = popup.exec( QCursor::pos() );
00864   if ( status >= 0 ) {
00865     currentAttendee()->setStatus( (Attendee::PartStat)status );
00866     updateCurrentItem();
00867     updateAttendeeInput();
00868   }
00869 }
00870 
00871 void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
00872 {
00873   if ( button == Qt::LeftButton && item == 0 )
00874     addNewAttendee();
00875 }
00876 
00877 void KOEditorFreeBusy::slotOrganizerChanged(const QString & newOrganizer)
00878 {
00879   if (newOrganizer==mCurrentOrganizer) return;
00880 
00881   QString name;
00882   QString email;
00883   bool success = KPIM::getNameAndMail( newOrganizer, name, email );
00884 
00885   if (!success) return;
00886 //
00887 
00888   Attendee *currentOrganizerAttendee = 0;
00889   Attendee *newOrganizerAttendee = 0;
00890 
00891   FreeBusyItem *anItem =
00892     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00893   while( anItem ) {
00894     Attendee *attendee = anItem->attendee();
00895     if( attendee->fullName() == mCurrentOrganizer )
00896       currentOrganizerAttendee = attendee;
00897 
00898     if( attendee->fullName() == newOrganizer )
00899       newOrganizerAttendee = attendee;
00900 
00901     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00902   }
00903 
00904   int answer = KMessageBox::No;
00905 
00906   if (currentOrganizerAttendee) {
00907     answer = KMessageBox::questionYesNo( this, i18n("You are changing the organiser of "
00908                                                     "this event, who is also attending, "
00909                                                     "do you want to change that attendee "
00910                                                     "as well?") );
00911   } else {
00912     answer = KMessageBox::Yes;
00913   }
00914 
00915   if (answer==KMessageBox::Yes) {
00916     if (currentOrganizerAttendee) {
00917       removeAttendee( currentOrganizerAttendee );
00918     }
00919 
00920     if (!newOrganizerAttendee) {
00921       Attendee *a = new Attendee( name, email, true );
00922       insertAttendee( a, false );
00923       updateAttendee();
00924     }
00925   }
00926 
00927   mCurrentOrganizer = newOrganizer;
00928 }
00929 
00930 bool KOEditorFreeBusy::eventFilter( QObject *watched, QEvent *event )
00931 {
00932   if ( watched == mGanttView->timeHeaderWidget() &&
00933        event->type() >= QEvent::MouseButtonPress && event->type() <= QEvent::MouseMove ) {
00934     return true;
00935   } else {
00936     return KOAttendeeEditor::eventFilter( watched, event );
00937   }
00938 }
00939 
00940 QListViewItem* KOEditorFreeBusy::hasExampleAttendee() const
00941 {
00942   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00943         item = static_cast<FreeBusyItem*>( item->nextSibling() ) ) {
00944     Attendee *attendee = item->attendee();
00945     Q_ASSERT( attendee );
00946     if ( isExampleAttendee( attendee ) )
00947         return item;
00948   }
00949   return 0;
00950 }
00951 
00952 #include "koeditorfreebusy.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys