korganizer

kogroupware.cpp
1 /*
2  This file is part of the Groupware/KOrganizer integration.
3 
4  Requires the TQt and KDE widget libraries, available at no cost at
5  http://www.trolltech.com and http://www.kde.org respectively
6 
7  Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
8  <info@klaralvdalens-datakonsult.se>
9 
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  GNU General Public License for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23  MA 02110-1301, USA.
24 
25  In addition, as a special exception, the copyright holders give
26  permission to link the code of this program with any edition of
27  the TQt library by Trolltech AS, Norway (or with modified versions
28  of TQt that use the same license as TQt), and distribute linked
29  combinations including the two. You must obey the GNU General
30  Public License in all respects for all of the code used other than
31  TQt. If you modify this file, you may extend this exception to
32  your version of the file, but you are not obligated to do so. If
33  you do not wish to do so, delete this exception statement from
34  your version.
35 */
36 
37 #include "kogroupware.h"
38 #include "freebusymanager.h"
39 #include "calendarview.h"
40 #include "mailscheduler.h"
41 #include "koprefs.h"
42 #include "koincidenceeditor.h"
43 #include <libemailfunctions/email.h>
44 #include <libkcal/attendee.h>
45 #include <libkcal/journal.h>
46 #include <libkcal/incidenceformatter.h>
47 #include <kdebug.h>
48 #include <tdemessagebox.h>
49 #include <kstandarddirs.h>
50 #include <kdirwatch.h>
51 #include <tqfile.h>
52 #include <tqregexp.h>
53 #include <tqdir.h>
54 #include <tqtimer.h>
55 
56 FreeBusyManager *KOGroupware::mFreeBusyManager = 0;
57 
58 KOGroupware *KOGroupware::mInstance = 0;
59 
60 KOGroupware *KOGroupware::create( CalendarView *view,
61  KCal::CalendarResources *calendar )
62 {
63  if( !mInstance )
64  mInstance = new KOGroupware( view, calendar );
65  return mInstance;
66 }
67 
68 KOGroupware *KOGroupware::instance()
69 {
70  // Doesn't create, that is the task of create()
71  Q_ASSERT( mInstance );
72  return mInstance;
73 }
74 
75 
76 KOGroupware::KOGroupware( CalendarView* view, KCal::CalendarResources* cal )
77  : TQObject( 0, "kmgroupware_instance" ), mView( view ), mCalendar( cal ), mDoNotNotify( false )
78 {
79  // Set up the dir watch of the three incoming dirs
80  KDirWatch* watcher = KDirWatch::self();
81  watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) );
82  watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) );
83  watcher->addDir( locateLocal( "data", "korganizer/income.counter/" ) );
84  watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) );
85  watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) );
86  watcher->addDir( locateLocal( "data", "korganizer/income.delegated/" ) );
87  connect( watcher, TQ_SIGNAL( dirty( const TQString& ) ),
88  this, TQ_SLOT( incomingDirChanged( const TQString& ) ) );
89  // Now set the ball rolling
90  TQTimer::singleShot( 0, this, TQ_SLOT(initialCheckForChanges()) );
91 }
92 
93 void KOGroupware::initialCheckForChanges()
94 {
95  incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) );
96  incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) );
97  incomingDirChanged( locateLocal( "data", "korganizer/income.counter/" ) );
98  incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) );
99  incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) );
100  incomingDirChanged( locateLocal( "data", "korganizer/income.delegated/" ) );
101 }
102 
103 void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase* changer )
104 {
105  // Call slot perhapsUploadFB if an incidence was added, changed or removed
106  connect( changer, TQ_SIGNAL( incidenceAdded( Incidence* ) ),
107  mFreeBusyManager, TQ_SLOT( slotPerhapsUploadFB() ) );
108  connect( changer, TQ_SIGNAL( incidenceChanged( Incidence*, Incidence*, KOGlobals::WhatChanged ) ),
109  mFreeBusyManager, TQ_SLOT( slotPerhapsUploadFB() ) );
110  connect( changer, TQ_SIGNAL( incidenceDeleted( Incidence * ) ),
111  mFreeBusyManager, TQ_SLOT( slotPerhapsUploadFB() ) );
112 }
113 
114 FreeBusyManager *KOGroupware::freeBusyManager()
115 {
116  if ( !mFreeBusyManager ) {
117  mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
118  mFreeBusyManager->setCalendar( mCalendar );
119  connect( mCalendar, TQ_SIGNAL( calendarChanged() ),
120  mFreeBusyManager, TQ_SLOT( slotPerhapsUploadFB() ) );
121  connect( mView, TQ_SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ),
122  this, TQ_SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) );
123  slotViewNewIncidenceChanger( mView->incidenceChanger() );
124  }
125 
126  return mFreeBusyManager;
127 }
128 
129 void KOGroupware::incomingDirChanged( const TQString& path )
130 {
131  const TQString incomingDirName = locateLocal( "data","korganizer/" )
132  + "income.";
133  if ( !path.startsWith( incomingDirName ) ) {
134  kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
135  return;
136  }
137  TQString action = path.mid( incomingDirName.length() );
138  while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
139  // Strip slashes at the end
140  action.truncate( action.length()-1 );
141 
142  // Handle accepted invitations
143  TQDir dir( path );
144  const TQStringList files = dir.entryList( TQDir::Files );
145  if ( files.isEmpty() )
146  // No more files here
147  return;
148 
149  // Read the file and remove it
150  TQFile f( path + "/" + files[0] );
151  if (!f.open(IO_ReadOnly)) {
152  kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
153  return;
154  }
155  TQTextStream t(&f);
156  t.setEncoding( TQTextStream::UnicodeUTF8 );
157  TQString receiver = KPIM::getFirstEmailAddress( t.readLine() );
158  TQString iCal = t.read();
159 
160  f.remove();
161 
162  ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
163  if ( !message ) {
164  TQString errorMessage;
165  if (mFormat.exception())
166  errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() );
167  kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing "
168  << errorMessage << endl;
169  KMessageBox::detailedError( mView,
170  i18n("Error while processing an invitation or update."),
171  errorMessage );
172  return;
173  }
174 
175  KCal::Scheduler::Method method =
176  static_cast<KCal::Scheduler::Method>( message->method() );
177  KCal::ScheduleMessage::Status status = message->status();
178  KCal::Incidence* incidence =
179  dynamic_cast<KCal::Incidence*>( message->event() );
180  if(!incidence) {
181  delete message;
182  return;
183  }
184  KCal::MailScheduler scheduler( mCalendar );
185  if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" )
186  || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) {
187  // Find myself and set my status. This can't be done in the scheduler,
188  // since this does not know the choice I made in the KMail bpf
189  KCal::Attendee::List attendees = incidence->attendees();
190  KCal::Attendee::List::ConstIterator it;
191  for ( it = attendees.begin(); it != attendees.end(); ++it ) {
192  if( (*it)->email() == receiver ) {
193  if ( action.startsWith( "accepted" ) )
194  (*it)->setStatus( KCal::Attendee::Accepted );
195  else if ( action.startsWith( "tentative" ) )
196  (*it)->setStatus( KCal::Attendee::Tentative );
197  else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) )
198  (*it)->setStatus( KCal::Attendee::Tentative );
199  else if ( action.startsWith( "delegated" ) )
200  (*it)->setStatus( KCal::Attendee::Delegated );
201  break;
202  }
203  }
204  if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) )
205  scheduler.acceptTransaction( incidence, method, status, receiver );
206  } else if ( action.startsWith( "cancel" ) )
207  // Delete the old incidence, if one is present
208  scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status, receiver );
209  else if ( action.startsWith( "reply" ) ) {
210  if ( method != Scheduler::Counter ) {
211  scheduler.acceptTransaction( incidence, method, status );
212  } else {
213  // accept counter proposal
214  scheduler.acceptCounterProposal( incidence );
215  // send update to all attendees
216  sendICalMessage( mView, Scheduler::Request, incidence, KOGlobals::INCIDENCEEDITED, false );
217  }
218  } else
219  kdError(5850) << "Unknown incoming action " << action << endl;
220 
221  if ( action.startsWith( "counter" ) ) {
222  mView->editIncidence( incidence, TQDate(), true );
223  KOIncidenceEditor *tmp = mView->editorDialog( incidence );
224  tmp->selectInvitationCounterProposal( true );
225  }
226  mView->updateView();
227 }
228 
229 class KOInvitationFormatterHelper : public InvitationFormatterHelper
230 {
231  public:
232  virtual TQString generateLinkURL( const TQString &id ) { return "kmail:groupware_request_" + id; }
233 };
234 
235 /* This function sends mails if necessary, and makes sure the user really
236  * want to change his calendar.
237  *
238  * Return true means accept the changes
239  * Return false means revert the changes
240  */
241 bool KOGroupware::sendICalMessage( TQWidget* parent,
243  Incidence* incidence,
244  KOGlobals::HowChanged action,
245  bool attendeeStatusChanged,
246  int dontAskForGroupware )
247 {
248  // If there are no attendees, don't bother
249  if( incidence->attendees().isEmpty() )
250  return true;
251 
252  bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
253  int rc = 0;
254  /*
255  * There are two scenarios:
256  * o "we" are the organizer, where "we" means any of the identities or mail
257  * addresses known to Kontact/PIM. If there are attendees, we need to mail
258  * them all, even if one or more of them are also "us". Otherwise there
259  * would be no way to invite a resource or our boss, other identities we
260  * also manage.
261  * o "we: are not the organizer, which means we changed the completion status
262  * of a todo, or we changed our attendee status from, say, tentative to
263  * accepted. In both cases we only mail the organizer. All other changes
264  * bring us out of sync with the organizer, so we won't mail, if the user
265  * insists on applying them.
266  */
267 
268  if ( dontAskForGroupware == 1 ) {
269  rc = KMessageBox::Yes;
270  }
271  else if ( dontAskForGroupware == 2 ) {
272  rc = KMessageBox::No;
273  }
274  else {
275  if ( isOrganizer ) {
276  /* We are the organizer. If there is more than one attendee, or if there is
277  * only one, and it's not the same as the organizer, ask the user to send
278  * mail. */
279  if ( incidence->attendees().count() > 1
280  || incidence->attendees().first()->email() != incidence->organizer().email() ) {
281 
282  TQString txt;
283  switch( action ) {
284  case KOGlobals::INCIDENCEEDITED:
285  txt = i18n( "You changed the invitation \"%1\".\n"
286  "Do you want to email the attendees an update message?" ).
287  arg( incidence->summary() );
288  break;
289  case KOGlobals::INCIDENCEDELETED:
290  Q_ASSERT( incidence->type() == "Event" || incidence->type() == "Todo" );
291  if ( incidence->type() == "Event" ) {
292  txt = i18n( "You removed the invitation \"%1\".\n"
293  "Do you want to email the attendees that the event is canceled?" ).
294  arg( incidence->summary() );
295  } else if ( incidence->type() == "Todo" ) {
296  txt = i18n( "You removed the invitation \"%1\".\n"
297  "Do you want to email the attendees that the todo is canceled?" ).
298  arg( incidence->summary() );
299  }
300  break;
301  case KOGlobals::INCIDENCEADDED:
302  if ( incidence->type() == "Event" ) {
303  txt = i18n( "The event \"%1\" includes other people.\n"
304  "Do you want to email the invitation to the attendees?" ).
305  arg( incidence->summary() );
306  } else if ( incidence->type() == "Todo" ) {
307  txt = i18n( "The todo \"%1\" includes other people.\n"
308  "Do you want to email the invitation to the attendees?" ).
309  arg( incidence->summary() );
310  } else {
311  txt = i18n( "This incidence includes other people. "
312  "Should an email be sent to the attendees?" );
313  }
314  break;
315  default:
316  kdError() << "Unsupported HowChanged action" << int( action ) << endl;
317  break;
318  }
319 
320  rc = KMessageBox::questionYesNo(
321  parent, txt, i18n( "Group Scheduling Email" ),
322  KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
323  } else {
324  return true;
325  }
326  } else if( incidence->type() == "Todo" ) {
327  if( method == Scheduler::Request )
328  // This is an update to be sent to the organizer
329  method = Scheduler::Reply;
330 
331  // Ask if the user wants to tell the organizer about the current status
332  TQString txt = i18n( "Do you want to send a status update to the "
333  "organizer of this task?");
334  rc = KMessageBox::questionYesNo( parent, txt, TQString(), i18n("Send Update"), i18n("Do Not Send") );
335  } else if( incidence->type() == "Event" ) {
336  TQString txt;
337  if ( attendeeStatusChanged && method == Scheduler::Request ) {
338  txt = i18n( "Your status as an attendee of this event changed. "
339  "Do you want to send a status update to the event organizer?" );
340  method = Scheduler::Reply;
341  rc = KMessageBox::questionYesNo( parent, txt, TQString(), i18n("Send Update"), i18n("Do Not Send") );
342  } else {
343  if( action == KOGlobals::INCIDENCEDELETED ) {
344  const TQStringList myEmails = KOPrefs::instance()->allEmails();
345  bool askConfirmation = false;
346  for ( TQStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
347  TQString email = *it;
348  Attendee *me = incidence->attendeeByMail(email);
349  if (me && (me->status()==KCal::Attendee::Accepted || me->status()==KCal::Attendee::Delegated)) {
350  askConfirmation = true;
351  break;
352  }
353  }
354 
355  if ( !askConfirmation ) {
356  return true;
357  }
358 
359  txt = i18n( "You had previously accepted an invitation to this event. "
360  "Do you want to send an updated response to the organizer "
361  "declining the invitation?" );
362  rc = KMessageBox::questionYesNo(
363  parent, txt, i18n( "Group Scheduling Email" ),
364  KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
365  setDoNotNotify( rc == KMessageBox::No );
366  } else {
367  txt = i18n( "You are not the organizer of this event. Editing it will "
368  "bring your calendar out of sync with the organizer's calendar. "
369  "Do you really want to edit it?" );
370  rc = KMessageBox::warningYesNo( parent, txt );
371  return ( rc == KMessageBox::Yes );
372  }
373  }
374  } else {
375  kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
376  return true;
377  }
378  }
379 
380  if ( rc == KMessageBox::Yes ) {
381  // We will be sending out a message here. Now make sure there is
382  // some summary
383  if( incidence->summary().isEmpty() )
384  incidence->setSummary( i18n("<No summary given>") );
385 
386  // Send the mail
387  KCal::MailScheduler scheduler( mCalendar );
388  scheduler.performTransaction( incidence, method );
389 
390  return true;
391  } else if ( rc == KMessageBox::No ) {
392  return true;
393  } else {
394  return false;
395  }
396 }
397 
398 void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const
399 {
400  if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication )
401  return;
402  if ( KOPrefs::instance()->outlookCompatCounterProposals() ) {
403  Incidence* tmp = oldEvent->clone();
404  tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) );
405  tmp->setDescription( newEvent->description() );
406  tmp->addComment( i18n("Proposed new meeting time: %1 - %2").
407  arg( IncidenceFormatter::dateToString( newEvent->dtStart() ),
408  IncidenceFormatter::dateToString( newEvent->dtEnd() ) ) );
409  KCal::MailScheduler scheduler( calendar );
410  scheduler.performTransaction( tmp, Scheduler::Reply );
411  delete tmp;
412  } else {
413  KCal::MailScheduler scheduler( calendar );
414  scheduler.performTransaction( newEvent, Scheduler::Counter );
415  }
416 }
417 
418 #include "kogroupware.moc"
This is the main calendar widget.
Definition: calendarview.h:82
PartStat status() const
virtual TQDateTime dtEnd() const
Event * clone()
const Attendee::List & attendees() const
Attendee * attendeeByMail(const TQString &) const
void addComment(const TQString &comment)
virtual TQDateTime dtStart() const
void setSummary(const TQString &summary)
TQString description() const
void setDescription(const TQString &description)
TQString summary() const
IncidenceBase * event()
This is the base class for the calendar component editors.
bool view(TQWidget *parent, Attachment *attachment)