korganizer

koeditorfreebusy.cpp
1/*
2 This file is part of KOrganizer.
3
4 Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
20 As a special exception, permission is given to link this program
21 with any edition of TQt, and distribute the resulting executable,
22 without including the source code for TQt in the source distribution.
23*/
24
25#include <tqtooltip.h>
26#include <tqlayout.h>
27#include <tqlabel.h>
28#include <tqcombobox.h>
29#include <tqpushbutton.h>
30#include <tqvaluevector.h>
31#include <tqwhatsthis.h>
32
33#include <kdebug.h>
34#include <tdelocale.h>
35#include <kiconloader.h>
36#include <tdemessagebox.h>
37
38#ifndef KORG_NOKABC
39#include <tdeabc/addresseedialog.h>
40#include <tdeabc/vcardconverter.h>
41#include <libtdepim/addressesdialog.h>
42#include <libtdepim/addresseelineedit.h>
43#include <libtdepim/distributionlist.h>
44#include <tdeabc/stdaddressbook.h>
45#endif
46
47#include <libkcal/event.h>
48#include <libkcal/freebusy.h>
49
50#include <libemailfunctions/email.h>
51
52#include <kdgantt/KDGanttView.h>
53#include <kdgantt/KDGanttViewTaskItem.h>
54#include <kdgantt/KDGanttViewSubwidgets.h>
55
56#include "koprefs.h"
57#include "koglobals.h"
58#include "kogroupware.h"
59#include "freebusymanager.h"
60#include "freebusyurldialog.h"
61
62#include "koeditorfreebusy.h"
63
64// The FreeBusyItem is the whole line for a given attendee.
65// Individual "busy" periods are created as sub-items of this item.
66//
67// We can't use the CustomListViewItem base class, since we need a
68// different inheritance hierarchy for supporting the Gantt view.
69class FreeBusyItem : public KDGanttViewTaskItem
70{
71 public:
72 FreeBusyItem( Attendee *attendee, KDGanttView *parent ) :
73 KDGanttViewTaskItem( parent, parent->lastItem() ), mAttendee( attendee ), mTimerID( 0 ),
74 mIsDownloading( false )
75 {
76 Q_ASSERT( attendee );
77 updateItem();
78 setFreeBusyPeriods( 0 );
79 setDisplaySubitemsAsGroup( true );
80 if ( listView () )
81 listView ()->setRootIsDecorated( false );
82 }
83 ~FreeBusyItem() {}
84
85 void updateItem();
86
87 Attendee *attendee() const { return mAttendee; }
88 void setFreeBusy( KCal::FreeBusy *fb ) { mFreeBusy = fb; }
89 KCal::FreeBusy* freeBusy() const { return mFreeBusy; }
90
91 void setFreeBusyPeriods( FreeBusy *fb );
92
93 TQString key( int column, bool ) const
94 {
95 TQMap<int,TQString>::ConstIterator it = mKeyMap.find( column );
96 if ( it == mKeyMap.end() ) return listViewText( column );
97 else return *it;
98 }
99
100 void setSortKey( int column, const TQString &key )
101 {
102 mKeyMap.insert( column, key );
103 }
104
105 TQString email() const { return mAttendee->email(); }
106 void setUpdateTimerID( int id ) { mTimerID = id; }
107 int updateTimerID() const { return mTimerID; }
108
109 void startDownload( bool forceDownload ) {
110 mIsDownloading = true;
111 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
112 if ( !m->retrieveFreeBusy( attendee()->email(), forceDownload ) )
113 mIsDownloading = false;
114 }
115 void setIsDownloading( bool d ) { mIsDownloading = d; }
116 bool isDownloading() const { return mIsDownloading; }
117
118 private:
119 Attendee *mAttendee;
120 KCal::FreeBusy *mFreeBusy;
121
122 TQMap<int,TQString> mKeyMap;
123
124 // This is used for the update timer
125 int mTimerID;
126
127 // Only run one download job at a time
128 bool mIsDownloading;
129};
130
131void FreeBusyItem::updateItem()
132{
133 TQString text = mAttendee->name() + " <" + mAttendee->email() + '>';
134 setListViewText( 0, text );
135 switch ( mAttendee->status() ) {
136 case Attendee::Accepted:
137 setPixmap( 0, KOGlobals::self()->smallIcon( "ok" ) );
138 break;
139 case Attendee::Declined:
140 setPixmap( 0, KOGlobals::self()->smallIcon( "no" ) );
141 break;
142 case Attendee::NeedsAction:
143 case Attendee::InProcess:
144 setPixmap( 0, KOGlobals::self()->smallIcon( "help" ) );
145 break;
146 case Attendee::Tentative:
147 setPixmap( 0, KOGlobals::self()->smallIcon( "apply" ) );
148 break;
149 case Attendee::Delegated:
150 setPixmap( 0, KOGlobals::self()->smallIcon( "mail-forward" ) );
151 break;
152 default:
153 setPixmap( 0, TQPixmap() );
154 }
155}
156
157
158// Set the free/busy periods for this attendee
159void FreeBusyItem::setFreeBusyPeriods( FreeBusy* fb )
160{
161 if( fb ) {
162 // Clean out the old entries
163 for( KDGanttViewItem* it = firstChild(); it; it = firstChild() )
164 delete it;
165
166 // Evaluate free/busy information
167 TQValueList<KCal::Period> busyPeriods = fb->busyPeriods();
168 for( TQValueList<KCal::Period>::Iterator it = busyPeriods.begin();
169 it != busyPeriods.end(); ++it ) {
170 Period per = *it;
171
172 KDGanttViewTaskItem *newSubItem = new KDGanttViewTaskItem( this );
173 newSubItem->setStartTime( per.start() );
174 newSubItem->setEndTime( per.end() );
175 newSubItem->setColors( TQt::red, TQt::red, TQt::red );
176
177 TQString toolTip = "<qt>";
178 toolTip += "<b>" + i18n( "Freebusy Period" ) + "</b>";
179 toolTip += "<br>----------------------<br>";
180 if ( !per.summary().isEmpty() ) {
181 toolTip += "<i>" + i18n( "Summary:" ) + "</i>" + "&nbsp;";
182 toolTip += per.summary();
183 toolTip += "<br>";
184 }
185 if ( !per.location().isEmpty() ) {
186 toolTip += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
187 toolTip += per.location();
188 toolTip += "<br>";
189 }
190 toolTip += "<i>" + i18n( "Start:" ) + "</i>" + "&nbsp;";
191 toolTip += TDEGlobal::locale()->formatDateTime( per.start() );
192 toolTip += "<br>";
193 toolTip += "<i>" + i18n( "End:" ) + "</i>" + "&nbsp;";
194 toolTip += TDEGlobal::locale()->formatDateTime( per.end() );
195 toolTip += "<br>";
196 toolTip += "</qt>";
197 newSubItem->setTooltipText( toolTip );
198 }
199 setFreeBusy( fb );
200 setShowNoInformation( false );
201 } else {
202 // No free/busy information
203 //debug only start
204 // int ii ;
205 // TQDateTime cur = TQDateTime::currentDateTime();
206 // for( ii = 0; ii < 10 ;++ii ) {
207 // KDGanttViewTaskItem* newSubItem = new KDGanttViewTaskItem( this );
208 // cur = cur.addSecs( 7200 );
209 // newSubItem->setStartTime( cur );
210 // cur = cur.addSecs( 7200 );
211 // newSubItem->setEndTime( cur );
212 // newSubItem->setColors( TQt::red, TQt::red, TQt::red );
213 // }
214 //debug only end
215 setFreeBusy( 0 );
216 setShowNoInformation( true );
217 }
218
219 // We are no longer downloading
220 mIsDownloading = false;
221}
222
224
225KOEditorFreeBusy::KOEditorFreeBusy( int spacing, TQWidget *parent,
226 const char *name )
227 : KOAttendeeEditor( parent, name )
228{
229 TQVBoxLayout *topLayout = new TQVBoxLayout( this );
230 topLayout->setSpacing( spacing );
231
232 initOrganizerWidgets( this, topLayout );
233
234 // Label for status summary information
235 // Uses the tooltip palette to highlight it
236 mIsOrganizer = false; // Will be set later. This is just valgrind silencing
237 mStatusSummaryLabel = new TQLabel( this );
238 mStatusSummaryLabel->setPalette( TQToolTip::palette() );
239 mStatusSummaryLabel->setFrameStyle( TQFrame::Plain | TQFrame::Box );
240 mStatusSummaryLabel->setLineWidth( 1 );
241 mStatusSummaryLabel->hide(); // Will be unhidden later if you are organizer
242 topLayout->addWidget( mStatusSummaryLabel );
243
244 // The control panel for the gantt widget
245 TQBoxLayout *controlLayout = new TQHBoxLayout( topLayout );
246
247 TQString whatsThis = i18n("Sets the zoom level on the Gantt chart. "
248 "'Hour' shows a range of several hours, "
249 "'Day' shows a range of a few days, "
250 "'Week' shows a range of a few months, "
251 "and 'Month' shows a range of a few years, "
252 "while 'Automatic' selects the range most "
253 "appropriate for the current event or to-do.");
254 TQLabel *label = new TQLabel( i18n( "Scale: " ), this );
255 TQWhatsThis::add( label, whatsThis );
256 controlLayout->addWidget( label );
257
258 scaleCombo = new TQComboBox( this );
259 TQWhatsThis::add( scaleCombo, whatsThis );
260 scaleCombo->insertItem( i18n( "Hour" ) );
261 scaleCombo->insertItem( i18n( "Day" ) );
262 scaleCombo->insertItem( i18n( "Week" ) );
263 scaleCombo->insertItem( i18n( "Month" ) );
264 scaleCombo->insertItem( i18n( "Automatic" ) );
265 scaleCombo->setCurrentItem( 0 ); // start with "hour"
266 connect( scaleCombo, TQ_SIGNAL( activated( int ) ),
267 TQ_SLOT( slotScaleChanged( int ) ) );
268 controlLayout->addWidget( scaleCombo );
269
270 TQPushButton *button = new TQPushButton( i18n( "Center on Start" ), this );
271 TQWhatsThis::add( button,
272 i18n("Centers the Gantt chart on the start time "
273 "and day of this event.") );
274 connect( button, TQ_SIGNAL( clicked() ), TQ_SLOT( slotCenterOnStart() ) );
275 controlLayout->addWidget( button );
276
277 controlLayout->addStretch( 1 );
278
279 button = new TQPushButton( i18n( "Pick Date" ), this );
280 TQWhatsThis::add( button,
281 i18n("Moves the event to a date and time when all the "
282 "attendees are free.") );
283 connect( button, TQ_SIGNAL( clicked() ), TQ_SLOT( slotPickDate() ) );
284 controlLayout->addWidget( button );
285
286 controlLayout->addStretch( 1 );
287
288 button = new TQPushButton( i18n("Reload"), this );
289 TQWhatsThis::add( button,
290 i18n("Reloads Free/Busy data for all attendees from "
291 "the corresponding servers.") );
292 controlLayout->addWidget( button );
293 connect( button, TQ_SIGNAL( clicked() ), TQ_SLOT( manualReload() ) );
294
295 mGanttView = new KDGanttView( this, "mGanttView" );
296 TQWhatsThis::add( mGanttView,
297 i18n("Shows the free/busy status of all attendees. "
298 "Double-clicking on an attendees entry in the "
299 "list will allow you to enter the location of their "
300 "Free/Busy Information.") );
301 topLayout->addWidget( mGanttView );
302 // Remove the predefined "Task Name" column
303 mGanttView->removeColumn( 0 );
304 mGanttView->addColumn( i18n("Attendee") );
305 if ( KOPrefs::instance()->mCompactDialogs ) {
306 mGanttView->setFixedHeight( 78 );
307 }
308 mGanttView->setHeaderVisible( true );
309 mGanttView->setScale( KDGanttView::Hour );
310 mGanttView->setShowHeaderPopupMenu( false, false, false, false, false, false );
311 // Initially, show 15 days back and forth
312 // set start to even hours, i.e. to 12:AM 0 Min 0 Sec
313 TQDateTime horizonStart = TQDateTime( TQDateTime::currentDateTime()
314 .addDays( -15 ).date() );
315 TQDateTime horizonEnd = TQDateTime::currentDateTime().addDays( 15 );
316 mGanttView->setHorizonStart( horizonStart );
317 mGanttView->setHorizonEnd( horizonEnd );
318 mGanttView->setCalendarMode( true );
319 //mGanttView->setDisplaySubitemsAsGroup( true );
320 mGanttView->setShowLegendButton( false );
321 // Initially, center to current date
322 mGanttView->centerTimelineAfterShow( TQDateTime::currentDateTime() );
323 if ( TDEGlobal::locale()->use12Clock() )
324 mGanttView->setHourFormat( KDGanttView::Hour_12 );
325 else
326 mGanttView->setHourFormat( KDGanttView::Hour_24_FourDigit );
327
328 // mEventRectangle is the colored rectangle representing the event being modified
329 mEventRectangle = new KDIntervalColorRectangle( mGanttView );
330 mEventRectangle->setColor( TQt::magenta );
331 mGanttView->addIntervalBackgroundColor( mEventRectangle );
332
333 connect( mGanttView, TQ_SIGNAL ( timeIntervalSelected( const TQDateTime &,
334 const TQDateTime & ) ),
335 mGanttView, TQ_SLOT( zoomToSelection( const TQDateTime &,
336 const TQDateTime & ) ) );
337 connect( mGanttView, TQ_SIGNAL( lvItemDoubleClicked( KDGanttViewItem * ) ),
338 TQ_SLOT( editFreeBusyUrl( KDGanttViewItem * ) ) );
339 connect( mGanttView, TQ_SIGNAL( intervalColorRectangleMoved( const TQDateTime&, const TQDateTime& ) ),
340 this, TQ_SLOT( slotIntervalColorRectangleMoved( const TQDateTime&, const TQDateTime& ) ) );
341
342 connect( mGanttView, TQ_SIGNAL(lvSelectionChanged(KDGanttViewItem*)),
343 this, TQ_SLOT(updateAttendeeInput()) );
344 connect( mGanttView, TQ_SIGNAL(lvItemLeftClicked(KDGanttViewItem*)),
345 this, TQ_SLOT(showAttendeeStatusMenu()) );
346 connect( mGanttView, TQ_SIGNAL(lvItemRightClicked(KDGanttViewItem*)),
347 this, TQ_SLOT(showAttendeeStatusMenu()) );
348 connect( mGanttView, TQ_SIGNAL(lvMouseButtonClicked(int, KDGanttViewItem*, const TQPoint&, int)),
349 this, TQ_SLOT(listViewClicked(int, KDGanttViewItem*)) );
350
351 FreeBusyManager *m = KOGroupware::instance()->freeBusyManager();
352 connect( m, TQ_SIGNAL( freeBusyRetrieved( KCal::FreeBusy *, const TQString & ) ),
353 TQ_SLOT( slotInsertFreeBusy( KCal::FreeBusy *, const TQString & ) ) );
354
355 connect( &mReloadTimer, TQ_SIGNAL( timeout() ), TQ_SLOT( autoReload() ) );
356
357 initEditWidgets( this, topLayout );
358 connect( mRemoveButton, TQ_SIGNAL(clicked()),
359 TQ_SLOT(removeAttendee()) );
360
361 slotOrganizerChanged( mOrganizerCombo->currentText() );
362 connect( mOrganizerCombo, TQ_SIGNAL( activated(const TQString&) ),
363 this, TQ_SLOT( slotOrganizerChanged(const TQString&) ) );
364
365 //suppress the buggy consequences of clicks on the time header widget
366 mGanttView->timeHeaderWidget()->installEventFilter( this );
367}
368
369KOEditorFreeBusy::~KOEditorFreeBusy()
370{
371}
372
373void KOEditorFreeBusy::removeAttendee( Attendee *attendee )
374{
375 FreeBusyItem *anItem =
376 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
377 while( anItem ) {
378 if( anItem->attendee() == attendee ) {
379 if ( anItem->updateTimerID() != 0 )
380 killTimer( anItem->updateTimerID() );
381 delete anItem;
382 updateStatusSummary();
383 break;
384 }
385 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
386 }
387}
388
389void KOEditorFreeBusy::insertAttendee( Attendee *attendee, bool readFBList )
390{
391 FreeBusyItem* item = new FreeBusyItem( attendee, mGanttView );
392 if ( readFBList )
393 updateFreeBusyData( item );
394 else {
395 clearSelection();
396 mGanttView->setSelected( item, true );
397 }
398 updateStatusSummary();
399 emit updateAttendeeSummary( mGanttView->childCount() );
400}
401
402void KOEditorFreeBusy::clearAttendees()
403{
404 mGanttView->clear();
405}
406
407
408void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
409{
410 mGanttView->setUpdateEnabled( enabled );
411}
412
413bool KOEditorFreeBusy::updateEnabled() const
414{
415 return mGanttView->getUpdateEnabled();
416}
417
418
419void KOEditorFreeBusy::readEvent( Event *event )
420{
421 bool block = updateEnabled();
422 setUpdateEnabled( false );
423 clearAttendees();
424
425 setDateTimes( event->dtStart(), event->dtEnd() );
426 mIsOrganizer = KOPrefs::instance()->thatIsMe( event->organizer().email() );
427 updateStatusSummary();
428 clearSelection();
429 KOAttendeeEditor::readEvent( event );
430
431 setUpdateEnabled( block );
432 emit updateAttendeeSummary( mGanttView->childCount() );
433}
434
435void KOEditorFreeBusy::slotIntervalColorRectangleMoved( const TQDateTime& start, const TQDateTime& end )
436{
437 kdDebug() << k_funcinfo << "slotIntervalColorRectangleMoved " << start << "," << end << endl;
438 mDtStart = start;
439 mDtEnd = end;
440 emit dateTimesChanged( start, end );
441}
442
443void KOEditorFreeBusy::setDateTimes( const TQDateTime &start, const TQDateTime &end )
444{
445 slotUpdateGanttView( start, end );
446}
447
448void KOEditorFreeBusy::slotScaleChanged( int newScale )
449{
450 // The +1 is for the Minute scale which we don't offer in the combo box.
451 KDGanttView::Scale scale = static_cast<KDGanttView::Scale>( newScale+1 );
452 mGanttView->setScale( scale );
453 slotCenterOnStart();
454}
455
456void KOEditorFreeBusy::slotCenterOnStart()
457{
458 mGanttView->centerTimeline( mDtStart );
459}
460
461void KOEditorFreeBusy::slotZoomToTime()
462{
463 mGanttView->zoomToFit();
464}
465
466void KOEditorFreeBusy::updateFreeBusyData( FreeBusyItem* item )
467{
468 if ( item->isDownloading() )
469 // This item is already in the process of fetching the FB list
470 return;
471
472 if ( item->updateTimerID() != 0 )
473 // An update timer is already running. Reset it
474 killTimer( item->updateTimerID() );
475
476 // This item does not have a download running, and no timer is set
477 // Do the download in five seconds
478 item->setUpdateTimerID( startTimer( 5000 ) );
479}
480
481void KOEditorFreeBusy::timerEvent( TQTimerEvent* event )
482{
483 killTimer( event->timerId() );
484 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
485 while( item ) {
486 if( item->updateTimerID() == event->timerId() ) {
487 item->setUpdateTimerID( 0 );
488 item->startDownload( mForceDownload );
489 return;
490 }
491 item = static_cast<FreeBusyItem *>( item->nextSibling() );
492 }
493}
494
495// Set the Free Busy list for everyone having this email address
496// If fb == 0, this disabled the free busy list for them
497void KOEditorFreeBusy::slotInsertFreeBusy( KCal::FreeBusy *fb,
498 const TQString &email )
499{
500 kdDebug(5850) << "KOEditorFreeBusy::slotInsertFreeBusy() " << email << endl;
501
502 if( fb )
503 fb->sortList();
504 bool block = mGanttView->getUpdateEnabled();
505 mGanttView->setUpdateEnabled( false );
506 for( KDGanttViewItem *it = mGanttView->firstChild(); it;
507 it = it->nextSibling() ) {
508 FreeBusyItem *item = static_cast<FreeBusyItem *>( it );
509 if( item->email() == email )
510 item->setFreeBusyPeriods( fb );
511 }
512 mGanttView->setUpdateEnabled( block );
513}
514
515
520void KOEditorFreeBusy::slotUpdateGanttView( const TQDateTime &dtFrom, const TQDateTime &dtTo )
521{
522 mDtStart = dtFrom;
523 mDtEnd = dtTo;
524 bool block = mGanttView->getUpdateEnabled( );
525 mGanttView->setUpdateEnabled( false );
526 TQDateTime horizonStart = TQDateTime( dtFrom.addDays( -15 ).date() );
527 mGanttView->setHorizonStart( horizonStart );
528 mGanttView->setHorizonEnd( dtTo.addDays( 15 ) );
529 mEventRectangle->setDateTimes( dtFrom, dtTo );
530 mGanttView->setUpdateEnabled( block );
531 mGanttView->centerTimelineAfterShow( dtFrom );
532}
533
534
538void KOEditorFreeBusy::slotPickDate()
539{
540 TQDateTime start = mDtStart;
541 TQDateTime end = mDtEnd;
542 bool success = findFreeSlot( start, end );
543
544 if( success ) {
545 if ( start == mDtStart && end == mDtEnd ) {
546 KMessageBox::information( this,
547 i18n( "The meeting already has suitable start/end times." ), TQString(),
548 "MeetingTimeOKFreeBusy" );
549 } else {
550 if ( KMessageBox::questionYesNo(
551 this,
552 i18n( "<qt>The next available time slot for the meeting is:<br>"
553 "Start: %1<br>End: %2<br>"
554 "Would you like to move the meeting to this time slot?</qt>" ).
555 arg( start.toString(), end.toString() ),
556 TQString(),
557 KStdGuiItem::yes(), KStdGuiItem::no(),
558 "MeetingMovedFreeBusy" ) == KMessageBox::Yes ) {
559 emit dateTimesChanged( start, end );
560 slotUpdateGanttView( start, end );
561 }
562 }
563 } else
564 KMessageBox::sorry( this, i18n( "No suitable date found." ) );
565}
566
567
572bool KOEditorFreeBusy::findFreeSlot( TQDateTime &dtFrom, TQDateTime &dtTo )
573{
574 if( tryDate( dtFrom, dtTo ) )
575 // Current time is acceptable
576 return true;
577
578 TQDateTime tryFrom = dtFrom;
579 TQDateTime tryTo = dtTo;
580
581 // Make sure that we never suggest a date in the past, even if the
582 // user originally scheduled the meeting to be in the past.
583 if( tryFrom < TQDateTime::currentDateTime() ) {
584 // The slot to look for is at least partially in the past.
585 int secs = tryFrom.secsTo( tryTo );
586 tryFrom = TQDateTime::currentDateTime();
587 tryTo = tryFrom.addSecs( secs );
588 }
589
590 bool found = false;
591 while( !found ) {
592 found = tryDate( tryFrom, tryTo );
593 // PENDING(kalle) Make the interval configurable
594 if( !found && dtFrom.daysTo( tryFrom ) > 365 )
595 break; // don't look more than one year in the future
596 }
597
598 dtFrom = tryFrom;
599 dtTo = tryTo;
600
601 return found;
602}
603
604
613bool KOEditorFreeBusy::tryDate( TQDateTime& tryFrom, TQDateTime& tryTo )
614{
615 FreeBusyItem* currentItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
616 while( currentItem ) {
617 if( !tryDate( currentItem, tryFrom, tryTo ) ) {
618 // kdDebug(5850) << "++++date is not OK, new suggestion: " << tryFrom.toString() << " to " << tryTo.toString() << endl;
619 return false;
620 }
621
622 currentItem = static_cast<FreeBusyItem*>( currentItem->nextSibling() );
623 }
624
625 return true;
626}
627
635bool KOEditorFreeBusy::tryDate( FreeBusyItem *attendee,
636 TQDateTime &tryFrom, TQDateTime &tryTo )
637{
638 // If we don't have any free/busy information, assume the
639 // participant is free. Otherwise a participant without available
640 // information would block the whole allocation.
641 KCal::FreeBusy *fb = attendee->freeBusy();
642 if( !fb )
643 return true;
644
645 TQValueList<KCal::Period> busyPeriods = fb->busyPeriods();
646 for( TQValueList<KCal::Period>::Iterator it = busyPeriods.begin();
647 it != busyPeriods.end(); ++it ) {
648 if( (*it).end() <= tryFrom || // busy period ends before try period
649 (*it).start() >= tryTo ) // busy period starts after try period
650 continue;
651 else {
652 // the current busy period blocks the try period, try
653 // after the end of the current busy period
654 int secsDuration = tryFrom.secsTo( tryTo );
655 tryFrom = (*it).end();
656 tryTo = tryFrom.addSecs( secsDuration );
657 // try again with the new try period
658 tryDate( attendee, tryFrom, tryTo );
659 // we had to change the date at least once
660 return false;
661 }
662 }
663
664 return true;
665}
666
667void KOEditorFreeBusy::updateStatusSummary()
668{
669 FreeBusyItem *aItem =
670 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
671 int total = 0;
672 int accepted = 0;
673 int tentative = 0;
674 int declined = 0;
675 while( aItem ) {
676 ++total;
677 switch( aItem->attendee()->status() ) {
678 case Attendee::Accepted:
679 ++accepted;
680 break;
681 case Attendee::Tentative:
682 ++tentative;
683 break;
684 case Attendee::Declined:
685 ++declined;
686 break;
687 case Attendee::NeedsAction:
688 case Attendee::Delegated:
689 case Attendee::Completed:
690 case Attendee::InProcess:
691 case Attendee::None:
692 /* just to shut up the compiler */
693 break;
694 }
695 aItem = static_cast<FreeBusyItem *>( aItem->nextSibling() );
696 }
697 if( total > 1 && mIsOrganizer ) {
698 mStatusSummaryLabel->show();
699 mStatusSummaryLabel->setText(
700 i18n( "Of the %1 participants, %2 have accepted, %3"
701 " have tentatively accepted, and %4 have declined.")
702 .arg( total ).arg( accepted ).arg( tentative ).arg( declined ) );
703 } else {
704 mStatusSummaryLabel->hide();
705 }
706 mStatusSummaryLabel->adjustSize();
707}
708
709void KOEditorFreeBusy::triggerReload()
710{
711 mReloadTimer.start( 1000, true );
712}
713
714void KOEditorFreeBusy::cancelReload()
715{
716 mReloadTimer.stop();
717}
718
719void KOEditorFreeBusy::manualReload()
720{
721 mForceDownload = true;
722 reload();
723}
724
725void KOEditorFreeBusy::autoReload()
726{
727 mForceDownload = false;
728 reload();
729}
730
731void KOEditorFreeBusy::reload()
732{
733 kdDebug(5850) << "KOEditorFreeBusy::reload()" << endl;
734
735 FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() );
736 while( item ) {
737 if ( mForceDownload )
738 item->startDownload( mForceDownload );
739 else
740 updateFreeBusyData( item );
741
742 item = static_cast<FreeBusyItem *>( item->nextSibling() );
743 }
744}
745
746void KOEditorFreeBusy::editFreeBusyUrl( KDGanttViewItem *i )
747{
748 FreeBusyItem *item = static_cast<FreeBusyItem *>( i );
749 if ( !item ) return;
750
751 Attendee *attendee = item->attendee();
752
753 FreeBusyUrlDialog dialog( attendee, this );
754 dialog.exec();
755}
756
757void KOEditorFreeBusy::writeEvent(KCal::Event * event)
758{
759 event->clearAttendees();
760 TQValueVector<FreeBusyItem*> toBeDeleted;
761 for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
762 item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
763 {
764 Attendee *attendee = item->attendee();
765 Q_ASSERT( attendee );
766 /* Check if the attendee is a distribution list and expand it */
767 if ( attendee->email().isEmpty() ) {
768 KPIM::DistributionList list =
769 KPIM::DistributionList::findByName( TDEABC::StdAddressBook::self(), attendee->name() );
770 if ( !list.isEmpty() ) {
771 toBeDeleted.push_back( item ); // remove it once we are done expanding
772 KPIM::DistributionList::Entry::List entries = list.entries( TDEABC::StdAddressBook::self() );
773 KPIM::DistributionList::Entry::List::Iterator it( entries.begin() );
774 while ( it != entries.end() ) {
775 KPIM::DistributionList::Entry &e = ( *it );
776 ++it;
777 // this calls insertAttendee, which appends
778 insertAttendeeFromAddressee( e.addressee, attendee );
779 // TODO: duplicate check, in case it was already added manually
780 }
781 }
782 } else {
783 bool skip = false;
784 if ( attendee->email().endsWith( "example.net" ) ) {
785 if ( KMessageBox::warningYesNo( this, i18n("%1 does not look like a valid email address. "
786 "Are you sure you want to invite this participant?").arg( attendee->email() ),
787 i18n("Invalid email address") ) != KMessageBox::Yes ) {
788 skip = true;
789 }
790 }
791 if ( !skip ) {
792 event->addAttendee( new Attendee( *attendee ) );
793 }
794 }
795 }
796
797 KOAttendeeEditor::writeEvent( event );
798
799 // cleanup
800 TQValueVector<FreeBusyItem*>::iterator it;
801 for( it = toBeDeleted.begin(); it != toBeDeleted.end(); ++it ) {
802 delete *it;
803 }
804}
805
806KCal::Attendee * KOEditorFreeBusy::currentAttendee() const
807{
808 KDGanttViewItem *item = mGanttView->selectedItem();
809 FreeBusyItem *aItem = static_cast<FreeBusyItem*>( item );
810 if ( !aItem )
811 return 0;
812 return aItem->attendee();
813}
814
815void KOEditorFreeBusy::updateCurrentItem()
816{
817 FreeBusyItem* item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
818 if ( item ) {
819 item->updateItem();
820 updateFreeBusyData( item );
821 updateStatusSummary();
822 }
823}
824
825void KOEditorFreeBusy::removeAttendee()
826{
827 FreeBusyItem *item = static_cast<FreeBusyItem*>( mGanttView->selectedItem() );
828 if ( !item )
829 return;
830
831 FreeBusyItem *nextSelectedItem = static_cast<FreeBusyItem*>( item->nextSibling() );
832 if( mGanttView->childCount() == 1 )
833 nextSelectedItem = 0;
834 if( mGanttView->childCount() > 1 && item == mGanttView->lastItem() )
835 nextSelectedItem = static_cast<FreeBusyItem*>( mGanttView->firstChild() );
836
837 Attendee *delA = new Attendee( item->attendee()->name(), item->attendee()->email(),
838 item->attendee()->RSVP(), item->attendee()->status(),
839 item->attendee()->role(), item->attendee()->uid() );
840 mdelAttendees.append( delA );
841 delete item;
842
843 updateStatusSummary();
844 if( nextSelectedItem )
845 mGanttView->setSelected( nextSelectedItem, true );
846 updateAttendeeInput();
847 emit updateAttendeeSummary( mGanttView->childCount() );
848}
849
850void KOEditorFreeBusy::clearSelection() const
851{
852 KDGanttViewItem *item = mGanttView->selectedItem();
853 if (item) {
854 mGanttView->setSelected( item, false );
855 }
856 mGanttView->repaint();
857 if (item) {
858 item->repaint();
859 }
860}
861
862void KOEditorFreeBusy::setSelected( int index )
863{
864 int count = 0;
865 for( KDGanttViewItem *it = mGanttView->firstChild(); it; it = it->nextSibling() ) {
866 FreeBusyItem *item = static_cast<FreeBusyItem*>( it );
867 if ( count == index ) {
868 mGanttView->setSelected( item, true );
869 return;
870 }
871 count++;
872 }
873}
874
875int KOEditorFreeBusy::selectedIndex()
876{
877 int index = 0;
878 for ( KDGanttViewItem *it = mGanttView->firstChild(); it; it = it->nextSibling() ) {
879 FreeBusyItem *item = static_cast<FreeBusyItem*>( it );
880 if ( item->isSelected() ) {
881 break;
882 }
883 index++;
884 }
885 return index;
886}
887
888void KOEditorFreeBusy::changeStatusForMe(KCal::Attendee::PartStat status)
889{
890 const TQStringList myEmails = KOPrefs::instance()->allEmails();
891 for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
892 item = static_cast<FreeBusyItem*>( item->nextSibling() ) )
893 {
894 for ( TQStringList::ConstIterator it2( myEmails.begin() ), end( myEmails.end() ); it2 != end; ++it2 ) {
895 if ( item->attendee()->email() == *it2 ) {
896 item->attendee()->setStatus( status );
897 item->updateItem();
898 }
899 }
900 }
901}
902
903void KOEditorFreeBusy::showAttendeeStatusMenu()
904{
905 if ( mGanttView->mapFromGlobal( TQCursor::pos() ).x() > 22 )
906 return;
907 TQPopupMenu popup;
908 popup.insertItem( SmallIcon( "help" ), Attendee::statusName( Attendee::NeedsAction ), Attendee::NeedsAction );
909 popup.insertItem( KOGlobals::self()->smallIcon( "ok" ), Attendee::statusName( Attendee::Accepted ), Attendee::Accepted );
910 popup.insertItem( KOGlobals::self()->smallIcon( "no" ), Attendee::statusName( Attendee::Declined ), Attendee::Declined );
911 popup.insertItem( KOGlobals::self()->smallIcon( "apply" ), Attendee::statusName( Attendee::Tentative ), Attendee::Tentative );
912 popup.insertItem( KOGlobals::self()->smallIcon( "mail-forward" ), Attendee::statusName( Attendee::Delegated ), Attendee::Delegated );
913 popup.insertItem( Attendee::statusName( Attendee::Completed ), Attendee::Completed );
914 popup.insertItem( KOGlobals::self()->smallIcon( "help" ), Attendee::statusName( Attendee::InProcess ), Attendee::InProcess );
915 popup.setItemChecked( currentAttendee()->status(), true );
916 int status = popup.exec( TQCursor::pos() );
917 if ( status >= 0 ) {
918 currentAttendee()->setStatus( (Attendee::PartStat)status );
919 updateCurrentItem();
920 updateAttendeeInput();
921 }
922}
923
924void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
925{
926 if ( button == TQt::LeftButton && item == 0 )
927 addNewAttendee();
928}
929
930void KOEditorFreeBusy::slotOrganizerChanged(const TQString & newOrganizer)
931{
932 if (newOrganizer==mCurrentOrganizer) return;
933
934 TQString name;
935 TQString email;
936 bool success = KPIM::getNameAndMail( newOrganizer, name, email );
937
938 if (!success) return;
939//
940
941 Attendee *currentOrganizerAttendee = 0;
942 Attendee *newOrganizerAttendee = 0;
943
944 FreeBusyItem *anItem =
945 static_cast<FreeBusyItem *>( mGanttView->firstChild() );
946 while( anItem ) {
947 Attendee *attendee = anItem->attendee();
948 if( attendee->fullName() == mCurrentOrganizer )
949 currentOrganizerAttendee = attendee;
950
951 if( attendee->fullName() == newOrganizer )
952 newOrganizerAttendee = attendee;
953
954 anItem = static_cast<FreeBusyItem *>( anItem->nextSibling() );
955 }
956
957 int answer = KMessageBox::No;
958
959 if (currentOrganizerAttendee) {
960 answer = KMessageBox::questionYesNo( this, i18n("You are changing the organiser of "
961 "this event, who is also attending, "
962 "do you want to change that attendee "
963 "as well?") );
964 } else {
965 answer = KMessageBox::Yes;
966 }
967
968 if (answer==KMessageBox::Yes) {
969 if (currentOrganizerAttendee) {
970 removeAttendee( currentOrganizerAttendee );
971 }
972
973 if (!newOrganizerAttendee) {
974 Attendee *a = new Attendee( name, email, true );
975 insertAttendee( a, false );
976 mnewAttendees.append( a );
977 updateAttendee();
978 }
979 }
980
981 mCurrentOrganizer = newOrganizer;
982}
983
984bool KOEditorFreeBusy::eventFilter( TQObject *watched, TQEvent *event )
985{
986 if ( watched == mGanttView->timeHeaderWidget() &&
987 event->type() >= TQEvent::MouseButtonPress && event->type() <= TQEvent::MouseMove ) {
988 return true;
989 } else {
990 return KOAttendeeEditor::eventFilter( watched, event );
991 }
992}
993
994TQListViewItem* KOEditorFreeBusy::hasExampleAttendee() const
995{
996 for ( FreeBusyItem *item = static_cast<FreeBusyItem *>( mGanttView->firstChild() ); item;
997 item = static_cast<FreeBusyItem*>( item->nextSibling() ) ) {
998 Attendee *attendee = item->attendee();
999 Q_ASSERT( attendee );
1000 if ( isExampleAttendee( attendee ) )
1001 return item;
1002 }
1003 return 0;
1004}
1005
1006#include "koeditorfreebusy.moc"
virtual TQDateTime dtEnd() const
virtual TQDateTime dtStart() const
Common base class for attendee editor and free busy view.