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" ) || action.startsWith( "counter" ) )
00194           (*it)->setStatus( KCal::Attendee::Tentative );
00195         else if ( action.startsWith( "delegated" ) )
00196           (*it)->setStatus( KCal::Attendee::Delegated );
00197         break;
00198       }
00199     }
00200     scheduler.acceptTransaction( incidence, method, status );
00201   } else if ( action.startsWith( "cancel" ) )
00202     // Delete the old incidence, if one is present
00203     scheduler.acceptTransaction( incidence, KCal::Scheduler::Cancel, status );
00204   else if ( action.startsWith( "reply" ) )
00205     scheduler.acceptTransaction( incidence, method, status );
00206   else
00207     kdError(5850) << "Unknown incoming action " << action << endl;
00208 
00209   if ( action.startsWith( "counter" ) ) {
00210     mView->editIncidence( incidence );
00211     KOIncidenceEditor *tmp = mView->editorDialog( incidence );
00212     tmp->selectInvitationCounterProposal( true );
00213   }
00214   mView->updateView();
00215 }
00216 
00217 class KOInvitationFormatterHelper : public InvitationFormatterHelper
00218 {
00219   public:
00220     virtual QString generateLinkURL( const QString &id ) { return "kmail:groupware_request_" + id; }
00221 };
00222 
00223 /* This function sends mails if necessary, and makes sure the user really
00224  * want to change his calendar.
00225  *
00226  * Return true means accept the changes
00227  * Return false means revert the changes
00228  */
00229 bool KOGroupware::sendICalMessage( QWidget* parent,
00230                                    KCal::Scheduler::Method method,
00231                                    Incidence* incidence, bool isDeleting,
00232                                    bool statusChanged )
00233 {
00234   // If there are no attendees, don't bother
00235   if( incidence->attendees().isEmpty() )
00236     return true;
00237 
00238   bool isOrganizer = KOPrefs::instance()->thatIsMe( incidence->organizer().email() );
00239   int rc = 0;
00240   /*
00241    * There are two scenarios:
00242    * o "we" are the organizer, where "we" means any of the identities or mail
00243    *   addresses known to Kontact/PIM. If there are attendees, we need to mail
00244    *   them all, even if one or more of them are also "us". Otherwise there
00245    *   would be no way to invite a resource or our boss, other identities we
00246    *   also manage.
00247    * o "we: are not the organizer, which means we changed the completion status
00248    *   of a todo, or we changed our attendee status from, say, tentative to
00249    *   accepted. In both cases we only mail the organizer. All other changes
00250    *   bring us out of sync with the organizer, so we won't mail, if the user
00251    *   insists on applying them.
00252    */
00253 
00254   if ( isOrganizer ) {
00255     /* We are the organizer. If there is more than one attendee, or if there is
00256      * only one, and it's not the same as the organizer, ask the user to send
00257      * mail. */
00258     if ( incidence->attendees().count() > 1
00259         || incidence->attendees().first()->email() != incidence->organizer().email() ) {
00260       QString type;
00261       if( incidence->type() == "Event") type = i18n("event");
00262       else if( incidence->type() == "Todo" ) type = i18n("task");
00263       else if( incidence->type() == "Journal" ) type = i18n("journal entry");
00264       else type = incidence->type();
00265       QString txt = i18n( "This %1 includes other people. "
00266           "Should email be sent out to the attendees?" )
00267         .arg( type );
00268       rc = KMessageBox::questionYesNoCancel( parent, txt,
00269           i18n("Group Scheduling Email"), i18n("Send Email"), i18n("Do Not Send") );
00270     } else {
00271       return true;
00272     }
00273   } else if( incidence->type() == "Todo" ) {
00274     if( method == Scheduler::Request )
00275       // This is an update to be sent to the organizer
00276       method = Scheduler::Reply;
00277 
00278     // Ask if the user wants to tell the organizer about the current status
00279     QString txt = i18n( "Do you want to send a status update to the "
00280                         "organizer of this task?");
00281     rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00282   } else if( incidence->type() == "Event" ) {
00283     QString txt;
00284     if ( statusChanged && method == Scheduler::Request ) {
00285       txt = i18n( "Your status as an attendee of this event "
00286           "changed. Do you want to send a status update to the "
00287           "organizer of this event?" );
00288       method = Scheduler::Reply;
00289       rc = KMessageBox::questionYesNo( parent, txt, QString::null, i18n("Send Update"), i18n("Do Not Send") );
00290     } else {
00291       if( isDeleting )
00292         txt = i18n( "You are not the organizer of this event. "
00293             "Deleting it will bring your calendar out of sync "
00294             "with the organizers calendar. Do you really want "
00295             "to delete it?" );
00296       else
00297         txt = i18n( "You are not the organizer of this event. "
00298             "Editing it will bring your calendar out of sync "
00299             "with the organizers calendar. Do you really want "
00300             "to edit it?" );
00301       rc = KMessageBox::warningYesNo( parent, txt );
00302       return ( rc == KMessageBox::Yes );
00303     }
00304   } else {
00305     kdWarning(5850) << "Groupware messages for Journals are not implemented yet!" << endl;
00306     return true;
00307   }
00308 
00309   if( rc == KMessageBox::Yes ) {
00310     // We will be sending out a message here. Now make sure there is
00311     // some summary
00312     if( incidence->summary().isEmpty() )
00313       incidence->setSummary( i18n("<No summary given>") );
00314 
00315     // Send the mail
00316     KCal::MailScheduler scheduler( mCalendar );
00317     scheduler.performTransaction( incidence, method );
00318 
00319     return true;
00320   } else if( rc == KMessageBox::No )
00321     return true;
00322   else
00323     return false;
00324 }
00325 
00326 
00327 #include "kogroupware.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys