kogroupware.cpp
00001 /* 00002 This file is part of the Groupware/KOrganizer integration. 00003 00004 Requires the TQt and KDE widget libraries, available at no cost at 00005 http://www.trolltech.com and http://www.kde.org respectively 00006 00007 Copyright (c) 2002-2004 Klarälvdalens Datakonsult AB 00008 <info@klaralvdalens-datakonsult.se> 00009 00010 This program is free software; you can redistribute it and/or modify 00011 it under the terms of the GNU General Public License as published by 00012 the Free Software Foundation; either version 2 of the License, or 00013 (at your option) any later version. 00014 00015 This program is distributed in the hope that it will be useful, 00016 but WITHOUT ANY WARRANTY; without even the implied warranty of 00017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00018 GNU General Public License for more details. 00019 00020 You should have received a copy of the GNU General Public License 00021 along with this program; if not, write to the Free Software 00022 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00023 MA 02110-1301, USA. 00024 00025 In addition, as a special exception, the copyright holders give 00026 permission to link the code of this program with any edition of 00027 the TQt library by Trolltech AS, Norway (or with modified versions 00028 of TQt that use the same license as TQt), and distribute linked 00029 combinations including the two. You must obey the GNU General 00030 Public License in all respects for all of the code used other than 00031 TQt. If you modify this file, you may extend this exception to 00032 your version of the file, but you are not obligated to do so. If 00033 you do not wish to do so, delete this exception statement from 00034 your version. 00035 */ 00036 00037 #include "kogroupware.h" 00038 #include "freebusymanager.h" 00039 #include "calendarview.h" 00040 #include "mailscheduler.h" 00041 #include "koprefs.h" 00042 #include "koincidenceeditor.h" 00043 #include <libemailfunctions/email.h> 00044 #include <libkcal/attendee.h> 00045 #include <libkcal/journal.h> 00046 #include <libkcal/incidenceformatter.h> 00047 #include <kdebug.h> 00048 #include <tdemessagebox.h> 00049 #include <kstandarddirs.h> 00050 #include <kdirwatch.h> 00051 #include <tqfile.h> 00052 #include <tqregexp.h> 00053 #include <tqdir.h> 00054 #include <tqtimer.h> 00055 00056 FreeBusyManager *KOGroupware::mFreeBusyManager = 0; 00057 00058 KOGroupware *KOGroupware::mInstance = 0; 00059 00060 KOGroupware *KOGroupware::create( CalendarView *view, 00061 KCal::CalendarResources *calendar ) 00062 { 00063 if( !mInstance ) 00064 mInstance = new KOGroupware( view, calendar ); 00065 return mInstance; 00066 } 00067 00068 KOGroupware *KOGroupware::instance() 00069 { 00070 // Doesn't create, that is the task of create() 00071 Q_ASSERT( mInstance ); 00072 return mInstance; 00073 } 00074 00075 00076 KOGroupware::KOGroupware( CalendarView* view, KCal::CalendarResources* cal ) 00077 : TQObject( 0, "kmgroupware_instance" ), mView( view ), mCalendar( cal ), mDoNotNotify( false ) 00078 { 00079 // Set up the dir watch of the three incoming dirs 00080 KDirWatch* watcher = KDirWatch::self(); 00081 watcher->addDir( locateLocal( "data", "korganizer/income.accepted/" ) ); 00082 watcher->addDir( locateLocal( "data", "korganizer/income.tentative/" ) ); 00083 watcher->addDir( locateLocal( "data", "korganizer/income.counter/" ) ); 00084 watcher->addDir( locateLocal( "data", "korganizer/income.cancel/" ) ); 00085 watcher->addDir( locateLocal( "data", "korganizer/income.reply/" ) ); 00086 watcher->addDir( locateLocal( "data", "korganizer/income.delegated/" ) ); 00087 connect( watcher, TQT_SIGNAL( dirty( const TQString& ) ), 00088 this, TQT_SLOT( incomingDirChanged( const TQString& ) ) ); 00089 // Now set the ball rolling 00090 TQTimer::singleShot( 0, this, TQT_SLOT(initialCheckForChanges()) ); 00091 } 00092 00093 void KOGroupware::initialCheckForChanges() 00094 { 00095 incomingDirChanged( locateLocal( "data", "korganizer/income.accepted/" ) ); 00096 incomingDirChanged( locateLocal( "data", "korganizer/income.tentative/" ) ); 00097 incomingDirChanged( locateLocal( "data", "korganizer/income.counter/" ) ); 00098 incomingDirChanged( locateLocal( "data", "korganizer/income.cancel/" ) ); 00099 incomingDirChanged( locateLocal( "data", "korganizer/income.reply/" ) ); 00100 incomingDirChanged( locateLocal( "data", "korganizer/income.delegated/" ) ); 00101 } 00102 00103 void KOGroupware::slotViewNewIncidenceChanger( IncidenceChangerBase* changer ) 00104 { 00105 // Call slot perhapsUploadFB if an incidence was added, changed or removed 00106 connect( changer, TQT_SIGNAL( incidenceAdded( Incidence* ) ), 00107 mFreeBusyManager, TQT_SLOT( slotPerhapsUploadFB() ) ); 00108 connect( changer, TQT_SIGNAL( incidenceChanged( Incidence*, Incidence*, KOGlobals::WhatChanged ) ), 00109 mFreeBusyManager, TQT_SLOT( slotPerhapsUploadFB() ) ); 00110 connect( changer, TQT_SIGNAL( incidenceDeleted( Incidence * ) ), 00111 mFreeBusyManager, TQT_SLOT( slotPerhapsUploadFB() ) ); 00112 } 00113 00114 FreeBusyManager *KOGroupware::freeBusyManager() 00115 { 00116 if ( !mFreeBusyManager ) { 00117 mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" ); 00118 mFreeBusyManager->setCalendar( mCalendar ); 00119 connect( mCalendar, TQT_SIGNAL( calendarChanged() ), 00120 mFreeBusyManager, TQT_SLOT( slotPerhapsUploadFB() ) ); 00121 connect( mView, TQT_SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ), 00122 this, TQT_SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) ); 00123 slotViewNewIncidenceChanger( mView->incidenceChanger() ); 00124 } 00125 00126 return mFreeBusyManager; 00127 } 00128 00129 void KOGroupware::incomingDirChanged( const TQString& path ) 00130 { 00131 const TQString incomingDirName = locateLocal( "data","korganizer/" ) 00132 + "income."; 00133 if ( !path.startsWith( incomingDirName ) ) { 00134 kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl; 00135 return; 00136 } 00137 TQString action = path.mid( incomingDirName.length() ); 00138 while ( action.length() > 0 && action[ action.length()-1 ] == '/' ) 00139 // Strip slashes at the end 00140 action.truncate( action.length()-1 ); 00141 00142 // Handle accepted invitations 00143 TQDir dir( path ); 00144 const TQStringList files = dir.entryList( TQDir::Files ); 00145 if ( files.isEmpty() ) 00146 // No more files here 00147 return; 00148 00149 // Read the file and remove it 00150 TQFile f( path + "/" + files[0] ); 00151 if (!f.open(IO_ReadOnly)) { 00152 kdError(5850) << "Can't open file '" << files[0] << "'" << endl; 00153 return; 00154 } 00155 TQTextStream t(&f); 00156 t.setEncoding( TQTextStream::UnicodeUTF8 ); 00157 TQString receiver = KPIM::getFirstEmailAddress( t.readLine() ); 00158 TQString iCal = t.read(); 00159 00160 f.remove(); 00161 00162 ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal ); 00163 if ( !message ) { 00164 TQString errorMessage; 00165 if (mFormat.exception()) 00166 errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() ); 00167 kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing " 00168 << errorMessage << endl; 00169 KMessageBox::detailedError( mView, 00170 i18n("Error while processing an invitation or update."), 00171 errorMessage ); 00172 return; 00173 } 00174 00175 KCal::Scheduler::Method method = 00176 static_cast<KCal::Scheduler::Method>( message->method() ); 00177 KCal::ScheduleMessage::Status status = message->status(); 00178 KCal::Incidence* incidence = 00179 dynamic_cast<KCal::Incidence*>( message->event() ); 00180 if(!incidence) { 00181 delete message; 00182 return; 00183 } 00184 KCal::MailScheduler scheduler( mCalendar ); 00185 if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" ) 00186 || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) { 00187 // Find myself and set my status. This can't be done in the scheduler, 00188 // since this does not know the choice I made in the KMail bpf 00189 KCal::Attendee::List attendees = incidence->attendees(); 00190 KCal::Attendee::List::ConstIterator it; 00191 for ( it = attendees.begin(); it != attendees.end(); ++it ) { 00192 if( (*it)->email() == receiver ) { 00193 if ( action.startsWith( "accepted" ) ) 00194 (*it)->setStatus( KCal::Attendee::Accepted ); 00195 else if ( action.startsWith( "tentative" ) ) 00196 (*it)->setStatus( KCal::Attendee::Tentative ); 00197 else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) ) 00198 (*it)->setStatus( KCal::Attendee::Tentative ); 00199 else if ( action.startsWith( "delegated" ) ) 00200 (*it)->setStatus( KCal::Attendee::Delegated ); 00201 break; 00202 } 00203 } 00204 if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) ) 00205 scheduler.acceptTransaction( incidence, method, status, receiver ); 00206 } else if ( action.startsWith( "cancel" ) ) 00207 // Delete the old incidence, if one is present 00208 scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status, receiver ); 00209 else if ( action.startsWith( "reply" ) ) { 00210 if ( method != Scheduler::Counter ) { 00211 scheduler.acceptTransaction( incidence, method, status ); 00212 } else { 00213 // accept counter proposal 00214 scheduler.acceptCounterProposal( incidence ); 00215 // send update to all attendees 00216 sendICalMessage( mView, Scheduler::Request, incidence, KOGlobals::INCIDENCEEDITED, false ); 00217 } 00218 } else 00219 kdError(5850) << "Unknown incoming action " << action << endl; 00220 00221 if ( action.startsWith( "counter" ) ) { 00222 mView->editIncidence( incidence, TQDate(), true ); 00223 KOIncidenceEditor *tmp = mView->editorDialog( incidence ); 00224 tmp->selectInvitationCounterProposal( true ); 00225 } 00226 mView->updateView(); 00227 } 00228 00229 class KOInvitationFormatterHelper : public InvitationFormatterHelper 00230 { 00231 public: 00232 virtual TQString generateLinkURL( const TQString &id ) { return "kmail:groupware_request_" + id; } 00233 }; 00234 00235 /* This function sends mails if necessary, and makes sure the user really 00236 * want to change his calendar. 00237 * 00238 * Return true means accept the changes 00239 * Return false means revert the changes 00240 */ 00241 bool KOGroupware::sendICalMessage( TQWidget* parent, 00242 KCal::Scheduler::Method method, 00243 Incidence* incidence, 00244 KOGlobals::HowChanged action, 00245 bool attendeeStatusChanged, 00246 int dontAskForGroupware ) 00247 { 00248 // If there are no attendees, don't bother 00249 if( incidence->attendees().isEmpty() ) 00250 return true; 00251 00252 bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() ); 00253 int rc = 0; 00254 /* 00255 * There are two scenarios: 00256 * o "we" are the organizer, where "we" means any of the identities or mail 00257 * addresses known to Kontact/PIM. If there are attendees, we need to mail 00258 * them all, even if one or more of them are also "us". Otherwise there 00259 * would be no way to invite a resource or our boss, other identities we 00260 * also manage. 00261 * o "we: are not the organizer, which means we changed the completion status 00262 * of a todo, or we changed our attendee status from, say, tentative to 00263 * accepted. In both cases we only mail the organizer. All other changes 00264 * bring us out of sync with the organizer, so we won't mail, if the user 00265 * insists on applying them. 00266 */ 00267 00268 if ( dontAskForGroupware == 1 ) { 00269 rc = KMessageBox::Yes; 00270 } 00271 else if ( dontAskForGroupware == 2 ) { 00272 rc = KMessageBox::No; 00273 } 00274 else { 00275 if ( isOrganizer ) { 00276 /* We are the organizer. If there is more than one attendee, or if there is 00277 * only one, and it's not the same as the organizer, ask the user to send 00278 * mail. */ 00279 if ( incidence->attendees().count() > 1 00280 || incidence->attendees().first()->email() != incidence->organizer().email() ) { 00281 00282 TQString txt; 00283 switch( action ) { 00284 case KOGlobals::INCIDENCEEDITED: 00285 txt = i18n( "You changed the invitation \"%1\".\n" 00286 "Do you want to email the attendees an update message?" ). 00287 arg( incidence->summary() ); 00288 break; 00289 case KOGlobals::INCIDENCEDELETED: 00290 Q_ASSERT( incidence->type() == "Event" || incidence->type() == "Todo" ); 00291 if ( incidence->type() == "Event" ) { 00292 txt = i18n( "You removed the invitation \"%1\".\n" 00293 "Do you want to email the attendees that the event is canceled?" ). 00294 arg( incidence->summary() ); 00295 } else if ( incidence->type() == "Todo" ) { 00296 txt = i18n( "You removed the invitation \"%1\".\n" 00297 "Do you want to email the attendees that the todo is canceled?" ). 00298 arg( incidence->summary() ); 00299 } 00300 break; 00301 case KOGlobals::INCIDENCEADDED: 00302 if ( incidence->type() == "Event" ) { 00303 txt = i18n( "The event \"%1\" includes other people.\n" 00304 "Do you want to email the invitation to the attendees?" ). 00305 arg( incidence->summary() ); 00306 } else if ( incidence->type() == "Todo" ) { 00307 txt = i18n( "The todo \"%1\" includes other people.\n" 00308 "Do you want to email the invitation to the attendees?" ). 00309 arg( incidence->summary() ); 00310 } else { 00311 txt = i18n( "This incidence includes other people. " 00312 "Should an email be sent to the attendees?" ); 00313 } 00314 break; 00315 default: 00316 kdError() << "Unsupported HowChanged action" << int( action ) << endl; 00317 break; 00318 } 00319 00320 rc = KMessageBox::questionYesNo( 00321 parent, txt, i18n( "Group Scheduling Email" ), 00322 KGuiItem( i18n( "Send Email" ) ), KGuiItem( i18n( "Do Not Send" ) ) ); 00323 } else { 00324 return true; 00325 } 00326 } else if( incidence->type() == "Todo" ) { 00327 if( method == Scheduler::Request ) 00328 // This is an update to be sent to the organizer 00329 method = Scheduler::Reply; 00330 00331 // Ask if the user wants to tell the organizer about the current status 00332 TQString txt = i18n( "Do you want to send a status update to the " 00333 "organizer of this task?"); 00334 rc = KMessageBox::questionYesNo( parent, txt, TQString(), i18n("Send Update"), i18n("Do Not Send") ); 00335 } else if( incidence->type() == "Event" ) { 00336 TQString txt; 00337 if ( attendeeStatusChanged && method == Scheduler::Request ) { 00338 txt = i18n( "Your status as an attendee of this event changed. " 00339 "Do you want to send a status update to the event organizer?" ); 00340 method = Scheduler::Reply; 00341 rc = KMessageBox::questionYesNo( parent, txt, TQString(), i18n("Send Update"), i18n("Do Not Send") ); 00342 } else { 00343 if( action == KOGlobals::INCIDENCEDELETED ) { 00344 const TQStringList myEmails = KOPrefs::instance()->allEmails(); 00345 bool askConfirmation = false; 00346 for ( TQStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) { 00347 TQString email = *it; 00348 Attendee *me = incidence->attendeeByMail(email); 00349 if (me && (me->status()==KCal::Attendee::Accepted || me->status()==KCal::Attendee::Delegated)) { 00350 askConfirmation = true; 00351 break; 00352 } 00353 } 00354 00355 if ( !askConfirmation ) { 00356 return true; 00357 } 00358 00359 txt = i18n( "You had previously accepted an invitation to this event. " 00360 "Do you want to send an updated response to the organizer " 00361 "declining the invitation?" ); 00362 rc = KMessageBox::questionYesNo( 00363 parent, txt, i18n( "Group Scheduling Email" ), 00364 KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) ); 00365 setDoNotNotify( rc == KMessageBox::No ); 00366 } else { 00367 txt = i18n( "You are not the organizer of this event. Editing it will " 00368 "bring your calendar out of sync with the organizer's calendar. " 00369 "Do you really want to edit it?" ); 00370 rc = KMessageBox::warningYesNo( parent, txt ); 00371 return ( rc == KMessageBox::Yes ); 00372 } 00373 } 00374 } else { 00375 kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl; 00376 return true; 00377 } 00378 } 00379 00380 if ( rc == KMessageBox::Yes ) { 00381 // We will be sending out a message here. Now make sure there is 00382 // some summary 00383 if( incidence->summary().isEmpty() ) 00384 incidence->setSummary( i18n("<No summary given>") ); 00385 00386 // Send the mail 00387 KCal::MailScheduler scheduler( mCalendar ); 00388 scheduler.performTransaction( incidence, method ); 00389 00390 return true; 00391 } else if ( rc == KMessageBox::No ) { 00392 return true; 00393 } else { 00394 return false; 00395 } 00396 } 00397 00398 void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const 00399 { 00400 if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication ) 00401 return; 00402 if ( KOPrefs::instance()->outlookCompatCounterProposals() ) { 00403 Incidence* tmp = oldEvent->clone(); 00404 tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) ); 00405 tmp->setDescription( newEvent->description() ); 00406 tmp->addComment( i18n("Proposed new meeting time: %1 - %2"). 00407 arg( IncidenceFormatter::dateToString( newEvent->dtStart() ), 00408 IncidenceFormatter::dateToString( newEvent->dtEnd() ) ) ); 00409 KCal::MailScheduler scheduler( calendar ); 00410 scheduler.performTransaction( tmp, Scheduler::Reply ); 00411 delete tmp; 00412 } else { 00413 KCal::MailScheduler scheduler( calendar ); 00414 scheduler.performTransaction( newEvent, Scheduler::Counter ); 00415 } 00416 } 00417 00418 #include "kogroupware.moc"