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>" + " "; 00182 toolTip += per.summary(); 00183 toolTip += "<br>"; 00184 } 00185 if ( !per.location().isEmpty() ) { 00186 toolTip += "<i>" + i18n( "Location:" ) + "</i>" + " "; 00187 toolTip += per.location(); 00188 toolTip += "<br>"; 00189 } 00190 toolTip += "<i>" + i18n( "Start:" ) + "</i>" + " "; 00191 toolTip += KGlobal::locale()->formatDateTime( per.start() ); 00192 toolTip += "<br>"; 00193 toolTip += "<i>" + i18n( "End:" ) + "</i>" + " "; 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"