New Appointment -> New Task
[kdepim.git] / calendarsupport / groupware.cpp
blob4d036f40bf970373c1ba8cf142e9a7d6c1acedc6
1 /*
2 Requires the Qt and KDE widget libraries, available at no cost at
3 http://www.trolltech.com and http://www.kde.org respectively
5 Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB
6 <info@klaralvdalens-datakonsult.se>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 MA 02110-1301, USA.
23 In addition, as a special exception, the copyright holders give
24 permission to link the code of this program with any edition of
25 the Qt library by Trolltech AS, Norway (or with modified versions
26 of Qt that use the same license as Qt), and distribute linked
27 combinations including the two. You must obey the GNU General
28 Public License in all respects for all of the code used other than
29 Qt. If you modify this file, you may extend this exception to
30 your version of the file, but you are not obligated to do so. If
31 you do not wish to do so, delete this exception statement from
32 your version.
35 #include "groupware.h"
36 #include "calendaradaptor.h"
37 #include "kcalprefs.h"
38 #include "mailscheduler.h"
40 #include <KCalUtils/IncidenceFormatter>
41 #include <KCalUtils/Stringify>
43 #include <KLocale>
44 #include <KMessageBox>
46 using namespace CalendarSupport;
48 Groupware *Groupware::mInstance = 0;
50 GroupwareUiDelegate::~GroupwareUiDelegate()
54 Groupware *Groupware::create( CalendarSupport::Calendar *calendar, GroupwareUiDelegate *delegate )
56 if ( !mInstance ) {
57 mInstance = new Groupware( calendar, delegate );
59 return mInstance;
62 Groupware *Groupware::instance()
64 // Doesn't create, that is the task of create()
65 Q_ASSERT( mInstance );
66 return mInstance;
69 Groupware::Groupware( CalendarSupport::Calendar *cal, GroupwareUiDelegate *delegate )
70 : QObject( 0 ), mCalendar( cal ), mDelegate( delegate ), mDoNotNotify( false )
72 setObjectName( QLatin1String( "kmgroupware_instance" ) );
75 bool Groupware::handleInvitation( const QString &receiver, const QString &iCal,
76 const QString &type )
78 const QString action = type;
80 CalendarAdaptor::Ptr adaptor( new CalendarAdaptor( mCalendar, 0 ) );
81 KCalCore::ScheduleMessage *message = mFormat.parseScheduleMessage( adaptor, iCal );
82 if ( !message ) {
83 QString errorMessage;
84 if ( mFormat.exception() ) {
85 errorMessage =
86 i18n( "Error message: %1", KCalUtils::Stringify::errorMessage( *mFormat.exception() ) );
88 kDebug() << "Error parsing" << errorMessage;
89 KMessageBox::detailedError(
91 i18n( "Error while processing an invitation or update." ),
92 errorMessage );
93 return false;
96 KCalCore::iTIPMethod method = static_cast<KCalCore::iTIPMethod>( message->method() );
97 KCalCore::ScheduleMessage::Status status = message->status();
98 KCalCore::Incidence::Ptr incidence = message->event().staticCast<KCalCore::Incidence>();
99 if ( !incidence ) {
100 delete message;
101 return false;
103 MailScheduler scheduler( mCalendar );
104 if ( action.startsWith( QLatin1String( "accepted" ) ) ||
105 action.startsWith( QLatin1String( "tentative" ) ) ||
106 action.startsWith( QLatin1String( "delegated" ) ) ||
107 action.startsWith( QLatin1String( "counter" ) ) ) {
108 // Find myself and set my status. This can't be done in the scheduler,
109 // since this does not know the choice I made in the KMail bpf
110 KCalCore::Attendee::List attendees = incidence->attendees();
111 KCalCore::Attendee::List::ConstIterator it;
112 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
113 if ( (*it)->email() == receiver ) {
114 if ( action.startsWith( QLatin1String( "accepted" ) ) ) {
115 (*it)->setStatus( KCalCore::Attendee::Accepted );
116 } else if ( action.startsWith( QLatin1String( "tentative" ) ) ) {
117 (*it)->setStatus( KCalCore::Attendee::Tentative );
118 } else if ( KCalPrefs::instance()->outlookCompatCounterProposals() &&
119 action.startsWith( QLatin1String( "counter" ) ) ) {
120 (*it)->setStatus( KCalCore::Attendee::Tentative );
121 } else if ( action.startsWith( QLatin1String( "delegated" ) ) ) {
122 (*it)->setStatus( KCalCore::Attendee::Delegated );
124 break;
127 if ( KCalPrefs::instance()->outlookCompatCounterProposals() ||
128 !action.startsWith( QLatin1String( "counter" ) ) ) {
129 scheduler.acceptTransaction( incidence, method, status, receiver );
131 } else if ( action.startsWith( QLatin1String( "cancel" ) ) ) {
132 // Delete the old incidence, if one is present
133 scheduler.acceptTransaction( incidence, KCalCore::iTIPCancel, status, receiver );
134 } else if ( action.startsWith( QLatin1String( "reply" ) ) ) {
135 if ( method != KCalCore::iTIPCounter ) {
136 scheduler.acceptTransaction( incidence, method, status, QString() );
137 } else {
138 // accept counter proposal
139 scheduler.acceptCounterProposal( incidence );
140 // send update to all attendees
141 sendICalMessage( 0, KCalCore::iTIPRequest, incidence,
142 IncidenceChanger::INCIDENCEEDITED, false );
144 } else {
145 kError() << "Unknown incoming action" << action;
148 if ( mDelegate && action.startsWith( QLatin1String( "counter" ) ) ) {
149 Akonadi::Item item;
150 item.setPayload( KCalCore::Incidence::Ptr( incidence->clone() ) );
151 mDelegate->requestIncidenceEditor( item );
153 delete message;
154 return true;
157 class KOInvitationFormatterHelper : public KCalUtils::InvitationFormatterHelper
159 public:
160 virtual QString generateLinkURL( const QString &id )
162 return QLatin1String( "kmail:groupware_request_" ) + id;
166 /* This function sends mails if necessary, and makes sure the user really
167 * want to change his calendar.
169 * Return true means accept the changes
170 * Return false means revert the changes
172 bool Groupware::sendICalMessage( QWidget *parent,
173 KCalCore::iTIPMethod method,
174 const KCalCore::Incidence::Ptr &incidence,
175 IncidenceChanger::HowChanged action,
176 bool attendeeStatusChanged )
178 // If there are no attendees, don't bother
179 if ( incidence->attendees().isEmpty() ) {
180 return true;
183 bool isOrganizer = KCalPrefs::instance()->thatIsMe( incidence->organizer()->email() );
184 int rc = 0;
186 * There are two scenarios:
187 * o "we" are the organizer, where "we" means any of the identities or mail
188 * addresses known to Kontact/PIM. If there are attendees, we need to mail
189 * them all, even if one or more of them are also "us". Otherwise there
190 * would be no way to invite a resource or our boss, other identities we
191 * also manage.
192 * o "we: are not the organizer, which means we changed the completion status
193 * of a todo, or we changed our attendee status from, say, tentative to
194 * accepted. In both cases we only mail the organizer. All other changes
195 * bring us out of sync with the organizer, so we won't mail, if the user
196 * insists on applying them.
199 if ( isOrganizer ) {
200 /* We are the organizer. If there is more than one attendee, or if there is
201 * only one, and it's not the same as the organizer, ask the user to send
202 * mail. */
203 if ( incidence->attendees().count() > 1 ||
204 incidence->attendees().first()->email() != incidence->organizer()->email() ) {
206 QString txt;
207 switch( action ) {
208 case IncidenceChanger::INCIDENCEEDITED:
209 txt = i18n( "You changed the invitation \"%1\".\n"
210 "Do you want to email the attendees an update message?",
211 incidence->summary() );
212 break;
213 case IncidenceChanger::INCIDENCEDELETED:
214 if ( incidence->type() == KCalCore::IncidenceBase::TypeEvent ) {
215 txt = i18n( "You removed the invitation \"%1\".\n"
216 "Do you want to email the attendees that the event is canceled?",
217 incidence->summary() );
218 } else if ( incidence->type() == KCalCore::IncidenceBase::TypeTodo ) {
219 txt = i18n( "You removed the invitation \"%1\".\n"
220 "Do you want to email the attendees that the todo is canceled?",
221 incidence->summary() );
222 } else if ( incidence->type() == KCalCore::IncidenceBase::TypeJournal ) {
223 txt = i18n( "You removed the invitation \"%1\".\n"
224 "Do you want to email the attendees that the journal is canceled?",
225 incidence->summary() );
227 break;
228 case IncidenceChanger::INCIDENCEADDED:
229 if ( incidence->type() == KCalCore::IncidenceBase::TypeEvent ) {
230 txt = i18n( "The event \"%1\" includes other people.\n"
231 "Do you want to email the invitation to the attendees?",
232 incidence->summary() );
233 } else if ( incidence->type() == KCalCore::IncidenceBase::TypeTodo ) {
234 txt = i18n( "The todo \"%1\" includes other people.\n"
235 "Do you want to email the invitation to the attendees?",
236 incidence->summary() );
237 } else {
238 txt = i18n( "This incidence includes other people. "
239 "Should an email be sent to the attendees?" );
241 break;
242 default:
243 kError() << "Unsupported HowChanged action" << int( action );
244 break;
247 rc = KMessageBox::questionYesNo(
248 parent, txt, i18n( "Group Scheduling Email" ),
249 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
250 } else {
251 return true;
253 } else if ( incidence->type() == KCalCore::IncidenceBase::TypeTodo ) {
254 if ( method == KCalCore::iTIPRequest ) {
255 // This is an update to be sent to the organizer
256 method = KCalCore::iTIPReply;
258 // Ask if the user wants to tell the organizer about the current status
259 QString txt = i18n( "Do you want to send a status update to the "
260 "organizer of this task?" );
261 rc = KMessageBox::questionYesNo(
262 parent, txt, QString(),
263 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
264 } else if ( incidence->type() == KCalCore::IncidenceBase::TypeEvent ) {
265 QString txt;
266 if ( attendeeStatusChanged && method == KCalCore::iTIPRequest ) {
267 txt = i18n( "Your status as an attendee of this event changed. "
268 "Do you want to send a status update to the event organizer?" );
269 method = KCalCore::iTIPReply;
270 rc = KMessageBox::questionYesNo(
271 parent, txt, QString(),
272 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
273 } else {
274 if ( action == IncidenceChanger::INCIDENCEDELETED ) {
275 const QStringList myEmails = KCalPrefs::instance()->allEmails();
276 bool askConfirmation = false;
277 for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
278 QString email = *it;
279 KCalCore::Attendee::Ptr me = incidence->attendeeByMail(email);
280 if ( me &&
281 ( me->status() == KCalCore::Attendee::Accepted ||
282 me->status() == KCalCore::Attendee::Delegated ) ) {
283 askConfirmation = true;
284 break;
288 if ( !askConfirmation ) {
289 return true;
292 txt = i18n( "You had previously accepted an invitation to this event. "
293 "Do you want to send an updated response to the organizer "
294 "declining the invitation?" );
295 rc = KMessageBox::questionYesNo(
296 parent, txt, i18n( "Group Scheduling Email" ),
297 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
298 setDoNotNotify( rc == KMessageBox::No );
299 } else if ( action == IncidenceChanger::INCIDENCEADDED ) {
300 // We just got this event from the groupware stack, so add it right away
301 // the notification mail was sent on the KMail side.
302 return true;
303 } else {
304 txt = i18n( "You are not the organizer of this event. Editing it will "
305 "bring your calendar out of sync with the organizer's calendar. "
306 "Do you really want to edit it?" );
307 rc = KMessageBox::warningYesNo( parent, txt );
308 return rc == KMessageBox::Yes;
311 } else {
312 kWarning() << "Groupware messages for Journals are not implemented yet!";
313 return true;
316 if ( rc == KMessageBox::Yes ) {
317 // We will be sending out a message here. Now make sure there is
318 // some summary
319 if ( incidence->summary().isEmpty() ) {
320 incidence->setSummary( i18n( "<placeholder>No summary given</placeholder>" ) );
322 // Send the mail
323 MailScheduler scheduler( mCalendar );
324 if ( scheduler.performTransaction( incidence, method ) ) {
325 return true;
327 rc = KMessageBox::questionYesNo(
328 parent,
329 i18n( "Sending group scheduling email failed." ),
330 i18n( "Group Scheduling Email" ),
331 KGuiItem( i18n( "Abort Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
332 return rc == KMessageBox::No;
333 } else if ( rc == KMessageBox::No ) {
334 return true;
335 } else {
336 return false;
340 void Groupware::sendCounterProposal( KCalCore::Event::Ptr oldEvent,
341 KCalCore::Event::Ptr newEvent ) const
343 if ( !oldEvent || !newEvent || *oldEvent == *newEvent ||
344 !KCalPrefs::instance()->mUseGroupwareCommunication ) {
345 return;
347 if ( KCalPrefs::instance()->outlookCompatCounterProposals() ) {
348 KCalCore::Incidence::Ptr tmp = KCalCore::Incidence::Ptr( oldEvent->clone() );
349 tmp->setSummary( i18n( "Counter proposal: %1", newEvent->summary() ) );
350 tmp->setDescription( newEvent->description() );
351 tmp->addComment( i18n( "Proposed new meeting time: %1 - %2",
352 KCalUtils::IncidenceFormatter::dateToString( newEvent->dtStart() ),
353 KCalUtils::IncidenceFormatter::dateToString( newEvent->dtEnd() ) ) );
354 MailScheduler scheduler( mCalendar );
355 scheduler.performTransaction( tmp, KCalCore::iTIPReply );
356 } else {
357 MailScheduler scheduler( mCalendar );
358 scheduler.performTransaction( newEvent, KCalCore::iTIPCounter );
362 #include "groupware.moc"