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   button = new QPushButton( i18n( "Zoom to Fit" ), this );
00258   QWhatsThis::add( button,
00259            i18n("Zooms the Gantt chart so that you can see the "
00260             "entire duration of the event on it.") );
00261   connect( button, SIGNAL( clicked() ), SLOT( slotZoomToTime() ) );
00262   controlLayout->addWidget( button );
00263 
00264   controlLayout->addStretch( 1 );
00265 
00266   button = new QPushButton( i18n( "Pick Date" ), this );
00267   QWhatsThis::add( button,
00268            i18n("Moves the event to a date and time when all the "
00269             "attendees are free.") );
00270   connect( button, SIGNAL( clicked() ), SLOT( slotPickDate() ) );
00271   controlLayout->addWidget( button );
00272 
00273   controlLayout->addStretch( 1 );
00274 
00275   button = new QPushButton( i18n("Reload"), this );
00276   QWhatsThis::add( button,
00277            i18n("Reloads Free/Busy data for all attendees from "
00278             "the corresponding servers.") );
00279   controlLayout->addWidget( button );
00280   connect( button, SIGNAL( clicked() ), SLOT( manualReload() ) );
00281 
00282   mGanttView = new KDGanttView( this, "mGanttView" );
00283   QWhatsThis::add( mGanttView,
00284            i18n("Shows the free/busy status of all attendees. "
00285             "Double-clicking on an attendees entry in the "
00286             "list will allow you to enter the location of their "
00287             "Free/Busy Information.") );
00288   topLayout->addWidget( mGanttView );
00289   // Remove the predefined "Task Name" column
00290   mGanttView->removeColumn( 0 );
00291   mGanttView->addColumn( i18n("Attendee") );
00292   if ( KOPrefs::instance()->mCompactDialogs ) {
00293     mGanttView->setFixedHeight( 78 );
00294   }
00295   mGanttView->setHeaderVisible( true );
00296   mGanttView->setScale( KDGanttView::Hour );
00297   mGanttView->setShowHeaderPopupMenu( true, true, true, false, false, true );
00298   // Initially, show 15 days back and forth
00299   // set start to even hours, i.e. to 12:AM 0 Min 0 Sec
00300   QDateTime horizonStart = QDateTime( QDateTime::currentDateTime()
00301                            .addDays( -15 ).date() );
00302   QDateTime horizonEnd = QDateTime::currentDateTime().addDays( 15 );
00303   mGanttView->setHorizonStart( horizonStart );
00304   mGanttView->setHorizonEnd( horizonEnd );
00305   mGanttView->setCalendarMode( true );
00306   //mGanttView->setDisplaySubitemsAsGroup( true );
00307   mGanttView->setShowLegendButton( false );
00308   // Initially, center to current date
00309   mGanttView->centerTimelineAfterShow( QDateTime::currentDateTime() );
00310   if ( KGlobal::locale()->use12Clock() )
00311     mGanttView->setHourFormat( KDGanttView::Hour_12 );
00312   else
00313     mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
00314 
00315   // mEventRectangle is the colored rectangle representing the event being modified
00316   mEventRectangle = new KDIntervalColorRectangle( mGanttView );
00317   mEventRectangle->setColor( Qt::magenta );
00318   mGanttView->addIntervalBackgroundColor( mEventRectangle );
00319 
00320   connect( mGanttView, SIGNAL ( timeIntervalSelected( const QDateTime &,
00321                                                       const QDateTime & ) ),
00322            mGanttView, SLOT( zoomToSelection( const QDateTime &,
00323                                               const  QDateTime & ) ) );
00324   connect( mGanttView, SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
00325            SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
00326   connect( mGanttView, SIGNAL( intervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ),
00327            this, SLOT( slotIntervalColorRectangleMoved( const QDateTime&, const QDateTime& ) ) );
00328 
00329   connect( mGanttView, SIGNAL(lvSelectionChanged(KDGanttViewItem*)),
00330           this, SLOT(updateAttendeeInput()) );
00331   connect( mGanttView, SIGNAL(lvItemLeftClicked(KDGanttViewItem*)),
00332            this, SLOT(showAttendeeStatusMenu()) );
00333   connect( mGanttView, SIGNAL(lvItemRightClicked(KDGanttViewItem*)),
00334            this, SLOT(showAttendeeStatusMenu()) );
00335   connect( mGanttView, SIGNAL(lvMouseButtonClicked(int, KDGanttViewItem*, const QPoint&, int)),
00336            this, SLOT(listViewClicked(int, KDGanttViewItem*)) );
00337 
00338   FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
00339   connect( m, SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const QString & ) ),
00340            SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const QString & ) ) );
00341 
00342   connect( &mReloadTimer, SIGNAL( timeout() ), SLOT( autoReload() ) );
00343 
00344   initEditWidgets( this, topLayout );
00345   connect( mRemoveButton, SIGNAL(clicked()),
00346            SLOT(removeAttendee()) );
00347 }
00348 
00349 KOEditorFreeBusy::~KOEditorFreeBusy()
00350 {
00351 }
00352 
00353 void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
00354 {
00355   FreeBusyItem *anItem =
00356       static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00357   while( anItem ) {
00358     if( anItem->attendee() == attendee ) {
00359       if ( anItem->updateTimerID() != 0 )
00360         killTimer( anItem->updateTimerID() );
00361       delete anItem;
00362       updateStatusSummary();
00363       break;
00364     }
00365     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00366   }
00367 }
00368 
00369 void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
00370 {
00371   FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
00372   if ( readFBList )
00373     updateFreeBusyData( item );
00374   else {
00375     clearSelection();
00376     mGanttView->setSelected( item, true );
00377   }
00378   updateStatusSummary();
00379   emit updateAttendeeSummary( mGanttView->childCount() );
00380 }
00381 
00382 void KOEditorFreeBusy::clearAttendees()
00383 {
00384   mGanttView->clear();
00385 }
00386 
00387 
00388 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
00389 {
00390   mGanttView->setUpdateEnabled( enabled );
00391 }
00392 
00393 bool KOEditorFreeBusy::updateEnabled() const
00394 {
00395   return mGanttView->getUpdateEnabled();
00396 }
00397 
00398 
00399 void KOEditorFreeBusy::readEvent( Event *event )
00400 {
00401   bool block = updateEnabled();
00402   setUpdateEnabled( false );
00403   clearAttendees();
00404 
00405   setDateTimes( event->dtStart(), event->dtEnd() );
00406   mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
00407   updateStatusSummary();
00408   clearSelection();
00409   KOAttendeeEditor::readEvent( event );
00410 
00411   setUpdateEnabled( block );
00412   emit updateAttendeeSummary( mGanttView->childCount() );
00413 }
00414 
00415 void KOEditorFreeBusy::slotIntervalColorRectangleMoved( const QDateTime& start, const QDateTime& end )
00416 {
00417   kdDebug() << k_funcinfo << "slotIntervalColorRectangleMoved " << start << "," << end << endl;
00418   mDtStart = start;
00419   mDtEnd = end;
00420   emit dateTimesChanged( start, end );
00421 }
00422 
00423 void KOEditorFreeBusy::setDateTimes( const QDateTime &start, const QDateTime &end )
00424 {
00425   slotUpdateGanttView( start, end );
00426 }
00427 
00428 void KOEditorFreeBusy::slotScaleChanged( int newScale )
00429 {
00430   // The +1 is for the Minute scale which we don't offer in the combo box.
00431   KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
00432   mGanttView->setScale( scale );
00433   slotCenterOnStart();
00434 }
00435 
00436 void KOEditorFreeBusy::slotCenterOnStart()
00437 {
00438   mGanttView->centerTimeline( mDtStart );
00439 }
00440 
00441 void KOEditorFreeBusy::slotZoomToTime()
00442 {
00443   mGanttView->zoomToFit();
00444 }
00445 
00446 void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
00447 {
00448   if ( item->isDownloading() )
00449     // This item is already in the process of fetching the FB list
00450     return;
00451 
00452   if ( item->updateTimerID() != 0 )
00453     // An update timer is already running. Reset it
00454     killTimer( item->updateTimerID() );
00455 
00456   // This item does not have a download running, and no timer is set
00457   // Do the download in five seconds
00458   item->setUpdateTimerID( startTimer( 5000 ) );
00459 }
00460 
00461 void KOEditorFreeBusy::timerEvent( QTimerEvent* event )
00462 {
00463   killTimer( event->timerId() );
00464   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00465   while( item ) {
00466     if( item->updateTimerID() == event->timerId() ) {
00467       item->setUpdateTimerID( 0 );
00468       item->startDownload( mForceDownload );
00469       return;
00470     }
00471     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00472   }
00473 }
00474 
00475 // Set the Free Busy list for everyone having this email address
00476 // If fb == 0, this disabled the free busy list for them
00477 void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
00478                                            const QString &email )
00479 {
00480   kdDebug(5850) << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
00481 
00482   if( fb )
00483     fb->sortList();
00484   bool block = mGanttView->getUpdateEnabled();
00485   mGanttView->setUpdateEnabled( false );
00486   for( KDGanttViewItem *it = mGanttView->firstChild(); it;
00487        it = it->nextSibling() ) {
00488     FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
00489     if( item->email() == email )
00490       item->setFreeBusyPeriods( fb );
00491   }
00492   mGanttView->setUpdateEnabled( block );
00493 }
00494 
00495 
00500 void KOEditorFreeBusy::slotUpdateGanttView( const QDateTime &dtFrom, const QDateTime &dtTo )
00501 {
00502   mDtStart = dtFrom;
00503   mDtEnd = dtTo;
00504   bool block = mGanttView->getUpdateEnabled( );
00505   mGanttView->setUpdateEnabled( false );
00506   QDateTime horizonStart = QDateTime( dtFrom.addDays( -15 ).date() );
00507   mGanttView->setHorizonStart( horizonStart  );
00508   mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
00509   mEventRectangle->setDateTimes( dtFrom, dtTo );
00510   mGanttView->setUpdateEnabled( block );
00511   mGanttView->centerTimelineAfterShow( dtFrom );
00512 }
00513 
00514 
00518 void KOEditorFreeBusy::slotPickDate()
00519 {
00520   QDateTime start = mDtStart;
00521   QDateTime end = mDtEnd;
00522   bool success = findFreeSlot( start, end );
00523 
00524   if( success ) {
00525     if ( start == mDtStart && end == mDtEnd ) {
00526       KMessageBox::information( this,
00527           i18n( "The meeting already has suitable start/end times." ), QString::null,
00528           "MeetingTimeOKFreeBusy" );
00529     } else {
00530       emit dateTimesChanged( start, end );
00531       slotUpdateGanttView( start, end );
00532       KMessageBox::information( this,
00533           i18n( "The meeting has been moved to\nStart: %1\nEnd: %2." )
00534           .arg( start.toString() ).arg( end.toString() ), QString::null,
00535           "MeetingMovedFreeBusy" );
00536     }
00537   } else
00538     KMessageBox::sorry( this, i18n( "No suitable date found." ) );
00539 }
00540 
00541 
00546 bool KOEditorFreeBusy::findFreeSlot( QDateTime &dtFrom, QDateTime &dtTo )
00547 {
00548   if( tryDate( dtFrom, dtTo ) )
00549     // Current time is acceptable
00550     return true;
00551 
00552   QDateTime tryFrom = dtFrom;
00553   QDateTime tryTo = dtTo;
00554 
00555   // Make sure that we never suggest a date in the past, even if the
00556   // user originally scheduled the meeting to be in the past.
00557   if( tryFrom < QDateTime::currentDateTime() ) {
00558     // The slot to look for is at least partially in the past.
00559     int secs = tryFrom.secsTo( tryTo );
00560     tryFrom = QDateTime::currentDateTime();
00561     tryTo = tryFrom.addSecs( secs );
00562   }
00563 
00564   bool found = false;
00565   while( !found ) {
00566     found = tryDate( tryFrom, tryTo );
00567     // PENDING(kalle) Make the interval configurable
00568     if( !found && dtFrom.daysTo( tryFrom ) > 365 )
00569       break; // don't look more than one year in the future
00570   }
00571 
00572   dtFrom = tryFrom;
00573   dtTo = tryTo;
00574 
00575   return found;
00576 }
00577 
00578 
00587 bool KOEditorFreeBusy::tryDate( QDateTime& tryFrom, QDateTime& tryTo )
00588 {
00589   FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
00590   while( currentItem ) {
00591     if( !tryDate( currentItem, tryFrom, tryTo ) ) {
00592       // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl;
00593       return false;
00594     }
00595 
00596     currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
00597   }
00598 
00599   return true;
00600 }
00601 
00609 bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
00610                                 QDateTime &tryFrom, QDateTime &tryTo )
00611 {
00612   // If we don't have any free/busy information, assume the
00613   // participant is free. Otherwise a participant without available
00614   // information would block the whole allocation.
00615   KCal::FreeBusy *fb = attendee->freeBusy();
00616   if( !fb )
00617     return true;
00618 
00619   QValueList<KCal::Period> busyPeriods = fb->busyPeriods();
00620   for( QValueList<KCal::Period>::Iterator it = busyPeriods.begin();
00621        it != busyPeriods.end(); ++it ) {
00622     if( (*it).end() <= tryFrom || // busy period ends before try period
00623     (*it).start() >= tryTo )  // busy period starts after try period
00624       continue;
00625     else {
00626       // the current busy period blocks the try period, try
00627       // after the end of the current busy period
00628       int secsDuration = tryFrom.secsTo( tryTo );
00629       tryFrom = (*it).end();
00630       tryTo = tryFrom.addSecs( secsDuration );
00631       // try again with the new try period
00632       tryDate( attendee, tryFrom, tryTo );
00633       // we had to change the date at least once
00634       return false;
00635     }
00636   }
00637 
00638   return true;
00639 }
00640 
00641 void KOEditorFreeBusy::updateStatusSummary()
00642 {
00643   FreeBusyItem *aItem =
00644     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00645   int total = 0;
00646   int accepted = 0;
00647   int tentative = 0;
00648   int declined = 0;
00649   while( aItem ) {
00650     ++total;
00651     switch( aItem->attendee()->status() ) {
00652     case Attendee::Accepted:
00653       ++accepted;
00654       break;
00655     case Attendee::Tentative:
00656       ++tentative;
00657       break;
00658     case Attendee::Declined:
00659       ++declined;
00660       break;
00661     case Attendee::NeedsAction:
00662     case Attendee::Delegated:
00663     case Attendee::Completed:
00664     case Attendee::InProcess:
00665       /* just to shut up the compiler */
00666       break;
00667     }
00668     aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
00669   }
00670   if( total > 1 && mIsOrganizer ) {
00671     mStatusSummaryLabel->show();
00672     mStatusSummaryLabel->setText(
00673         i18n( "Of the %1 participants, %2 have accepted, %3"
00674               " have tentatively accepted, and %4 have declined.")
00675         .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
00676   } else {
00677     mStatusSummaryLabel->hide();
00678   }
00679   mStatusSummaryLabel->adjustSize();
00680 }
00681 
00682 void KOEditorFreeBusy::triggerReload()
00683 {
00684   mReloadTimer.start( 1000, true );
00685 }
00686 
00687 void KOEditorFreeBusy::cancelReload()
00688 {
00689   mReloadTimer.stop();
00690 }
00691 
00692 void KOEditorFreeBusy::manualReload()
00693 {
00694   mForceDownload = true;
00695   reload();
00696 }
00697 
00698 void KOEditorFreeBusy::autoReload()
00699 {
00700   mForceDownload = false;
00701   reload();
00702 }
00703 
00704 void KOEditorFreeBusy::reload()
00705 {
00706   kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
00707 
00708   FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00709   while( item ) {
00710     if (  mForceDownload )
00711       item->startDownload( mForceDownload );
00712     else
00713       updateFreeBusyData( item );
00714 
00715     item = static_cast<FreeBusyItem *>( item->nextSibling() );
00716   }
00717 }
00718 
00719 void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
00720 {
00721   FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
00722   if ( !item ) return;
00723 
00724   Attendee *attendee = item->attendee();
00725 
00726   FreeBusyUrlDialog dialog( attendee, this );
00727   dialog.exec();
00728 }
00729 
00730 void KOEditorFreeBusy::writeEvent(KCal::Event * event)
00731 {
00732   event->clearAttendees();
00733   QValueVector<FreeBusyItem*> toBeDeleted;
00734   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00735         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00736   {
00737     Attendee *attendee = item->attendee();
00738     Q_ASSERT( attendee );
00739     /* Check if the attendee is a distribution list and expand it */
00740     if ( attendee->email().isEmpty() ) {
00741       KPIM::DistributionList list =
00742         KPIM::DistributionList::findByName( KABC::StdAddressBook::self(), attendee->name() );
00743       if ( !list.isEmpty() ) {
00744         toBeDeleted.push_back( item ); // remove it once we are done expanding
00745         KPIM::DistributionList::Entry::List entries = list.entries( KABC::StdAddressBook::self() );
00746         KPIM::DistributionList::Entry::List::Iterator it( entries.begin() );
00747         while ( it != entries.end() ) {
00748           KPIM::DistributionList::Entry &e = ( *it );
00749           ++it;
00750           // this calls insertAttendee, which appends
00751           insertAttendeeFromAddressee( e.addressee, attendee );
00752           // TODO: duplicate check, in case it was already added manually
00753         }
00754       }
00755     } else {
00756       bool skip = false;
00757       if ( attendee->email().endsWith( "example.net" ) ) {
00758         if ( KMessageBox::warningYesNo( this, i18n("%1 does not look like a valid email address. "
00759                 "Are you sure you want to invite this participant?").arg( attendee->email() ),
00760               i18n("Invalid email address") ) != KMessageBox::Yes ) {
00761           skip = true;
00762         }
00763       }
00764       if ( !skip ) {
00765         event->addAttendee( new Attendee( *attendee ) );
00766       }
00767     }
00768   }
00769 
00770   KOAttendeeEditor::writeEvent( event );
00771 
00772   // cleanup
00773   QValueVector<FreeBusyItem*>::iterator it;
00774   for( it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it ) {
00775     delete *it;
00776   }
00777 }
00778 
00779 KCal::Attendee * KOEditorFreeBusy::currentAttendee() const
00780 {
00781   KDGanttViewItem *item = mGanttView->selectedItem();
00782   FreeBusyItem *aItem = static_cast<FreeBusyItem*>( item );
00783   if ( !aItem )
00784     return 0;
00785   return aItem->attendee();
00786 }
00787 
00788 void KOEditorFreeBusy::updateCurrentItem()
00789 {
00790   FreeBusyItem* item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00791   if ( item ) {
00792     item->updateItem();
00793     updateFreeBusyData( item );
00794     updateStatusSummary();
00795   }
00796 }
00797 
00798 void KOEditorFreeBusy::removeAttendee()
00799 {
00800   FreeBusyItem *item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
00801   if ( !item )
00802     return;
00803 
00804   Attendee *delA = new Attendee( item->attendee()->name(), item->attendee()->email(),
00805                                  item->attendee()->RSVP(), item->attendee()->status(),
00806                                  item->attendee()->role(), item->attendee()->uid() );
00807   mdelAttendees.append( delA );
00808   delete item;
00809 
00810   updateStatusSummary();
00811   updateAttendeeInput();
00812   emit updateAttendeeSummary( mGanttView->childCount() );
00813 }
00814 
00815 void KOEditorFreeBusy::clearSelection() const
00816 {
00817   KDGanttViewItem *item = mGanttView->selectedItem();
00818   if ( item )
00819     mGanttView->setSelected( item, false );
00820   mGanttView->repaint();
00821   item->repaint();
00822 }
00823 
00824 void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
00825 {
00826   const QStringList myEmails = KOPrefs::instance()->allEmails();
00827   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00828         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00829   {
00830     for ( QStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
00831       if ( item->attendee()->email() == *it2 ) {
00832         item->attendee()->setStatus( status );
00833         item->updateItem();
00834       }
00835     }
00836   }
00837 }
00838 
00839 void KOEditorFreeBusy::showAttendeeStatusMenu()
00840 {
00841   if ( mGanttView->mapFromGlobal( QCursor::pos() ).x() > 22 )
00842     return;
00843   QPopupMenu popup;
00844   popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
00845   popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
00846   popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
00847   popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
00848   popup.insertItem( KOGlobals::self()->smallIcon( "mail_forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
00849   popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
00850   popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
00851   popup.setItemChecked( currentAttendee()->status(), true );
00852   int status = popup.exec( QCursor::pos() );
00853   if ( status >= 0 ) {
00854     currentAttendee()->setStatus( (Attendee::PartStat)status );
00855     updateCurrentItem();
00856     updateAttendeeInput();
00857   }
00858 }
00859 
00860 void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
00861 {
00862   if ( button == Qt::LeftButton && item == 0 )
00863     addNewAttendee();
00864 }
00865 
00866 #include "koeditorfreebusy.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys