/* kmgroupware.cpp This file is part of KMail. Copyright (c) 2003 Bo Thorsen Copyright (c) 2002 Karl-Heinz Zimmer Copyright (c) 2003 Steffen Hansen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "kmgroupware.h" #include "kfileio.h" #include "kmmainwin.h" #include "kmmainwidget.h" #include "kmfoldermgr.h" #include "kmfoldertree.h" #include "kmcomposewin.h" #include "kmidentity.h" #include "identitymanager.h" #include "kmacctmgr.h" #include "kmgroupwarefuncs.h" #include "kmcommands.h" #include "kmfolderindex.h" #include "kmkernel.h" #include "objecttreeparser.h" #include "kmailicalifaceimpl.h" using KMail::ObjectTreeParser; #include #include #include using namespace KCal; #include #include #include #include using namespace KABC; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //----------------------------------------------------------------------------- KMGroupware::KMGroupware( QObject* parent, const char* name ) : QObject( parent, name ), mUseGroupware( false ), mMainWidget( 0 ) { } //----------------------------------------------------------------------------- KMGroupware::~KMGroupware() { } void KMGroupware::readConfig() { KConfigGroup options( KMKernel::config(), "Groupware" ); // Do not read the config for this, if it's not setup at all if( options.readEntry( "Enabled", "notset" ) == "notset" ) return; mUseGroupware = options.readBoolEntry( "Enabled", true ); } bool KMGroupware::vPartFoundAndDecoded( KMMessage* msg, QString& s ) { assert( msg ); if( ( DwMime::kTypeText == msg->type() && ( DwMime::kSubtypeVCal == msg->subtype() || DwMime::kSubtypeXVCard == msg->subtype() ) ) || ( DwMime::kTypeApplication == msg->type() && DwMime::kSubtypeOctetStream == msg->subtype() ) ) { s = QString::fromUtf8( msg->bodyDecoded() ); return true; } else if( DwMime::kTypeMultipart == msg->type() && (DwMime::kSubtypeMixed == msg->subtype() ) || (DwMime::kSubtypeAlternative == msg->subtype() )) { // kdDebug(5006) << "KMGroupware looking for TNEF data" << endl; DwBodyPart* dwPart = msg->findDwBodyPart( DwMime::kTypeApplication, DwMime::kSubtypeMsTNEF ); if( !dwPart ) dwPart = msg->findDwBodyPart( DwMime::kTypeApplication, DwMime::kSubtypeOctetStream ); if( dwPart ){ // kdDebug(5006) << "KMGroupware analyzing TNEF data" << endl; KMMessagePart msgPart; KMMessage::bodyPart(dwPart, &msgPart); return KMGroupware::msTNEFToVPart( msgPart.bodyDecodedBinary(), s ); } else { dwPart = msg->findDwBodyPart( DwMime::kTypeText, DwMime::kSubtypeVCal ); if (dwPart) { KMMessagePart msgPart; KMMessage::bodyPart(dwPart, &msgPart); s = msgPart.body(); return true; } } }else if( DwMime::kTypeMultipart == msg->type() && DwMime::kSubtypeMixed == msg->subtype() ){ } return false; } //----------------------------------------------------------------------------- // Special Contacts methods called by KMKernel's DCOP functions //----------------------------------------------------------------------------- void KMGroupware::requestAddresses( QString fname ) { QFile file( fname ); if( file.open( IO_WriteOnly ) ) { QTextStream ts( &file ); ts.setEncoding( QTextStream::UnicodeUTF8 ); KMFolder* contacts = kmkernel->iCalIface().folderFromType( "Contact" ); if( contacts ) { QString s; for( int i=0; icount(); ++i ) { bool unget = !contacts->isMessage(i); if( KMGroupware::vPartFoundAndDecoded( contacts->getMsg( i ), s ) ) { ts << s; s.replace('\n', "\\n"); s.truncate(65); } if( unget ) contacts->unGetMsg(i); } }else { kdDebug(5006) << "+++KMGroupware::requestAddresses(): Contacts folder does not exist" << endl; } file.close(); }else { kdDebug(5006) << "+++KMGroupware::requestAddresses(): could not open file" << endl; } } //-------------- bool KMGroupware::storeAddresses( QString fname, QStringList delUIDs ) { KMFolder* contacts = kmkernel->iCalIface().folderFromType( "Contact" ); if( !contacts ) { kdDebug(5006) << "KMGroupware::storeAddresses(): Contacts folder does not exist" << endl; return false; } QFile file( fname ); QStringList vCards; if( file.open( IO_ReadOnly ) ) { QTextStream ts( &file ); ts.setEncoding( QTextStream::UnicodeUTF8 ); QString currentVCard; while( !ts.eof() ) { QString line; line = ts.readLine(); if( line.isEmpty() ) { // New vCard vCards << currentVCard; currentVCard = ""; } else { // Continue current vCard currentVCard += line + "\r\n"; } } file.close(); }else{ kdDebug(5006) << "KMGroupware::storeAddresses(): could not open file" << endl; return false; } for( QStringList::iterator it = delUIDs.begin(); it != delUIDs.end(); ++it ) { KMMessage* msg = kmkernel->iCalIface().findMessageByUID( *it, contacts ); if( msg ) kmkernel->iCalIface().deleteMsg( msg ); else kdDebug(5006) << "vCard not found, cannot remove: " << *it << endl; } for( QStringList::iterator it2 = vCards.begin(); it2 != vCards.end(); ++it2 ) { QCString vCard( (*it2).utf8() ); QString uid( "UID" ); QString name( "NAME" ); vPartMicroParser( vCard, uid, name ); KMMessage* msg = kmkernel->iCalIface().findMessageByUID( uid, contacts ); if( !msg ) { // This is a new vCard, make a message to store it in msg = new KMMessage(); // makes a "Content-Type=text/plain" message msg->initHeader(); msg->setType( DwMime::kTypeText ); msg->setSubtype( DwMime::kSubtypeXVCard ); msg->setHeaderField( "Content-Type", "Text/X-VCard; charset=\"utf-8\"" ); msg->setSubject( "Contact" ); msg->setTo( name ); // add missing headers/content: msg->setBodyEncoded( vCard ); // mark the message as read and store it in our Contacts folder msg->touch(); contacts->addMsg( msg ); } else { // Figure out if the contact have been changed QString s; if( vPartFoundAndDecoded( msg, s ) && s.utf8() != vCard ) { msg->setBodyEncoded( vCard ); msg->setTo( name ); } } } return true; } KMGroupware::VCalType KMGroupware::getVCalType( const QString &vCal ) { // This is ugly: We can't even use vPartMicroParser() here, because // we are actually looking for the _second_ BEGIN: line. // PENDING(kalle) We might need to look for even more things here, // like journals. if( vCal.find( QRegExp( "BEGIN:\\s*VEVENT" ) ) != -1 ) return vCalEvent; else if( vCal.find( QRegExp( "BEGIN:\\s*VTODO" ) ) != -1 ) return vCalTodo; return vCalUnknown; } //----------------------------------------------------------------------------- void KMGroupware::processVCalRequest( const QCString& receiver, const QString& vCalIn, QString& choice ) { #if 0 // FIXME: Reinstate Outlook workaround // If we are in legacy mode, and there is more than one receiver, we // need to ask the user which address to use KMMessage* msgOld = mMainWidget->headers()->currentMsg(); KConfigGroup options( KMKernel::config(), "Groupware" ); QString fromAddress; // this variable is only used in legacy mode if( options.readBoolEntry( "LegacyMangleFromToHeaders", false ) ) { QStringList toAddresses = KMMessage::splitEmailAddrList( msgOld->to() ); if( toAddresses.count() <= 1 ) // only one address: no problem, we can spare the user the dialog // and just take the from address fromAddress = msgOld->to(); else { // We have more than one To: address and are in legacy mode. Next // try is to search the identities for one of the email addresses // in the toAddresses list. for( QStringList::Iterator sit = toAddresses.begin(); sit != toAddresses.end(); ++sit ) { if( KMMessage::getEmailAddr( *sit ) == kmkernel->identityManager()->defaultIdentity().emailAddr().local8Bit() ) { // our default identity was contained in the To: list, // copy that from To: to From: fromAddress = *sit; break; // We are done } } // If we still haven't found anything, we have to ask the user // what to do. if( fromAddress.isEmpty() ) { bool bOk; fromAddress = KInputDialog::getItem( i18n( "Select Address" ), i18n( "In order to let Outlook(tm) recognize you as the receiver, you need to indicate which one of the following addresses is your email address:" ), toAddresses, 0, false, &bOk, kmkernel->mainWin() ); if( !bOk ) // If the user didn't select anything, just take the // first one so that we have something at all. fromAddress = toAddresses.first(); } } } #endif QByteArray data, replyData; QCString replyType; QDataStream arg( data, IO_WriteOnly ); arg << choice << receiver << vCalIn; if( kapp->dcopClient()->call( "korganizer", "KOrganizerIface", "eventRequest(QString,QCString,QString)", data, replyType, replyData ) && replyType == "bool" ) { bool rc; QDataStream replyStream( replyData, IO_ReadOnly ); replyStream >> rc; kdDebug(5006) << "KOrganizer call succeeded, rc = " << rc << endl; if( rc && mMainWidget ) mMainWidget->slotTrashMsg(); } else kdDebug(5006) << "KOrganizer call failed"; } //----------------------------------------------------------------------------- void KMGroupware::processVCalReply( const QCString& sender, const QString& vCal, const QString& choice ) { VCalType type = getVCalType( vCal ); if( type == vCalUnknown ) { kdDebug(5006) << "processVCalReply called with something that is not a vCal\n"; return; } if( choice == "enter" ) { // step 1: call Organizer QByteArray data, replyData; QCString replyType; QDataStream arg( data, IO_WriteOnly ); arg << sender << vCal; if( kapp->dcopClient()->call( "korganizer", "KOrganizerIface", "eventReply(QCString,QString)", data, replyType, replyData ) && replyType == "bool" ) { bool rc; QDataStream replyStream( replyData, IO_ReadOnly ); replyStream >> rc; kdDebug(5006) << "KOrganizer call succeeded, rc = " << rc << endl; if( rc ) kdDebug(5006) << "KOrganizer call succeeded\n"; } else kdDebug(5006) << "KOrganizer call failed\n"; // step 2: inform user that Organizer was updated KMessageBox::information( kmkernel->mainWin(), (type == vCalEvent ? i18n("The answer was registered in your calendar.") : i18n("The answer was registered in your task list.")), QString::null, "groupwareBox"); } else if( choice == "cancel" ) { #if 0 // TODO: Implement this with DCOP QString uid( "UID" ); QString descr("DESCRIPTION"); QString summary("SUMMARY"); vPartMicroParser( vCal.utf8(), uid, descr, summary ); if( type == vCalEvent ) { emit signalEventDeleted( uid ); KMessageBox::information( kmkernel->mainWin(), i18n("The event %1 was deleted from your calendar.") .arg( descr) ); } else if( type == vCalTodo ) { emit signalTaskDeleted( uid ); KMessageBox::information( kmkernel->mainWin(), i18n("The task was deleted from your tasks") .arg( summary ) ); } #endif } else { // Don't know what to do, so better not delete the mail return; } // An answer was saved, so trash the message if( mMainWidget ) mMainWidget->slotTrashMsg(); } //----------------------------------------------------------------------------- bool KMGroupware::vPartToHTML( int /*aUpdateCounter*/, const QString& vCal, QString fname, QString& prefix, QString& postfix ) const { VCalType type = getVCalType( vCal ); if( type == vCalUnknown ) { kdDebug(5006) << "Unknown incidence!\n"; return false; } CalendarLocal cl; ICalFormat format; format.fromString(&cl, vCal); // make a shallow copy of the event list Event::List eventList = cl.events(); // the events will be deleted automatically when cl is destroyed eventList.setAutoDelete(false); if( eventList.count() == 0 ) // This could be a task // TODO: Handle tasks return false; // parse the first event out of the vcal // ### is it legal to have several events per mail? Event* event = eventList.first(); QString sLocation = event->location(); QString sDtEnd = event->dtEndTimeStr(); QString sDtStart = event->dtStartTimeStr(); QString sDescr = event->description().simplifyWhiteSpace(); QString sMethod; // = event->method(); //###TODO actually the scheduler needs to do that QString sAttendee; Attendee::List attendees = event->attendees(); if( attendees.count() == 0 ) { kdDebug(5006) << "No attendees in the iCal!\n"; return false; } if( attendees.count() != 1 ) kdDebug(5006) << "Warning: attendeecount in the reply should be 1 but is " << attendees.count() << endl; Attendee* attendee = *attendees.begin(); // FIXME: This is a temporary workaround to get the method sMethod = "METHOD"; vPartMicroParser( vCal.utf8(), sMethod ); QString sSummary = event->summary(); kdDebug(5006) << "Event stuff: " << sLocation << ", " << sDtEnd << ", " << sDtStart << ", " << sDescr << ", " << sMethod << ", " << sAttendee << endl; string2HTML( sLocation ); string2HTML( sDescr ); sMethod = sMethod.lower(); QString typeString; if( type == vCalEvent ) typeString = i18n("calendar"); else typeString = i18n("tasks"); if( sMethod == "request" ) { if( type == vCalEvent ) { prefix = i18n("You have been invited to a meeting"); prefix += "
"; if( !sLocation.isEmpty() ) prefix.append( i18n( "The meeting will take place in %1 from %2 to %3" ) .arg( sLocation ).arg( sDtStart ).arg( sDtEnd ) ); else prefix.append( i18n( "The meeting will take place from %1 to %2" ) .arg( sDtStart ) .arg( sDtEnd ) ); } else { prefix = i18n( "You have been assigned a task:
%1" ).arg( sSummary ); } } else if( sMethod == "reply" ) { switch( attendee->status() ) { case Attendee::Accepted: if( type == vCalEvent ) prefix = i18n("Sender accepts the invitation to meet in %1
from %2 to %3.") .arg( sLocation ).arg( sDtStart ).arg( sDtEnd ); else if( type == vCalTodo ) prefix = i18n( "Sender accepts the task %1." ).arg(sSummary ); break; case Attendee::Tentative: if( type == vCalEvent ) prefix = i18n("Sender tentatively accepts the invitation to meet in %1
from %2 to %3.") .arg( sLocation ).arg( sDtStart ).arg( sDtEnd ); else if( type == vCalTodo ) prefix = i18n( "Sender tentatively accepts the task %1." ). arg(sSummary ); break; case Attendee::Declined: if( type == vCalEvent ) prefix = i18n("Sender declines the invitation to meet in %1
from %2 to %3.") .arg( sLocation ).arg( sDtStart ).arg( sDtEnd ); else if( vCalTodo ) prefix = i18n( "Sender declines the task %1." ).arg( sSummary ); break; default: if( type == vCalEvent ) { prefix = i18n("This is an unknown reply to the event in %1 from %2 to %3") .arg( sLocation ).arg( sDtStart ).arg( sDtEnd ); } else if( type == vCalTodo ) { prefix = i18n("This is an unknown reply to the task %1").arg(sSummary); } } } else if( sMethod == "cancel" ) { if( type == vCalEvent ) { prefix = i18n("The event %1 was canceled").arg(sSummary); } else if( type == vCalTodo ) { prefix = i18n("The task %1 was canceled").arg(sSummary); } } // show the 'buttons' (only if in groupware mode) if( mUseGroupware ) { prefix.append( "
 
 
 " ); if( sMethod == "request" || sMethod == "update" ) { // Accept prefix.append( QString("") .arg(fname) ); prefix.append( i18n("[Accept]") ); prefix.append( QString("   ") ); // Accept conditionally prefix.append( QString("") .arg( fname ) ); prefix.append( i18n("[Accept cond.]") ); prefix.append( QString("   ") ); // Decline prefix.append( QString("") .arg( fname ) ); prefix.append( i18n("[Decline]") ); prefix.append( QString("   " ) ); if( type == vCalEvent ) { // Check my calendar... prefix.append(QString("") .arg(fname)); prefix.append(i18n("[Check my calendar...]")); prefix.append(QString("")); } } else if( sMethod == "reply" ) { // Enter this into my calendar prefix.append(QString("") .arg(fname)); if( type == vCalEvent ) prefix.append(i18n("[Enter this into my calendar]")); else prefix.append(i18n("[Enter this into my tasks]")); prefix.append(QString("")); } else if( sMethod == "cancel" ) { // Cancel event from my calendar prefix.append( QString("") .arg( fname ) ); prefix.append( i18n("[Remove this from my calendar]")); prefix.append(QString("")); } prefix.append( "
" ); } if( sMethod == "request" || sMethod == "cancel" ) { sDescr.prepend( "
 
 
" + i18n("Description:") + "
 " ); sDescr.append( "
" ); prefix.append( sDescr ); } prefix.append(" 
 
"); prefix.append(i18n("Original message:")); prefix.append("
 "); // postfix: postfix = "
"; return true; } //----------------------------------------------------------------------------- QString stringProp( KTNEFMessage* tnefMsg, const Q_UINT32& key, const QString& fallback = QString::null) { return tnefMsg->findProp( key < 0x10000 ? key & 0xFFFF : key >> 16, fallback ); } QString sNamedProp( KTNEFMessage* tnefMsg, const QString& name, const QString& fallback = QString::null) { return tnefMsg->findNamedProp( name, fallback ); } //----------------------------------------------------------------------------- bool KMGroupware::msTNEFToVPart( const QByteArray& tnef, QString& vPart ) { // Note: vPart is not erased but // keeps it's initial data if it cannot be decoded bool bOk = false; KTNEFParser parser; QBuffer buf( tnef ); CalendarLocal cal; Addressee addressee; VCardConverter cardConv; ICalFormat calFormat; Event* event = new Event(); if( parser.openDevice( &buf ) ) { KTNEFMessage* tnefMsg = parser.message(); //QMap props = parser.message()->properties(); // everything depends from property PR_MESSAGE_CLASS // (this is added by KTNEFParser): QString msgClass = tnefMsg->findProp(0x001A, QString::null, true).upper(); if( !msgClass.isEmpty() ){ // Match the old class names that might be used by Outlook for // compatibility with Microsoft Mail for Windows for Workgroups 3.1. bool bCompatClassAppointment = false; bool bCompatMethodRequest = false; bool bCompatMethodCancled = false; bool bCompatMethodAccepted = false; bool bCompatMethodAcceptedCond = false; bool bCompatMethodDeclined = false; if( msgClass.startsWith( "IPM.MICROSOFT SCHEDULE." ) ){ bCompatClassAppointment = true; if( msgClass.endsWith( ".MTGREQ" ) ) bCompatMethodRequest = true; if( msgClass.endsWith( ".MTGCNCL" ) ) bCompatMethodCancled = true; if( msgClass.endsWith( ".MTGRESPP" ) ) bCompatMethodAccepted = true; if( msgClass.endsWith( ".MTGRESPA" ) ) bCompatMethodAcceptedCond = true; if( msgClass.endsWith( ".MTGRESPN" ) ) bCompatMethodDeclined = true; } bool bCompatClassNote = (msgClass == "IPM.MICROSOFT MAIL.NOTE"); if( bCompatClassAppointment || "IPM.APPOINTMENT" == msgClass ){ // compose a vCal bool bIsReply = false; QString prodID; prodID += "-//Microsoft Corporation//Outlook "; prodID += tnefMsg->findNamedProp("0x8554", "9.0"); prodID += "MIMEDIR/EN\n"; prodID += "VERSION:2.0\n"; calFormat.setApplication("Outlook", prodID); Scheduler::Method method; if( bCompatMethodRequest ) method = Scheduler::Request; else if ( bCompatMethodCancled ) method = Scheduler::Cancel; else if ( bCompatMethodAccepted || bCompatMethodAcceptedCond || bCompatMethodDeclined ){ method = Scheduler::Reply; bIsReply = true; } else{ // pending(khz): verify whether "0x0c17" is the right tag ??? // // at the moment we think there are REQUESTS and UPDATES // // but WHAT ABOUT REPLIES ??? // // if( tnefMsg->findProp(0x0c17) == "1" ) bIsReply = true; method = Scheduler::Request; } /// ### FIXME Need to get this attribute written ScheduleMessage schedMsg(event, method, ScheduleMessage::Unknown /*???*/); QString sSenderSearchKeyEmail( tnefMsg->findProp(0x0C1D) ); if( !sSenderSearchKeyEmail.isEmpty() ){ int colon = sSenderSearchKeyEmail.find(':'); if( sSenderSearchKeyEmail.find(':') == -1 ) // may be e.g. "SMTP:KHZ@KDE.ORG" sSenderSearchKeyEmail.remove(0, colon+1); } QString s( tnefMsg->findProp(0x0e04) ); QStringList attendees = QStringList::split(';', s); if( attendees.count() ){ for ( QStringList::Iterator it = attendees.begin(); it != attendees.end(); ++it ) { // skip all entries that have no '@' since these are no mail addresses if( (*it).find('@') == -1 ){ s = (*it).stripWhiteSpace(); Attendee *attendee = new Attendee(s,s, true); if (bIsReply) { if (bCompatMethodAccepted) attendee->setStatus(Attendee::Accepted); if (bCompatMethodAcceptedCond) attendee->setStatus(Attendee::Declined); if (bCompatMethodDeclined) attendee->setStatus(Attendee::Tentative); } else { attendee->setStatus(Attendee::NeedsAction); attendee->setRole(Attendee::ReqParticipant); } event->addAttendee(attendee); } } } else { // Oops, no attendees? // This must be old style, let us use the PR_SENDER_SEARCH_KEY. s = sSenderSearchKeyEmail; if( !s.isEmpty() ){ Attendee *attendee = new Attendee(QString::null,QString::null, true); if (bIsReply) { if (bCompatMethodAccepted) attendee->setStatus(Attendee::Accepted); if (bCompatMethodAcceptedCond) attendee->setStatus(Attendee::Declined); if (bCompatMethodDeclined) attendee->setStatus(Attendee::Tentative); } else { attendee->setStatus(Attendee::NeedsAction); attendee->setRole(Attendee::ReqParticipant); } event->addAttendee(attendee); } } s = tnefMsg->findProp(0x0c1f); // look for organizer property if( s.isEmpty() && !bIsReply ) s = sSenderSearchKeyEmail; if( !s.isEmpty() ) event->setOrganizer(s); s = tnefMsg->findProp(0x8516) .replace(QChar('-'), QString::null) .replace(QChar(':'), QString::null); event->setDtStart(QDateTime::fromString(s)); // ## Format?? s = tnefMsg->findProp(0x8517) .replace(QChar('-'), QString::null) .replace(QChar(':'), QString::null); event->setDtEnd(QDateTime::fromString(s)); s = tnefMsg->findProp(0x8208); event->setLocation(s); // is it OK to set this to OPAQUE always ?? //vPart += "TRANSP:OPAQUE\n"; ###FIXME, portme! //vPart += "SEQUENCE:0\n"; // is "0x0023" OK - or should we look for "0x0003" ?? s = tnefMsg->findProp(0x0023); event->setUid(s); // pending(khz): is this value in local timezone ?? must it be adjusted ?? // most likely this is a bug in the server or in Outlook - we ignore it for now. s = tnefMsg->findProp(0x8202) .replace(QChar('-'), QString::null) .replace(QChar(':'), QString::null); // event->setDtStamp(QDateTime::fromString(s)); // ### libkcal always uses currentDateTime() s = tnefMsg->findNamedProp("Keywords"); event->setCategories(s); s = tnefMsg->findProp(0x1000); event->setDescription(s); s = tnefMsg->findProp(0x0070); event->setSummary(s); s = tnefMsg->findProp(0x0026); event->setPriority(s.toInt()); // is reminder flag set ? if(!tnefMsg->findProp(0x8503).isEmpty()) { Alarm *alarm = new Alarm(event); QDateTime highNoonTime( pureISOToLocalQDateTime( tnefMsg->findProp(0x8502) .replace(QChar('-'), "") .replace(QChar(':'), "") ) ); QDateTime wakeMeUpTime( pureISOToLocalQDateTime( tnefMsg->findProp(0x8560, "") .replace(QChar('-'), "") .replace(QChar(':'), "") ) ); alarm->setTime(wakeMeUpTime); if( highNoonTime.isValid() && wakeMeUpTime.isValid() ) alarm->setStartOffset(Duration(highNoonTime, wakeMeUpTime)); else // default: wake them up 15 minutes before the appointment alarm->setStartOffset(Duration(15*60)); alarm->setDisplayAlarm(i18n("Reminder")); // sorry: the different action types are not known (yet) // so we always set 'DISPLAY' (no sounds, no images...) event->addAlarm(alarm); } cal.addEvent(event); bOk = true; // we finished composing a vCal }else if( bCompatClassNote || "IPM.CONTACT" == msgClass ){ addressee.setUid(stringProp(tnefMsg, attMSGID)); addressee.setFormattedName(stringProp(tnefMsg, MAPI_TAG_PR_DISPLAY_NAME)); addressee.insertEmail(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_EMAIL1EMAILADDRESS),true); addressee.insertEmail(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_EMAIL2EMAILADDRESS),false); addressee.insertEmail(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_EMAIL3EMAILADDRESS),false); addressee.insertCustom("KADDRESSBOOK", "X-IMAddress", sNamedProp(tnefMsg, MAPI_TAG_CONTACT_IMADDRESS)); addressee.insertCustom("KADDRESSBOOK", "X-SpousesName", stringProp(tnefMsg, MAPI_TAG_PR_SPOUSE_NAME)); addressee.insertCustom("KADDRESSBOOK", "X-ManagersName", stringProp(tnefMsg, MAPI_TAG_PR_MANAGER_NAME)); addressee.insertCustom("KADDRESSBOOK", "X-AssistantsName", stringProp(tnefMsg, MAPI_TAG_PR_ASSISTANT)); addressee.insertCustom("KADDRESSBOOK", "X-Department", stringProp(tnefMsg, MAPI_TAG_PR_DEPARTMENT_NAME)); addressee.insertCustom("KADDRESSBOOK", "X-Office", stringProp(tnefMsg, MAPI_TAG_PR_OFFICE_LOCATION)); addressee.insertCustom("KADDRESSBOOK", "X-Profession", stringProp(tnefMsg, MAPI_TAG_PR_PROFESSION)); QString s = tnefMsg->findProp( MAPI_TAG_PR_WEDDING_ANNIVERSARY) .replace(QChar('-'), QString::null) .replace(QChar(':'), QString::null); if( !s.isEmpty() ) addressee.insertCustom("KADDRESSBOOK", "X-Anniversary", s); addressee.setUrl(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_WEBPAGE)); // collect parts of Name entry addressee.setFamilyName(stringProp(tnefMsg, MAPI_TAG_PR_SURNAME)); addressee.setGivenName(stringProp(tnefMsg, MAPI_TAG_PR_GIVEN_NAME)); addressee.setAdditionalName(stringProp(tnefMsg, MAPI_TAG_PR_MIDDLE_NAME)); addressee.setPrefix(stringProp(tnefMsg, MAPI_TAG_PR_DISPLAY_NAME_PREFIX)); addressee.setSuffix(stringProp(tnefMsg, MAPI_TAG_PR_GENERATION)); addressee.setNickName(stringProp(tnefMsg, MAPI_TAG_PR_NICKNAME)); addressee.setRole(stringProp(tnefMsg, MAPI_TAG_PR_TITLE)); addressee.setOrganization(stringProp(tnefMsg, MAPI_TAG_PR_COMPANY_NAME)); /* the MAPI property ID of this (multiline) )field is unknown: vPart += stringProp(tnefMsg, "\n","NOTE", ... , "" ); */ Address adr; adr.setPostOfficeBox(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_PO_BOX)); adr.setStreet(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STREET)); adr.setLocality(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_CITY)); adr.setRegion(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_STATE_OR_PROVINCE)); adr.setPostalCode(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_POSTAL_CODE)); adr.setCountry(stringProp(tnefMsg, MAPI_TAG_PR_HOME_ADDRESS_COUNTRY)); adr.setType(Address::Home); addressee.insertAddress(adr); adr.setPostOfficeBox(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOBOX)); adr.setStreet(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTREET)); adr.setLocality(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCITY)); adr.setRegion(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSSTATE)); adr.setPostalCode(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSPOSTALCODE)); adr.setCountry(sNamedProp(tnefMsg, MAPI_TAG_CONTACT_BUSINESSADDRESSCOUNTRY)); adr.setType(Address::Work); addressee.insertAddress(adr); adr.setPostOfficeBox(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_PO_BOX)); adr.setStreet(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STREET)); adr.setLocality(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_CITY)); adr.setRegion(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_STATE_OR_PROVINCE)); adr.setPostalCode(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_POSTAL_CODE)); adr.setCountry(stringProp(tnefMsg, MAPI_TAG_PR_OTHER_ADDRESS_COUNTRY)); adr.setType(Address::Dom); addressee.insertAddress(adr); // problem: the 'other' address was stored by KOrganizer in // a line looking like the following one: // vPart += "\nADR;TYPE=dom;TYPE=intl;TYPE=parcel;TYPE=postal;TYPE=work;TYPE=home:other_pobox;;other_str1\nother_str2;other_loc;other_region;other_pocode;other_country QString nr; nr = stringProp(tnefMsg, MAPI_TAG_PR_HOME_TELEPHONE_NUMBER); addressee.insertPhoneNumber(KABC::PhoneNumber(nr,PhoneNumber::Home)); nr = stringProp(tnefMsg, MAPI_TAG_PR_BUSINESS_TELEPHONE_NUMBER); addressee.insertPhoneNumber(KABC::PhoneNumber(nr,PhoneNumber::Work)); nr = stringProp(tnefMsg, MAPI_TAG_PR_MOBILE_TELEPHONE_NUMBER); addressee.insertPhoneNumber(KABC::PhoneNumber(nr,PhoneNumber::Cell)); nr = stringProp(tnefMsg, MAPI_TAG_PR_HOME_FAX_NUMBER); addressee.insertPhoneNumber(KABC::PhoneNumber(nr,PhoneNumber::Fax|PhoneNumber::Home)); nr = stringProp(tnefMsg, MAPI_TAG_PR_BUSINESS_FAX_NUMBER); addressee.insertPhoneNumber(KABC::PhoneNumber(nr,PhoneNumber::Fax|PhoneNumber::Work)); s = tnefMsg->findProp( MAPI_TAG_PR_BIRTHDAY) .replace(QChar('-'), QString::null) .replace(QChar(':'), QString::null); if( !s.isEmpty() ) addressee.setBirthday(QDateTime::fromString(s)); bOk = (!addressee.isEmpty()); }else if( "IPM.NOTE" == msgClass ){ } // else if ... and so on ... } } // compose return string QString s; vPart = calFormat.toString(&cal); if (cardConv.addresseeToVCard(addressee, s, VCardConverter::v3_0)) vPart += s; return bOk; } //----------------------------------------------------------------------------- bool KMGroupware::msTNEFToHTML( KMReaderWin* reader, QString& vPart, QString fname, QString& prefix, QString& postfix ) const { QByteArray tnef( kFileToBytes( fname, false ) ); if( tnef.count() ) { int updateCounter = 0; if( msTNEFToVPart( tnef, vPart ) ){ QByteArray theBody( vPart.utf8() ); QString fname2( ObjectTreeParser::byteArrayToTempFile( reader, "groupware", "vPart_decoded.raw", theBody ) ); if( !fname2.isEmpty() ) return vPartToHTML( updateCounter, vPart, fname2, prefix, postfix ); } }else{ KMessageBox::error(0, i18n("Unable to open file %1.").arg(fname)); } return false; } //----------------------------------------------------------------------------- bool KMGroupware::foundGroupwareLink( const QString aUrl, QString& gwType, QString& gwAction, QString& gwAction2, QString& gwData ) { static QString gwPrefix("groupware_"); gwType = ""; gwAction = ""; gwAction2 = ""; gwData = ""; int i1 = aUrl.find( gwPrefix ); if( -1 < i1 ) { i1 += gwPrefix.length(); int i2 = aUrl.find("_", i1); if( i1 <= i2 ) { // retrieve gwType gwType = aUrl.mid( i1, i2-i1 ); i1 = i2+1; i2 = aUrl.find("_", i1); if( i1 <= i2 ) { // retrieve gwAction gwAction = aUrl.mid( i1, i2-i1 ); i1 = i2+1; i2 = aUrl.find("#", i1); if( i1 <= i2 ) { // retrieve gwAction2 gwAction2 = aUrl.mid( i1, i2-i1 ); i2 += 1; // retrieve gwData gwData = aUrl.mid( i2 ); } } } } return !gwType.isEmpty(); } bool KMGroupware::handleLink( const KURL &aUrl, KMMessage* msg ) { QString gwType, gwAction, gwAction2, gwData; if( !aUrl.hasRef() || !foundGroupwareLink( aUrl.path()+"#"+aUrl.ref(), gwType, gwAction, gwAction2, gwData ) ) // No groupware link to handle here return false; if( gwType != "vCal" || gwData.isEmpty() || ( "request" != gwAction && "reply" != gwAction && "cancel" != gwAction ) ) { // Then we can't handle it. But it is a groupware link, so we return true kdDebug(5006) << "Unhandled groupware link\n"; return true; } // Read the vCal QFile file( gwData ); if( !file.open( IO_ReadOnly ) ) { kdDebug(5006) << "Could not open file " << gwData << endl; return true; } QTextStream ts( &file ); ts.setEncoding( QTextStream::UnicodeUTF8 ); QString vCal = ts.read(); file.close(); // Find the receiver if we can QString receiver; if( msg ) { KMIdentity ident = kmkernel->identityManager()->identityForAddress( msg->to() ); if( ident != KMIdentity::null ) { receiver = ident.emailAddr(); } else { QStringList addrs = KMMessage::splitEmailAddrList( msg->to() ); bool ok; receiver = KInputDialog::getItem( i18n("Select Address"), i18n("None of your identities match the receiver " "of this message,
please choose which of " "the following addresses is yours:"), addrs, 0, FALSE, &ok, kmkernel->mainWin() ); if( !ok ) return false; } } // Find the sender if we can QCString sender = KMMessage::getEmailAddr( msg->from() ); if( "request" == gwAction ) processVCalRequest( receiver.utf8(), vCal, gwAction2 ); else if( "reply" == gwAction ) processVCalReply( sender, vCal, gwAction2 ); else if( "cancel" == gwAction ) /* Note, we pass gwAction here, not gwAction2 */ processVCalReply( sender, vCal, gwAction ); return true; } /*! This method handles incoming resource requests. It sends them off to KOrganizer for answering, records the result and sends an answer back. */ bool KMGroupware::incomingResourceMessage( KMAccount* /*acct*/, KMMessage* /*msg*/ ) { #if 0 // TODO: Reimplement with DCOP if( !mUseGroupware) return false; QString vCalIn; if( vPartFoundAndDecoded( msg, vCalIn ) ) return false; bool vCalInOK, vCalOutOK, isFree; QString vCalOut; QDateTime start, end; emit( signalResourceRequest( acct->intervals(), KMMessage::getEmailAddr( msg->to() ), vCalIn, vCalInOK, vCalOut, vCalOutOK, isFree, start, end ) ); if( !vCalInOK || !vCalOutOK ) return false; // parsing or generation error somewhere // Check whether we are supposed to answer automatically at all KConfigGroup options( KMKernel::config(), "Groupware" ); if( isFree && options.readBoolEntry( "AutoAccept", false ) ) return false; if( !isFree && options.readBoolEntry( "AutoDeclConflict", false ) ) return false; // Everything went fine so far, now attach the answer KMMessage* msgNew = 0; if( msg ){ msgNew = msg->createReply( false, false, vCalOut, false, true, TRUE ); msgNew->setType( DwMime::kTypeText ); msgNew->setSubtype( DwMime::kSubtypeVCal ); msgNew->setHeaderField("Content-Type", "text/calendar; method=REPLY; charset=\"utf-8\""); internal_directlySendMessage( msgNew ); } // And also record in the account. acct->addInterval( qMakePair( start, end ) ); #endif return true; } void KMGroupware::reloadFolderTree() const { // Make the folder tree show the icons or not if( mMainWidget && mMainWidget->folderTree() ) mMainWidget->folderTree()->reload(); } #include "kmgroupware.moc"