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