00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "incidenceformatter.h"
00025 #include "attachment.h"
00026 #include "event.h"
00027 #include "todo.h"
00028 #include "journal.h"
00029 #include "calendar.h"
00030 #include "calendarlocal.h"
00031 #include "icalformat.h"
00032 #include "freebusy.h"
00033 #include "calendarresources.h"
00034
00035 #include "kpimutils/email.h"
00036 #include "kabc/phonenumber.h"
00037 #include "kabc/vcardconverter.h"
00038 #include "kabc/stdaddressbook.h"
00039
00040 #include <kdatetime.h>
00041 #include <kglobal.h>
00042 #include <kiconloader.h>
00043 #include <klocale.h>
00044
00045 #include <QtCore/QBuffer>
00046 #include <QtCore/QList>
00047 #include <QtGui/QTextDocument>
00048 #include <QtGui/QApplication>
00049
00050 #include <time.h>
00051
00052 using namespace KCal;
00053
00054
00055
00056
00057
00058 static QString eventViewerAddLink( const QString &ref, const QString &text,
00059 bool newline = true )
00060 {
00061 QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00062 if ( newline ) {
00063 tmpStr += '\n';
00064 }
00065 return tmpStr;
00066 }
00067
00068 static QString eventViewerAddTag( const QString &tag, const QString &text )
00069 {
00070 int numLineBreaks = text.count( "\n" );
00071 QString str = '<' + tag + '>';
00072 QString tmpText = text;
00073 QString tmpStr = str;
00074 if( numLineBreaks >= 0 ) {
00075 if ( numLineBreaks > 0 ) {
00076 int pos = 0;
00077 QString tmp;
00078 for ( int i = 0; i <= numLineBreaks; i++ ) {
00079 pos = tmpText.indexOf( "\n" );
00080 tmp = tmpText.left( pos );
00081 tmpText = tmpText.right( tmpText.length() - pos - 1 );
00082 tmpStr += tmp + "<br>";
00083 }
00084 } else {
00085 tmpStr += tmpText;
00086 }
00087 }
00088 tmpStr += "</" + tag + '>';
00089 return tmpStr;
00090 }
00091
00092 static QString eventViewerFormatCategories( Incidence *event )
00093 {
00094 QString tmpStr;
00095 if ( !event->categoriesStr().isEmpty() ) {
00096 if ( event->categories().count() == 1 ) {
00097 tmpStr = eventViewerAddTag( "h3", i18n( "Category" ) );
00098 } else {
00099 tmpStr = eventViewerAddTag( "h3", i18n( "Categories" ) );
00100 }
00101 tmpStr += eventViewerAddTag( "p", event->categoriesStr() );
00102 }
00103 return tmpStr;
00104 }
00105
00106 static QString linkPerson( const QString &email, QString name, QString uid,
00107 const QString &iconPath )
00108 {
00109
00110
00111 if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00112 KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
00113 KABC::Addressee::List addressList = add_book->findByEmail( email );
00114 KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() );
00115 if ( !o.isEmpty() && addressList.size() < 2 ) {
00116 if ( name.isEmpty() ) {
00117
00118 name = o.formattedName();
00119 }
00120 uid = o.uid();
00121 } else {
00122
00123 uid.clear();
00124 }
00125 }
00126 kDebug() << "formatAttendees: uid =" << uid;
00127
00128
00129 QString tmpString = "<li>";
00130 if ( !uid.isEmpty() ) {
00131
00132 if ( name.isEmpty() ) {
00133
00134 tmpString += eventViewerAddLink( "uid:" + uid, email );
00135 } else {
00136 tmpString += eventViewerAddLink( "uid:" + uid, name );
00137 }
00138 } else {
00139
00140 tmpString += ( name.isEmpty() ? email : name );
00141 }
00142 tmpString += '\n';
00143
00144
00145 if ( !email.isEmpty() && !iconPath.isNull() ) {
00146 KCal::Person person( name, email );
00147 KUrl mailto;
00148 mailto.setProtocol( "mailto" );
00149 mailto.setPath( person.fullName() );
00150 tmpString += eventViewerAddLink( mailto.url(), "<img src=\"" + iconPath + "\">" );
00151 }
00152 tmpString += "</li>\n";
00153
00154 return tmpString;
00155 }
00156
00157 static QString eventViewerFormatAttendees( Incidence *event )
00158 {
00159 QString tmpStr;
00160 Attendee::List attendees = event->attendees();
00161 if ( attendees.count() ) {
00162 KIconLoader *iconLoader = KIconLoader::global();
00163 const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00164
00165
00166 tmpStr += eventViewerAddTag( "h4", i18n( "Organizer" ) );
00167 tmpStr += "<ul>";
00168 tmpStr += linkPerson( event->organizer().email(), event->organizer().name(),
00169 QString(), iconPath );
00170 tmpStr += "</ul>";
00171
00172
00173 tmpStr += eventViewerAddTag( "h4", i18n( "Attendees" ) );
00174 tmpStr += "<ul>";
00175 Attendee::List::ConstIterator it;
00176 for ( it = attendees.begin(); it != attendees.end(); ++it ) {
00177 Attendee *a = *it;
00178 tmpStr += linkPerson( a->email(), a->name(), a->uid(), iconPath );
00179 if ( !a->delegator().isEmpty() ) {
00180 tmpStr += i18n( " (delegated by %1)", a->delegator() );
00181 }
00182 if ( !a->delegate().isEmpty() ) {
00183 tmpStr += i18n( " (delegated to %1)", a->delegate() );
00184 }
00185 }
00186 tmpStr += "</ul>";
00187 }
00188 return tmpStr;
00189 }
00190
00191 static QString eventViewerFormatAttachments( Incidence *i )
00192 {
00193 QString tmpStr;
00194 Attachment::List as = i->attachments();
00195 if ( as.count() > 0 ) {
00196 Attachment::List::ConstIterator it;
00197 for ( it = as.begin(); it != as.end(); ++it ) {
00198 if ( (*it)->isUri() ) {
00199 tmpStr += eventViewerAddLink( (*it)->uri(), (*it)->label() );
00200 tmpStr += "<br>";
00201 }
00202 }
00203 }
00204 return tmpStr;
00205 }
00206
00207
00208
00209
00210
00211 static QString eventViewerFormatBirthday( Event *event )
00212 {
00213 if ( !event ) {
00214 return QString();
00215 }
00216 if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" ) {
00217 return QString();
00218 }
00219
00220 QString uid_1 = event->customProperty( "KABC", "UID-1" );
00221 QString name_1 = event->customProperty( "KABC", "NAME-1" );
00222 QString email_1= event->customProperty( "KABC", "EMAIL-1" );
00223
00224 KIconLoader *iconLoader = KIconLoader::global();
00225 const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
00226
00227 QString tmpString = "<ul>";
00228 tmpString += linkPerson( email_1, name_1, uid_1, iconPath );
00229
00230 if ( event->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00231 QString uid_2 = event->customProperty( "KABC", "UID-2" );
00232 QString name_2 = event->customProperty( "KABC", "NAME-2" );
00233 QString email_2= event->customProperty( "KABC", "EMAIL-2" );
00234 tmpString += linkPerson( email_2, name_2, uid_2, iconPath );
00235 }
00236
00237 tmpString += "</ul>";
00238 return tmpString;
00239 }
00240
00241 static QString eventViewerFormatHeader( Incidence *incidence )
00242 {
00243 QString tmpStr = "<table><tr>";
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269 tmpStr += "<td>" + eventViewerAddTag( "h2", incidence->richSummary() ) + "</td>";
00270 tmpStr += "</tr></table><br>";
00271
00272 return tmpStr;
00273 }
00274
00275 static QString eventViewerFormatEvent( Event *event )
00276 {
00277 if ( !event ) {
00278 return QString();
00279 }
00280
00281 QString tmpStr = eventViewerFormatHeader( event );
00282
00283 tmpStr += "<table>";
00284 if ( !event->location().isEmpty() ) {
00285 tmpStr += "<tr>";
00286 tmpStr += "<td align=\"right\"><b>" + i18n( "Location" ) + "</b></td>";
00287 tmpStr += "<td>" + event->richLocation() + "</td>";
00288 tmpStr += "</tr>";
00289 }
00290
00291 tmpStr += "<tr>";
00292 if ( event->allDay() ) {
00293 if ( event->isMultiDay() ) {
00294 tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00295 tmpStr += "<td>" + i18nc("<beginTime> - <endTime>","%1 - %2",
00296 event->dtStartDateStr( true, event->dtStart().timeSpec() ),
00297 event->dtEndDateStr( true, event->dtEnd().timeSpec() ) ) + "</td>";
00298 } else {
00299 tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00300 tmpStr += "<td>" +
00301 i18nc( "date as string","%1",
00302 event->dtStartDateStr( true, event->dtStart().timeSpec() ) ) + "</td>";
00303 }
00304 } else {
00305 if ( event->isMultiDay() ) {
00306 tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00307 tmpStr += "<td>" +
00308 i18nc( "<beginTime> - <endTime>","%1 - %2",
00309 event->dtStartStr( true, event->dtStart().timeSpec() ),
00310 event->dtEndStr( true, event->dtEnd().timeSpec() ) ) + "</td>";
00311 } else {
00312 tmpStr += "<td align=\"right\"><b>" + i18n( "Time" ) + "</b></td>";
00313 if ( event->hasEndDate() && event->dtStart() != event->dtEnd() ) {
00314 tmpStr += "<td>" +
00315 i18nc( "<beginTime> - <endTime>","%1 - %2",
00316 event->dtStartTimeStr( true, event->dtStart().timeSpec() ),
00317 event->dtEndTimeStr( true, event->dtEnd().timeSpec() ) ) + "</td>";
00318 } else {
00319 tmpStr += "<td>" + event->dtStartTimeStr( true, event->dtStart().timeSpec() ) + "</td>";
00320 }
00321 tmpStr += "</tr><tr>";
00322 tmpStr += "<td align=\"right\"><b>" + i18n( "Date" ) + "</b></td>";
00323 tmpStr += "<td>" +
00324 i18nc( "date as string","%1",
00325 event->dtStartDateStr( true, event->dtStart().timeSpec() ) ) + "</td>";
00326 }
00327 }
00328 tmpStr += "</tr>";
00329
00330 if ( event->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00331 tmpStr += "<tr>";
00332 tmpStr += "<td align=\"right\"><b>" + i18n( "Birthday" ) + "</b></td>";
00333 tmpStr += "<td>" + eventViewerFormatBirthday( event ) + "</td>";
00334 tmpStr += "</tr>";
00335 tmpStr += "</table>";
00336 return tmpStr;
00337 }
00338
00339 if ( !event->description().isEmpty() ) {
00340 tmpStr += "<tr>";
00341 tmpStr += "<td></td>";
00342 tmpStr += "<td>" + eventViewerAddTag( "p", event->richDescription() ) + "</td>";
00343 tmpStr += "</tr>";
00344 }
00345
00346 if ( event->categories().count() > 0 ) {
00347 tmpStr += "<tr>";
00348 tmpStr += "<td align=\"right\"><b>";
00349 tmpStr += i18np( "1 category", "%1 categories", event->categories().count() ) +
00350 "</b></td>";
00351 tmpStr += "<td>" + event->categoriesStr() + "</td>";
00352 tmpStr += "</tr>";
00353 }
00354
00355 if ( event->recurs() ) {
00356 KDateTime dt = event->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00357 tmpStr += "<tr>";
00358 tmpStr += "<td align=\"right\"><b>" + i18n( "Next Occurrence" )+ "</b></td>";
00359 tmpStr += "<td>" +
00360 KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) + "</td>";
00361 tmpStr += "</tr>";
00362 }
00363
00364 tmpStr += "<tr><td colspan=\"2\">";
00365 tmpStr += eventViewerFormatAttendees( event );
00366 tmpStr += "</td></tr>";
00367
00368 int attachmentCount = event->attachments().count();
00369 if ( attachmentCount > 0 ) {
00370 tmpStr += "<tr>";
00371 tmpStr += "<td align=\"right\"><b>";
00372 tmpStr += i18np( "1 attachment", "%1 attachments", attachmentCount )+ "</b></td>";
00373 tmpStr += "<td>" + eventViewerFormatAttachments( event ) + "</td>";
00374 tmpStr += "</tr>";
00375 }
00376
00377 tmpStr += "</table>";
00378 tmpStr += "<p><em>" +
00379 i18n( "Creation date: %1", KGlobal::locale()->formatDateTime(
00380 event->created().dateTime(), KLocale::ShortDate ) ) + "</em>";
00381 return tmpStr;
00382 }
00383
00384 static QString eventViewerFormatTodo( Todo *todo )
00385 {
00386 if ( !todo ) {
00387 return QString();
00388 }
00389
00390 QString tmpStr = eventViewerFormatHeader( todo );
00391
00392 if ( !todo->location().isEmpty() ) {
00393 tmpStr += eventViewerAddTag( "b", i18n(" Location: %1", todo->richLocation() ) );
00394 tmpStr += "<br>";
00395 }
00396
00397 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00398 tmpStr += i18n( "<b>Due on:</b> %1", todo->dtDueStr( true, todo->dtDue().timeSpec() ) );
00399 }
00400
00401 if ( !todo->description().isEmpty() ) {
00402 tmpStr += eventViewerAddTag( "p", todo->richDescription() );
00403 }
00404
00405 tmpStr += eventViewerFormatCategories( todo );
00406
00407 if ( todo->priority() > 0 ) {
00408 tmpStr += i18n( "<p><b>Priority:</b> %1</p>", todo->priority() );
00409 } else {
00410 tmpStr += i18n( "<p><b>Priority:</b> %1</p>", i18n( "Unspecified" ) );
00411 }
00412
00413 tmpStr += i18n( "<p><i>%1 % completed</i></p>", todo->percentComplete() );
00414
00415 if ( todo->recurs() ) {
00416 KDateTime dt = todo->recurrence()->getNextDateTime( KDateTime::currentUtcDateTime() );
00417 tmpStr += eventViewerAddTag( "p", "<em>" +
00418 i18n( "This is a recurring to-do. The next occurrence will be on %1.",
00419 KGlobal::locale()->formatDateTime( dt.dateTime(), KLocale::ShortDate ) ) + "</em>" );
00420 }
00421 tmpStr += eventViewerFormatAttendees( todo );
00422 tmpStr += eventViewerFormatAttachments( todo );
00423 tmpStr += "<p><em>" + i18n( "Creation date: %1",
00424 KGlobal::locale()->formatDateTime( todo->created().dateTime(), KLocale::ShortDate ) ) + "</em>";
00425 return tmpStr;
00426 }
00427
00428 static QString eventViewerFormatJournal( Journal *journal )
00429 {
00430 if ( !journal ) {
00431 return QString();
00432 }
00433
00434 QString tmpStr;
00435 if ( !journal->summary().isEmpty() ) {
00436 tmpStr+= eventViewerAddTag( "h2", journal->richSummary() );
00437 }
00438 tmpStr += eventViewerAddTag(
00439 "h3", i18n( "Journal for %1",
00440 journal->dtStartDateStr( false, journal->dtStart().timeSpec() ) ) );
00441 if ( !journal->description().isEmpty() ) {
00442 tmpStr += eventViewerAddTag( "p", journal->richDescription() );
00443 }
00444 return tmpStr;
00445 }
00446
00447 static QString eventViewerFormatFreeBusy( FreeBusy *fb )
00448 {
00449 if ( !fb ) {
00450 return QString();
00451 }
00452
00453 QString tmpStr(
00454 eventViewerAddTag(
00455 "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) );
00456 tmpStr += eventViewerAddTag(
00457 "h4", i18n( "Busy times in date range %1 - %2:",
00458 KGlobal::locale()->formatDate( fb->dtStart().date(), KLocale::ShortDate ),
00459 KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) ) );
00460
00461 QList<Period> periods = fb->busyPeriods();
00462
00463 QString text =
00464 eventViewerAddTag( "em",
00465 eventViewerAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
00466
00467 QList<Period>::iterator it;
00468 for ( it = periods.begin(); it != periods.end(); ++it ) {
00469 Period per = *it;
00470 if ( per.hasDuration() ) {
00471 int dur = per.duration().asSeconds();
00472 QString cont;
00473 if ( dur >= 3600 ) {
00474 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00475 dur %= 3600;
00476 }
00477 if ( dur >= 60 ) {
00478 cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
00479 dur %= 60;
00480 }
00481 if ( dur > 0 ) {
00482 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00483 }
00484 text += i18nc( "startDate for duration", "%1 for %2",
00485 KGlobal::locale()->formatDateTime(
00486 per.start().dateTime(), KLocale::LongDate ), cont );
00487 text += "<br>";
00488 } else {
00489 if ( per.start().date() == per.end().date() ) {
00490 text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00491 KGlobal::locale()->formatDate( per.start().date() ),
00492 KGlobal::locale()->formatTime( per.start().time() ),
00493 KGlobal::locale()->formatTime( per.end().time() ) );
00494 } else {
00495 text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
00496 KGlobal::locale()->formatDateTime(
00497 per.start().dateTime(), KLocale::LongDate ),
00498 KGlobal::locale()->formatDateTime(
00499 per.end().dateTime(), KLocale::LongDate ) );
00500 }
00501 text += "<br>";
00502 }
00503 }
00504 tmpStr += eventViewerAddTag( "p", text );
00505 return tmpStr;
00506 }
00507
00508
00509 class KCal::IncidenceFormatter::EventViewerVisitor : public IncidenceBase::Visitor
00510 {
00511 public:
00512 EventViewerVisitor() { mResult = ""; }
00513 bool act( IncidenceBase *incidence ) { return incidence->accept( *this ); }
00514 QString result() const { return mResult; }
00515 protected:
00516 bool visit( Event *event )
00517 {
00518 mResult = eventViewerFormatEvent( event );
00519 return !mResult.isEmpty();
00520 }
00521 bool visit( Todo *todo )
00522 {
00523 mResult = eventViewerFormatTodo( todo );
00524 return !mResult.isEmpty();
00525 }
00526 bool visit( Journal *journal )
00527 {
00528 mResult = eventViewerFormatJournal( journal );
00529 return !mResult.isEmpty();
00530 }
00531 bool visit( FreeBusy *fb )
00532 {
00533 mResult = eventViewerFormatFreeBusy( fb );
00534 return !mResult.isEmpty();
00535 }
00536
00537 protected:
00538 QString mResult;
00539 };
00540
00541
00542 QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
00543 {
00544 if ( !incidence ) {
00545 return QString();
00546 }
00547
00548 EventViewerVisitor v;
00549 if ( v.act( incidence ) ) {
00550 return v.result();
00551 } else {
00552 return QString();
00553 }
00554 }
00555
00556
00557
00558
00559
00560 static QString string2HTML( const QString &str )
00561 {
00562 return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
00563 }
00564
00565 static QString eventStartTimeStr( Event *event )
00566 {
00567 QString tmp;
00568 if ( ! event->allDay() ) {
00569 tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
00570 event->dtStartDateStr(), event->dtStartTimeStr() );
00571 } else {
00572 tmp = i18nc( "%1: Start Date", "%1 (time unspecified)", event->dtStartDateStr() );
00573 }
00574 return tmp;
00575 }
00576
00577 static QString eventEndTimeStr( Event *event )
00578 {
00579 QString tmp;
00580 if ( event->hasEndDate() && event->dtEnd().isValid() ) {
00581 if ( ! event->allDay() ) {
00582 tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2",
00583 event->dtEndDateStr(), event->dtEndTimeStr() );
00584 } else {
00585 tmp = i18nc( "%1: End Date", "%1 (time unspecified)", event->dtEndDateStr() );
00586 }
00587 } else {
00588 tmp = i18n( "Unspecified" );
00589 }
00590 return tmp;
00591 }
00592
00593 static QString invitationRow( const QString &cell1, const QString &cell2 )
00594 {
00595 return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
00596 }
00597
00598 static QString invitationsDetailsIncidence( Incidence *incidence )
00599 {
00600 QString html;
00601 QString descr;
00602 if ( !incidence->descriptionIsRich() ) {
00603 descr = string2HTML( incidence->description() );
00604 } else {
00605 descr = eventViewerAddTag( "p", incidence->richDescription() );
00606 }
00607 if( !descr.isEmpty() ) {
00608 html += "<br/><u>" + i18n( "Description:" ) + "</u><table border=\"0\"><tr><td> </td><td>";
00609 html += descr + "</td></tr></table>";
00610 }
00611 QStringList comments = incidence->comments();
00612 if ( !comments.isEmpty() ) {
00613 html += "<br><u>" + i18n( "Comments:" ) + "</u><table border=\"0\"><tr><td> </td><td><ul>";
00614 for ( int i = 0; i < comments.count(); ++i ) {
00615 html += "<li>" + string2HTML( comments[i] ) + "</li>";
00616 }
00617 html += "</ul></td></tr></table>";
00618 }
00619 return html;
00620 }
00621
00622 static QString invitationDetailsEvent( Event *event )
00623 {
00624
00625 if ( !event ) {
00626 return QString();
00627 }
00628
00629 QString html;
00630 QString tmp;
00631
00632 QString sSummary = i18n( "Summary unspecified" );
00633 if ( ! event->summary().isEmpty() ) {
00634 if ( !event->summaryIsRich() ) {
00635 sSummary = string2HTML( event->summary() );
00636 } else {
00637 sSummary = eventViewerAddTag( "p", event->richSummary() );
00638 }
00639 }
00640
00641 QString sLocation = i18n( "Location unspecified" );
00642 if ( ! event->location().isEmpty() ) {
00643 if ( !event->locationIsRich() ) {
00644 sLocation = string2HTML( event->location() );
00645 } else {
00646 sLocation = eventViewerAddTag( "p", event->richLocation() );
00647 }
00648 }
00649
00650 QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
00651 html = QString( "<div dir=\"%1\">\n" ).arg( dir );
00652 html += "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n";
00653
00654
00655 html += invitationRow( i18n( "What:" ), sSummary );
00656 html += invitationRow( i18n( "Where:" ), sLocation );
00657
00658
00659 html += invitationRow( i18n( "Start Time:" ), eventStartTimeStr( event ) );
00660
00661
00662 html += invitationRow( i18n( "End Time:" ), eventEndTimeStr( event ) );
00663
00664
00665 if ( !event->allDay() && event->hasEndDate() && event->dtEnd().isValid() ) {
00666 tmp.clear();
00667 QTime sDuration( 0, 0, 0 ), t;
00668 int secs = event->dtStart().secsTo( event->dtEnd() );
00669 t = sDuration.addSecs( secs );
00670 if ( t.hour() > 0 ) {
00671 tmp += i18np( "1 hour ", "%1 hours ", t.hour() );
00672 }
00673 if ( t.minute() > 0 ) {
00674 tmp += i18np( "1 minute ", "%1 minutes ", t.minute() );
00675 }
00676
00677 html += invitationRow( i18n( "Duration:" ), tmp );
00678 }
00679
00680 html += "</table>\n";
00681 html += invitationsDetailsIncidence( event );
00682 html += "</div>\n";
00683
00684 return html;
00685 }
00686
00687 static QString invitationDetailsTodo( Todo *todo )
00688 {
00689
00690 if ( !todo ) {
00691 return QString();
00692 }
00693
00694 QString sSummary = i18n( "Summary unspecified" );
00695 QString sDescr = i18n( "Description unspecified" );
00696 if ( ! todo->summary().isEmpty() ) {
00697 sSummary = todo->richSummary();
00698 }
00699 if ( ! todo->description().isEmpty() ) {
00700 sDescr = todo->description();
00701 }
00702 QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00703 html += invitationRow( i18n( "Summary:" ), sSummary );
00704 html += invitationRow( i18n( "Description:" ), sDescr );
00705 html += "</table>\n";
00706 html += invitationsDetailsIncidence( todo );
00707
00708 return html;
00709 }
00710
00711 static QString invitationDetailsJournal( Journal *journal )
00712 {
00713 if ( !journal ) {
00714 return QString();
00715 }
00716
00717 QString sSummary = i18n( "Summary unspecified" );
00718 QString sDescr = i18n( "Description unspecified" );
00719 if ( ! journal->summary().isEmpty() ) {
00720 sSummary = journal->richSummary();
00721 }
00722 if ( ! journal->description().isEmpty() ) {
00723 sDescr = journal->richDescription();
00724 }
00725 QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00726 html += invitationRow( i18n( "Summary:" ), sSummary );
00727 html += invitationRow( i18n( "Date:" ),
00728 journal->dtStartDateStr( false, journal->dtStart().timeSpec() ) );
00729 html += invitationRow( i18n( "Description:" ), sDescr );
00730 html += "</table>\n";
00731 html += invitationsDetailsIncidence( journal );
00732
00733 return html;
00734 }
00735
00736 static QString invitationDetailsFreeBusy( FreeBusy *fb )
00737 {
00738 if ( !fb ) {
00739 return QString();
00740 }
00741
00742 QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
00743 html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() );
00744 html += invitationRow( i18n( "Start date:" ),
00745 fb->dtStartDateStr( true, fb->dtStart().timeSpec() ) );
00746 html += invitationRow( i18n( "End date:" ),
00747 KGlobal::locale()->formatDate( fb->dtEnd().date(), KLocale::ShortDate ) );
00748 html += "<tr><td colspan=2><hr></td></tr>\n";
00749 html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
00750
00751 QList<Period> periods = fb->busyPeriods();
00752 QList<Period>::iterator it;
00753 for ( it = periods.begin(); it != periods.end(); ++it ) {
00754 Period per = *it;
00755 if ( per.hasDuration() ) {
00756 int dur = per.duration().asSeconds();
00757 QString cont;
00758 if ( dur >= 3600 ) {
00759 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00760 dur %= 3600;
00761 }
00762 if ( dur >= 60 ) {
00763 cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
00764 dur %= 60;
00765 }
00766 if ( dur > 0 ) {
00767 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00768 }
00769 html += invitationRow(
00770 QString(), i18nc( "startDate for duration", "%1 for %2",
00771 KGlobal::locale()->formatDateTime(
00772 per.start().dateTime(), KLocale::LongDate ), cont ) );
00773 } else {
00774 QString cont;
00775 if ( per.start().date() == per.end().date() ) {
00776 cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00777 KGlobal::locale()->formatDate( per.start().date() ),
00778 KGlobal::locale()->formatTime( per.start().time() ),
00779 KGlobal::locale()->formatTime( per.end().time() ) );
00780 } else {
00781 cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
00782 KGlobal::locale()->formatDateTime(
00783 per.start().dateTime(), KLocale::LongDate ),
00784 KGlobal::locale()->formatDateTime(
00785 per.end().dateTime(), KLocale::LongDate ) );
00786 }
00787
00788 html += invitationRow( QString(), cont );
00789 }
00790 }
00791
00792 html += "</table>\n";
00793 return html;
00794 }
00795
00796 static QString invitationHeaderEvent( Event *event, ScheduleMessage *msg )
00797 {
00798 if ( !msg || !event ) {
00799 return QString();
00800 }
00801
00802 switch ( msg->method() ) {
00803 case iTIPPublish:
00804 return i18n( "This event has been published" );
00805 case iTIPRequest:
00806 if ( event->revision() > 0 ) {
00807 return i18n( "<h3>This meeting has been updated</h3>" );
00808 } else {
00809 return i18n( "You have been invited to this meeting" );
00810 }
00811 case iTIPRefresh:
00812 return i18n( "This invitation was refreshed" );
00813 case iTIPCancel:
00814 return i18n( "This meeting has been canceled" );
00815 case iTIPAdd:
00816 return i18n( "Addition to the meeting invitation" );
00817 case iTIPReply:
00818 {
00819 Attendee::List attendees = event->attendees();
00820 if( attendees.count() == 0 ) {
00821 kDebug() << "No attendees in the iCal reply!";
00822 return QString();
00823 }
00824 if ( attendees.count() != 1 ) {
00825 kDebug() << "Warning: attendeecount in the reply should be 1"
00826 << "but is" << attendees.count();
00827 }
00828 Attendee *attendee = *attendees.begin();
00829 QString attendeeName = attendee->name();
00830 if ( attendeeName.isEmpty() ) {
00831 attendeeName = attendee->email();
00832 }
00833 if ( attendeeName.isEmpty() ) {
00834 attendeeName = i18n( "Sender" );
00835 }
00836
00837 QString delegatorName, dummy;
00838 KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
00839 if ( delegatorName.isEmpty() ) {
00840 delegatorName = attendee->delegator();
00841 }
00842
00843 switch( attendee->status() ) {
00844 case Attendee::NeedsAction:
00845 return i18n( "%1 indicates this invitation still needs some action", attendeeName );
00846 case Attendee::Accepted:
00847 if ( delegatorName.isEmpty() ) {
00848 return i18n( "%1 accepts this meeting invitation", attendeeName );
00849 }
00850 return i18n( "%1 accepts this meeting invitation on behalf of %2",
00851 attendeeName, delegatorName );
00852 case Attendee::Tentative:
00853 if ( delegatorName.isEmpty() ) {
00854 return i18n( "%1 tentatively accepts this meeting invitation", attendeeName );
00855 }
00856 return i18n( "%1 tentatively accepts this meeting invitation on behalf of %2",
00857 attendeeName, delegatorName );
00858 case Attendee::Declined:
00859 if ( delegatorName.isEmpty() ) {
00860 return i18n( "%1 declines this meeting invitation", attendeeName );
00861 }
00862 return i18n( "%1 declines this meeting invitation on behalf of %2",
00863 attendeeName, delegatorName );
00864 case Attendee::Delegated:
00865 {
00866 QString delegate, dummy;
00867 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
00868 if ( delegate.isEmpty() ) {
00869 delegate = attendee->delegate();
00870 }
00871 if ( !delegate.isEmpty() ) {
00872 return i18n( "%1 has delegated this meeting invitation to %2", attendeeName, delegate );
00873 }
00874 return i18n( "%1 has delegated this meeting invitation", attendeeName );
00875 }
00876 case Attendee::Completed:
00877 return i18n( "This meeting invitation is now completed" );
00878 case Attendee::InProcess:
00879 return i18n( "%1 is still processing the invitation", attendeeName );
00880 default:
00881 return i18n( "Unknown response to this meeting invitation" );
00882 }
00883 break;
00884 }
00885 case iTIPCounter:
00886 return i18n( "Sender makes this counter proposal" );
00887 case iTIPDeclineCounter:
00888 return i18n( "Sender declines the counter proposal" );
00889 case iTIPNoMethod:
00890 return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
00891 }
00892 return QString();
00893 }
00894
00895 static QString invitationHeaderTodo( Todo *todo, ScheduleMessage *msg )
00896 {
00897 if ( !msg || !todo ) {
00898 return QString();
00899 }
00900
00901 switch ( msg->method() ) {
00902 case iTIPPublish:
00903 return i18n( "This to-do has been published" );
00904 case iTIPRequest:
00905 if ( todo->revision() > 0 ) {
00906 return i18n( "This to-do has been updated" );
00907 } else {
00908 return i18n( "You have been assigned this to-do" );
00909 }
00910 case iTIPRefresh:
00911 return i18n( "This to-do was refreshed" );
00912 case iTIPCancel:
00913 return i18n( "This to-do was canceled" );
00914 case iTIPAdd:
00915 return i18n( "Addition to the to-do" );
00916 case iTIPReply:
00917 {
00918 Attendee::List attendees = todo->attendees();
00919 if ( attendees.count() == 0 ) {
00920 kDebug() << "No attendees in the iCal reply!";
00921 return QString();
00922 }
00923 if ( attendees.count() != 1 ) {
00924 kDebug() << "Warning: attendeecount in the reply should be 1"
00925 << "but is" << attendees.count();
00926 }
00927 Attendee *attendee = *attendees.begin();
00928 switch( attendee->status() ) {
00929 case Attendee::NeedsAction:
00930 return i18n( "Sender indicates this to-do assignment still needs some action" );
00931 case Attendee::Accepted:
00932 return i18n( "Sender accepts this to-do" );
00933 case Attendee::Tentative:
00934 return i18n( "Sender tentatively accepts this to-do" );
00935 case Attendee::Declined:
00936 return i18n( "Sender declines this to-do" );
00937 case Attendee::Delegated:
00938 {
00939 QString delegate, dummy;
00940 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
00941 if ( delegate.isEmpty() ) {
00942 delegate = attendee->delegate();
00943 }
00944 if ( !delegate.isEmpty() ) {
00945 return i18n( "Sender has delegated this request for the to-do to %1", delegate );
00946 }
00947 return i18n( "Sender has delegated this request for the to-do " );
00948 }
00949 case Attendee::Completed:
00950 return i18n( "The request for this to-do is now completed" );
00951 case Attendee::InProcess:
00952 return i18n( "Sender is still processing the invitation" );
00953 default:
00954 return i18n( "Unknown response to this to-do" );
00955 }
00956 break;
00957 }
00958 case iTIPCounter:
00959 return i18n( "Sender makes this counter proposal" );
00960 case iTIPDeclineCounter:
00961 return i18n( "Sender declines the counter proposal" );
00962 case iTIPNoMethod:
00963 return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
00964 }
00965 return QString();
00966 }
00967
00968 static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
00969 {
00970
00971 if ( !msg || !journal ) {
00972 return QString();
00973 }
00974
00975 switch ( msg->method() ) {
00976 case iTIPPublish:
00977 return i18n( "This journal has been published" );
00978 case iTIPRequest:
00979 return i18n( "You have been assigned this journal" );
00980 case iTIPRefresh:
00981 return i18n( "This journal was refreshed" );
00982 case iTIPCancel:
00983 return i18n( "This journal was canceled" );
00984 case iTIPAdd:
00985 return i18n( "Addition to the journal" );
00986 case iTIPReply:
00987 {
00988 Attendee::List attendees = journal->attendees();
00989 if ( attendees.count() == 0 ) {
00990 kDebug() << "No attendees in the iCal reply!";
00991 return QString();
00992 }
00993
00994 if( attendees.count() != 1 ) {
00995 kDebug() << "Warning: attendeecount in the reply should be 1"
00996 << "but is" << attendees.count();
00997 }
00998
00999 Attendee *attendee = *attendees.begin();
01000 switch( attendee->status() ) {
01001 case Attendee::NeedsAction:
01002 return i18n( "Sender indicates this journal assignment still needs some action" );
01003 case Attendee::Accepted:
01004 return i18n( "Sender accepts this journal" );
01005 case Attendee::Tentative:
01006 return i18n( "Sender tentatively accepts this journal" );
01007 case Attendee::Declined:
01008 return i18n( "Sender declines this journal" );
01009 case Attendee::Delegated:
01010 return i18n( "Sender has delegated this request for the journal" );
01011 case Attendee::Completed:
01012 return i18n( "The request for this journal is now completed" );
01013 case Attendee::InProcess:
01014 return i18n( "Sender is still processing the invitation" );
01015 default:
01016 return i18n( "Unknown response to this journal" );
01017 }
01018 break;
01019 }
01020 case iTIPCounter:
01021 return i18n( "Sender makes this counter proposal" );
01022 case iTIPDeclineCounter:
01023 return i18n( "Sender declines the counter proposal" );
01024 case iTIPNoMethod:
01025 return i18n( "Error: iMIP message with unknown method: '%1'", msg->method() );
01026 }
01027 return QString();
01028 }
01029
01030 static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
01031 {
01032 if ( !msg || !fb ) {
01033 return QString();
01034 }
01035
01036 switch ( msg->method() ) {
01037 case iTIPPublish:
01038 return i18n( "This free/busy list has been published" );
01039 case iTIPRequest:
01040 return i18n( "The free/busy list has been requested" );
01041 case iTIPRefresh:
01042 return i18n( "This free/busy list was refreshed" );
01043 case iTIPCancel:
01044 return i18n( "This free/busy list was canceled" );
01045 case iTIPAdd:
01046 return i18n( "Addition to the free/busy list" );
01047 case iTIPNoMethod:
01048 default:
01049 return i18n( "Error: Free/Busy iMIP message with unknown method: '%1'", msg->method() );
01050 }
01051 }
01052
01053
01054 class KCal::IncidenceFormatter::ScheduleMessageVisitor : public IncidenceBase::Visitor
01055 {
01056 public:
01057 ScheduleMessageVisitor() : mMessage(0) { mResult = ""; }
01058 bool act( IncidenceBase *incidence, ScheduleMessage *msg )
01059 {
01060 mMessage = msg;
01061 return incidence->accept( *this );
01062 }
01063 QString result() const { return mResult; }
01064
01065 protected:
01066 QString mResult;
01067 ScheduleMessage *mMessage;
01068 };
01069
01070 class KCal::IncidenceFormatter::InvitationHeaderVisitor :
01071 public IncidenceFormatter::ScheduleMessageVisitor
01072 {
01073 protected:
01074 bool visit( Event *event )
01075 {
01076 mResult = invitationHeaderEvent( event, mMessage );
01077 return !mResult.isEmpty();
01078 }
01079 bool visit( Todo *todo )
01080 {
01081 mResult = invitationHeaderTodo( todo, mMessage );
01082 return !mResult.isEmpty();
01083 }
01084 bool visit( Journal *journal )
01085 {
01086 mResult = invitationHeaderJournal( journal, mMessage );
01087 return !mResult.isEmpty();
01088 }
01089 bool visit( FreeBusy *fb )
01090 {
01091 mResult = invitationHeaderFreeBusy( fb, mMessage );
01092 return !mResult.isEmpty();
01093 }
01094 };
01095
01096 class KCal::IncidenceFormatter::InvitationBodyVisitor
01097 : public IncidenceFormatter::ScheduleMessageVisitor
01098 {
01099 protected:
01100 bool visit( Event *event )
01101 {
01102 mResult = invitationDetailsEvent( event );
01103 return !mResult.isEmpty();
01104 }
01105 bool visit( Todo *todo )
01106 {
01107 mResult = invitationDetailsTodo( todo );
01108 return !mResult.isEmpty();
01109 }
01110 bool visit( Journal *journal )
01111 {
01112 mResult = invitationDetailsJournal( journal );
01113 return !mResult.isEmpty();
01114 }
01115 bool visit( FreeBusy *fb )
01116 {
01117 mResult = invitationDetailsFreeBusy( fb );
01118 return !mResult.isEmpty();
01119 }
01120 };
01121
01122
01123 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
01124 {
01125 return id;
01126 }
01127
01128 class IncidenceFormatter::IncidenceCompareVisitor :
01129 public IncidenceBase::Visitor
01130 {
01131 public:
01132 IncidenceCompareVisitor() : mExistingIncidence(0) {}
01133 bool act( IncidenceBase *incidence, Incidence *existingIncidence )
01134 {
01135 mExistingIncidence = existingIncidence;
01136 return incidence->accept( *this );
01137 }
01138
01139 QString result() const
01140 {
01141 if ( mChanges.isEmpty() ) {
01142 return QString();
01143 }
01144 QString html = "<div align=\"left\"><ul><li>";
01145 html += mChanges.join( "</li><li>" );
01146 html += "</li><ul></div>";
01147 return html;
01148 }
01149
01150 protected:
01151 bool visit( Event *event )
01152 {
01153 compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01154 compareIncidences( event, mExistingIncidence );
01155 return !mChanges.isEmpty();
01156 }
01157 bool visit( Todo *todo )
01158 {
01159 compareIncidences( todo, mExistingIncidence );
01160 return !mChanges.isEmpty();
01161 }
01162 bool visit( Journal *journal )
01163 {
01164 compareIncidences( journal, mExistingIncidence );
01165 return !mChanges.isEmpty();
01166 }
01167 bool visit( FreeBusy *fb )
01168 {
01169 Q_UNUSED( fb );
01170 return !mChanges.isEmpty();
01171 }
01172
01173 private:
01174 void compareEvents( Event *newEvent, Event *oldEvent )
01175 {
01176 if ( !oldEvent || !newEvent ) {
01177 return;
01178 }
01179 if ( oldEvent->dtStart() != newEvent->dtStart() ||
01180 oldEvent->allDay() != newEvent->allDay() ) {
01181 mChanges += i18n( "The begin of the meeting has been changed from %1 to %2",
01182 eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01183 }
01184 if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01185 oldEvent->allDay() != newEvent->allDay() ) {
01186 mChanges += i18n( "The end of the meeting has been changed from %1 to %2",
01187 eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01188 }
01189 }
01190
01191 void compareIncidences( Incidence *newInc, Incidence *oldInc )
01192 {
01193 if ( !oldInc || !newInc ) {
01194 return;
01195 }
01196
01197 if ( oldInc->summary() != newInc->summary() ) {
01198 mChanges += i18n( "The summary has been changed to: \"%1\"",
01199 newInc->richSummary() );
01200 }
01201
01202 if ( oldInc->location() != newInc->location() ) {
01203 mChanges += i18n( "The location has been changed to: \"%1\"",
01204 newInc->richLocation() );
01205 }
01206
01207 if ( oldInc->description() != newInc->description() ) {
01208 mChanges += i18n( "The description has been changed to: \"%1\"",
01209 newInc->richDescription() );
01210 }
01211
01212 Attendee::List oldAttendees = oldInc->attendees();
01213 Attendee::List newAttendees = newInc->attendees();
01214 for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01215 it != newAttendees.constEnd(); ++it ) {
01216 Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01217 if ( !oldAtt ) {
01218 mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01219 } else {
01220 if ( oldAtt->status() != (*it)->status() ) {
01221 mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01222 (*it)->fullName(), (*it)->statusStr() );
01223 }
01224 }
01225 }
01226
01227 for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01228 it != oldAttendees.constEnd(); ++it ) {
01229 Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01230 if ( !newAtt ) {
01231 mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01232 }
01233 }
01234 }
01235
01236 private:
01237 Incidence *mExistingIncidence;
01238 QStringList mChanges;
01239 };
01240
01241 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01242 {
01243 QString res( "<a href=\"%1\"><b>%2</b></a>" );
01244 return res.arg( generateLinkURL( id ) ).arg( text );
01245 return res;
01246 }
01247
01248 Calendar *InvitationFormatterHelper::calendar() const
01249 {
01250 return 0;
01251 }
01252
01253
01254
01255 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01256 {
01257 CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01258 if ( !cal || !incidence ) {
01259 return true;
01260 }
01261
01262 ResourceCalendar *res = cal->resource( incidence );
01263 if ( !res ) {
01264 return true;
01265 }
01266
01267 const QString subRes = res->subresourceIdentifier( incidence );
01268 if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01269 return false;
01270 }
01271 return true;
01272 }
01273
01274 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01275 InvitationFormatterHelper *helper )
01276 {
01277 if ( invitation.isEmpty() ) {
01278 return QString();
01279 }
01280
01281 ICalFormat format;
01282
01283
01284 ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01285
01286 if( !msg ) {
01287 kDebug() << "Failed to parse the scheduling message";
01288 Q_ASSERT( format.exception() );
01289 kDebug() << format.exception()->message();
01290 return QString();
01291 }
01292
01293 IncidenceBase *incBase = msg->event();
01294
01295 Incidence *existingIncidence = 0;
01296 if ( helper->calendar() ) {
01297 existingIncidence = helper->calendar()->incidence( incBase->uid() );
01298 if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
01299 existingIncidence = 0;
01300 }
01301 if ( !existingIncidence ) {
01302 const Incidence::List list = helper->calendar()->incidences();
01303 for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01304 if ( (*it)->schedulingID() == incBase->uid() &&
01305 incidenceOwnedByMe( helper->calendar(), *it ) ) {
01306 existingIncidence = *it;
01307 break;
01308 }
01309 }
01310 }
01311 }
01312
01313
01314 QString html;
01315
01316 QString tableStyle = QString::fromLatin1(
01317 "style=\"border: solid 1px; margin: 0em;\"" );
01318 QString tableHead = QString::fromLatin1(
01319 "<div align=\"center\">"
01320 "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01321 "<tr><td>" ).arg( tableStyle );
01322
01323 html += tableHead;
01324 InvitationHeaderVisitor headerVisitor;
01325
01326 if ( !headerVisitor.act( incBase, msg ) ) {
01327 return QString();
01328 }
01329 html += "<h3>" + headerVisitor.result() + "</h3>";
01330
01331 InvitationBodyVisitor bodyVisitor;
01332 if ( !bodyVisitor.act( incBase, msg ) ) {
01333 return QString();
01334 }
01335 html += bodyVisitor.result();
01336
01337 if ( msg->method() == iTIPRequest ) {
01338 IncidenceCompareVisitor compareVisitor;
01339 if ( compareVisitor.act( incBase, existingIncidence ) ) {
01340 html +=
01341 i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
01342 html += compareVisitor.result();
01343 }
01344 }
01345
01346 html += "<br/>";
01347 html += "<table border=\"0\" cellspacing=\"0\"><tr><td> </td></tr><tr>";
01348
01349 #if 0
01350
01351 html += helper->makeLinkURL( "accept", i18n( "[Enter this into my calendar]" ) );
01352 html += "</td><td> </td><td>";
01353 #endif
01354
01355
01356
01357 Incidence *incidence = dynamic_cast<Incidence*>( incBase );
01358 switch ( msg->method() ) {
01359 case iTIPPublish:
01360 case iTIPRequest:
01361 case iTIPRefresh:
01362 case iTIPAdd:
01363 {
01364 if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
01365 if ( incBase->type() == "Todo" ) {
01366 html += "<td colspan=\"13\">";
01367 html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01368 } else {
01369 html += "<td colspan=\"9\">";
01370 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01371 }
01372 html += "</td></tr><tr>";
01373 }
01374 html += "<td>";
01375
01376 if ( !existingIncidence ) {
01377
01378 html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) );
01379 html += "</td><td> </td><td>";
01380 html += helper->makeLink( "accept_conditionally",
01381 i18nc( "Accept conditionally", "[Accept cond.]" ) );
01382 html += "</td><td> </td><td>";
01383
01384 html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01385 html += "</td><td> </td><td>";
01386
01387 html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) );
01388 html += "</td><td> </td><td>";
01389
01390
01391 html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) );
01392 html += "</td><td> </td><td>";
01393
01394
01395 html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) );
01396
01397 if ( incBase->type() == "Event" ) {
01398 html += "</b></a></td><td> </td><td>";
01399 html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01400 }
01401 }
01402 break;
01403 }
01404
01405 case iTIPCancel:
01406
01407 html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01408 break;
01409
01410 case iTIPReply:
01411
01412 if ( incBase->type() == "Todo" ) {
01413 html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01414 } else {
01415 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01416 }
01417 break;
01418
01419 case iTIPCounter:
01420 case iTIPDeclineCounter:
01421 case iTIPNoMethod:
01422 break;
01423 }
01424
01425 html += "</td></tr></table>";
01426
01427 html += "</td></tr></table><br></div>";
01428
01429 return html;
01430 }
01431
01432
01433
01434
01435
01436
01437 class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01438 {
01439 public:
01440 ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01441
01442 bool act( IncidenceBase *incidence, bool richText=true )
01443 {
01444 mRichText = richText;
01445 mResult = "";
01446 return incidence ? incidence->accept( *this ) : false;
01447 }
01448 QString result() const { return mResult; }
01449
01450 protected:
01451 bool visit( Event *event );
01452 bool visit( Todo *todo );
01453 bool visit( Journal *journal );
01454 bool visit( FreeBusy *fb );
01455
01456 QString dateRangeText( Event *event );
01457 QString dateRangeText( Todo *todo );
01458 QString dateRangeText( Journal *journal );
01459 QString dateRangeText( FreeBusy *fb );
01460
01461 QString generateToolTip( Incidence *incidence, QString dtRangeText );
01462
01463 protected:
01464 bool mRichText;
01465 QString mResult;
01466 };
01467
01468 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event )
01469 {
01470
01471
01472
01473 QString ret;
01474 QString tmp;
01475 if ( event->isMultiDay() ) {
01476
01477 tmp = event->dtStartStr( true, event->dtStart().timeSpec() );
01478 ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
01479
01480 tmp = event->dtEndStr( true, event->dtEnd().timeSpec() );
01481 ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
01482
01483 } else {
01484
01485 ret += "<br>" +
01486 i18n( "<i>Date:</i> %1",
01487 event->dtStartDateStr(
01488 true, event->dtStart().timeSpec() ) );
01489 if ( !event->allDay() ) {
01490 if ( event->dtStartTimeStr( true, event->dtStart().timeSpec() ) ==
01491 event->dtEndTimeStr( true, event->dtEnd().timeSpec() ) ) {
01492
01493 tmp = "<br>" +
01494
01495
01496 i18nc( "time for event, to prevent ugly line breaks", "<i>Time:</i> %1",
01497 event->dtStartTimeStr(
01498 true, event->dtStart().timeSpec() ) );
01499 } else {
01500 tmp = "<br>" +
01501
01502
01503 i18nc( "time range for event, to prevent ugly line breaks",
01504 "<i>Time:</i> %1 - %2",
01505 event->dtStartTimeStr(
01506 true, event->dtStart().timeSpec() ),
01507 event->dtEndTimeStr(
01508 true, event->dtEnd().timeSpec() ) );
01509 }
01510 ret += tmp;
01511 }
01512 }
01513 return ret.replace( " ", " " );
01514 }
01515
01516 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo )
01517 {
01518
01519
01520
01521 QString ret;
01522 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01523
01524
01525
01526 ret += "<br>" + i18n( "<i>Start:</i> %1",
01527 todo->dtStartStr(
01528 true, false, todo->dtStart().timeSpec() ) ) ;
01529 }
01530 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01531 ret += "<br>" + i18n( "<i>Due:</i> %1",
01532 todo->dtDueStr(
01533 true, todo->dtDue().timeSpec() ) );
01534 }
01535 if ( todo->isCompleted() ) {
01536 ret += "<br>" +
01537 i18n( "<i>Completed:</i> %1", todo->completedStr() );
01538 } else {
01539 ret += "<br>" +
01540 i18nc( "percent complete", "%1 % completed", todo->percentComplete() );
01541 }
01542
01543 return ret.replace( " ", " " );
01544 }
01545
01546 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
01547 {
01548
01549
01550
01551 QString ret;
01552 if ( journal->dtStart().isValid() ) {
01553 ret += "<br>" +
01554 i18n( "<i>Date:</i> %1",
01555 journal->dtStartDateStr( false, journal->dtStart().timeSpec() ) );
01556 }
01557 return ret.replace( " ", " " );
01558 }
01559
01560 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01561 {
01562
01563
01564
01565 QString ret;
01566 ret = "<br>" +
01567 i18n( "<i>Period start:</i> %1",
01568 KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
01569 ret += "<br>" +
01570 i18n( "<i>Period start:</i> %1",
01571 KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
01572 return ret.replace( " ", " " );
01573 }
01574
01575 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01576 {
01577 mResult = generateToolTip( event, dateRangeText( event ) );
01578 return !mResult.isEmpty();
01579 }
01580
01581 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01582 {
01583 mResult = generateToolTip( todo, dateRangeText( todo ) );
01584 return !mResult.isEmpty();
01585 }
01586
01587 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01588 {
01589 mResult = generateToolTip( journal, dateRangeText( journal ) );
01590 return !mResult.isEmpty();
01591 }
01592
01593 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01594 {
01595
01596 mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
01597 mResult += dateRangeText( fb );
01598 mResult += "</qt>";
01599 return !mResult.isEmpty();
01600 }
01601
01602 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
01603 QString dtRangeText )
01604 {
01605
01606
01607 if ( !incidence ) {
01608 return QString();
01609 }
01610
01611 QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
01612
01613 tmp += dtRangeText;
01614
01615 if ( !incidence->location().isEmpty() ) {
01616
01617 tmp += "<br>" +
01618 i18n( "<i>Location:</i> %1", incidence->richLocation() );
01619 }
01620
01621 if ( !incidence->description().isEmpty() ) {
01622 QString desc( incidence->description() );
01623 if ( !incidence->descriptionIsRich() ) {
01624 if ( desc.length() > 120 ) {
01625 desc = desc.left( 120 ) + "...";
01626 }
01627 desc = Qt::escape( desc ).replace( "\n", "<br>" );
01628 } else {
01629
01630 }
01631 tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
01632 }
01633 tmp += "</qt>";
01634 return tmp;
01635 }
01636
01637
01638 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
01639 {
01640 ToolTipVisitor v;
01641 if ( v.act( incidence, richText ) ) {
01642 return v.result();
01643 } else {
01644 return QString();
01645 }
01646 }
01647
01648
01649
01650
01651
01652 static QString mailBodyIncidence( Incidence *incidence )
01653 {
01654 QString body;
01655 if ( !incidence->summary().isEmpty() ) {
01656 body += i18n( "Summary: %1\n", incidence->richSummary() );
01657 }
01658 if ( !incidence->organizer().isEmpty() ) {
01659 body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
01660 }
01661 if ( !incidence->location().isEmpty() ) {
01662 body += i18n( "Location: %1\n", incidence->richLocation() );
01663 }
01664 return body;
01665 }
01666
01667
01668 class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
01669 {
01670 public:
01671 MailBodyVisitor() : mResult( "" ) {}
01672
01673 bool act( IncidenceBase *incidence )
01674 {
01675 mResult = "";
01676 return incidence ? incidence->accept( *this ) : false;
01677 }
01678 QString result() const
01679 {
01680 return mResult;
01681 }
01682
01683 protected:
01684 bool visit( Event *event );
01685 bool visit( Todo *todo );
01686 bool visit( Journal *journal );
01687 bool visit( FreeBusy * )
01688 {
01689 mResult = i18n( "This is a Free Busy Object" );
01690 return !mResult.isEmpty();
01691 }
01692 protected:
01693 QString mResult;
01694 };
01695
01696 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
01697 {
01698 QString recurrence[]= {
01699 i18nc( "no recurrence", "None" ),
01700 i18nc( "event recurs by minutes", "Minutely" ),
01701 i18nc( "event recurs by hours", "Hourly" ),
01702 i18nc( "event recurs by days", "Daily" ),
01703 i18nc( "event recurs by weeks", "Weekly" ),
01704 i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
01705 i18nc( "event recurs same day each month", "Monthly Same Day" ),
01706 i18nc( "event recurs same month each year", "Yearly Same Month" ),
01707 i18nc( "event recurs same day each year", "Yearly Same Day" ),
01708 i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
01709 };
01710
01711 mResult = mailBodyIncidence( event );
01712 mResult += i18n( "Start Date: %1\n",
01713 event->dtStartDateStr( true, event->dtStart().timeSpec() ) );
01714 if ( !event->allDay() ) {
01715 mResult += i18n( "Start Time: %1\n",
01716 event->dtStartTimeStr( true, event->dtStart().timeSpec() ) );
01717 }
01718 if ( event->dtStart() != event->dtEnd() ) {
01719 mResult += i18n( "End Date: %1\n",
01720 event->dtEndDateStr( true, event->dtStart().timeSpec() ) );
01721 }
01722 if ( !event->allDay() ) {
01723 mResult += i18n( "End Time: %1\n",
01724 event->dtEndTimeStr( true, event->dtStart().timeSpec() ) );
01725 }
01726 if ( event->recurs() ) {
01727 Recurrence *recur = event->recurrence();
01728
01729 mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
01730 mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
01731
01732 if ( recur->duration() > 0 ) {
01733 mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
01734 mResult += '\n';
01735 } else {
01736 if ( recur->duration() != -1 ) {
01737
01738 QString endstr;
01739 if ( event->allDay() ) {
01740 endstr = KGlobal::locale()->formatDate( recur->endDate() );
01741 } else {
01742 endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
01743 }
01744 mResult += i18n( "Repeat until: %1\n", endstr );
01745 } else {
01746 mResult += i18n( "Repeats forever\n" );
01747 }
01748 }
01749 }
01750
01751 QString details = event->richDescription();
01752 if ( !details.isEmpty() ) {
01753 mResult += i18n( "Details:\n%1\n", details );
01754 }
01755 return !mResult.isEmpty();
01756 }
01757
01758 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
01759 {
01760 mResult = mailBodyIncidence( todo );
01761
01762 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01763 mResult += i18n( "Start Date: %1\n",
01764 todo->dtStartDateStr( true, false, todo->dtStart().timeSpec() ) );
01765 if ( !todo->allDay() ) {
01766 mResult += i18n( "Start Time: %1\n",
01767 todo->dtStartTimeStr( true, false, todo->dtStart().timeSpec() ) );
01768 }
01769 }
01770 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01771 mResult += i18n( "Due Date: %1\n",
01772 todo->dtDueDateStr( true, todo->dtDue().timeSpec() ) );
01773 if ( !todo->allDay() ) {
01774 mResult += i18n( "Due Time: %1\n",
01775 todo->dtDueTimeStr( true, todo->dtDue().timeSpec() ) );
01776 }
01777 }
01778 QString details = todo->richDescription();
01779 if ( !details.isEmpty() ) {
01780 mResult += i18n( "Details:\n%1\n", details );
01781 }
01782 return !mResult.isEmpty();
01783 }
01784
01785 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
01786 {
01787 mResult = mailBodyIncidence( journal );
01788 mResult += i18n( "Date: %1\n", journal->dtStartDateStr( true, journal->dtStart().timeSpec() ) );
01789 if ( !journal->allDay() ) {
01790 mResult += i18n( "Time: %1\n", journal->dtStartTimeStr( true, journal->dtStart().timeSpec() ) );
01791 }
01792 if ( !journal->description().isEmpty() ) {
01793 mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
01794 }
01795 return !mResult.isEmpty();
01796 }
01797
01798
01799 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
01800 {
01801 if ( !incidence ) {
01802 return QString();
01803 }
01804
01805 MailBodyVisitor v;
01806 if ( v.act( incidence ) ) {
01807 return v.result();
01808 }
01809 return QString();
01810 }
01811
01812 static QString recurEnd( Incidence *incidence )
01813 {
01814 QString endstr;
01815 if ( incidence->allDay() ) {
01816 endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
01817 } else {
01818 endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
01819 }
01820 return endstr;
01821 }
01822
01823 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
01824 {
01825 if ( !incidence->recurs() ) {
01826 return i18n( "No recurrence" );
01827 }
01828
01829 Recurrence *recur = incidence->recurrence();
01830 switch ( recur->recurrenceType() ) {
01831 case Recurrence::rNone:
01832 return i18n( "No recurrence" );
01833 case Recurrence::rMinutely:
01834 if ( recur->duration() != -1 ) {
01835 return i18np( "Recurs every minute until %2",
01836 "Recurs every %1 minutes until %2",
01837 recur->frequency(), recurEnd( incidence ) );
01838 }
01839 return i18np( "Recurs every minute",
01840 "Recurs every %1 minutes", recur->frequency() );
01841 case Recurrence::rHourly:
01842 if ( recur->duration() != -1 ) {
01843 return i18np( "Recurs hourly until %2",
01844 "Recurs every %1 hours until %2",
01845 recur->frequency(), recurEnd( incidence ) );
01846 }
01847 return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
01848 case Recurrence::rDaily:
01849 if ( recur->duration() != -1 ) {
01850 return i18np( "Recurs daily until %2",
01851 "Recurs every %1 days until %2",
01852 recur->frequency(), recurEnd( incidence ) );
01853 }
01854 return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
01855 case Recurrence::rWeekly:
01856 if ( recur->duration() != -1 ) {
01857 return i18np( "Recurs weekly until %2",
01858 "Recurs every %1 weeks until %2",
01859 recur->frequency(), recurEnd( incidence ) );
01860 }
01861 return i18np( "Recurs weekly", "Recurs every %1 weeks", recur->frequency() );
01862 case Recurrence::rMonthlyPos:
01863 case Recurrence::rMonthlyDay:
01864 if ( recur->duration() != -1 ) {
01865 return i18n( "Recurs monthly until %1", recurEnd( incidence ) );
01866 }
01867 return i18n( "Recurs monthly" );
01868 case Recurrence::rYearlyMonth:
01869 case Recurrence::rYearlyDay:
01870 case Recurrence::rYearlyPos:
01871 if ( recur->duration() != -1 ) {
01872 return i18n( "Recurs yearly until %1", recurEnd( incidence ) );
01873 }
01874 return i18n( "Recurs yearly" );
01875 default:
01876 return i18n( "Incidence recurs" );
01877 }
01878 }