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 <tdestandarddirs.h>
50#include <kdirwatch.h>
51#include <tqfile.h>
52#include <tqregexp.h>
53#include <tqdir.h>
54#include <tqtimer.h>
55
56FreeBusyManager *KOGroupware::mFreeBusyManager = 0;
57
58KOGroupware *KOGroupware::mInstance = 0;
59
60KOGroupware *KOGroupware::create( CalendarView *view,
61 KCal::CalendarResources *calendar )
62{
63 if( !mInstance )
64 mInstance = new KOGroupware( view, calendar );
65 return mInstance;
66}
67
68KOGroupware *KOGroupware::instance()
69{
70 // Doesn't create, that is the task of create()
71 Q_ASSERT( mInstance );
72 return mInstance;
73}
74
75
76KOGroupware::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
93void 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
103void 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
114FreeBusyManager *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
129void 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
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
229class 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 */
241bool 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
398void 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()
Attendee * attendeeByMail(const TQString &) const
const Attendee::List & attendees() 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)