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 )
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   KCal::MailScheduler scheduler( mCalendar );
00183   if ( action.startsWith( "accepted" ) || action.startsWith( "tentative" )
00184        || action.startsWith( "delegated" ) || action.startsWith( "counter" ) ) {
00185     // Find myself and set my status. This can't be done in the scheduler,
00186     // since this does not know the choice I made in the KMail bpf
00187     KCal::Attendee::List attendees = incidence->attendees();
00188     KCal::Attendee::List::ConstIterator it;
00189     for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00190       if( (*it)->email() == receiver ) {
00191         if ( action.startsWith( "accepted" ) )
00192           (*it)->setStatus( KCal::Attendee::Accepted );
00193         else if ( action.startsWith( "tentative" ) )
00194           (*it)->setStatus( KCal::Attendee::Tentative );
00195         else if ( KOPrefs::instance()->outlookCompatCounterProposals() && action.startsWith( "counter" ) )
00196           (*it)->setStatus( KCal::Attendee::Tentative );
00197         else if ( action.startsWith( "delegated" ) )
00198           (*it)->setStatus( KCal::Attendee::Delegated );
00199         break;
00200       }
00201     }
00202     if ( KOPrefs::instance()->outlookCompatCounterProposals() || !action.startsWith( "counter" ) )
00203       scheduler.acceptTransaction( incidence, method, status );
00204   } else if ( action.startsWith( "cancel" ) )
00205     // Delete the old incidence, if one is present
00206     scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status );
00207   else if ( action.startsWith( "reply" ) ) {
00208     if ( method != Scheduler::Counter ) {
00209       scheduler.acceptTransaction( incidence, method, status );
00210     } else {
00211       // accept counter proposal
00212       scheduler.acceptCounterProposal( incidence );
00213       // send update to all attendees
00214       sendICalMessage( mView, Scheduler::Request, incidence );
00215     }
00216   } else
00217     kdError(5850) << "Unknown incoming action " << action << endl;
00218 
00219   if ( action.startsWith( "counter" ) ) {
00220     mView->editIncidence( incidence, true );
00221     KOIncidenceEditor *tmp = mView->editorDialog( incidence );
00222     tmp->selectInvitationCounterProposal( true );
00223   }
00224   mView->updateView();
00225 }
00226 
00227 class KOInvitationFormatterHelper : public InvitationFormatterHelper
00228 {
00229   public:
00230     virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
00231 };
00232 
00233 /* This function sends mails if necessary, and makes sure the user really
00234  * want to change his calendar.
00235  *
00236  * Return true means accept the changes
00237  * Return false means revert the changes
00238  */
00239 bool KOGroupware::sendICalMessage( QWidget* parent,
00240                                    KCal::Scheduler::Method method,
00241                                    Incidence* incidence, bool isDeleting,
00242                                    bool statusChanged )
00243 {
00244   // If there are no attendees, don't bother
00245   if( incidence->attendees().isEmpty() )
00246     return true;
00247 
00248   bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
00249   int rc = 0;
00250   /*
00251    * There are two scenarios:
00252    * o "we" are the organizer, where "we" means any of the identities or mail
00253    *   addresses known to Kontact/PIM. If there are attendees, we need to mail
00254    *   them all, even if one or more of them are also "us". Otherwise there
00255    *   would be no way to invite a resource or our boss, other identities we
00256    *   also manage.
00257    * o "we: are not the organizer, which means we changed the completion status
00258    *   of a todo, or we changed our attendee status from, say, tentative to
00259    *   accepted. In both cases we only mail the organizer. All other changes
00260    *   bring us out of sync with the organizer, so we won't mail, if the user
00261    *   insists on applying them.
00262    */
00263 
00264   if ( isOrganizer ) {
00265     /* We are the organizer. If there is more than one attendee, or if there is
00266      * only one, and it's not the same as the organizer, ask the user to send
00267      * mail. */
00268     if ( incidence->attendees().count() > 1
00269         || incidence->attendees().first()->email() != incidence->organizer().email() ) {
00270       QString type;
00271       if( incidence->type() == "Event") type = i18n("event");
00272       else if( incidence->type() == "Todo" ) type = i18n("task");
00273       else if( incidence->type() == "Journal" ) type = i18n("journal entry");
00274       else type = incidence->type();
00275       QString txt = i18n( "This %1 includes other people. "
00276           "Should email be sent out to the attendees?" )
00277         .arg( type );
00278       rc = KMessageBox::questionYesNoCancel( parent, txt,
00279           i18n("Group Scheduling Email"), i18n("Send Email"), i18n("Do Not Send") );
00280     } else {
00281       return true;
00282     }
00283   } else if( incidence->type() == "Todo" ) {
00284     if( method == Scheduler::Request )
00285       // This is an update to be sent to the organizer
00286       method = Scheduler::Reply;
00287 
00288     // Ask if the user wants to tell the organizer about the current status
00289     QString txt = i18n( "Do you want to send a status update to the "
00290                         "organizer of this task?");
00291     rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00292   } else if( incidence->type() == "Event" ) {
00293     QString txt;
00294     if ( statusChanged && method == Scheduler::Request ) {
00295       txt = i18n( "Your status as an attendee of this event "
00296           "changed. Do you want to send a status update to the "
00297           "organizer of this event?" );
00298       method = Scheduler::Reply;
00299       rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00300     } else {
00301       if( isDeleting ) {
00302         const QStringList myEmails = KOPrefs::instance()->allEmails();
00303         bool askConfirmation = false;
00304         for ( QStringList::ConstIterator it = myEmails.begin(); it != myEmails.end(); ++it ) {
00305           QString email = *it;
00306           Attendee *me = incidence->attendeeByMail(email);
00307           if (me && (me->status()==KCal::Attendee::Accepted || me->status()==KCal::Attendee::Delegated)) {
00308             askConfirmation = true;
00309             break;
00310           }
00311         }
00312 
00313         if ( !askConfirmation ) {
00314           return true;
00315         }
00316 
00317         txt = i18n( "You are not the organizer of this event, "
00318             "but you were supposed to attend. Do you really want "
00319             "to delete it and notify the organizer?" );
00320       } else {
00321         txt = i18n( "You are not the organizer of this event. "
00322             "Editing it will bring your calendar out of sync "
00323             "with the organizers calendar. Do you really want "
00324             "to edit it?" );
00325       }
00326       rc = KMessageBox::warningYesNo( parent, txt );
00327       return ( rc == KMessageBox::Yes );
00328     }
00329   } else {
00330     kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
00331     return true;
00332   }
00333 
00334   if( rc == KMessageBox::Yes ) {
00335     // We will be sending out a message here. Now make sure there is
00336     // some summary
00337     if( incidence->summary().isEmpty() )
00338       incidence->setSummary( i18n("<No summary given>") );
00339 
00340     // Send the mail
00341     KCal::MailScheduler scheduler( mCalendar );
00342     scheduler.performTransaction( incidence, method );
00343 
00344     return true;
00345   } else if( rc == KMessageBox::No )
00346     return true;
00347   else
00348     return false;
00349 }
00350 
00351 void KOGroupware::sendCounterProposal(KCal::Calendar *calendar, KCal::Event * oldEvent, KCal::Event * newEvent) const
00352 {
00353   if ( !oldEvent || !newEvent || *oldEvent == *newEvent || !KOPrefs::instance()->mUseGroupwareCommunication )
00354     return;
00355   if ( KOPrefs::instance()->outlookCompatCounterProposals() ) {
00356     Incidence* tmp = oldEvent->clone();
00357     tmp->setSummary( i18n("Counter proposal: %1").arg( newEvent->summary() ) );
00358     tmp->setDescription( newEvent->description() );
00359     tmp->addComment( i18n("Proposed new meeting time: %1 - %2").arg( newEvent->dtStartStr(), newEvent->dtEndStr() ) );
00360     KCal::MailScheduler scheduler( calendar );
00361     scheduler.performTransaction( tmp, Scheduler::Reply );
00362     delete tmp;
00363   } else {
00364     KCal::MailScheduler scheduler( calendar );
00365     scheduler.performTransaction( newEvent, Scheduler::Counter );
00366   }
00367 }
00368 
00369 #include "kogroupware.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys