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