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.
69 class 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 
131 void 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
159 void 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 
225 KOEditorFreeBusy::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 
369 KOEditorFreeBusy::~KOEditorFreeBusy()
370 {
371 }
372 
373 void 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 
389 void 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 
402 void KOEditorFreeBusy::clearAttendees()
403 {
404  mGanttView->clear();
405 }
406 
407 
408 void KOEditorFreeBusy::setUpdateEnabled( bool enabled )
409 {
410  mGanttView->setUpdateEnabled( enabled );
411 }
412 
413 bool KOEditorFreeBusy::updateEnabled() const
414 {
415  return mGanttView->getUpdateEnabled();
416 }
417 
418 
419 void 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 
435 void 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 
443 void KOEditorFreeBusy::setDateTimes( const TQDateTime &start, const TQDateTime &end )
444 {
445  slotUpdateGanttView( start, end );
446 }
447 
448 void 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 
456 void KOEditorFreeBusy::slotCenterOnStart()
457 {
458  mGanttView->centerTimeline( mDtStart );
459 }
460 
461 void KOEditorFreeBusy::slotZoomToTime()
462 {
463  mGanttView->zoomToFit();
464 }
465 
466 void 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 
481 void 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
497 void 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 
520 void 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 
538 void 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 
572 bool 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 
613 bool 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 
635 bool 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 
667 void 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 
709 void KOEditorFreeBusy::triggerReload()
710 {
711  mReloadTimer.start( 1000, true );
712 }
713 
714 void KOEditorFreeBusy::cancelReload()
715 {
716  mReloadTimer.stop();
717 }
718 
719 void KOEditorFreeBusy::manualReload()
720 {
721  mForceDownload = true;
722  reload();
723 }
724 
725 void KOEditorFreeBusy::autoReload()
726 {
727  mForceDownload = false;
728  reload();
729 }
730 
731 void 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 
746 void 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 
757 void 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 
806 KCal::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 
815 void 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 
825 void 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 
850 void 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 
862 void 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 
875 int 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 
888 void 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 
903 void 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 
924 void KOEditorFreeBusy::listViewClicked(int button, KDGanttViewItem * item)
925 {
926  if ( button == TQt::LeftButton && item == 0 )
927  addNewAttendee();
928 }
929 
930 void 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 
984 bool 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 
994 TQListViewItem* 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.