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       /* just to shut up the compiler */
00668       break;
00669     }
00670     aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00671   }
00672   if( total > 1 && mIsOrganizer ) {
00673     mStatusSummaryLabel->show();
00674     mStatusSummaryLabel->setText(
00675         i18n( "Of the %1 participants, %2 have accepted, %3"
00676               " have tentatively accepted, and %4 have declined.")
00677         .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00678   } else {
00679     mStatusSummaryLabel->hide();
00680   }
00681   mStatusSummaryLabel->adjustSize();
00682 }
00683 
00684 void KOEditorFreeBusy::triggerReload()
00685 {
00686   mReloadTimer.start( 1000, true );
00687 }
00688 
00689 void KOEditorFreeBusy::cancelReload()
00690 {
00691   mReloadTimer.stop();
00692 }
00693 
00694 void KOEditorFreeBusy::manualReload()
00695 {
00696   mForceDownload = true;
00697   reload();
00698 }
00699 
00700 void KOEditorFreeBusy::autoReload()
00701 {
00702   mForceDownload = false;
00703   reload();
00704 }
00705 
00706 void KOEditorFreeBusy::reload()
00707 {
00708   kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
00709 
00710   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00711   while( item ) {
00712     if (  mForceDownload )
00713       item->startDownload( mForceDownload );
00714     else
00715       updateFreeBusyData( item );
00716 
00717     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00718   }
00719 }
00720 
00721 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00722 {
00723   FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00724   if ( !item ) return;
00725 
00726   Attendee *attendee = item->attendee();
00727 
00728   FreeBusyUrlDialog dialog( attendee, this );
00729   dialog.exec();
00730 }
00731 
00732 void KOEditorFreeBusy::writeEvent(KCal::Event * event)
00733 {
00734   event->clearAttendees();
00735   QValueVector<FreeBusyItem*> toBeDeleted;
00736   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00737         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00738   {
00739     Attendee *attendee = item->attendee();
00740     Q_ASSERT( attendee );
00741     /* Check if the attendee is a distribution list and expand it */
00742     if ( attendee->email().isEmpty() ) {
00743       KPIM::DistributionList list =
00744         KPIM::DistributionList::findByName( KABC::StdAddressBook::self(), attendee->name() );
00745       if ( !list.isEmpty() ) {
00746         toBeDeleted.push_back( item ); // remove it once we are done expanding
00747         KPIM::DistributionList::Entry::List entries = list.entries( KABC::StdAddressBook::self() );
00748         KPIM::DistributionList::Entry::List::Iterator it( entries.begin() );
00749         while ( it != entries.end() ) {
00750           KPIM::DistributionList::Entry &e = ( *it );
00751           ++it;
00752           // this calls insertAttendee, which appends
00753           insertAttendeeFromAddressee( e.addressee, attendee );
00754           // TODO: duplicate check, in case it was already added manually
00755         }
00756       }
00757     } else {
00758       bool skip = false;
00759       if ( attendee->email().endsWith( "example.net" ) ) {
00760         if ( KMessageBox::warningYesNo( this, i18n("%1 does not look like a valid email address. "
00761                 "Are you sure you want to invite this participant?").arg( attendee->email() ),
00762               i18n("Invalid email address") ) != KMessageBox::Yes ) {
00763           skip = true;
00764         }
00765       }
00766       if ( !skip ) {
00767         event->addAttendee( new Attendee( *attendee ) );
00768       }
00769     }
00770   }
00771 
00772   KOAttendeeEditor::writeEvent( event );
00773 
00774   // cleanup
00775   QValueVector<FreeBusyItem*>::iterator it;
00776   for( it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it ) {
00777     delete *it;
00778   }
00779 }
00780 
00781 KCal::Attendee * KOEditorFreeBusy::currentAttendee() const
00782 {
00783   KDGanttViewItem *item = mGanttView->selectedItem();
00784   FreeBusyItem *aItem = static_cast<FreeBusyItem*>( item );
00785   if ( !aItem )
00786     return 0;
00787   return aItem->attendee();
00788 }
00789 
00790 void KOEditorFreeBusy::updateCurrentItem()
00791 {
00792   FreeBusyItem* item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00793   if ( item ) {
00794     item->updateItem();
00795     updateFreeBusyData( item );
00796     updateStatusSummary();
00797   }
00798 }
00799 
00800 void KOEditorFreeBusy::removeAttendee()
00801 {
00802   FreeBusyItem *item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00803   if ( !item )
00804     return;
00805 
00806   Attendee *delA = new Attendee( item->attendee()->name(), item->attendee()->email(),
00807                                  item->attendee()->RSVP(), item->attendee()->status(),
00808                                  item->attendee()->role(), item->attendee()->uid() );
00809   mdelAttendees.append( delA );
00810   delete item;
00811 
00812   updateStatusSummary();
00813   updateAttendeeInput();
00814   emit updateAttendeeSummary( mGanttView->childCount() );
00815 }
00816 
00817 void KOEditorFreeBusy::clearSelection() const
00818 {
00819   KDGanttViewItem *item = mGanttView->selectedItem();
00820   if ( item )
00821     mGanttView->setSelected( item, false );
00822   mGanttView->repaint();
00823   item->repaint();
00824 }
00825 
00826 void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
00827 {
00828   const QStringList myEmails = KOPrefs::instance()->allEmails();
00829   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00830         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00831   {
00832     for ( QStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
00833       if ( item->attendee()->email() == *it2 ) {
00834         item->attendee()->setStatus( status );
00835         item->updateItem();
00836       }
00837     }
00838   }
00839 }
00840 
00841 void KOEditorFreeBusy::showAttendeeStatusMenu()
00842 {
00843   if ( mGanttView->mapFromGlobal( QCursor::pos() ).x() > 22 )
00844     return;
00845   QPopupMenu popup;
00846   popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
00847   popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
00848   popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
00849   popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
00850   popup.insertItem( KOGlobals::self()->smallIcon( "mail_forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
00851   popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
00852   popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
00853   popup.setItemChecked( currentAttendee()->status(), true );
00854   int status = popup.exec( QCursor::pos() );
00855   if ( status >= 0 ) {
00856     currentAttendee()->setStatus( (Attendee::PartStat)status );
00857     updateCurrentItem();
00858     updateAttendeeInput();
00859   }
00860 }
00861 
00862 void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
00863 {
00864   if ( button == Qt::LeftButton && item == 0 )
00865     addNewAttendee();
00866 }
00867 
00868 void KOEditorFreeBusy::slotOrganizerChanged(const QString & newOrganizer)
00869 {
00870   if (newOrganizer==mCurrentOrganizer) return;
00871 
00872   QString name;
00873   QString email;
00874   bool success = KPIM::getNameAndMail( newOrganizer, name, email );
00875 
00876   if (!success) return;
00877 //
00878 
00879   Attendee *currentOrganizerAttendee = 0;
00880   Attendee *newOrganizerAttendee = 0;
00881 
00882   FreeBusyItem *anItem =
00883     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00884   while( anItem ) {
00885     Attendee *attendee = anItem->attendee();
00886     if( attendee->fullName() == mCurrentOrganizer )
00887       currentOrganizerAttendee = attendee;
00888 
00889     if( attendee->fullName() == newOrganizer )
00890       newOrganizerAttendee = attendee;
00891 
00892     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00893   }
00894 
00895   int answer = KMessageBox::No;
00896 
00897   if (currentOrganizerAttendee) {
00898     answer = KMessageBox::questionYesNo( this, i18n("You are changing the organiser of "
00899                                                     "this event, who is also attending, "
00900                                                     "do you want to change that attendee "
00901                                                     "as well?") );
00902   } else {
00903     answer = KMessageBox::Yes;
00904   }
00905 
00906   if (answer==KMessageBox::Yes) {
00907     if (currentOrganizerAttendee) {
00908       removeAttendee( currentOrganizerAttendee );
00909     }
00910 
00911     if (!newOrganizerAttendee) {
00912       Attendee *a = new Attendee( name, email, true );
00913       insertAttendee( a, false );
00914       updateAttendee();
00915     }
00916   }
00917 
00918   mCurrentOrganizer = newOrganizer;
00919 }
00920 
00921 bool KOEditorFreeBusy::eventFilter( QObject *watched, QEvent *event )
00922 {
00923   if ( watched == mGanttView->timeHeaderWidget() &&
00924        event->type() >= QEvent::MouseButtonPress && event->type() <= QEvent::MouseMove ) {
00925     return true;
00926   } else {
00927     return KOAttendeeEditor::eventFilter( watched, event );
00928   }
00929 }
00930 
00931 bool KOEditorFreeBusy::hasExampleAttendee() const
00932 {
00933   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00934         item = static_cast<FreeBusyItem*>( item->nextSibling() ) ) {
00935     Attendee *attendee = item->attendee();
00936     Q_ASSERT( attendee );
00937     if ( isExampleAttendee( attendee ) )
00938         return true;
00939   }
00940   return false;
00941 }
00942 
00943 #include "koeditorfreebusy.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys