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 <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     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 += KGlobal::locale()->formatDateTime( per.start() );
00192       toolTip += "<br>";
00193       toolTip += "<i>" + i18n( "End:" ) + "</i>" + "&nbsp;";
00194       toolTip += KGlobal::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 ( KGlobal::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( KABC::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( KABC::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   mGanttView->repaint();
00856   item->repaint();
00857 }
00858 
00859 void KOEditorFreeBusy::setSelected( int index )
00860 {
00861   int count = 0;
00862   for( KDGanttViewItem *it = mGanttView->firstChild(); it; it = it->nextSibling() ) {
00863     FreeBusyItem *item = static_cast<FreeBusyItem*>( it );
00864     if ( count == index ) {
00865       mGanttView->setSelected( item, true );
00866       return;
00867     }
00868     count++;
00869   }
00870 }
00871 
00872 int KOEditorFreeBusy::selectedIndex()
00873 {
00874   int index = 0;
00875   for ( KDGanttViewItem *it = mGanttView->firstChild(); it; it = it->nextSibling() ) {
00876     FreeBusyItem *item = static_cast<FreeBusyItem*>( it );
00877     if ( item->isSelected() ) {
00878       break;
00879     }
00880     index++;
00881   }
00882   return index;
00883 }
00884 
00885 void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
00886 {
00887   const TQStringList myEmails = KOPrefs::instance()->allEmails();
00888   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00889         item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
00890   {
00891     for ( TQStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
00892       if ( item->attendee()->email() == *it2 ) {
00893         item->attendee()->setStatus( status );
00894         item->updateItem();
00895       }
00896     }
00897   }
00898 }
00899 
00900 void KOEditorFreeBusy::showAttendeeStatusMenu()
00901 {
00902   if ( mGanttView->mapFromGlobal( TQCursor::pos() ).x() > 22 )
00903     return;
00904   TQPopupMenu popup;
00905   popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
00906   popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
00907   popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
00908   popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
00909   popup.insertItem( KOGlobals::self()->smallIcon( "mail_forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
00910   popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
00911   popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
00912   popup.setItemChecked( currentAttendee()->status(), true );
00913   int status = popup.exec( TQCursor::pos() );
00914   if ( status >= 0 ) {
00915     currentAttendee()->setStatus( (Attendee::PartStat)status );
00916     updateCurrentItem();
00917     updateAttendeeInput();
00918   }
00919 }
00920 
00921 void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
00922 {
00923   if ( button == Qt::LeftButton && item == 0 )
00924     addNewAttendee();
00925 }
00926 
00927 void KOEditorFreeBusy::slotOrganizerChanged(const TQString & newOrganizer)
00928 {
00929   if (newOrganizer==mCurrentOrganizer) return;
00930 
00931   TQString name;
00932   TQString email;
00933   bool success = KPIM::getNameAndMail( newOrganizer, name, email );
00934 
00935   if (!success) return;
00936 //
00937 
00938   Attendee *currentOrganizerAttendee = 0;
00939   Attendee *newOrganizerAttendee = 0;
00940 
00941   FreeBusyItem *anItem =
00942     static_cast<FreeBusyItem *>( mGanttView->firstChild() );
00943   while( anItem ) {
00944     Attendee *attendee = anItem->attendee();
00945     if( attendee->fullName() == mCurrentOrganizer )
00946       currentOrganizerAttendee = attendee;
00947 
00948     if( attendee->fullName() == newOrganizer )
00949       newOrganizerAttendee = attendee;
00950 
00951     anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
00952   }
00953 
00954   int answer = KMessageBox::No;
00955 
00956   if (currentOrganizerAttendee) {
00957     answer = KMessageBox::questionYesNo( this, i18n("You are changing the organiser of "
00958                                                     "this event, who is also attending, "
00959                                                     "do you want to change that attendee "
00960                                                     "as well?") );
00961   } else {
00962     answer = KMessageBox::Yes;
00963   }
00964 
00965   if (answer==KMessageBox::Yes) {
00966     if (currentOrganizerAttendee) {
00967       removeAttendee( currentOrganizerAttendee );
00968     }
00969 
00970     if (!newOrganizerAttendee) {
00971       Attendee *a = new Attendee( name, email, true );
00972       insertAttendee( a, false );
00973       mnewAttendees.append( a );
00974       updateAttendee();
00975     }
00976   }
00977 
00978   mCurrentOrganizer = newOrganizer;
00979 }
00980 
00981 bool KOEditorFreeBusy::eventFilter( TQObject *watched, TQEvent *event )
00982 {
00983   if ( TQT_BASE_OBJECT(watched) == TQT_BASE_OBJECT(mGanttView->timeHeaderWidget()) &&
00984        event->type() >= TQEvent::MouseButtonPress && event->type() <= TQEvent::MouseMove ) {
00985     return true;
00986   } else {
00987     return KOAttendeeEditor::eventFilter( watched, event );
00988   }
00989 }
00990 
00991 TQListViewItem* KOEditorFreeBusy::hasExampleAttendee() const
00992 {
00993   for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
00994         item = static_cast<FreeBusyItem*>( item->nextSibling() ) ) {
00995     Attendee *attendee = item->attendee();
00996     Q_ASSERT( attendee );
00997     if ( isExampleAttendee( attendee ) )
00998         return item;
00999   }
01000   return 0;
01001 }
01002 
01003 #include "koeditorfreebusy.moc"