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,
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
37 #include "kogroupware.h"
38 #include "calendarview.h"
39 #include "freebusymanager.h"
41 #include "koincidenceeditor.h"
42 #include "mailscheduler.h"
44 #include <KCal/CalendarResources>
45 #include <KCal/IncidenceFormatter>
46 #include <KPIMUtils/Email>
49 #include <KMessageBox>
50 #include <KStandardDirs>
56 FreeBusyManager
*KOGroupware::mFreeBusyManager
= 0;
58 KOGroupware
*KOGroupware::mInstance
= 0;
60 KOGroupware
*KOGroupware::create( CalendarView
*view
,
61 KCal::CalendarResources
*calendar
)
64 mInstance
= new KOGroupware( view
, calendar
);
69 KOGroupware
*KOGroupware::instance()
71 // Doesn't create, that is the task of create()
72 Q_ASSERT( 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
;
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
148 const QStringList files
= dir
.entryList( QDir::Files
);
149 if ( files
.isEmpty() ) {
150 // No more files here
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] << "'";
160 t
.setCodec( "UTF-8" );
161 QString receiver
= KPIMUtils::firstEmailAddress( t
.readLine() );
162 QString iCal
= t
.readAll();
166 ScheduleMessage
*message
= mFormat
.parseScheduleMessage( mCalendar
, iCal
);
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." ),
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() );
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
);
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() );
221 // accept counter proposal
222 scheduler
.acceptCounterProposal( incidence
);
223 // send update to all attendees
224 sendICalMessage( mView
, iTIPRequest
, incidence
);
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 );
239 class KOInvitationFormatterHelper
: public InvitationFormatterHelper
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
,
256 // If there are no attendees, don't bother
257 if ( incidence
->attendees().isEmpty() ) {
261 bool isOrganizer
= KOPrefs::instance()->thatIsMe( incidence
->organizer().email() );
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
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.
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
281 if ( incidence
->attendees().count() > 1 ||
282 incidence
->attendees().first()->email() != incidence
->organizer().email() ) {
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" );
291 type
= incidence
->type();
293 QString txt
= i18n( "This %1 includes other people. "
294 "Should email be sent out to the attendees?",
296 rc
= KMessageBox::questionYesNoCancel(
297 parent
, txt
, i18n( "Group Scheduling Email" ),
298 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
302 } else if ( incidence
->type() == "Todo" ) {
303 if ( method
== iTIPRequest
) {
304 // This is an update to be sent to the organizer
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" ) {
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?" );
319 rc
= KMessageBox::questionYesNo(
320 parent
, txt
, QString(),
321 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
324 const QStringList myEmails
= KOPrefs::instance()->allEmails();
325 bool askConfirmation
= false;
326 for ( QStringList::ConstIterator it
= myEmails
.begin(); it
!= myEmails
.end(); ++it
) {
328 Attendee
*me
= incidence
->attendeeByMail(email
);
330 ( me
->status() == KCal::Attendee::Accepted
||
331 me
->status() == KCal::Attendee::Delegated
) ) {
332 askConfirmation
= true;
337 if ( !askConfirmation
) {
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
);
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
;
357 kWarning() << "Groupware messages for Journals are not implemented yet!";
361 if ( rc
== KMessageBox::Yes
) {
362 // We will be sending out a message here. Now make sure there is
364 if ( incidence
->summary().isEmpty() ) {
365 incidence
->setSummary( i18n( "<placeholder>No summary given</placeholder>" ) );
368 MailScheduler
scheduler( mCalendar
);
369 scheduler
.performTransaction( incidence
, method
);
371 } else if ( rc
== KMessageBox::No
) {
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
) {
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
);
397 MailScheduler
scheduler( calendar
);
398 scheduler
.performTransaction( newEvent
, iTIPCounter
);
402 #include "kogroupware.moc"