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