korganizer

kogroupware.cpp

00001 /*
00002   This file is part of the Groupware/KOrganizer integration.
00003 
00004   Requires the Qt 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 Qt library by Trolltech AS, Norway (or with modified versions
00028   of Qt that use the same license as Qt), 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   Qt.  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 <kmessagebox.h>
00049 #include <kstandarddirs.h>
00050 #include <kdirwatch.h>
00051 #include <qfile.h>
00052 #include <qregexp.h>
00053 #include <qdir.h>
00054 #include <qtimer.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    : QObject( 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, SIGNAL( dirty( const QString& ) ),
00088            this, SLOT( incomingDirChanged( const QString& ) ) );
00089   // Now set the ball rolling
00090   QTimer::singleShot( 0, this, 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, SIGNAL( incidenceAdded( Incidence* ) ),
00107              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00108     connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence*, int ) ),
00109              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00110     connect( changer, SIGNAL( incidenceChanged( Incidence*, Incidence* ) ),
00111              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) ) ;
00112     connect( changer, SIGNAL( incidenceDeleted( Incidence * ) ),
00113              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00114 }
00115 
00116 FreeBusyManager *KOGroupware::freeBusyManager()
00117 {
00118   if ( !mFreeBusyManager ) {
00119     mFreeBusyManager = new FreeBusyManager( this, "freebusymanager" );
00120     mFreeBusyManager->setCalendar( mCalendar );
00121     connect( mCalendar, SIGNAL( calendarChanged() ),
00122              mFreeBusyManager, SLOT( slotPerhapsUploadFB() ) );
00123     connect( mView, SIGNAL( newIncidenceChanger( IncidenceChangerBase* ) ),
00124              this, SLOT( slotViewNewIncidenceChanger( IncidenceChangerBase* ) ) );
00125     slotViewNewIncidenceChanger( mView->incidenceChanger() );
00126   }
00127 
00128   return mFreeBusyManager;
00129 }
00130 
00131 void KOGroupware::incomingDirChanged( const QString& path )
00132 {
00133   const QString incomingDirName = locateLocal( "data","korganizer/" )
00134                                   + "income.";
00135   if ( !path.startsWith( incomingDirName ) ) {
00136     kdDebug(5850) << "incomingDirChanged: Wrong dir " << path << endl;
00137     return;
00138   }
00139   QString action = path.mid( incomingDirName.length() );
00140   while ( action.length() > 0 && action[ action.length()-1 ] == '/' )
00141     // Strip slashes at the end
00142     action.truncate( action.length()-1 );
00143 
00144   // Handle accepted invitations
00145   QDir dir( path );
00146   const QStringList files = dir.entryList( QDir::Files );
00147   if ( files.isEmpty() )
00148     // No more files here
00149     return;
00150 
00151   // Read the file and remove it
00152   QFile f( path + "/" + files[0] );
00153   if (!f.open(IO_ReadOnly)) {
00154     kdError(5850) << "Can't open file '" << files[0] << "'" << endl;
00155     return;
00156   }
00157   QTextStream t(&f);
00158   t.setEncoding( QTextStream::UnicodeUTF8 );
00159   QString receiver = KPIM::getFirstEmailAddress( t.readLine() );
00160   QString iCal = t.read();
00161 
00162   f.remove();
00163 
00164   ScheduleMessage *message = mFormat.parseScheduleMessage( mCalendar, iCal );
00165   if ( !message ) {
00166     QString errorMessage;
00167     if (mFormat.exception())
00168       errorMessage = i18n( "Error message: %1" ).arg( mFormat.exception()->message() );
00169     kdDebug(5850) << "MailScheduler::retrieveTransactions() Error parsing "
00170                   << errorMessage << endl;
00171     KMessageBox::detailedError( mView,
00172         i18n("Error while processing an invitation or update."),
00173         errorMessage );
00174     return;
00175   }
00176 
00177   KCal::Scheduler::Method method =
00178     static_cast<KCal::Scheduler::Method>( message->method() );
00179   KCal::ScheduleMessage::Status status = message->status();
00180   KCal::Incidence* incidence =
00181     dynamic_cast<KCal::Incidence*>( message->event() );
00182   if(!incidence) {
00183     delete message;
00184     return;
00185   }
00186   KCal::MailScheduler scheduler( mCalendar );
00187   if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" )
00188        || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) {
00189     // Find myself and set my status. This can't be done in the scheduler,
00190     // since this does not know the choice I made in the KMail bpf
00191     KCal::Attendee::List attendees = incidence->attendees();
00192     KCal::Attendee::List::ConstIterator it;
00193     for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00194       if( (*it)->email() == receiver ) {
00195         if ( action.startsWith( "accepted" ) )
00196           (*it)->setStatus( KCal::Attendee::Accepted );
00197         else if ( action.startsWith( "tentative" ) )
00198           (*it)->setStatus( KCal::Attendee::Tentative );
00199         else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) )
00200           (*it)->setStatus( KCal::Attendee::Tentative );
00201         else if ( action.startsWith( "delegated" ) )
00202           (*it)->setStatus( KCal::Attendee::Delegated );
00203         break;
00204       }
00205     }
00206     if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) )
00207       scheduler.acceptTransaction( incidence, method, status, receiver );
00208   } else if ( action.startsWith( "cancel" ) )
00209     // Delete the old incidence, if one is present
00210     scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status );
00211   else if ( action.startsWith( "reply" ) ) {
00212     if ( method != Scheduler::Counter ) {
00213       scheduler.acceptTransaction( incidence, method, status );
00214     } else {
00215       // accept counter proposal
00216       scheduler.acceptCounterProposal( incidence );
00217       // send update to all attendees
00218       sendICalMessage( mView, Scheduler::Request, incidence );
00219     }
00220   } else
00221     kdError(5850) << "Unknown incoming action " << action << endl;
00222 
00223   if ( action.startsWith( "counter" ) ) {
00224     mView->editIncidence( incidence, true );
00225     KOIncidenceEditor *tmp = mView->editorDialog( incidence );
00226     tmp->selectInvitationCounterProposal( true );
00227   }
00228   mView->updateView();
00229 }
00230 
00231 class KOInvitationFormatterHelper : public InvitationFormatterHelper
00232 {
00233   public:
00234     virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
00235 };
00236 
00237 /* This function sends mails if necessary, and makes sure the user really
00238  * want to change his calendar.
00239  *
00240  * Return true means accept the changes
00241  * Return false means revert the changes
00242  */
00243 bool KOGroupware::sendICalMessage( QWidget* parent,
00244                                    KCal::Scheduler::Method method,
00245                                    Incidence* incidence, bool isDeleting,
00246                                    bool statusChanged )
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 ( isOrganizer ) {
00269     /* We are the organizer. If there is more than one attendee, or if there is
00270      * only one, and it's not the same as the organizer, ask the user to send
00271      * mail. */
00272     if ( incidence->attendees().count() > 1
00273         || incidence->attendees().first()->email() != incidence->organizer().email() ) {
00274       QString type;
00275       if( incidence->type() == "Event") type = i18n("event");
00276       else if( incidence->type() == "Todo" ) type = i18n("task");
00277       else if( incidence->type() == "Journal" ) type = i18n("journal entry");
00278       else type = incidence->type();
00279       QString txt = i18n( "This %1 includes other people. "
00280           "Should email be sent out to the attendees?" )
00281         .arg( type );
00282       rc = KMessageBox::questionYesNoCancel( parent, txt,
00283           i18n("Group Scheduling Email"), i18n("Send Email"), i18n("Do Not Send") );
00284     } else {
00285       return true;
00286     }
00287   } else if( incidence->type() == "Todo" ) {
00288     if( method == Scheduler::Request )
00289       // This is an update to be sent to the organizer
00290       method = Scheduler::Reply;
00291 
00292     // Ask if the user wants to tell the organizer about the current status
00293     QString txt = i18n( "Do you want to send a status update to the "
00294                         "organizer of this task?");
00295     rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00296   } else if( incidence->type() == "Event" ) {
00297     QString txt;
00298     if ( statusChanged && method == Scheduler::Request ) {
00299       txt = i18n( "Your status as an attendee of this event changed. "
00300                   "Do you want to send a status update to the event organizer?" );
00301       method = Scheduler::Reply;
00302       rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00303     } else {
00304       if( isDeleting ) {
00305         const QStringList myEmails = KOPrefs::instance()->allEmails();
00306         bool askConfirmation = false;
00307         for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
00308           QString email = *it;
00309           Attendee *me = incidence->attendeeByMail(email);
00310           if (me && (me->status()==KCal::Attendee::Accepted || me->status()==KCal::Attendee::Delegated)) {
00311             askConfirmation = true;
00312             break;
00313           }
00314         }
00315 
00316         if ( !askConfirmation ) {
00317           return true;
00318         }
00319 
00320         txt = i18n( "You had previously accepted an invitation to this event. "
00321                     "Do you want to send an updated response to the organizer "
00322                     "declining the invitation?" );
00323         rc = KMessageBox::questionYesNo(
00324           parent, txt, i18n( "Group Scheduling Email" ),
00325           KGuiItem( i18n( "Send Update" ) ), KGuiItem( i18n( "Do Not Send" ) ) );
00326         setDoNotNotify( rc == KMessageBox::No );
00327       } else {
00328         txt = i18n( "You are not the organizer of this event. Editing it will "
00329                     "bring your calendar out of sync with the organizer's calendar. "
00330                     "Do you really want to edit it?" );
00331         rc = KMessageBox::warningYesNo( parent, txt );
00332         return ( rc == KMessageBox::Yes );
00333       }
00334     }
00335   } else {
00336     kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
00337     return true;
00338   }
00339 
00340   if ( rc == KMessageBox::Yes ) {
00341     // We will be sending out a message here. Now make sure there is
00342     // some summary
00343     if( incidence->summary().isEmpty() )
00344       incidence->setSummary( i18n("<No summary given>") );
00345 
00346     // Send the mail
00347     KCal::MailScheduler scheduler( mCalendar );
00348     scheduler.performTransaction( incidence, method );
00349 
00350     return true;
00351   } else if ( rc == KMessageBox::No ) {
00352     return true;
00353   } else {
00354     return false;
00355   }
00356 }
00357 
00358 void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const
00359 {
00360   if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication )
00361     return;
00362   if ( KOPrefs::instance()->outlookCompatCounterProposals() ) {
00363     Incidence* tmp = oldEvent->clone();
00364     tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) );
00365     tmp->setDescription( newEvent->description() );
00366     tmp->addComment( i18n("Proposed new meeting time: %1 - %2").arg( newEvent->dtStartStr(), newEvent->dtEndStr() ) );
00367     KCal::MailScheduler scheduler( calendar );
00368     scheduler.performTransaction( tmp, Scheduler::Reply );
00369     delete tmp;
00370   } else {
00371     KCal::MailScheduler scheduler( calendar );
00372     scheduler.performTransaction( newEvent, Scheduler::Counter );
00373   }
00374 }
00375 
00376 #include "kogroupware.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys