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 if (!existingIncidence) {
01136 return false;
01137 }
01138 Incidence *inc = dynamic_cast<Incidence *>( incidence );
01139 if ( inc && inc->revision() <= existingIncidence->revision() ) {
01140 return false;
01141 }
01142 mExistingIncidence = existingIncidence;
01143 return incidence->accept( *this );
01144 }
01145
01146 QString result() const
01147 {
01148 if ( mChanges.isEmpty() ) {
01149 return QString();
01150 }
01151 QString html = "<div align=\"left\"><ul><li>";
01152 html += mChanges.join( "</li><li>" );
01153 html += "</li><ul></div>";
01154 return html;
01155 }
01156
01157 protected:
01158 bool visit( Event *event )
01159 {
01160 compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
01161 compareIncidences( event, mExistingIncidence );
01162 return !mChanges.isEmpty();
01163 }
01164 bool visit( Todo *todo )
01165 {
01166 compareIncidences( todo, mExistingIncidence );
01167 return !mChanges.isEmpty();
01168 }
01169 bool visit( Journal *journal )
01170 {
01171 compareIncidences( journal, mExistingIncidence );
01172 return !mChanges.isEmpty();
01173 }
01174 bool visit( FreeBusy *fb )
01175 {
01176 Q_UNUSED( fb );
01177 return !mChanges.isEmpty();
01178 }
01179
01180 private:
01181 void compareEvents( Event *newEvent, Event *oldEvent )
01182 {
01183 if ( !oldEvent || !newEvent ) {
01184 return;
01185 }
01186 if ( oldEvent->dtStart() != newEvent->dtStart() ||
01187 oldEvent->allDay() != newEvent->allDay() ) {
01188 mChanges += i18n( "The begin of the meeting has been changed from %1 to %2",
01189 eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
01190 }
01191 if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
01192 oldEvent->allDay() != newEvent->allDay() ) {
01193 mChanges += i18n( "The end of the meeting has been changed from %1 to %2",
01194 eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
01195 }
01196 }
01197
01198 void compareIncidences( Incidence *newInc, Incidence *oldInc )
01199 {
01200 if ( !oldInc || !newInc ) {
01201 return;
01202 }
01203
01204 if ( oldInc->summary() != newInc->summary() ) {
01205 mChanges += i18n( "The summary has been changed to: \"%1\"",
01206 newInc->richSummary() );
01207 }
01208
01209 if ( oldInc->location() != newInc->location() ) {
01210 mChanges += i18n( "The location has been changed to: \"%1\"",
01211 newInc->richLocation() );
01212 }
01213
01214 if ( oldInc->description() != newInc->description() ) {
01215 mChanges += i18n( "The description has been changed to: \"%1\"",
01216 newInc->richDescription() );
01217 }
01218
01219 Attendee::List oldAttendees = oldInc->attendees();
01220 Attendee::List newAttendees = newInc->attendees();
01221 for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
01222 it != newAttendees.constEnd(); ++it ) {
01223 Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
01224 if ( !oldAtt ) {
01225 mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
01226 } else {
01227 if ( oldAtt->status() != (*it)->status() ) {
01228 mChanges += i18n( "The status of attendee %1 has been changed to: %2",
01229 (*it)->fullName(), (*it)->statusStr() );
01230 }
01231 }
01232 }
01233
01234 for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
01235 it != oldAttendees.constEnd(); ++it ) {
01236 Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
01237 if ( !newAtt ) {
01238 mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
01239 }
01240 }
01241 }
01242
01243 private:
01244 Incidence *mExistingIncidence;
01245 QStringList mChanges;
01246 };
01247
01248 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
01249 {
01250 QString res( "<a href=\"%1\"><b>%2</b></a>" );
01251 return res.arg( generateLinkURL( id ) ).arg( text );
01252 return res;
01253 }
01254
01255 Calendar *InvitationFormatterHelper::calendar() const
01256 {
01257 return 0;
01258 }
01259
01260
01261
01262 static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
01263 {
01264 CalendarResources* cal = dynamic_cast<CalendarResources*>( calendar );
01265 if ( !cal || !incidence ) {
01266 return true;
01267 }
01268
01269 ResourceCalendar *res = cal->resource( incidence );
01270 if ( !res ) {
01271 return true;
01272 }
01273
01274 const QString subRes = res->subresourceIdentifier( incidence );
01275 if ( !subRes.contains( "/.INBOX.directory/" ) ) {
01276 return false;
01277 }
01278 return true;
01279 }
01280
01281 QString IncidenceFormatter::formatICalInvitation( QString invitation, Calendar *mCalendar,
01282 InvitationFormatterHelper *helper )
01283 {
01284 if ( invitation.isEmpty() ) {
01285 return QString();
01286 }
01287
01288 ICalFormat format;
01289
01290
01291 ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
01292
01293 if( !msg ) {
01294 kDebug() << "Failed to parse the scheduling message";
01295 Q_ASSERT( format.exception() );
01296 kDebug() << format.exception()->message();
01297 return QString();
01298 }
01299
01300 IncidenceBase *incBase = msg->event();
01301 incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
01302
01303 Incidence *existingIncidence = 0;
01304 if ( helper->calendar() ) {
01305 existingIncidence = helper->calendar()->incidence( incBase->uid() );
01306 if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
01307 existingIncidence = 0;
01308 }
01309 if ( !existingIncidence ) {
01310 const Incidence::List list = helper->calendar()->incidences();
01311 for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
01312 if ( (*it)->schedulingID() == incBase->uid() &&
01313 incidenceOwnedByMe( helper->calendar(), *it ) ) {
01314 existingIncidence = *it;
01315 break;
01316 }
01317 }
01318 }
01319 }
01320
01321
01322 QString html;
01323
01324 QString tableStyle = QString::fromLatin1(
01325 "style=\"border: solid 1px; margin: 0em;\"" );
01326 QString tableHead = QString::fromLatin1(
01327 "<div align=\"center\">"
01328 "<table width=\"80%\" cellpadding=\"1\" cellspacing=\"0\" %1>"
01329 "<tr><td>" ).arg( tableStyle );
01330
01331 html += tableHead;
01332 InvitationHeaderVisitor headerVisitor;
01333
01334 if ( !headerVisitor.act( incBase, msg ) ) {
01335 return QString();
01336 }
01337 html += "<h3>" + headerVisitor.result() + "</h3>";
01338
01339 InvitationBodyVisitor bodyVisitor;
01340 if ( !bodyVisitor.act( incBase, msg ) ) {
01341 return QString();
01342 }
01343 html += bodyVisitor.result();
01344
01345 if ( msg->method() == iTIPRequest ) {
01346 IncidenceCompareVisitor compareVisitor;
01347 if ( compareVisitor.act( incBase, existingIncidence ) ) {
01348 html +=
01349 i18n( "<p align=\"left\">The following changes have been made by the organizer:</p>" );
01350 html += compareVisitor.result();
01351 }
01352 }
01353
01354 html += "<br/>";
01355 html += "<table border=\"0\" cellspacing=\"0\"><tr><td> </td></tr><tr>";
01356
01357 #if 0
01358
01359 html += helper->makeLinkURL( "accept", i18n( "[Enter this into my calendar]" ) );
01360 html += "</td><td> </td><td>";
01361 #endif
01362
01363
01364
01365 Incidence *incidence = dynamic_cast<Incidence*>( incBase );
01366 switch ( msg->method() ) {
01367 case iTIPPublish:
01368 case iTIPRequest:
01369 case iTIPRefresh:
01370 case iTIPAdd:
01371 {
01372 if ( incidence && incidence->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
01373 if ( incBase->type() == "Todo" ) {
01374 html += "<td colspan=\"13\">";
01375 html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01376 } else {
01377 html += "<td colspan=\"9\">";
01378 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01379 }
01380 html += "</td></tr><tr>";
01381 }
01382 html += "<td>";
01383
01384 if ( !existingIncidence ) {
01385
01386 html += helper->makeLink( "accept", i18nc( "accept to-do request", "[Accept]" ) );
01387 html += "</td><td> </td><td>";
01388 html += helper->makeLink( "accept_conditionally",
01389 i18nc( "Accept conditionally", "[Accept cond.]" ) );
01390 html += "</td><td> </td><td>";
01391
01392 html += helper->makeLink( "counter", i18n( "[Counter proposal]" ) );
01393 html += "</td><td> </td><td>";
01394
01395 html += helper->makeLink( "decline", i18nc( "decline to-do request", "[Decline]" ) );
01396 html += "</td><td> </td><td>";
01397
01398
01399 html += helper->makeLink( "delegate", i18nc( "delegate to-do to another", "[Delegate]" ) );
01400 html += "</td><td> </td><td>";
01401
01402
01403 html += helper->makeLink( "forward", i18nc( "forward request to another", "[Forward]" ) );
01404
01405 if ( incBase->type() == "Event" ) {
01406 html += "</b></a></td><td> </td><td>";
01407 html += helper->makeLink( "check_calendar", i18n("[Check my calendar]" ) );
01408 }
01409 }
01410 break;
01411 }
01412
01413 case iTIPCancel:
01414
01415 html += helper->makeLink( "cancel", i18n( "[Remove this from my calendar]" ) );
01416 break;
01417
01418 case iTIPReply:
01419
01420 if ( incBase->type() == "Todo" ) {
01421 html += helper->makeLink( "reply", i18n( "[Enter this into my to-do list]" ) );
01422 } else {
01423 html += helper->makeLink( "reply", i18n( "[Enter this into my calendar]" ) );
01424 }
01425 break;
01426
01427 case iTIPCounter:
01428 case iTIPDeclineCounter:
01429 case iTIPNoMethod:
01430 break;
01431 }
01432
01433 html += "</td></tr></table>";
01434
01435 html += "</td></tr></table><br></div>";
01436
01437 return html;
01438 }
01439
01440
01441
01442
01443
01444
01445 class KCal::IncidenceFormatter::ToolTipVisitor : public IncidenceBase::Visitor
01446 {
01447 public:
01448 ToolTipVisitor() : mRichText( true ), mResult( "" ) {}
01449
01450 bool act( IncidenceBase *incidence, bool richText=true )
01451 {
01452 mRichText = richText;
01453 mResult = "";
01454 return incidence ? incidence->accept( *this ) : false;
01455 }
01456 QString result() const { return mResult; }
01457
01458 protected:
01459 bool visit( Event *event );
01460 bool visit( Todo *todo );
01461 bool visit( Journal *journal );
01462 bool visit( FreeBusy *fb );
01463
01464 QString dateRangeText( Event *event );
01465 QString dateRangeText( Todo *todo );
01466 QString dateRangeText( Journal *journal );
01467 QString dateRangeText( FreeBusy *fb );
01468
01469 QString generateToolTip( Incidence *incidence, QString dtRangeText );
01470
01471 protected:
01472 bool mRichText;
01473 QString mResult;
01474 };
01475
01476 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event )
01477 {
01478
01479
01480
01481 QString ret;
01482 QString tmp;
01483 if ( event->isMultiDay() ) {
01484
01485 tmp = event->dtStartStr( true, event->dtStart().timeSpec() );
01486 ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
01487
01488 tmp = event->dtEndStr( true, event->dtEnd().timeSpec() );
01489 ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
01490
01491 } else {
01492
01493 ret += "<br>" +
01494 i18n( "<i>Date:</i> %1",
01495 event->dtStartDateStr(
01496 true, event->dtStart().timeSpec() ) );
01497 if ( !event->allDay() ) {
01498 const QString dtStartTime = event->dtStartTimeStr( true, event->dtStart().timeSpec() );
01499 const QString dtEndTime = event->dtEndTimeStr( true, event->dtEnd().timeSpec() );
01500 if ( dtStartTime == dtEndTime ) {
01501
01502 tmp = "<br>" +
01503
01504
01505 i18nc( "time for event, to prevent ugly line breaks", "<i>Time:</i> %1",
01506 dtStartTime );
01507 } else {
01508 tmp = "<br>" +
01509
01510
01511 i18nc( "time range for event, to prevent ugly line breaks",
01512 "<i>Time:</i> %1 - %2",
01513 dtStartTime, dtEndTime );
01514 }
01515 ret += tmp;
01516 }
01517 }
01518 return ret.replace( " ", " " );
01519 }
01520
01521 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo )
01522 {
01523
01524
01525
01526 QString ret;
01527 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01528
01529
01530
01531 ret += "<br>" + i18n( "<i>Start:</i> %1",
01532 todo->dtStartStr(
01533 true, false, todo->dtStart().timeSpec() ) ) ;
01534 }
01535 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01536 ret += "<br>" + i18n( "<i>Due:</i> %1",
01537 todo->dtDueStr(
01538 true, todo->dtDue().timeSpec() ) );
01539 }
01540 if ( todo->isCompleted() ) {
01541 ret += "<br>" +
01542 i18n( "<i>Completed:</i> %1", todo->completedStr() );
01543 } else {
01544 ret += "<br>" +
01545 i18nc( "percent complete", "%1 % completed", todo->percentComplete() );
01546 }
01547
01548 return ret.replace( " ", " " );
01549 }
01550
01551 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
01552 {
01553
01554
01555
01556 QString ret;
01557 if ( journal->dtStart().isValid() ) {
01558 ret += "<br>" +
01559 i18n( "<i>Date:</i> %1",
01560 journal->dtStartDateStr( false, journal->dtStart().timeSpec() ) );
01561 }
01562 return ret.replace( " ", " " );
01563 }
01564
01565 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
01566 {
01567
01568
01569
01570 QString ret;
01571 ret = "<br>" +
01572 i18n( "<i>Period start:</i> %1",
01573 KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
01574 ret += "<br>" +
01575 i18n( "<i>Period start:</i> %1",
01576 KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
01577 return ret.replace( " ", " " );
01578 }
01579
01580 bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
01581 {
01582 mResult = generateToolTip( event, dateRangeText( event ) );
01583 return !mResult.isEmpty();
01584 }
01585
01586 bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
01587 {
01588 mResult = generateToolTip( todo, dateRangeText( todo ) );
01589 return !mResult.isEmpty();
01590 }
01591
01592 bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
01593 {
01594 mResult = generateToolTip( journal, dateRangeText( journal ) );
01595 return !mResult.isEmpty();
01596 }
01597
01598 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
01599 {
01600
01601 mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
01602 mResult += dateRangeText( fb );
01603 mResult += "</qt>";
01604 return !mResult.isEmpty();
01605 }
01606
01607 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
01608 QString dtRangeText )
01609 {
01610
01611
01612 if ( !incidence ) {
01613 return QString();
01614 }
01615
01616 QString tmp = "<qt><b>"+ incidence->richSummary() + "</b>";
01617
01618 tmp += dtRangeText;
01619
01620 if ( !incidence->location().isEmpty() ) {
01621
01622 tmp += "<br>" +
01623 i18n( "<i>Location:</i> %1", incidence->richLocation() );
01624 }
01625
01626 if ( !incidence->description().isEmpty() ) {
01627 QString desc( incidence->description() );
01628 if ( !incidence->descriptionIsRich() ) {
01629 if ( desc.length() > 120 ) {
01630 desc = desc.left( 120 ) + "...";
01631 }
01632 desc = Qt::escape( desc ).replace( "\n", "<br>" );
01633 } else {
01634
01635 }
01636 tmp += "<br>----------<br>" + i18n( "<i>Description:</i>" ) + "<br>" + desc;
01637 }
01638 tmp += "</qt>";
01639 return tmp;
01640 }
01641
01642
01643 QString IncidenceFormatter::toolTipString( IncidenceBase *incidence, bool richText )
01644 {
01645 ToolTipVisitor v;
01646 if ( v.act( incidence, richText ) ) {
01647 return v.result();
01648 } else {
01649 return QString();
01650 }
01651 }
01652
01653
01654
01655
01656
01657 static QString mailBodyIncidence( Incidence *incidence )
01658 {
01659 QString body;
01660 if ( !incidence->summary().isEmpty() ) {
01661 body += i18n( "Summary: %1\n", incidence->richSummary() );
01662 }
01663 if ( !incidence->organizer().isEmpty() ) {
01664 body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
01665 }
01666 if ( !incidence->location().isEmpty() ) {
01667 body += i18n( "Location: %1\n", incidence->richLocation() );
01668 }
01669 return body;
01670 }
01671
01672
01673 class KCal::IncidenceFormatter::MailBodyVisitor : public IncidenceBase::Visitor
01674 {
01675 public:
01676 MailBodyVisitor() : mResult( "" ) {}
01677
01678 bool act( IncidenceBase *incidence )
01679 {
01680 mResult = "";
01681 return incidence ? incidence->accept( *this ) : false;
01682 }
01683 QString result() const
01684 {
01685 return mResult;
01686 }
01687
01688 protected:
01689 bool visit( Event *event );
01690 bool visit( Todo *todo );
01691 bool visit( Journal *journal );
01692 bool visit( FreeBusy * )
01693 {
01694 mResult = i18n( "This is a Free Busy Object" );
01695 return !mResult.isEmpty();
01696 }
01697 protected:
01698 QString mResult;
01699 };
01700
01701 bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
01702 {
01703 QString recurrence[]= {
01704 i18nc( "no recurrence", "None" ),
01705 i18nc( "event recurs by minutes", "Minutely" ),
01706 i18nc( "event recurs by hours", "Hourly" ),
01707 i18nc( "event recurs by days", "Daily" ),
01708 i18nc( "event recurs by weeks", "Weekly" ),
01709 i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
01710 i18nc( "event recurs same day each month", "Monthly Same Day" ),
01711 i18nc( "event recurs same month each year", "Yearly Same Month" ),
01712 i18nc( "event recurs same day each year", "Yearly Same Day" ),
01713 i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
01714 };
01715
01716 mResult = mailBodyIncidence( event );
01717 mResult += i18n( "Start Date: %1\n",
01718 event->dtStartDateStr( true, event->dtStart().timeSpec() ) );
01719 if ( !event->allDay() ) {
01720 mResult += i18n( "Start Time: %1\n",
01721 event->dtStartTimeStr( true, event->dtStart().timeSpec() ) );
01722 }
01723 if ( event->dtStart() != event->dtEnd() ) {
01724 mResult += i18n( "End Date: %1\n",
01725 event->dtEndDateStr( true, event->dtStart().timeSpec() ) );
01726 }
01727 if ( !event->allDay() ) {
01728 mResult += i18n( "End Time: %1\n",
01729 event->dtEndTimeStr( true, event->dtStart().timeSpec() ) );
01730 }
01731 if ( event->recurs() ) {
01732 Recurrence *recur = event->recurrence();
01733
01734 mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
01735 mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
01736
01737 if ( recur->duration() > 0 ) {
01738 mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
01739 mResult += '\n';
01740 } else {
01741 if ( recur->duration() != -1 ) {
01742
01743 QString endstr;
01744 if ( event->allDay() ) {
01745 endstr = KGlobal::locale()->formatDate( recur->endDate() );
01746 } else {
01747 endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
01748 }
01749 mResult += i18n( "Repeat until: %1\n", endstr );
01750 } else {
01751 mResult += i18n( "Repeats forever\n" );
01752 }
01753 }
01754 }
01755
01756 QString details = event->richDescription();
01757 if ( !details.isEmpty() ) {
01758 mResult += i18n( "Details:\n%1\n", details );
01759 }
01760 return !mResult.isEmpty();
01761 }
01762
01763 bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
01764 {
01765 mResult = mailBodyIncidence( todo );
01766
01767 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01768 mResult += i18n( "Start Date: %1\n",
01769 todo->dtStartDateStr( true, false, todo->dtStart().timeSpec() ) );
01770 if ( !todo->allDay() ) {
01771 mResult += i18n( "Start Time: %1\n",
01772 todo->dtStartTimeStr( true, false, todo->dtStart().timeSpec() ) );
01773 }
01774 }
01775 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01776 mResult += i18n( "Due Date: %1\n",
01777 todo->dtDueDateStr( true, todo->dtDue().timeSpec() ) );
01778 if ( !todo->allDay() ) {
01779 mResult += i18n( "Due Time: %1\n",
01780 todo->dtDueTimeStr( true, todo->dtDue().timeSpec() ) );
01781 }
01782 }
01783 QString details = todo->richDescription();
01784 if ( !details.isEmpty() ) {
01785 mResult += i18n( "Details:\n%1\n", details );
01786 }
01787 return !mResult.isEmpty();
01788 }
01789
01790 bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
01791 {
01792 mResult = mailBodyIncidence( journal );
01793 mResult += i18n( "Date: %1\n", journal->dtStartDateStr( true, journal->dtStart().timeSpec() ) );
01794 if ( !journal->allDay() ) {
01795 mResult += i18n( "Time: %1\n", journal->dtStartTimeStr( true, journal->dtStart().timeSpec() ) );
01796 }
01797 if ( !journal->description().isEmpty() ) {
01798 mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
01799 }
01800 return !mResult.isEmpty();
01801 }
01802
01803
01804 QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
01805 {
01806 if ( !incidence ) {
01807 return QString();
01808 }
01809
01810 MailBodyVisitor v;
01811 if ( v.act( incidence ) ) {
01812 return v.result();
01813 }
01814 return QString();
01815 }
01816
01817 static QString recurEnd( Incidence *incidence )
01818 {
01819 QString endstr;
01820 if ( incidence->allDay() ) {
01821 endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
01822 } else {
01823 endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
01824 }
01825 return endstr;
01826 }
01827
01828 QString IncidenceFormatter::recurrenceString( Incidence *incidence )
01829 {
01830 if ( !incidence->recurs() ) {
01831 return i18n( "No recurrence" );
01832 }
01833
01834 Recurrence *recur = incidence->recurrence();
01835 switch ( recur->recurrenceType() ) {
01836 case Recurrence::rNone:
01837 return i18n( "No recurrence" );
01838 case Recurrence::rMinutely:
01839 if ( recur->duration() != -1 ) {
01840 return i18np( "Recurs every minute until %2",
01841 "Recurs every %1 minutes until %2",
01842 recur->frequency(), recurEnd( incidence ) );
01843 }
01844 return i18np( "Recurs every minute",
01845 "Recurs every %1 minutes", recur->frequency() );
01846 case Recurrence::rHourly:
01847 if ( recur->duration() != -1 ) {
01848 return i18np( "Recurs hourly until %2",
01849 "Recurs every %1 hours until %2",
01850 recur->frequency(), recurEnd( incidence ) );
01851 }
01852 return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
01853 case Recurrence::rDaily:
01854 if ( recur->duration() != -1 ) {
01855 return i18np( "Recurs daily until %2",
01856 "Recurs every %1 days until %2",
01857 recur->frequency(), recurEnd( incidence ) );
01858 }
01859 return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
01860 case Recurrence::rWeekly:
01861 if ( recur->duration() != -1 ) {
01862 return i18np( "Recurs weekly until %2",
01863 "Recurs every %1 weeks until %2",
01864 recur->frequency(), recurEnd( incidence ) );
01865 }
01866 return i18np( "Recurs weekly", "Recurs every %1 weeks", recur->frequency() );
01867 case Recurrence::rMonthlyPos:
01868 case Recurrence::rMonthlyDay:
01869 if ( recur->duration() != -1 ) {
01870 return i18n( "Recurs monthly until %1", recurEnd( incidence ) );
01871 }
01872 return i18n( "Recurs monthly" );
01873 case Recurrence::rYearlyMonth:
01874 case Recurrence::rYearlyDay:
01875 case Recurrence::rYearlyPos:
01876 if ( recur->duration() != -1 ) {
01877 return i18n( "Recurs yearly until %1", recurEnd( incidence ) );
01878 }
01879 return i18n( "Recurs yearly" );
01880 default:
01881 return i18n( "Incidence recurs" );
01882 }
01883 }