krop's commit fixes my problem in a better way, reverting
[kdepim.git] / korganizer / kogroupware.cpp
blobc25445881667e49530a01a90ae073abd32b1b510
1 /*
2 This file is part of the Groupware/KOrganizer integration.
4 Requires the Qt and KDE widget libraries, available at no cost at
5 http://www.trolltech.com and http://www.kde.org respectively
7 Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
8 <info@klaralvdalens-datakonsult.se>
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.
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.
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.
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 Qt library by Trolltech AS, Norway (or with modified versions
28 of Qt that use the same license as Qt), 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 Qt. 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.
37 #include "kogroupware.h"
38 #include "calendarview.h"
39 #include "freebusymanager.h"
40 #include "koprefs.h"
41 #include "koincidenceeditor.h"
42 #include "mailscheduler.h"
44 #include <KCal/CalendarResources>
45 #include <KCal/IncidenceFormatter>
46 #include <KPIMUtils/Email>
48 #include <KDirWatch>
49 #include <KMessageBox>
50 #include <KStandardDirs>
52 #include <QDir>
53 #include <QFile>
54 #include <QTimer>
56 FreeBusyManager *KOGroupware::mFreeBusyManager = 0;
58 KOGroupware *KOGroupware::mInstance = 0;
60 KOGroupware *KOGroupware::create( CalendarView *view,
61 KCal::CalendarResources *calendar )
63 if ( !mInstance ) {
64 mInstance = new KOGroupware( view, calendar );
66 return mInstance;
69 KOGroupware *KOGroupware::instance()
71 // Doesn't create, that is the task of create()
72 Q_ASSERT( mInstance );
73 return mInstance;
76 KOGroupware::KOGroupware( CalendarView *view, KCal::CalendarResources *cal )
77 : QObject( 0 ), mView( view ), mCalendar( cal ), mDoNotNotify( false )
79 setObjectName( "kmgroupware_instance" );
80 // Set up the dir watch of the three incoming dirs
81 KDirWatch *watcher = KDirWatch::self();
82 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.accepted/" ) );
83 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.tentative/" ) );
84 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.counter/" ) );
85 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.cancel/" ) );
86 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.reply/" ) );
87 watcher->addDir( KStandardDirs::locateLocal( "data", "korganizer/income.delegated/" ) );
88 connect( watcher, SIGNAL(dirty(const QString&)),
89 this, SLOT(incomingDirChanged(const QString&)) );
90 // Now set the ball rolling
91 QTimer::singleShot( 0, this, SLOT(initialCheckForChanges()) );
94 void KOGroupware::initialCheckForChanges()
96 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.accepted/" ) );
97 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.tentative/" ) );
98 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.counter/" ) );
99 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.cancel/" ) );
100 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.reply/" ) );
101 incomingDirChanged( KStandardDirs::locateLocal( "data", "korganizer/income.delegated/" ) );
103 if ( !mFreeBusyManager ) {
104 mFreeBusyManager = new FreeBusyManager( this );
105 mFreeBusyManager->setObjectName( "freebusymanager" );
106 mFreeBusyManager->setCalendar( mCalendar );
107 connect( mCalendar, SIGNAL(calendarChanged()),
108 mFreeBusyManager, SLOT(slotPerhapsUploadFB()) );
109 connect( mView, SIGNAL(newIncidenceChanger(IncidenceChangerBase*)),
110 this, SLOT(slotViewNewIncidenceChanger(IncidenceChangerBase*)) );
111 slotViewNewIncidenceChanger( mView->incidenceChanger() );
115 void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase *changer )
117 // Call slot perhapsUploadFB if an incidence was added, changed or removed
118 connect( changer, SIGNAL(incidenceAdded(Incidence*)),
119 mFreeBusyManager, SLOT(slotPerhapsUploadFB()) );
120 connect( changer, SIGNAL(incidenceChanged(Incidence*,Incidence*,int)),
121 mFreeBusyManager, SLOT(slotPerhapsUploadFB()) );
122 connect( changer, SIGNAL(incidenceChanged(Incidence*,Incidence*)),
123 mFreeBusyManager, SLOT(slotPerhapsUploadFB()) ) ;
124 connect( changer, SIGNAL(incidenceDeleted(Incidence*)),
125 mFreeBusyManager, SLOT(slotPerhapsUploadFB()) );
128 FreeBusyManager *KOGroupware::freeBusyManager()
130 return mFreeBusyManager;
133 void KOGroupware::incomingDirChanged( const QString &path )
135 const QString incomingDirName = KStandardDirs::locateLocal( "data","korganizer/" ) + "income.";
136 if ( !path.startsWith( incomingDirName ) ) {
137 kDebug() << "Wrong dir" << path;
138 return;
140 QString action = path.mid( incomingDirName.length() );
141 while ( action.length() > 0 && action[ action.length()-1 ] == '/' ) {
142 // Strip slashes at the end
143 action.truncate( action.length() - 1 );
146 // Handle accepted invitations
147 QDir dir( path );
148 const QStringList files = dir.entryList( QDir::Files );
149 if ( files.isEmpty() ) {
150 // No more files here
151 return;
153 // Read the file and remove it
154 QFile f( path + '/' + files[0] );
155 if ( !f.open( QIODevice::ReadOnly ) ) {
156 kError() << "Can't open file '" << files[0] << "'";
157 return;
159 QTextStream t( &f );
160 t.setCodec( "UTF-8" );
161 QString receiver = KPIMUtils::firstEmailAddress( t.readLine() );
162 QString iCal = t.readAll();
164 f.remove();
166 ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
167 if ( !message ) {
168 QString errorMessage;
169 if ( mFormat.exception() ) {
170 errorMessage = i18n( "Error message: %1", mFormat.exception()->message() );
172 kDebug() << "Error parsing" << errorMessage;
173 KMessageBox::detailedError( mView,
174 i18n( "Error while processing an invitation or update." ),
175 errorMessage );
176 return;
179 KCal::iTIPMethod method = static_cast<KCal::iTIPMethod>( message->method() );
180 KCal::ScheduleMessage::Status status = message->status();
181 KCal::Incidence *incidence = dynamic_cast<KCal::Incidence*>( message->event() );
182 if( !incidence ) {
183 delete message;
184 return;
186 MailScheduler scheduler( mCalendar );
187 if ( action.startsWith( QLatin1String( "accepted" ) ) ||
188 action.startsWith( QLatin1String( "tentative" ) ) ||
189 action.startsWith( QLatin1String( "delegated" ) ) ||
190 action.startsWith( QLatin1String( "counter" ) ) ) {
191 // Find myself and set my status. This can't be done in the scheduler,
192 // since this does not know the choice I made in the KMail bpf
193 KCal::Attendee::List attendees = incidence->attendees();
194 KCal::Attendee::List::ConstIterator it;
195 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
196 if ( (*it)->email() == receiver ) {
197 if ( action.startsWith( QLatin1String( "accepted" ) ) ) {
198 (*it)->setStatus( KCal::Attendee::Accepted );
199 } else if ( action.startsWith( QLatin1String( "tentative" ) ) ) {
200 (*it)->setStatus( KCal::Attendee::Tentative );
201 } else if ( KOPrefs::instance()->outlookCompatCounterProposals() &&
202 action.startsWith( QLatin1String( "counter" ) ) ) {
203 (*it)->setStatus( KCal::Attendee::Tentative );
204 } else if ( action.startsWith( QLatin1String( "delegated" ) ) ) {
205 (*it)->setStatus( KCal::Attendee::Delegated );
207 break;
210 if ( KOPrefs::instance()->outlookCompatCounterProposals() ||
211 !action.startsWith( QLatin1String( "counter" ) ) ) {
212 scheduler.acceptTransaction( incidence, method, status, receiver );
214 } else if ( action.startsWith( QLatin1String( "cancel" ) ) ) {
215 // Delete the old incidence, if one is present
216 scheduler.acceptTransaction( incidence, KCal::iTIPCancel, status, receiver );
217 } else if ( action.startsWith( QLatin1String( "reply" ) ) ) {
218 if ( method != iTIPCounter ) {
219 scheduler.acceptTransaction( incidence, method, status, QString() );
220 } else {
221 // accept counter proposal
222 scheduler.acceptCounterProposal( incidence );
223 // send update to all attendees
224 sendICalMessage( mView, iTIPRequest, incidence );
226 } else {
227 kError() << "Unknown incoming action" << action;
230 if ( action.startsWith( QLatin1String( "counter" ) ) ) {
231 mView->editIncidence( incidence, true );
232 KOIncidenceEditor *tmp = mView->editorDialog( incidence );
233 tmp->selectInvitationCounterProposal( true );
235 mView->updateView();
236 delete message;
239 class KOInvitationFormatterHelper : public InvitationFormatterHelper
241 public:
242 virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
245 /* This function sends mails if necessary, and makes sure the user really
246 * want to change his calendar.
248 * Return true means accept the changes
249 * Return false means revert the changes
251 bool KOGroupware::sendICalMessage( QWidget *parent,
252 KCal::iTIPMethod method,
253 Incidence *incidence, bool isDeleting,
254 bool statusChanged )
256 // If there are no attendees, don't bother
257 if ( incidence->attendees().isEmpty() ) {
258 return true;
261 bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
262 int rc = 0;
264 * There are two scenarios:
265 * o "we" are the organizer, where "we" means any of the identities or mail
266 * addresses known to Kontact/PIM. If there are attendees, we need to mail
267 * them all, even if one or more of them are also "us". Otherwise there
268 * would be no way to invite a resource or our boss, other identities we
269 * also manage.
270 * o "we: are not the organizer, which means we changed the completion status
271 * of a todo, or we changed our attendee status from, say, tentative to
272 * accepted. In both cases we only mail the organizer. All other changes
273 * bring us out of sync with the organizer, so we won't mail, if the user
274 * insists on applying them.
277 if ( isOrganizer ) {
278 /* We are the organizer. If there is more than one attendee, or if there is
279 * only one, and it's not the same as the organizer, ask the user to send
280 * mail. */
281 if ( incidence->attendees().count() > 1 ||
282 incidence->attendees().first()->email() != incidence->organizer().email() ) {
283 QString type;
284 if ( incidence->type() == "Event" ) {
285 type = i18nc( "incidence type is event", "event" );
286 } else if ( incidence->type() == "Todo" ) {
287 type = i18nc( "incidence type is to-do/task", "task" );
288 } else if ( incidence->type() == "Journal" ) {
289 type = i18nc( "incidence type is journal", "journal entry" );
290 } else {
291 type = incidence->type();
293 QString txt = i18n( "This %1 includes other people. "
294 "Should email be sent out to the attendees?",
295 type );
296 rc = KMessageBox::questionYesNoCancel(
297 parent, txt, i18n( "Group Scheduling Email" ),
298 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
299 } else {
300 return true;
302 } else if ( incidence->type() == "Todo" ) {
303 if ( method == iTIPRequest ) {
304 // This is an update to be sent to the organizer
305 method = iTIPReply;
307 // Ask if the user wants to tell the organizer about the current status
308 QString txt = i18n( "Do you want to send a status update to the "
309 "organizer of this task?" );
310 rc = KMessageBox::questionYesNo(
311 parent, txt, QString(),
312 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
313 } else if ( incidence->type() == "Event" ) {
314 QString txt;
315 if ( statusChanged && method == iTIPRequest ) {
316 txt = i18n( "Your status as an attendee of this event changed. "
317 "Do you want to send a status update to the event organizer?" );
318 method = iTIPReply;
319 rc = KMessageBox::questionYesNo(
320 parent, txt, QString(),
321 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
322 } else {
323 if ( isDeleting ) {
324 const QStringList myEmails = KOPrefs::instance()->allEmails();
325 bool askConfirmation = false;
326 for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
327 QString email = *it;
328 Attendee *me = incidence->attendeeByMail(email);
329 if ( me &&
330 ( me->status() == KCal::Attendee::Accepted ||
331 me->status() == KCal::Attendee::Delegated ) ) {
332 askConfirmation = true;
333 break;
337 if ( !askConfirmation ) {
338 return true;
341 txt = i18n( "You had previously accepted an invitation to this event. "
342 "Do you want to send an updated response to the organizer "
343 "declining the invitation?" );
344 rc = KMessageBox::questionYesNo(
345 parent, txt, i18n( "Group Scheduling Email" ),
346 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
347 setDoNotNotify( rc == KMessageBox::No );
348 } else {
349 txt = i18n( "You are not the organizer of this event. Editing it will "
350 "bring your calendar out of sync with the organizer's calendar. "
351 "Do you really want to edit it?" );
352 rc = KMessageBox::warningYesNo( parent, txt );
353 return rc == KMessageBox::Yes;
356 } else {
357 kWarning() << "Groupware messages for Journals are not implemented yet!";
358 return true;
361 if ( rc == KMessageBox::Yes ) {
362 // We will be sending out a message here. Now make sure there is
363 // some summary
364 if ( incidence->summary().isEmpty() ) {
365 incidence->setSummary( i18n( "<placeholder>No summary given</placeholder>" ) );
367 // Send the mail
368 MailScheduler scheduler( mCalendar );
369 scheduler.performTransaction( incidence, method );
370 return true;
371 } else if ( rc == KMessageBox::No ) {
372 return true;
373 } else {
374 return false;
378 void KOGroupware::sendCounterProposal( KCal::Calendar *calendar,
379 KCal::Event *oldEvent,
380 KCal::Event *newEvent ) const
382 if ( !oldEvent || !newEvent || *oldEvent == *newEvent ||
383 !KOPrefs::instance()->mUseGroupwareCommunication ) {
384 return;
386 if ( KOPrefs::instance()->outlookCompatCounterProposals() ) {
387 Incidence *tmp = oldEvent->clone();
388 tmp->setSummary( i18n( "Counter proposal: %1", newEvent->summary() ) );
389 tmp->setDescription( newEvent->description() );
390 tmp->addComment( i18n( "Proposed new meeting time: %1 - %2",
391 IncidenceFormatter::dateToString( newEvent->dtStart() ),
392 IncidenceFormatter::dateToString( newEvent->dtEnd() ) ) );
393 MailScheduler scheduler( calendar );
394 scheduler.performTransaction( tmp, KCal::iTIPReply );
395 delete tmp;
396 } else {
397 MailScheduler scheduler( calendar );
398 scheduler.performTransaction( newEvent, iTIPCounter );
402 #include "kogroupware.moc"