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