00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00036 #include "incidenceformatter.h"
00037 #include "stringify.h"
00038
00039 #include <kcalcore/event.h>
00040 #include <kcalcore/freebusy.h>
00041 #include <kcalcore/icalformat.h>
00042 #include <kcalcore/journal.h>
00043 #include <kcalcore/memorycalendar.h>
00044 #include <kcalcore/todo.h>
00045 #include <kcalcore/visitor.h>
00046 using namespace KCalCore;
00047
00048 #include <kpimutils/email.h>
00049
00050 #include <KCalendarSystem>
00051 #include <KDebug>
00052 #include <KEMailSettings>
00053 #include <KIconLoader>
00054 #include <KLocale>
00055 #include <KMimeType>
00056 #include <KSystemTimeZone>
00057
00058 #include <QtCore/QBitArray>
00059 #include <QtGui/QApplication>
00060 #include <QtGui/QPalette>
00061 #include <QtGui/QTextDocument>
00062
00063 using namespace KCalUtils;
00064 using namespace IncidenceFormatter;
00065
00066
00067
00068
00069
00070
00071 static QString htmlAddLink( const QString &ref, const QString &text,
00072 bool newline = true )
00073 {
00074 QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
00075 if ( newline ) {
00076 tmpStr += '\n';
00077 }
00078 return tmpStr;
00079 }
00080
00081 static QString htmlAddMailtoLink( const QString &email, const QString &name )
00082 {
00083 QString str;
00084
00085 if ( !email.isEmpty() ) {
00086 Person person( name, email );
00087 KUrl mailto;
00088 mailto.setProtocol( "mailto" );
00089 mailto.setPath( person.fullName() );
00090 const QString iconPath =
00091 KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small );
00092 str = htmlAddLink( mailto.url(), "<img valign=\"top\" src=\"" + iconPath + "\">" );
00093 }
00094 return str;
00095 }
00096
00097 static QString htmlAddUidLink( const QString &email, const QString &name, const QString &uid )
00098 {
00099 QString str;
00100
00101 if ( !uid.isEmpty() ) {
00102
00103 if ( name.isEmpty() ) {
00104
00105 str += htmlAddLink( "uid:" + uid, email );
00106 } else {
00107 str += htmlAddLink( "uid:" + uid, name );
00108 }
00109 }
00110 return str;
00111 }
00112
00113 static QString htmlAddTag( const QString &tag, const QString &text )
00114 {
00115 int numLineBreaks = text.count( "\n" );
00116 QString str = '<' + tag + '>';
00117 QString tmpText = text;
00118 QString tmpStr = str;
00119 if( numLineBreaks >= 0 ) {
00120 if ( numLineBreaks > 0 ) {
00121 int pos = 0;
00122 QString tmp;
00123 for ( int i = 0; i <= numLineBreaks; ++i ) {
00124 pos = tmpText.indexOf( "\n" );
00125 tmp = tmpText.left( pos );
00126 tmpText = tmpText.right( tmpText.length() - pos - 1 );
00127 tmpStr += tmp + "<br>";
00128 }
00129 } else {
00130 tmpStr += tmpText;
00131 }
00132 }
00133 tmpStr += "</" + tag + '>';
00134 return tmpStr;
00135 }
00136
00137 static QPair<QString, QString> searchNameAndUid( const QString &email, const QString &name,
00138 const QString &uid )
00139 {
00140
00141
00142
00143 QPair<QString, QString>s;
00144 s.first = name;
00145 s.second = uid;
00146 if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
00147 s.second.clear();
00148 }
00149 return s;
00150 }
00151
00152 static QString searchName( const QString &email, const QString &name )
00153 {
00154 const QString printName = name.isEmpty() ? email : name;
00155 return printName;
00156 }
00157
00158 static bool iamAttendee( Attendee::Ptr attendee )
00159 {
00160
00161
00162 bool iam = false;
00163 KEMailSettings settings;
00164 QStringList profiles = settings.profiles();
00165 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
00166 settings.setProfile( *it );
00167 if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
00168 iam = true;
00169 break;
00170 }
00171 }
00172 return iam;
00173 }
00174
00175 static bool iamOrganizer( Incidence::Ptr incidence )
00176 {
00177
00178
00179 if ( !incidence ) {
00180 return false;
00181 }
00182
00183 bool iam = false;
00184 KEMailSettings settings;
00185 QStringList profiles = settings.profiles();
00186 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
00187 settings.setProfile( *it );
00188 if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer()->email() ) {
00189 iam = true;
00190 break;
00191 }
00192 }
00193 return iam;
00194 }
00195
00196 static bool senderIsOrganizer( Incidence::Ptr incidence, const QString &sender )
00197 {
00198
00199
00200 if ( !incidence || sender.isEmpty() ) {
00201 return true;
00202 }
00203
00204 bool isorg = true;
00205 QString senderName, senderEmail;
00206 if ( KPIMUtils::extractEmailAddressAndName( sender, senderEmail, senderName ) ) {
00207
00208 if ( incidence->organizer()->email() != senderEmail &&
00209 incidence->organizer()->name() != senderName ) {
00210 isorg = false;
00211 }
00212 }
00213 return isorg;
00214 }
00215
00216 static bool attendeeIsOrganizer( const Incidence::Ptr &incidence, const Attendee::Ptr &attendee )
00217 {
00218 if ( incidence && attendee &&
00219 ( incidence->organizer()->email() == attendee->email() ) ) {
00220 return true;
00221 } else {
00222 return false;
00223 }
00224 }
00225
00226 static QString organizerName( const Incidence::Ptr incidence, const QString &defName )
00227 {
00228 QString tName;
00229 if ( !defName.isEmpty() ) {
00230 tName = defName;
00231 } else {
00232 tName = i18n( "Organizer Unknown" );
00233 }
00234
00235 QString name;
00236 if ( incidence ) {
00237 name = incidence->organizer()->name();
00238 if ( name.isEmpty() ) {
00239 name = incidence->organizer()->email();
00240 }
00241 }
00242 if ( name.isEmpty() ) {
00243 name = tName;
00244 }
00245 return name;
00246 }
00247
00248 static QString firstAttendeeName( const Incidence::Ptr &incidence, const QString &defName )
00249 {
00250 QString tName;
00251 if ( !defName.isEmpty() ) {
00252 tName = defName;
00253 } else {
00254 tName = i18n( "Sender" );
00255 }
00256
00257 QString name;
00258 if ( incidence ) {
00259 Attendee::List attendees = incidence->attendees();
00260 if( attendees.count() > 0 ) {
00261 Attendee::Ptr attendee = *attendees.begin();
00262 name = attendee->name();
00263 if ( name.isEmpty() ) {
00264 name = attendee->email();
00265 }
00266 }
00267 }
00268 if ( name.isEmpty() ) {
00269 name = tName;
00270 }
00271 return name;
00272 }
00273
00274 static QString rsvpStatusIconPath( Attendee::PartStat status )
00275 {
00276 QString iconPath;
00277 switch ( status ) {
00278 case Attendee::Accepted:
00279 iconPath = KIconLoader::global()->iconPath( "dialog-ok-apply", KIconLoader::Small );
00280 break;
00281 case Attendee::Declined:
00282 iconPath = KIconLoader::global()->iconPath( "dialog-cancel", KIconLoader::Small );
00283 break;
00284 case Attendee::NeedsAction:
00285 iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
00286 break;
00287 case Attendee::InProcess:
00288 iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
00289 break;
00290 case Attendee::Tentative:
00291 iconPath = KIconLoader::global()->iconPath( "dialog-ok", KIconLoader::Small );
00292 break;
00293 case Attendee::Delegated:
00294 iconPath = KIconLoader::global()->iconPath( "mail-forward", KIconLoader::Small );
00295 break;
00296 case Attendee::Completed:
00297 iconPath = KIconLoader::global()->iconPath( "mail-mark-read", KIconLoader::Small );
00298 default:
00299 break;
00300 }
00301 return iconPath;
00302 }
00303
00304
00305
00306
00307
00308
00309
00310
00311 static QString displayViewFormatPerson( const QString &email, const QString &name,
00312 const QString &uid, const QString &iconPath )
00313 {
00314
00315 QPair<QString, QString> s = searchNameAndUid( email, name, uid );
00316 const QString printName = s.first;
00317 const QString printUid = s.second;
00318
00319 QString personString;
00320 if ( !iconPath.isEmpty() ) {
00321 personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + " ";
00322 }
00323
00324
00325 if ( !printUid.isEmpty() ) {
00326 personString += htmlAddUidLink( email, printName, printUid );
00327 } else {
00328
00329 personString += ( printName.isEmpty() ? email : printName );
00330 }
00331
00332
00333 if ( !email.isEmpty() ) {
00334 personString += " " + htmlAddMailtoLink( email, printName );
00335 }
00336
00337 return personString;
00338 }
00339
00340 static QString displayViewFormatPerson( const QString &email, const QString &name,
00341 const QString &uid, Attendee::PartStat status )
00342 {
00343 return displayViewFormatPerson( email, name, uid, rsvpStatusIconPath( status ) );
00344 }
00345
00346 static bool incOrganizerOwnsCalendar( const Calendar::Ptr &calendar,
00347 const Incidence::Ptr &incidence )
00348 {
00349
00350
00351
00352 Q_UNUSED( calendar );
00353 return iamOrganizer( incidence );
00354 }
00355
00356 static QString displayViewFormatAttendeeRoleList( Incidence::Ptr incidence, Attendee::Role role,
00357 bool showStatus )
00358 {
00359 QString tmpStr;
00360 Attendee::List::ConstIterator it;
00361 Attendee::List attendees = incidence->attendees();
00362
00363 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
00364 Attendee::Ptr a = *it;
00365 if ( a->role() != role ) {
00366
00367 continue;
00368 }
00369 if ( attendeeIsOrganizer( incidence, a ) ) {
00370
00371 continue;
00372 }
00373 tmpStr += displayViewFormatPerson( a->email(), a->name(), a->uid(),
00374 showStatus ? a->status() : Attendee::None );
00375 if ( !a->delegator().isEmpty() ) {
00376 tmpStr += i18n( " (delegated by %1)", a->delegator() );
00377 }
00378 if ( !a->delegate().isEmpty() ) {
00379 tmpStr += i18n( " (delegated to %1)", a->delegate() );
00380 }
00381 tmpStr += "<br>";
00382 }
00383 if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
00384 tmpStr.chop( 4 );
00385 }
00386 return tmpStr;
00387 }
00388
00389 static QString displayViewFormatAttendees( Calendar::Ptr calendar, Incidence::Ptr incidence )
00390 {
00391 QString tmpStr, str;
00392
00393
00394 int attendeeCount = incidence->attendees().count();
00395 if ( attendeeCount > 1 ||
00396 ( attendeeCount == 1 &&
00397 !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
00398
00399 QPair<QString, QString> s = searchNameAndUid( incidence->organizer()->email(),
00400 incidence->organizer()->name(),
00401 QString() );
00402 tmpStr += "<tr>";
00403 tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
00404 const QString iconPath =
00405 KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
00406 tmpStr += "<td>" + displayViewFormatPerson( incidence->organizer()->email(),
00407 s.first, s.second, iconPath ) +
00408 "</td>";
00409 tmpStr += "</tr>";
00410 }
00411
00412
00413
00414 bool showStatus = incOrganizerOwnsCalendar( calendar, incidence );
00415
00416
00417 str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
00418 if ( !str.isEmpty() ) {
00419 tmpStr += "<tr>";
00420 tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
00421 tmpStr += "<td>" + str + "</td>";
00422 tmpStr += "</tr>";
00423 }
00424
00425
00426 str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
00427 if ( !str.isEmpty() ) {
00428 tmpStr += "<tr>";
00429 tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
00430 tmpStr += "<td>" + str + "</td>";
00431 tmpStr += "</tr>";
00432 }
00433
00434
00435 str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
00436 if ( !str.isEmpty() ) {
00437 tmpStr += "<tr>";
00438 tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
00439 tmpStr += "<td>" + str + "</td>";
00440 tmpStr += "</tr>";
00441 }
00442
00443
00444 str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
00445 if ( !str.isEmpty() ) {
00446 tmpStr += "<tr>";
00447 tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
00448 tmpStr += "<td>" + str + "</td>";
00449 tmpStr += "</tr>";
00450 }
00451
00452 return tmpStr;
00453 }
00454
00455 static QString displayViewFormatAttachments( Incidence::Ptr incidence )
00456 {
00457 QString tmpStr;
00458 Attachment::List as = incidence->attachments();
00459 Attachment::List::ConstIterator it;
00460 int count = 0;
00461 for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
00462 count++;
00463 if ( (*it)->isUri() ) {
00464 QString name;
00465 if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) {
00466 name = i18n( "Show mail" );
00467 } else {
00468 if ( (*it)->label().isEmpty() ) {
00469 name = (*it)->uri();
00470 } else {
00471 name = (*it)->label();
00472 }
00473 }
00474 tmpStr += htmlAddLink( (*it)->uri(), name );
00475 } else {
00476 tmpStr += (*it)->label();
00477 }
00478 if ( count < as.count() ) {
00479 tmpStr += "<br>";
00480 }
00481 }
00482 return tmpStr;
00483 }
00484
00485 static QString displayViewFormatCategories( Incidence::Ptr incidence )
00486 {
00487
00488 return incidence->categories().join( ", " );
00489 }
00490
00491 static QString displayViewFormatCreationDate( Incidence::Ptr incidence, KDateTime::Spec spec )
00492 {
00493 KDateTime kdt = incidence->created().toTimeSpec( spec );
00494 return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) );
00495 }
00496
00497 static QString displayViewFormatBirthday( Event::Ptr event )
00498 {
00499 if ( !event ) {
00500 return QString();
00501 }
00502 if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" &&
00503 event->customProperty( "KABC", "ANNIVERSARY" ) != "YES" ) {
00504 return QString();
00505 }
00506
00507 QString uid_1 = event->customProperty( "KABC", "UID-1" );
00508 QString name_1 = event->customProperty( "KABC", "NAME-1" );
00509 QString email_1= event->customProperty( "KABC", "EMAIL-1" );
00510
00511 QString tmpStr = displayViewFormatPerson( email_1, name_1, uid_1, QString() );
00512 return tmpStr;
00513 }
00514
00515 static QString displayViewFormatHeader( Incidence::Ptr incidence )
00516 {
00517 QString tmpStr = "<table><tr>";
00518
00519
00520 KIconLoader *iconLoader = KIconLoader::global();
00521 tmpStr += "<td>";
00522
00523 QString iconPath;
00524 if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
00525 iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small );
00526 } else if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
00527 iconPath = iconLoader->iconPath( "view-calendar-wedding-anniversary", KIconLoader::Small );
00528 } else {
00529 iconPath = iconLoader->iconPath( incidence->iconName(), KIconLoader::Small );
00530 }
00531 tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
00532
00533 if ( incidence->hasEnabledAlarms() ) {
00534 tmpStr += "<img valign=\"top\" src=\"" +
00535 iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
00536 "\">";
00537 }
00538 if ( incidence->recurs() ) {
00539 tmpStr += "<img valign=\"top\" src=\"" +
00540 iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
00541 "\">";
00542 }
00543 if ( incidence->isReadOnly() ) {
00544 tmpStr += "<img valign=\"top\" src=\"" +
00545 iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
00546 "\">";
00547 }
00548 tmpStr += "</td>";
00549
00550 tmpStr += "<td>";
00551 tmpStr += "<b><u>" + incidence->richSummary() + "</u></b>";
00552 tmpStr += "</td>";
00553
00554 tmpStr += "</tr></table>";
00555
00556 return tmpStr;
00557 }
00558
00559 static QString displayViewFormatEvent( const Calendar::Ptr calendar, const QString &sourceName,
00560 const Event::Ptr &event,
00561 const QDate &date, KDateTime::Spec spec )
00562 {
00563 if ( !event ) {
00564 return QString();
00565 }
00566
00567 QString tmpStr = displayViewFormatHeader( event );
00568
00569 tmpStr += "<table>";
00570 tmpStr += "<col width=\"25%\"/>";
00571 tmpStr += "<col width=\"75%\"/>";
00572
00573 const QString calStr = calendar ? resourceString( calendar, event ) : sourceName;
00574 if ( !calStr.isEmpty() ) {
00575 tmpStr += "<tr>";
00576 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00577 tmpStr += "<td>" + calStr + "</td>";
00578 tmpStr += "</tr>";
00579 }
00580
00581 if ( !event->location().isEmpty() ) {
00582 tmpStr += "<tr>";
00583 tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00584 tmpStr += "<td>" + event->richLocation() + "</td>";
00585 tmpStr += "</tr>";
00586 }
00587
00588 KDateTime startDt = event->dtStart();
00589 KDateTime endDt = event->dtEnd();
00590 if ( event->recurs() ) {
00591 if ( date.isValid() ) {
00592 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
00593 int diffDays = startDt.daysTo( kdt );
00594 kdt = kdt.addSecs( -1 );
00595 startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
00596 if ( event->hasEndDate() ) {
00597 endDt = endDt.addDays( diffDays );
00598 if ( startDt > endDt ) {
00599 startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
00600 endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
00601 }
00602 }
00603 }
00604 }
00605
00606 tmpStr += "<tr>";
00607 if ( event->allDay() ) {
00608 if ( event->isMultiDay() ) {
00609 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00610 tmpStr += "<td>" +
00611 i18nc( "<beginTime> - <endTime>","%1 - %2",
00612 dateToString( startDt, false, spec ),
00613 dateToString( endDt, false, spec ) ) +
00614 "</td>";
00615 } else {
00616 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00617 tmpStr += "<td>" +
00618 i18nc( "date as string","%1",
00619 dateToString( startDt, false, spec ) ) +
00620 "</td>";
00621 }
00622 } else {
00623 if ( event->isMultiDay() ) {
00624 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00625 tmpStr += "<td>" +
00626 i18nc( "<beginTime> - <endTime>","%1 - %2",
00627 dateToString( startDt, false, spec ),
00628 dateToString( endDt, false, spec ) ) +
00629 "</td>";
00630 } else {
00631 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00632 tmpStr += "<td>" +
00633 i18nc( "date as string", "%1",
00634 dateToString( startDt, false, spec ) ) +
00635 "</td>";
00636
00637 tmpStr += "</tr><tr>";
00638 tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
00639 if ( event->hasEndDate() && startDt != endDt ) {
00640 tmpStr += "<td>" +
00641 i18nc( "<beginTime> - <endTime>","%1 - %2",
00642 timeToString( startDt, true, spec ),
00643 timeToString( endDt, true, spec ) ) +
00644 "</td>";
00645 } else {
00646 tmpStr += "<td>" +
00647 timeToString( startDt, true, spec ) +
00648 "</td>";
00649 }
00650 }
00651 }
00652 tmpStr += "</tr>";
00653
00654 QString durStr = durationString( event );
00655 if ( !durStr.isEmpty() ) {
00656 tmpStr += "<tr>";
00657 tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00658 tmpStr += "<td>" + durStr + "</td>";
00659 tmpStr += "</tr>";
00660 }
00661
00662 if ( event->recurs() ) {
00663 tmpStr += "<tr>";
00664 tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00665 tmpStr += "<td>" +
00666 recurrenceString( event ) +
00667 "</td>";
00668 tmpStr += "</tr>";
00669 }
00670
00671 const bool isBirthday = event->customProperty( "KABC", "BIRTHDAY" ) == "YES";
00672 const bool isAnniversary = event->customProperty( "KABC", "ANNIVERSARY" ) == "YES";
00673
00674 if ( isBirthday || isAnniversary ) {
00675 tmpStr += "<tr>";
00676 if ( isAnniversary ) {
00677 tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
00678 } else {
00679 tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
00680 }
00681 tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
00682 tmpStr += "</tr>";
00683 tmpStr += "</table>";
00684 return tmpStr;
00685 }
00686
00687 if ( !event->description().isEmpty() ) {
00688 tmpStr += "<tr>";
00689 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00690 tmpStr += "<td>" + event->richDescription() + "</td>";
00691 tmpStr += "</tr>";
00692 }
00693
00694
00695
00696 int reminderCount = event->alarms().count();
00697 if ( reminderCount > 0 && event->hasEnabledAlarms() ) {
00698 tmpStr += "<tr>";
00699 tmpStr += "<td><b>" +
00700 i18np( "Reminder:", "Reminders:", reminderCount ) +
00701 "</b></td>";
00702 tmpStr += "<td>" + reminderStringList( event ).join( "<br>" ) + "</td>";
00703 tmpStr += "</tr>";
00704 }
00705
00706 tmpStr += displayViewFormatAttendees( calendar, event );
00707
00708 int categoryCount = event->categories().count();
00709 if ( categoryCount > 0 ) {
00710 tmpStr += "<tr>";
00711 tmpStr += "<td><b>";
00712 tmpStr += i18np( "Category:", "Categories:", categoryCount ) +
00713 "</b></td>";
00714 tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
00715 tmpStr += "</tr>";
00716 }
00717
00718 int attachmentCount = event->attachments().count();
00719 if ( attachmentCount > 0 ) {
00720 tmpStr += "<tr>";
00721 tmpStr += "<td><b>" +
00722 i18np( "Attachment:", "Attachments:", attachmentCount ) +
00723 "</b></td>";
00724 tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
00725 tmpStr += "</tr>";
00726 }
00727 tmpStr += "</table>";
00728
00729 tmpStr += "<p><em>" + displayViewFormatCreationDate( event, spec ) + "</em>";
00730
00731 return tmpStr;
00732 }
00733
00734 static QString displayViewFormatTodo( const Calendar::Ptr &calendar, const QString &sourceName,
00735 const Todo::Ptr &todo,
00736 const QDate &date, KDateTime::Spec spec )
00737 {
00738 if ( !todo ) {
00739 return QString();
00740 }
00741
00742 QString tmpStr = displayViewFormatHeader( todo );
00743
00744 tmpStr += "<table>";
00745 tmpStr += "<col width=\"25%\"/>";
00746 tmpStr += "<col width=\"75%\"/>";
00747
00748 const QString calStr = calendar ? resourceString( calendar, todo ) : sourceName;
00749 if ( !calStr.isEmpty() ) {
00750 tmpStr += "<tr>";
00751 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00752 tmpStr += "<td>" + calStr + "</td>";
00753 tmpStr += "</tr>";
00754 }
00755
00756 if ( !todo->location().isEmpty() ) {
00757 tmpStr += "<tr>";
00758 tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
00759 tmpStr += "<td>" + todo->richLocation() + "</td>";
00760 tmpStr += "</tr>";
00761 }
00762
00763 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
00764 KDateTime startDt = todo->dtStart();
00765 if ( todo->recurs() ) {
00766 if ( date.isValid() ) {
00767 startDt.setDate( date );
00768 }
00769 }
00770 tmpStr += "<tr>";
00771 tmpStr += "<td><b>" +
00772 i18nc( "to-do start date/time", "Start:" ) +
00773 "</b></td>";
00774 tmpStr += "<td>" +
00775 dateTimeToString( startDt, todo->allDay(), false, spec ) +
00776 "</td>";
00777 tmpStr += "</tr>";
00778 }
00779
00780 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
00781 KDateTime dueDt = todo->dtDue();
00782 if ( todo->recurs() ) {
00783 if ( date.isValid() ) {
00784 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
00785 kdt = kdt.addSecs( -1 );
00786 dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
00787 }
00788 }
00789 tmpStr += "<tr>";
00790 tmpStr += "<td><b>" +
00791 i18nc( "to-do due date/time", "Due:" ) +
00792 "</b></td>";
00793 tmpStr += "<td>" +
00794 dateTimeToString( dueDt, todo->allDay(), false, spec ) +
00795 "</td>";
00796 tmpStr += "</tr>";
00797 }
00798
00799 QString durStr = durationString( todo );
00800 if ( !durStr.isEmpty() ) {
00801 tmpStr += "<tr>";
00802 tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
00803 tmpStr += "<td>" + durStr + "</td>";
00804 tmpStr += "</tr>";
00805 }
00806
00807 if ( todo->recurs() ) {
00808 tmpStr += "<tr>";
00809 tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
00810 tmpStr += "<td>" +
00811 recurrenceString( todo ) +
00812 "</td>";
00813 tmpStr += "</tr>";
00814 }
00815
00816 if ( !todo->description().isEmpty() ) {
00817 tmpStr += "<tr>";
00818 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00819 tmpStr += "<td>" + todo->richDescription() + "</td>";
00820 tmpStr += "</tr>";
00821 }
00822
00823
00824
00825 int reminderCount = todo->alarms().count();
00826 if ( reminderCount > 0 && todo->hasEnabledAlarms() ) {
00827 tmpStr += "<tr>";
00828 tmpStr += "<td><b>" +
00829 i18np( "Reminder:", "Reminders:", reminderCount ) +
00830 "</b></td>";
00831 tmpStr += "<td>" + reminderStringList( todo ).join( "<br>" ) + "</td>";
00832 tmpStr += "</tr>";
00833 }
00834
00835 tmpStr += displayViewFormatAttendees( calendar, todo );
00836
00837 int categoryCount = todo->categories().count();
00838 if ( categoryCount > 0 ) {
00839 tmpStr += "<tr>";
00840 tmpStr += "<td><b>" +
00841 i18np( "Category:", "Categories:", categoryCount ) +
00842 "</b></td>";
00843 tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
00844 tmpStr += "</tr>";
00845 }
00846
00847 if ( todo->priority() > 0 ) {
00848 tmpStr += "<tr>";
00849 tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
00850 tmpStr += "<td>";
00851 tmpStr += QString::number( todo->priority() );
00852 tmpStr += "</td>";
00853 tmpStr += "</tr>";
00854 }
00855
00856 tmpStr += "<tr>";
00857 if ( todo->isCompleted() ) {
00858 tmpStr += "<td><b>" + i18nc( "Completed: date", "Completed:" ) + "</b></td>";
00859 tmpStr += "<td>";
00860 tmpStr += Stringify::todoCompletedDateTime( todo );
00861 } else {
00862 tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
00863 tmpStr += "<td>";
00864 tmpStr += i18n( "%1%", todo->percentComplete() );
00865 }
00866 tmpStr += "</td>";
00867 tmpStr += "</tr>";
00868
00869 int attachmentCount = todo->attachments().count();
00870 if ( attachmentCount > 0 ) {
00871 tmpStr += "<tr>";
00872 tmpStr += "<td><b>" +
00873 i18np( "Attachment:", "Attachments:", attachmentCount ) +
00874 "</b></td>";
00875 tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
00876 tmpStr += "</tr>";
00877 }
00878 tmpStr += "</table>";
00879
00880 tmpStr += "<p><em>" + displayViewFormatCreationDate( todo, spec ) + "</em>";
00881
00882 return tmpStr;
00883 }
00884
00885 static QString displayViewFormatJournal( const Calendar::Ptr &calendar, const QString &sourceName,
00886 const Journal::Ptr &journal, KDateTime::Spec spec )
00887 {
00888 if ( !journal ) {
00889 return QString();
00890 }
00891
00892 QString tmpStr = displayViewFormatHeader( journal );
00893
00894 tmpStr += "<table>";
00895 tmpStr += "<col width=\"25%\"/>";
00896 tmpStr += "<col width=\"75%\"/>";
00897
00898 const QString calStr = calendar ? resourceString( calendar, journal ) : sourceName;
00899 if ( !calStr.isEmpty() ) {
00900 tmpStr += "<tr>";
00901 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
00902 tmpStr += "<td>" + calStr + "</td>";
00903 tmpStr += "</tr>";
00904 }
00905
00906 tmpStr += "<tr>";
00907 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
00908 tmpStr += "<td>" +
00909 dateToString( journal->dtStart(), false, spec ) +
00910 "</td>";
00911 tmpStr += "</tr>";
00912
00913 if ( !journal->description().isEmpty() ) {
00914 tmpStr += "<tr>";
00915 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
00916 tmpStr += "<td>" + journal->richDescription() + "</td>";
00917 tmpStr += "</tr>";
00918 }
00919
00920 int categoryCount = journal->categories().count();
00921 if ( categoryCount > 0 ) {
00922 tmpStr += "<tr>";
00923 tmpStr += "<td><b>" +
00924 i18np( "Category:", "Categories:", categoryCount ) +
00925 "</b></td>";
00926 tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
00927 tmpStr += "</tr>";
00928 }
00929
00930 tmpStr += "</table>";
00931
00932 tmpStr += "<p><em>" + displayViewFormatCreationDate( journal, spec ) + "</em>";
00933
00934 return tmpStr;
00935 }
00936
00937 static QString displayViewFormatFreeBusy( const Calendar::Ptr &calendar, const QString &sourceName,
00938 const FreeBusy::Ptr &fb, KDateTime::Spec spec )
00939 {
00940 Q_UNUSED( calendar );
00941 Q_UNUSED( sourceName );
00942 if ( !fb ) {
00943 return QString();
00944 }
00945
00946 QString tmpStr(
00947 htmlAddTag(
00948 "h2", i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) ) );
00949
00950 tmpStr += htmlAddTag( "h4",
00951 i18n( "Busy times in date range %1 - %2:",
00952 dateToString( fb->dtStart(), true, spec ),
00953 dateToString( fb->dtEnd(), true, spec ) ) );
00954
00955 QString text =
00956 htmlAddTag( "em",
00957 htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
00958
00959 Period::List periods = fb->busyPeriods();
00960 Period::List::iterator it;
00961 for ( it = periods.begin(); it != periods.end(); ++it ) {
00962 Period per = *it;
00963 if ( per.hasDuration() ) {
00964 int dur = per.duration().asSeconds();
00965 QString cont;
00966 if ( dur >= 3600 ) {
00967 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
00968 dur %= 3600;
00969 }
00970 if ( dur >= 60 ) {
00971 cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
00972 dur %= 60;
00973 }
00974 if ( dur > 0 ) {
00975 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
00976 }
00977 text += i18nc( "startDate for duration", "%1 for %2",
00978 dateTimeToString( per.start(), false, true, spec ),
00979 cont );
00980 text += "<br>";
00981 } else {
00982 if ( per.start().date() == per.end().date() ) {
00983 text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
00984 dateToString( per.start(), true, spec ),
00985 timeToString( per.start(), true, spec ),
00986 timeToString( per.end(), true, spec ) );
00987 } else {
00988 text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
00989 dateTimeToString( per.start(), false, true, spec ),
00990 dateTimeToString( per.end(), false, true, spec ) );
00991 }
00992 text += "<br>";
00993 }
00994 }
00995 tmpStr += htmlAddTag( "p", text );
00996 return tmpStr;
00997 }
00998
00999
01000
01001 class KCalUtils::IncidenceFormatter::EventViewerVisitor : public Visitor
01002 {
01003 public:
01004 EventViewerVisitor()
01005 : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
01006
01007 bool act( const Calendar::Ptr &calendar, IncidenceBase::Ptr incidence, const QDate &date,
01008 KDateTime::Spec spec=KDateTime::Spec() )
01009 {
01010 mCalendar = calendar;
01011 mSourceName.clear();
01012 mDate = date;
01013 mSpec = spec;
01014 mResult = "";
01015 return incidence->accept( *this, incidence );
01016 }
01017
01018 bool act( const QString &sourceName, IncidenceBase::Ptr incidence, const QDate &date,
01019 KDateTime::Spec spec=KDateTime::Spec() )
01020 {
01021 mSourceName = sourceName;
01022 mDate = date;
01023 mSpec = spec;
01024 mResult = "";
01025 return incidence->accept( *this, incidence );
01026 }
01027
01028 QString result() const { return mResult; }
01029
01030 protected:
01031 bool visit( Event::Ptr event )
01032 {
01033 mResult = displayViewFormatEvent( mCalendar, mSourceName, event, mDate, mSpec );
01034 return !mResult.isEmpty();
01035 }
01036 bool visit( Todo::Ptr todo )
01037 {
01038 mResult = displayViewFormatTodo( mCalendar, mSourceName, todo, mDate, mSpec );
01039 return !mResult.isEmpty();
01040 }
01041 bool visit( Journal::Ptr journal )
01042 {
01043 mResult = displayViewFormatJournal( mCalendar, mSourceName, journal, mSpec );
01044 return !mResult.isEmpty();
01045 }
01046 bool visit( FreeBusy::Ptr fb )
01047 {
01048 mResult = displayViewFormatFreeBusy( mCalendar, mSourceName, fb, mSpec );
01049 return !mResult.isEmpty();
01050 }
01051
01052 protected:
01053 Calendar::Ptr mCalendar;
01054 QString mSourceName;
01055 QDate mDate;
01056 KDateTime::Spec mSpec;
01057 QString mResult;
01058 };
01059
01060
01061 QString IncidenceFormatter::extensiveDisplayStr( const Calendar::Ptr &calendar,
01062 const IncidenceBase::Ptr &incidence,
01063 const QDate &date,
01064 KDateTime::Spec spec )
01065 {
01066 if ( !incidence ) {
01067 return QString();
01068 }
01069
01070 EventViewerVisitor v;
01071 if ( v.act( calendar, incidence, date, spec ) ) {
01072 return v.result();
01073 } else {
01074 return QString();
01075 }
01076 }
01077
01078 QString IncidenceFormatter::extensiveDisplayStr( const QString &sourceName,
01079 const IncidenceBase::Ptr &incidence,
01080 const QDate &date,
01081 KDateTime::Spec spec )
01082 {
01083 if ( !incidence ) {
01084 return QString();
01085 }
01086
01087 EventViewerVisitor v;
01088 if ( v.act( sourceName, incidence, date, spec ) ) {
01089 return v.result();
01090 } else {
01091 return QString();
01092 }
01093 }
01094
01095
01096
01097
01098
01099 static QString string2HTML( const QString &str )
01100 {
01101 return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
01102 }
01103
01104 static QString cleanHtml( const QString &html )
01105 {
01106 QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
01107 rx.indexIn( html );
01108 QString body = rx.cap( 1 );
01109
01110 return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
01111 }
01112
01113 static QString invitationSummary( const Incidence::Ptr &incidence, bool noHtmlMode )
01114 {
01115 QString summaryStr = i18n( "Summary unspecified" );
01116 if ( !incidence->summary().isEmpty() ) {
01117 if ( !incidence->summaryIsRich() ) {
01118 summaryStr = Qt::escape( incidence->summary() );
01119 } else {
01120 summaryStr = incidence->richSummary();
01121 if ( noHtmlMode ) {
01122 summaryStr = cleanHtml( summaryStr );
01123 }
01124 }
01125 }
01126 return summaryStr;
01127 }
01128
01129 static QString invitationLocation( const Incidence::Ptr &incidence, bool noHtmlMode )
01130 {
01131 QString locationStr = i18n( "Location unspecified" );
01132 if ( !incidence->location().isEmpty() ) {
01133 if ( !incidence->locationIsRich() ) {
01134 locationStr = Qt::escape( incidence->location() );
01135 } else {
01136 locationStr = incidence->richLocation();
01137 if ( noHtmlMode ) {
01138 locationStr = cleanHtml( locationStr );
01139 }
01140 }
01141 }
01142 return locationStr;
01143 }
01144
01145 static QString eventStartTimeStr( const Event::Ptr &event )
01146 {
01147 QString tmp;
01148 if ( !event->allDay() ) {
01149 tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
01150 dateToString( event->dtStart(), true, KSystemTimeZones::local() ),
01151 timeToString( event->dtStart(), true, KSystemTimeZones::local() ) );
01152 } else {
01153 tmp = i18nc( "%1: Start Date", "%1 (all day)",
01154 dateToString( event->dtStart(), true, KSystemTimeZones::local() ) );
01155 }
01156 return tmp;
01157 }
01158
01159 static QString eventEndTimeStr( const Event::Ptr &event )
01160 {
01161 QString tmp;
01162 if ( event->hasEndDate() && event->dtEnd().isValid() ) {
01163 if ( !event->allDay() ) {
01164 tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2",
01165 dateToString( event->dtEnd(), true, KSystemTimeZones::local() ),
01166 timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
01167 } else {
01168 tmp = i18nc( "%1: End Date", "%1 (all day)",
01169 dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
01170 }
01171 }
01172 return tmp;
01173 }
01174
01175 static QString htmlInvitationDetailsBegin()
01176 {
01177 QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
01178 return QString( "<div dir=\"%1\">\n" ).arg( dir );
01179 }
01180
01181 static QString htmlInvitationDetailsEnd()
01182 {
01183 return "</div>\n";
01184 }
01185
01186 static QString htmlInvitationDetailsTableBegin()
01187 {
01188 return "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
01189 }
01190
01191 static QString htmlInvitationDetailsTableEnd()
01192 {
01193 return "</table>\n";
01194 }
01195
01196 static QString diffColor()
01197 {
01198
01199
01200
01201 return QColor( Qt::red ).name();
01202 }
01203
01204 static QString noteColor()
01205 {
01206
01207 return qApp->palette().color( QPalette::Active, QPalette::Highlight ).name();
01208 }
01209
01210 static QString htmlRow( const QString &title, const QString &value )
01211 {
01212 if ( !value.isEmpty() ) {
01213 return "<tr><td>" + title + "</td><td>" + value + "</td></tr>\n";
01214 } else {
01215 return QString();
01216 }
01217 }
01218
01219 static QString htmlRow( const QString &title, const QString &value, const QString &oldvalue )
01220 {
01221
01222 if ( value.isEmpty() ) {
01223 return QString();
01224 }
01225
01226
01227 if ( oldvalue.isEmpty() || value == oldvalue ) {
01228 return htmlRow( title, value );
01229 }
01230
01231
01232 QString color = diffColor();
01233 QString newtitle = "<font color=\"" + color + "\">" + title + "</font>";
01234 QString newvalue = "<font color=\"" + color + "\">" + value + "</font>" +
01235 " " +
01236 "(<strike>" + oldvalue + "</strike>)";
01237 return htmlRow( newtitle, newvalue );
01238
01239 }
01240
01241 static Attendee::Ptr findDelegatedFromMyAttendee( const Incidence::Ptr &incidence )
01242 {
01243
01244
01245 Attendee::Ptr attendee;
01246 if ( !incidence ) {
01247 return attendee;
01248 }
01249
01250 KEMailSettings settings;
01251 QStringList profiles = settings.profiles();
01252 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
01253 settings.setProfile( *it );
01254
01255 QString delegatorName, delegatorEmail;
01256 Attendee::List attendees = incidence->attendees();
01257 Attendee::List::ConstIterator it2;
01258 for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
01259 Attendee::Ptr a = *it2;
01260 KPIMUtils::extractEmailAddressAndName( a->delegator(), delegatorEmail, delegatorName );
01261 if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
01262 attendee = a;
01263 break;
01264 }
01265 }
01266 }
01267 return attendee;
01268 }
01269
01270 static Attendee::Ptr findMyAttendee( const Incidence::Ptr &incidence )
01271 {
01272
01273
01274 Attendee::Ptr attendee;
01275 if ( !incidence ) {
01276 return attendee;
01277 }
01278
01279 KEMailSettings settings;
01280 QStringList profiles = settings.profiles();
01281 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
01282 settings.setProfile( *it );
01283
01284 Attendee::List attendees = incidence->attendees();
01285 Attendee::List::ConstIterator it2;
01286 for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
01287 Attendee::Ptr a = *it2;
01288 if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
01289 attendee = a;
01290 break;
01291 }
01292 }
01293 }
01294 return attendee;
01295 }
01296
01297 static Attendee::Ptr findAttendee( const Incidence::Ptr &incidence,
01298 const QString &email )
01299 {
01300
01301
01302 Attendee::Ptr attendee;
01303 if ( !incidence ) {
01304 return attendee;
01305 }
01306
01307 Attendee::List attendees = incidence->attendees();
01308 Attendee::List::ConstIterator it;
01309 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
01310 Attendee::Ptr a = *it;
01311 if ( email == a->email() ) {
01312 attendee = a;
01313 break;
01314 }
01315 }
01316 return attendee;
01317 }
01318
01319 static bool rsvpRequested( const Incidence::Ptr &incidence )
01320 {
01321 if ( !incidence ) {
01322 return false;
01323 }
01324
01325
01326
01327 bool rsvp = true;
01328 Attendee::List attendees = incidence->attendees();
01329 Attendee::List::ConstIterator it;
01330 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
01331 if ( it == attendees.constBegin() ) {
01332 rsvp = (*it)->RSVP();
01333 } else {
01334 if ( (*it)->RSVP() != rsvp ) {
01335 rsvp = true;
01336 break;
01337 }
01338 }
01339 }
01340 return rsvp;
01341 }
01342
01343 static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
01344 {
01345 if ( rsvpRequested ) {
01346 if ( role.isEmpty() ) {
01347 return i18n( "Your response is requested" );
01348 } else {
01349 return i18n( "Your response as <b>%1</b> is requested", role );
01350 }
01351 } else {
01352 if ( role.isEmpty() ) {
01353 return i18n( "No response is necessary" );
01354 } else {
01355 return i18n( "No response as <b>%1</b> is necessary", role );
01356 }
01357 }
01358 }
01359
01360 static QString myStatusStr( Incidence::Ptr incidence )
01361 {
01362 QString ret;
01363 Attendee::Ptr a = findMyAttendee( incidence );
01364 if ( a &&
01365 a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
01366 ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)",
01367 Stringify::attendeeStatus( a->status() ) );
01368 }
01369 return ret;
01370 }
01371
01372 static QString invitationNote( const QString &title, const QString ¬e,
01373 const QString &tag, const QString &color )
01374 {
01375 QString noteStr;
01376 if ( !note.isEmpty() ) {
01377 noteStr += "<table border=\"0\" style=\"margin-top:4px;\">";
01378 noteStr += "<tr><center><td>";
01379 if ( !color.isEmpty() ) {
01380 noteStr += "<font color=\"" + color + "\">";
01381 }
01382 if ( !title.isEmpty() ) {
01383 if ( !tag.isEmpty() ) {
01384 noteStr += htmlAddTag( tag, title );
01385 } else {
01386 noteStr += title;
01387 }
01388 }
01389 noteStr += " " + note;
01390 if ( !color.isEmpty() ) {
01391 noteStr += "</font>";
01392 }
01393 noteStr += "</td></center></tr>";
01394 noteStr += "</table>";
01395 }
01396 return noteStr;
01397 }
01398
01399 static QString invitationPerson( const QString &email, const QString &name, const QString &uid,
01400 const QString &comment )
01401 {
01402 QPair<QString, QString> s = searchNameAndUid( email, name, uid );
01403 const QString printName = s.first;
01404 const QString printUid = s.second;
01405
01406 QString personString;
01407
01408 if ( !printUid.isEmpty() ) {
01409 personString = htmlAddUidLink( email, printName, printUid );
01410 } else {
01411
01412 personString = ( printName.isEmpty() ? email : printName );
01413 }
01414 if ( !comment.isEmpty() ) {
01415 personString = i18nc( "name (comment)", "%1 (%2)", personString, comment );
01416 }
01417 personString += '\n';
01418
01419
01420 if ( !email.isEmpty() ) {
01421 personString += " " + htmlAddMailtoLink( email, printName );
01422 }
01423 personString += '\n';
01424
01425 return personString;
01426 }
01427
01428 static QString invitationDetailsIncidence( const Incidence::Ptr &incidence, bool noHtmlMode )
01429 {
01430
01431
01432
01433
01434 QString html;
01435 QString descr;
01436 QStringList comments;
01437
01438 if ( incidence->comments().isEmpty() ) {
01439 if ( !incidence->description().isEmpty() ) {
01440
01441 if ( !incidence->descriptionIsRich() ) {
01442 comments << string2HTML( incidence->description() );
01443 } else {
01444 comments << incidence->richDescription();
01445 if ( noHtmlMode ) {
01446 comments[0] = cleanHtml( comments[0] );
01447 }
01448 comments[0] = htmlAddTag( "p", comments[0] );
01449 }
01450 }
01451
01452 } else {
01453
01454 foreach ( const QString &c, incidence->comments() ) {
01455 if ( !c.isEmpty() ) {
01456
01457 if ( !Qt::mightBeRichText( c ) ) {
01458 comments << string2HTML( c );
01459 } else {
01460 if ( noHtmlMode ) {
01461 comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) );
01462 } else {
01463 comments << c;
01464 }
01465 }
01466 }
01467 }
01468 if ( !incidence->description().isEmpty() ) {
01469
01470 if ( !incidence->descriptionIsRich() ) {
01471 descr = string2HTML( incidence->description() );
01472 } else {
01473 descr = incidence->richDescription();
01474 if ( noHtmlMode ) {
01475 descr = cleanHtml( descr );
01476 }
01477 descr = htmlAddTag( "p", descr );
01478 }
01479 }
01480 }
01481
01482 if( !descr.isEmpty() ) {
01483 html += "<p>";
01484 html += "<table border=\"0\" style=\"margin-top:4px;\">";
01485 html += "<tr><td><center>" +
01486 htmlAddTag( "u", i18n( "Description:" ) ) +
01487 "</center></td></tr>";
01488 html += "<tr><td>" + descr + "</td></tr>";
01489 html += "</table>";
01490 }
01491
01492 if ( !comments.isEmpty() ) {
01493 html += "<p>";
01494 html += "<table border=\"0\" style=\"margin-top:4px;\">";
01495 html += "<tr><td><center>" +
01496 htmlAddTag( "u", i18n( "Comments:" ) ) +
01497 "</center></td></tr>";
01498 html += "<tr><td>";
01499 if ( comments.count() > 1 ) {
01500 html += "<ul>";
01501 for ( int i=0; i < comments.count(); ++i ) {
01502 html += "<li>" + comments[i] + "</li>";
01503 }
01504 html += "</ul>";
01505 } else {
01506 html += comments[0];
01507 }
01508 html += "</td></tr>";
01509 html += "</table>";
01510 }
01511 return html;
01512 }
01513
01514 static QString invitationDetailsEvent( const Event::Ptr &event, bool noHtmlMode,
01515 KDateTime::Spec spec )
01516 {
01517
01518 if ( !event ) {
01519 return QString();
01520 }
01521
01522 QString html = htmlInvitationDetailsBegin();
01523 html += htmlInvitationDetailsTableBegin();
01524
01525
01526 html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
01527 html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
01528
01529
01530 if ( event->dtStart().date() == event->dtEnd().date() ) {
01531 html += htmlRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
01532 if ( !event->allDay() ) {
01533 html += htmlRow( i18n( "Time:" ),
01534 timeToString( event->dtStart(), true, spec ) +
01535 " - " +
01536 timeToString( event->dtEnd(), true, spec ) );
01537 }
01538 } else {
01539 html += htmlRow( i18nc( "starting date", "From:" ),
01540 dateToString( event->dtStart(), false, spec ) );
01541 if ( !event->allDay() ) {
01542 html += htmlRow( i18nc( "starting time", "At:" ),
01543 timeToString( event->dtStart(), true, spec ) );
01544 }
01545 if ( event->hasEndDate() ) {
01546 html += htmlRow( i18nc( "ending date", "To:" ),
01547 dateToString( event->dtEnd(), false, spec ) );
01548 if ( !event->allDay() ) {
01549 html += htmlRow( i18nc( "ending time", "At:" ),
01550 timeToString( event->dtEnd(), true, spec ) );
01551 }
01552 } else {
01553 html += htmlRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) );
01554 }
01555 }
01556
01557
01558 html += htmlRow( i18n( "Duration:" ), durationString( event ) );
01559
01560
01561 if ( event->recurs() ) {
01562 html += htmlRow( i18n( "Recurrence:" ), recurrenceString( event ) );
01563 }
01564
01565 html += htmlInvitationDetailsTableEnd();
01566 html += invitationDetailsIncidence( event, noHtmlMode );
01567 html += htmlInvitationDetailsEnd();
01568
01569 return html;
01570 }
01571
01572 static QString invitationDetailsEvent( const Event::Ptr &event, const Event::Ptr &oldevent,
01573 const ScheduleMessage::Ptr message, bool noHtmlMode,
01574 KDateTime::Spec spec )
01575 {
01576 if ( !oldevent ) {
01577 return invitationDetailsEvent( event, noHtmlMode, spec );
01578 }
01579
01580 QString html;
01581
01582
01583 if ( message->method() == iTIPDeclineCounter ) {
01584 html += "<br>";
01585 html += invitationNote( QString(),
01586 i18n( "Please respond again to the original proposal." ),
01587 QString(), noteColor() );
01588 }
01589
01590 html += htmlInvitationDetailsBegin();
01591 html += htmlInvitationDetailsTableBegin();
01592
01593 html += htmlRow( i18n( "What:" ),
01594 invitationSummary( event, noHtmlMode ),
01595 invitationSummary( oldevent, noHtmlMode ) );
01596
01597 html += htmlRow( i18n( "Where:" ),
01598 invitationLocation( event, noHtmlMode ),
01599 invitationLocation( oldevent, noHtmlMode ) );
01600
01601
01602 if ( event->dtStart().date() == event->dtEnd().date() ) {
01603 html += htmlRow( i18n( "Date:" ),
01604 dateToString( event->dtStart(), false ),
01605 dateToString( oldevent->dtStart(), false ) );
01606 QString spanStr, oldspanStr;
01607 if ( !event->allDay() ) {
01608 spanStr = timeToString( event->dtStart(), true ) +
01609 " - " +
01610 timeToString( event->dtEnd(), true );
01611 }
01612 if ( !oldevent->allDay() ) {
01613 oldspanStr = timeToString( oldevent->dtStart(), true ) +
01614 " - " +
01615 timeToString( oldevent->dtEnd(), true );
01616 }
01617 html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
01618 } else {
01619 html += htmlRow( i18nc( "Starting date of an event", "From:" ),
01620 dateToString( event->dtStart(), false ),
01621 dateToString( oldevent->dtStart(), false ) );
01622 QString startStr, oldstartStr;
01623 if ( !event->allDay() ) {
01624 startStr = timeToString( event->dtStart(), true );
01625 }
01626 if ( !oldevent->allDay() ) {
01627 oldstartStr = timeToString( oldevent->dtStart(), true );
01628 }
01629 html += htmlRow( i18nc( "Starting time of an event", "At:" ), startStr, oldstartStr );
01630 if ( event->hasEndDate() ) {
01631 html += htmlRow( i18nc( "Ending date of an event", "To:" ),
01632 dateToString( event->dtEnd(), false ),
01633 dateToString( oldevent->dtEnd(), false ) );
01634 QString endStr, oldendStr;
01635 if ( !event->allDay() ) {
01636 endStr = timeToString( event->dtEnd(), true );
01637 }
01638 if ( !oldevent->allDay() ) {
01639 oldendStr = timeToString( oldevent->dtEnd(), true );
01640 }
01641 html += htmlRow( i18nc( "Starting time of an event", "At:" ), endStr, oldendStr );
01642 } else {
01643 QString endStr = i18n( "no end date specified" );
01644 QString oldendStr;
01645 if ( !oldevent->hasEndDate() ) {
01646 oldendStr = i18n( "no end date specified" );
01647 } else {
01648 oldendStr = dateTimeToString( oldevent->dtEnd(), oldevent->allDay(), false );
01649 }
01650 html += htmlRow( i18nc( "Ending date of an event", "To:" ), endStr, oldendStr );
01651 }
01652 }
01653
01654 html += htmlRow( i18n( "Duration:" ), durationString( event ), durationString( oldevent ) );
01655
01656 QString recurStr, oldrecurStr;
01657 if ( event->recurs() || oldevent->recurs() ) {
01658 recurStr = recurrenceString( event );
01659 oldrecurStr = recurrenceString( oldevent );
01660 }
01661 html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01662
01663 html += htmlInvitationDetailsTableEnd();
01664 html += invitationDetailsIncidence( event, noHtmlMode );
01665 html += htmlInvitationDetailsEnd();
01666
01667 return html;
01668 }
01669
01670 static QString invitationDetailsTodo( const Todo::Ptr &todo, bool noHtmlMode,
01671 KDateTime::Spec spec )
01672 {
01673
01674 if ( !todo ) {
01675 return QString();
01676 }
01677
01678 QString html = htmlInvitationDetailsBegin();
01679 html += htmlInvitationDetailsTableBegin();
01680
01681
01682 html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
01683 html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
01684
01685 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01686 html += htmlRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
01687 if ( !todo->allDay() ) {
01688 html += htmlRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) );
01689 }
01690 }
01691 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01692 html += htmlRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
01693 if ( !todo->allDay() ) {
01694 html += htmlRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) );
01695 }
01696 } else {
01697 html += htmlRow( i18n( "Due Date:" ), i18nc( "Due Date: None", "None" ) );
01698 }
01699
01700
01701 html += htmlRow( i18n( "Duration:" ), durationString( todo ) );
01702
01703
01704 if ( todo->percentComplete() > 0 ) {
01705 html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) );
01706 }
01707
01708
01709 if ( todo->recurs() ) {
01710 html += htmlRow( i18n( "Recurrence:" ), recurrenceString( todo ) );
01711 }
01712
01713 html += htmlInvitationDetailsTableEnd();
01714 html += invitationDetailsIncidence( todo, noHtmlMode );
01715 html += htmlInvitationDetailsEnd();
01716
01717 return html;
01718 }
01719
01720 static QString invitationDetailsTodo( const Todo::Ptr &todo, const Todo::Ptr &oldtodo,
01721 const ScheduleMessage::Ptr message, bool noHtmlMode,
01722 KDateTime::Spec spec )
01723 {
01724 if ( !oldtodo ) {
01725 return invitationDetailsTodo( todo, noHtmlMode, spec );
01726 }
01727
01728 QString html;
01729
01730
01731 if ( message->method() == iTIPDeclineCounter ) {
01732 html += "<br>";
01733 html += invitationNote( QString(),
01734 i18n( "Please respond again to the original proposal." ),
01735 QString(), noteColor() );
01736 }
01737
01738 html += htmlInvitationDetailsBegin();
01739 html += htmlInvitationDetailsTableBegin();
01740
01741 html += htmlRow( i18n( "What:" ),
01742 invitationSummary( todo, noHtmlMode ),
01743 invitationSummary( todo, noHtmlMode ) );
01744
01745 html += htmlRow( i18n( "Where:" ),
01746 invitationLocation( todo, noHtmlMode ),
01747 invitationLocation( oldtodo, noHtmlMode ) );
01748
01749 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
01750 html += htmlRow( i18n( "Start Date:" ),
01751 dateToString( todo->dtStart(), false ),
01752 dateToString( oldtodo->dtStart(), false ) );
01753 QString startTimeStr, oldstartTimeStr;
01754 if ( !todo->allDay() || !oldtodo->allDay() ) {
01755 startTimeStr = todo->allDay() ?
01756 i18n( "All day" ) : timeToString( todo->dtStart(), false );
01757 oldstartTimeStr = oldtodo->allDay() ?
01758 i18n( "All day" ) : timeToString( oldtodo->dtStart(), false );
01759 }
01760 html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
01761 }
01762 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
01763 html += htmlRow( i18n( "Due Date:" ),
01764 dateToString( todo->dtDue(), false ),
01765 dateToString( oldtodo->dtDue(), false ) );
01766 QString endTimeStr, oldendTimeStr;
01767 if ( !todo->allDay() || !oldtodo->allDay() ) {
01768 endTimeStr = todo->allDay() ?
01769 i18n( "All day" ) : timeToString( todo->dtDue(), false );
01770 oldendTimeStr = oldtodo->allDay() ?
01771 i18n( "All day" ) : timeToString( oldtodo->dtDue(), false );
01772 }
01773 html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
01774 } else {
01775 QString dueStr = i18nc( "Due Date: None", "None" );
01776 QString olddueStr;
01777 if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
01778 olddueStr = i18nc( "Due Date: None", "None" );
01779 } else {
01780 olddueStr = dateTimeToString( oldtodo->dtDue(), oldtodo->allDay(), false );
01781 }
01782 html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
01783 }
01784
01785 html += htmlRow( i18n( "Duration:" ), durationString( todo ), durationString( oldtodo ) );
01786
01787 QString completionStr, oldcompletionStr;
01788 if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) {
01789 completionStr = i18n( "%1%", todo->percentComplete() );
01790 oldcompletionStr = i18n( "%1%", oldtodo->percentComplete() );
01791 }
01792 html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr );
01793
01794 QString recurStr, oldrecurStr;
01795 if ( todo->recurs() || oldtodo->recurs() ) {
01796 recurStr = recurrenceString( todo );
01797 oldrecurStr = recurrenceString( oldtodo );
01798 }
01799 html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
01800
01801 html += htmlInvitationDetailsTableEnd();
01802 html += invitationDetailsIncidence( todo, noHtmlMode );
01803
01804 html += htmlInvitationDetailsEnd();
01805
01806 return html;
01807 }
01808
01809 static QString invitationDetailsJournal( const Journal::Ptr &journal, bool noHtmlMode,
01810 KDateTime::Spec spec )
01811 {
01812 if ( !journal ) {
01813 return QString();
01814 }
01815
01816 QString html = htmlInvitationDetailsBegin();
01817 html += htmlInvitationDetailsTableBegin();
01818
01819 html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
01820 html += htmlRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
01821
01822 html += htmlInvitationDetailsTableEnd();
01823 html += invitationDetailsIncidence( journal, noHtmlMode );
01824 html += htmlInvitationDetailsEnd();
01825
01826 return html;
01827 }
01828
01829 static QString invitationDetailsJournal( const Journal::Ptr &journal,
01830 const Journal::Ptr &oldjournal,
01831 bool noHtmlMode, KDateTime::Spec spec )
01832 {
01833 if ( !oldjournal ) {
01834 return invitationDetailsJournal( journal, noHtmlMode, spec );
01835 }
01836
01837 QString html = htmlInvitationDetailsBegin();
01838 html += htmlInvitationDetailsTableBegin();
01839
01840 html += htmlRow( i18n( "What:" ),
01841 invitationSummary( journal, noHtmlMode ),
01842 invitationSummary( oldjournal, noHtmlMode ) );
01843
01844 html += htmlRow( i18n( "Date:" ),
01845 dateToString( journal->dtStart(), false, spec ),
01846 dateToString( oldjournal->dtStart(), false, spec ) );
01847
01848 html += htmlInvitationDetailsTableEnd();
01849 html += invitationDetailsIncidence( journal, noHtmlMode );
01850 html += htmlInvitationDetailsEnd();
01851
01852 return html;
01853 }
01854
01855 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, bool noHtmlMode,
01856 KDateTime::Spec spec )
01857 {
01858 Q_UNUSED( noHtmlMode );
01859
01860 if ( !fb ) {
01861 return QString();
01862 }
01863
01864 QString html = htmlInvitationDetailsTableBegin();
01865
01866 html += htmlRow( i18n( "Person:" ), fb->organizer()->fullName() );
01867 html += htmlRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
01868 html += htmlRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
01869
01870 html += "<tr><td colspan=2><hr></td></tr>\n";
01871 html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
01872
01873 Period::List periods = fb->busyPeriods();
01874 Period::List::iterator it;
01875 for ( it = periods.begin(); it != periods.end(); ++it ) {
01876 Period per = *it;
01877 if ( per.hasDuration() ) {
01878 int dur = per.duration().asSeconds();
01879 QString cont;
01880 if ( dur >= 3600 ) {
01881 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
01882 dur %= 3600;
01883 }
01884 if ( dur >= 60 ) {
01885 cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
01886 dur %= 60;
01887 }
01888 if ( dur > 0 ) {
01889 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
01890 }
01891 html += htmlRow( QString(),
01892 i18nc( "startDate for duration", "%1 for %2",
01893 KGlobal::locale()->formatDateTime(
01894 per.start().dateTime(), KLocale::LongDate ),
01895 cont ) );
01896 } else {
01897 QString cont;
01898 if ( per.start().date() == per.end().date() ) {
01899 cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
01900 KGlobal::locale()->formatDate( per.start().date() ),
01901 KGlobal::locale()->formatTime( per.start().time() ),
01902 KGlobal::locale()->formatTime( per.end().time() ) );
01903 } else {
01904 cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
01905 KGlobal::locale()->formatDateTime(
01906 per.start().dateTime(), KLocale::LongDate ),
01907 KGlobal::locale()->formatDateTime(
01908 per.end().dateTime(), KLocale::LongDate ) );
01909 }
01910
01911 html += htmlRow( QString(), cont );
01912 }
01913 }
01914
01915 html += htmlInvitationDetailsTableEnd();
01916 return html;
01917 }
01918
01919 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, const FreeBusy::Ptr &oldfb,
01920 bool noHtmlMode, KDateTime::Spec spec )
01921 {
01922 Q_UNUSED( oldfb );
01923 return invitationDetailsFreeBusy( fb, noHtmlMode, spec );
01924 }
01925
01926 static bool replyMeansCounter( const Incidence::Ptr &incidence )
01927 {
01928 Q_UNUSED( incidence );
01929 return false;
01944 }
01945
01946 static QString invitationHeaderEvent( const Event::Ptr &event,
01947 const Incidence::Ptr &existingIncidence,
01948 ScheduleMessage::Ptr msg, const QString &sender )
01949 {
01950 if ( !msg || !event ) {
01951 return QString();
01952 }
01953
01954 switch ( msg->method() ) {
01955 case iTIPPublish:
01956 return i18n( "This invitation has been published" );
01957 case iTIPRequest:
01958 if ( existingIncidence && event->revision() > 0 ) {
01959 QString orgStr = organizerName( event, sender );
01960 if ( senderIsOrganizer( event, sender ) ) {
01961 return i18n( "This invitation has been updated by the organizer %1", orgStr );
01962 } else {
01963 return i18n( "This invitation has been updated by %1 as a representative of %2",
01964 sender, orgStr );
01965 }
01966 }
01967 if ( iamOrganizer( event ) ) {
01968 return i18n( "I created this invitation" );
01969 } else {
01970 QString orgStr = organizerName( event, sender );
01971 if ( senderIsOrganizer( event, sender ) ) {
01972 return i18n( "You received an invitation from %1", orgStr );
01973 } else {
01974 return i18n( "You received an invitation from %1 as a representative of %2",
01975 sender, orgStr );
01976 }
01977 }
01978 case iTIPRefresh:
01979 return i18n( "This invitation was refreshed" );
01980 case iTIPCancel:
01981 if ( iamOrganizer( event ) ) {
01982 return i18n( "This invitation has been canceled" );
01983 } else {
01984 return i18n( "The organizer has removed you from the invitation" );
01985 }
01986 case iTIPAdd:
01987 return i18n( "Addition to the invitation" );
01988 case iTIPReply:
01989 {
01990 if ( replyMeansCounter( event ) ) {
01991 return i18n( "%1 makes this counter proposal", firstAttendeeName( event, sender ) );
01992 }
01993
01994 Attendee::List attendees = event->attendees();
01995 if( attendees.count() == 0 ) {
01996 kDebug() << "No attendees in the iCal reply!";
01997 return QString();
01998 }
01999 if ( attendees.count() != 1 ) {
02000 kDebug() << "Warning: attendeecount in the reply should be 1"
02001 << "but is" << attendees.count();
02002 }
02003 QString attendeeName = firstAttendeeName( event, sender );
02004
02005 QString delegatorName, dummy;
02006 Attendee::Ptr attendee = *attendees.begin();
02007 KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
02008 if ( delegatorName.isEmpty() ) {
02009 delegatorName = attendee->delegator();
02010 }
02011
02012 switch( attendee->status() ) {
02013 case Attendee::NeedsAction:
02014 return i18n( "%1 indicates this invitation still needs some action", attendeeName );
02015 case Attendee::Accepted:
02016 if ( event->revision() > 0 ) {
02017 if ( !sender.isEmpty() ) {
02018 return i18n( "This invitation has been updated by attendee %1", sender );
02019 } else {
02020 return i18n( "This invitation has been updated by an attendee" );
02021 }
02022 } else {
02023 if ( delegatorName.isEmpty() ) {
02024 return i18n( "%1 accepts this invitation", attendeeName );
02025 } else {
02026 return i18n( "%1 accepts this invitation on behalf of %2",
02027 attendeeName, delegatorName );
02028 }
02029 }
02030 case Attendee::Tentative:
02031 if ( delegatorName.isEmpty() ) {
02032 return i18n( "%1 tentatively accepts this invitation", attendeeName );
02033 } else {
02034 return i18n( "%1 tentatively accepts this invitation on behalf of %2",
02035 attendeeName, delegatorName );
02036 }
02037 case Attendee::Declined:
02038 if ( delegatorName.isEmpty() ) {
02039 return i18n( "%1 declines this invitation", attendeeName );
02040 } else {
02041 return i18n( "%1 declines this invitation on behalf of %2",
02042 attendeeName, delegatorName );
02043 }
02044 case Attendee::Delegated:
02045 {
02046 QString delegate, dummy;
02047 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02048 if ( delegate.isEmpty() ) {
02049 delegate = attendee->delegate();
02050 }
02051 if ( !delegate.isEmpty() ) {
02052 return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
02053 } else {
02054 return i18n( "%1 has delegated this invitation", attendeeName );
02055 }
02056 }
02057 case Attendee::Completed:
02058 return i18n( "This invitation is now completed" );
02059 case Attendee::InProcess:
02060 return i18n( "%1 is still processing the invitation", attendeeName );
02061 case Attendee::None:
02062 return i18n( "Unknown response to this invitation" );
02063 }
02064 break;
02065 }
02066 case iTIPCounter:
02067 return i18n( "%1 makes this counter proposal",
02068 firstAttendeeName( event, i18n( "Sender" ) ) );
02069
02070 case iTIPDeclineCounter:
02071 {
02072 QString orgStr = organizerName( event, sender );
02073 if ( senderIsOrganizer( event, sender ) ) {
02074 return i18n( "%1 declines your counter proposal", orgStr );
02075 } else {
02076 return i18n( "%1 declines your counter proposal on behalf of %2", sender, orgStr );
02077 }
02078 }
02079
02080 case iTIPNoMethod:
02081 return i18n( "Error: Event iTIP message with unknown method" );
02082 }
02083 kError() << "encountered an iTIP method that we do not support";
02084 return QString();
02085 }
02086
02087 static QString invitationHeaderTodo( const Todo::Ptr &todo,
02088 const Incidence::Ptr &existingIncidence,
02089 ScheduleMessage::Ptr msg, const QString &sender )
02090 {
02091 if ( !msg || !todo ) {
02092 return QString();
02093 }
02094
02095 switch ( msg->method() ) {
02096 case iTIPPublish:
02097 return i18n( "This to-do has been published" );
02098 case iTIPRequest:
02099 if ( existingIncidence && todo->revision() > 0 ) {
02100 QString orgStr = organizerName( todo, sender );
02101 if ( senderIsOrganizer( todo, sender ) ) {
02102 return i18n( "This to-do has been updated by the organizer %1", orgStr );
02103 } else {
02104 return i18n( "This to-do has been updated by %1 as a representative of %2",
02105 sender, orgStr );
02106 }
02107 } else {
02108 if ( iamOrganizer( todo ) ) {
02109 return i18n( "I created this to-do" );
02110 } else {
02111 QString orgStr = organizerName( todo, sender );
02112 if ( senderIsOrganizer( todo, sender ) ) {
02113 return i18n( "You have been assigned this to-do by %1", orgStr );
02114 } else {
02115 return i18n( "You have been assigned this to-do by %1 as a representative of %2",
02116 sender, orgStr );
02117 }
02118 }
02119 }
02120 case iTIPRefresh:
02121 return i18n( "This to-do was refreshed" );
02122 case iTIPCancel:
02123 if ( iamOrganizer( todo ) ) {
02124 return i18n( "This to-do was canceled" );
02125 } else {
02126 return i18n( "The organizer has removed you from this to-do" );
02127 }
02128 case iTIPAdd:
02129 return i18n( "Addition to the to-do" );
02130 case iTIPReply:
02131 {
02132 if ( replyMeansCounter( todo ) ) {
02133 return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02134 }
02135
02136 Attendee::List attendees = todo->attendees();
02137 if ( attendees.count() == 0 ) {
02138 kDebug() << "No attendees in the iCal reply!";
02139 return QString();
02140 }
02141 if ( attendees.count() != 1 ) {
02142 kDebug() << "Warning: attendeecount in the reply should be 1"
02143 << "but is" << attendees.count();
02144 }
02145 QString attendeeName = firstAttendeeName( todo, sender );
02146
02147 QString delegatorName, dummy;
02148 Attendee::Ptr attendee = *attendees.begin();
02149 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName );
02150 if ( delegatorName.isEmpty() ) {
02151 delegatorName = attendee->delegator();
02152 }
02153
02154 switch( attendee->status() ) {
02155 case Attendee::NeedsAction:
02156 return i18n( "%1 indicates this to-do assignment still needs some action",
02157 attendeeName );
02158 case Attendee::Accepted:
02159 if ( todo->revision() > 0 ) {
02160 if ( !sender.isEmpty() ) {
02161 if ( todo->isCompleted() ) {
02162 return i18n( "This to-do has been completed by assignee %1", sender );
02163 } else {
02164 return i18n( "This to-do has been updated by assignee %1", sender );
02165 }
02166 } else {
02167 if ( todo->isCompleted() ) {
02168 return i18n( "This to-do has been completed by an assignee" );
02169 } else {
02170 return i18n( "This to-do has been updated by an assignee" );
02171 }
02172 }
02173 } else {
02174 if ( delegatorName.isEmpty() ) {
02175 return i18n( "%1 accepts this to-do", attendeeName );
02176 } else {
02177 return i18n( "%1 accepts this to-do on behalf of %2",
02178 attendeeName, delegatorName );
02179 }
02180 }
02181 case Attendee::Tentative:
02182 if ( delegatorName.isEmpty() ) {
02183 return i18n( "%1 tentatively accepts this to-do", attendeeName );
02184 } else {
02185 return i18n( "%1 tentatively accepts this to-do on behalf of %2",
02186 attendeeName, delegatorName );
02187 }
02188 case Attendee::Declined:
02189 if ( delegatorName.isEmpty() ) {
02190 return i18n( "%1 declines this to-do", attendeeName );
02191 } else {
02192 return i18n( "%1 declines this to-do on behalf of %2",
02193 attendeeName, delegatorName );
02194 }
02195 case Attendee::Delegated:
02196 {
02197 QString delegate, dummy;
02198 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
02199 if ( delegate.isEmpty() ) {
02200 delegate = attendee->delegate();
02201 }
02202 if ( !delegate.isEmpty() ) {
02203 return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate );
02204 } else {
02205 return i18n( "%1 has delegated this to-do", attendeeName );
02206 }
02207 }
02208 case Attendee::Completed:
02209 return i18n( "The request for this to-do is now completed" );
02210 case Attendee::InProcess:
02211 return i18n( "%1 is still processing the to-do", attendeeName );
02212 case Attendee::None:
02213 return i18n( "Unknown response to this to-do" );
02214 }
02215 break;
02216 }
02217 case iTIPCounter:
02218 return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
02219
02220 case iTIPDeclineCounter:
02221 {
02222 QString orgStr = organizerName( todo, sender );
02223 if ( senderIsOrganizer( todo, sender ) ) {
02224 return i18n( "%1 declines the counter proposal", orgStr );
02225 } else {
02226 return i18n( "%1 declines the counter proposal on behalf of %2", sender, orgStr );
02227 }
02228 }
02229
02230 case iTIPNoMethod:
02231 return i18n( "Error: To-do iTIP message with unknown method" );
02232 }
02233 kError() << "encountered an iTIP method that we do not support";
02234 return QString();
02235 }
02236
02237 static QString invitationHeaderJournal( const Journal::Ptr &journal,
02238 ScheduleMessage::Ptr msg )
02239 {
02240 if ( !msg || !journal ) {
02241 return QString();
02242 }
02243
02244 switch ( msg->method() ) {
02245 case iTIPPublish:
02246 return i18n( "This journal has been published" );
02247 case iTIPRequest:
02248 return i18n( "You have been assigned this journal" );
02249 case iTIPRefresh:
02250 return i18n( "This journal was refreshed" );
02251 case iTIPCancel:
02252 return i18n( "This journal was canceled" );
02253 case iTIPAdd:
02254 return i18n( "Addition to the journal" );
02255 case iTIPReply:
02256 {
02257 if ( replyMeansCounter( journal ) ) {
02258 return i18n( "Sender makes this counter proposal" );
02259 }
02260
02261 Attendee::List attendees = journal->attendees();
02262 if ( attendees.count() == 0 ) {
02263 kDebug() << "No attendees in the iCal reply!";
02264 return QString();
02265 }
02266 if( attendees.count() != 1 ) {
02267 kDebug() << "Warning: attendeecount in the reply should be 1 "
02268 << "but is " << attendees.count();
02269 }
02270 Attendee::Ptr attendee = *attendees.begin();
02271
02272 switch( attendee->status() ) {
02273 case Attendee::NeedsAction:
02274 return i18n( "Sender indicates this journal assignment still needs some action" );
02275 case Attendee::Accepted:
02276 return i18n( "Sender accepts this journal" );
02277 case Attendee::Tentative:
02278 return i18n( "Sender tentatively accepts this journal" );
02279 case Attendee::Declined:
02280 return i18n( "Sender declines this journal" );
02281 case Attendee::Delegated:
02282 return i18n( "Sender has delegated this request for the journal" );
02283 case Attendee::Completed:
02284 return i18n( "The request for this journal is now completed" );
02285 case Attendee::InProcess:
02286 return i18n( "Sender is still processing the invitation" );
02287 case Attendee::None:
02288 return i18n( "Unknown response to this journal" );
02289 }
02290 break;
02291 }
02292 case iTIPCounter:
02293 return i18n( "Sender makes this counter proposal" );
02294 case iTIPDeclineCounter:
02295 return i18n( "Sender declines the counter proposal" );
02296 case iTIPNoMethod:
02297 return i18n( "Error: Journal iTIP message with unknown method" );
02298 }
02299 kError() << "encountered an iTIP method that we do not support";
02300 return QString();
02301 }
02302
02303 static QString invitationHeaderFreeBusy( const FreeBusy::Ptr &fb,
02304 ScheduleMessage::Ptr msg )
02305 {
02306 if ( !msg || !fb ) {
02307 return QString();
02308 }
02309
02310 switch ( msg->method() ) {
02311 case iTIPPublish:
02312 return i18n( "This free/busy list has been published" );
02313 case iTIPRequest:
02314 return i18n( "The free/busy list has been requested" );
02315 case iTIPRefresh:
02316 return i18n( "This free/busy list was refreshed" );
02317 case iTIPCancel:
02318 return i18n( "This free/busy list was canceled" );
02319 case iTIPAdd:
02320 return i18n( "Addition to the free/busy list" );
02321 case iTIPReply:
02322 return i18n( "Reply to the free/busy list" );
02323 case iTIPCounter:
02324 return i18n( "Sender makes this counter proposal" );
02325 case iTIPDeclineCounter:
02326 return i18n( "Sender declines the counter proposal" );
02327 case iTIPNoMethod:
02328 return i18n( "Error: Free/Busy iTIP message with unknown method" );
02329 }
02330 kError() << "encountered an iTIP method that we do not support";
02331 return QString();
02332 }
02333
02334
02335 static QString invitationAttendeeList( const Incidence::Ptr &incidence )
02336 {
02337 QString tmpStr;
02338 if ( !incidence ) {
02339 return tmpStr;
02340 }
02341 if ( incidence->type() == Incidence::TypeTodo ) {
02342 tmpStr += i18n( "Assignees" );
02343 } else {
02344 tmpStr += i18n( "Invitation List" );
02345 }
02346
02347 int count=0;
02348 Attendee::List attendees = incidence->attendees();
02349 if ( !attendees.isEmpty() ) {
02350 QStringList comments;
02351 Attendee::List::ConstIterator it;
02352 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02353 Attendee::Ptr a = *it;
02354 if ( !iamAttendee( a ) ) {
02355 count++;
02356 if ( count == 1 ) {
02357 tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02358 }
02359 tmpStr += "<tr>";
02360 tmpStr += "<td>";
02361 comments.clear();
02362 if ( attendeeIsOrganizer( incidence, a ) ) {
02363 comments << i18n( "organizer" );
02364 }
02365 if ( !a->delegator().isEmpty() ) {
02366 comments << i18n( " (delegated by %1)", a->delegator() );
02367 }
02368 if ( !a->delegate().isEmpty() ) {
02369 comments << i18n( " (delegated to %1)", a->delegate() );
02370 }
02371 tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02372 tmpStr += "</td>";
02373 tmpStr += "</tr>";
02374 }
02375 }
02376 }
02377 if ( count ) {
02378 tmpStr += "</table>";
02379 } else {
02380 tmpStr.clear();
02381 }
02382
02383 return tmpStr;
02384 }
02385
02386 static QString invitationRsvpList( const Incidence::Ptr &incidence, const Attendee::Ptr &sender )
02387 {
02388 QString tmpStr;
02389 if ( !incidence ) {
02390 return tmpStr;
02391 }
02392 if ( incidence->type() == Incidence::TypeTodo ) {
02393 tmpStr += i18n( "Assignees" );
02394 } else {
02395 tmpStr += i18n( "Invitation List" );
02396 }
02397
02398 int count=0;
02399 Attendee::List attendees = incidence->attendees();
02400 if ( !attendees.isEmpty() ) {
02401 QStringList comments;
02402 Attendee::List::ConstIterator it;
02403 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
02404 Attendee::Ptr a = *it;
02405 if ( !attendeeIsOrganizer( incidence, a ) ) {
02406 QString statusStr = Stringify::attendeeStatus( a->status () );
02407 if ( sender && ( a->email() == sender->email() ) ) {
02408
02409
02410 if ( a->status() != sender->status() ) {
02411 statusStr = i18n( "%1 (<i>unrecorded</i>)",
02412 Stringify::attendeeStatus( sender->status() ) );
02413 }
02414 a = sender;
02415 }
02416 count++;
02417 if ( count == 1 ) {
02418 tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
02419 }
02420 tmpStr += "<tr>";
02421 tmpStr += "<td>";
02422 comments.clear();
02423 if ( iamAttendee( a ) ) {
02424 comments << i18n( "myself" );
02425 }
02426 if ( !a->delegator().isEmpty() ) {
02427 comments << i18n( " (delegated by %1)", a->delegator() );
02428 }
02429 if ( !a->delegate().isEmpty() ) {
02430 comments << i18n( " (delegated to %1)", a->delegate() );
02431 }
02432 tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
02433 tmpStr += "</td>";
02434 tmpStr += "<td>" + statusStr + "</td>";
02435 tmpStr += "</tr>";
02436 }
02437 }
02438 }
02439 if ( count ) {
02440 tmpStr += "</table>";
02441 } else {
02442 tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>";
02443 }
02444
02445 return tmpStr;
02446 }
02447
02448 static QString invitationAttachments( InvitationFormatterHelper *helper,
02449 const Incidence::Ptr &incidence )
02450 {
02451 QString tmpStr;
02452 if ( !incidence ) {
02453 return tmpStr;
02454 }
02455
02456 Attachment::List attachments = incidence->attachments();
02457 if ( !attachments.isEmpty() ) {
02458 tmpStr += i18n( "Attached Documents:" ) + "<ol>";
02459
02460 Attachment::List::ConstIterator it;
02461 for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
02462 Attachment::Ptr a = *it;
02463 tmpStr += "<li>";
02464
02465 KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
02466 const QString iconStr = ( mimeType ?
02467 mimeType->iconName( a->uri() ) :
02468 QString( "application-octet-stream" ) );
02469 const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
02470 if ( !iconPath.isEmpty() ) {
02471 tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
02472 }
02473 tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
02474 tmpStr += "</li>";
02475 }
02476 tmpStr += "</ol>";
02477 }
02478
02479 return tmpStr;
02480 }
02481
02482
02483 class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor
02484 {
02485 public:
02486 ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; }
02487 bool act( const IncidenceBase::Ptr &incidence,
02488 const Incidence::Ptr &existingIncidence,
02489 ScheduleMessage::Ptr msg, const QString &sender )
02490 {
02491 mExistingIncidence = existingIncidence;
02492 mMessage = msg;
02493 mSender = sender;
02494 return incidence->accept( *this, incidence );
02495 }
02496 QString result() const { return mResult; }
02497
02498 protected:
02499 QString mResult;
02500 Incidence::Ptr mExistingIncidence;
02501 ScheduleMessage::Ptr mMessage;
02502 QString mSender;
02503 };
02504
02505 class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor :
02506 public IncidenceFormatter::ScheduleMessageVisitor
02507 {
02508 protected:
02509 bool visit( Event::Ptr event )
02510 {
02511 mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
02512 return !mResult.isEmpty();
02513 }
02514 bool visit( Todo::Ptr todo )
02515 {
02516 mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
02517 return !mResult.isEmpty();
02518 }
02519 bool visit( Journal::Ptr journal )
02520 {
02521 mResult = invitationHeaderJournal( journal, mMessage );
02522 return !mResult.isEmpty();
02523 }
02524 bool visit( FreeBusy::Ptr fb )
02525 {
02526 mResult = invitationHeaderFreeBusy( fb, mMessage );
02527 return !mResult.isEmpty();
02528 }
02529 };
02530
02531 class KCalUtils::IncidenceFormatter::InvitationBodyVisitor
02532 : public IncidenceFormatter::ScheduleMessageVisitor
02533 {
02534 public:
02535 InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
02536 : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
02537
02538 protected:
02539 bool visit( Event::Ptr event )
02540 {
02541 Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>();
02542 mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec );
02543 return !mResult.isEmpty();
02544 }
02545 bool visit( Todo::Ptr todo )
02546 {
02547 Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>();
02548 mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec );
02549 return !mResult.isEmpty();
02550 }
02551 bool visit( Journal::Ptr journal )
02552 {
02553 Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>();
02554 mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec );
02555 return !mResult.isEmpty();
02556 }
02557 bool visit( FreeBusy::Ptr fb )
02558 {
02559 mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec );
02560 return !mResult.isEmpty();
02561 }
02562
02563 private:
02564 bool mNoHtmlMode;
02565 KDateTime::Spec mSpec;
02566 };
02567
02568
02569 InvitationFormatterHelper::InvitationFormatterHelper()
02570 : d( 0 )
02571 {
02572 }
02573
02574 InvitationFormatterHelper::~InvitationFormatterHelper()
02575 {
02576 }
02577
02578 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
02579 {
02580 return id;
02581 }
02582
02583
02584 class IncidenceFormatter::IncidenceCompareVisitor : public Visitor
02585 {
02586 public:
02587 IncidenceCompareVisitor() {}
02588 bool act( const IncidenceBase::Ptr &incidence,
02589 const Incidence::Ptr &existingIncidence )
02590 {
02591 if ( !existingIncidence ) {
02592 return false;
02593 }
02594 Incidence::Ptr inc = incidence.staticCast<Incidence>();
02595 if ( !inc || !existingIncidence ||
02596 inc->revision() <= existingIncidence->revision() ) {
02597 return false;
02598 }
02599 mExistingIncidence = existingIncidence;
02600 return incidence->accept( *this, incidence );
02601 }
02602
02603 QString result() const
02604 {
02605 if ( mChanges.isEmpty() ) {
02606 return QString();
02607 }
02608 QString html = "<div align=\"left\"><ul><li>";
02609 html += mChanges.join( "</li><li>" );
02610 html += "</li><ul></div>";
02611 return html;
02612 }
02613
02614 protected:
02615 bool visit( Event::Ptr event )
02616 {
02617 compareEvents( event, mExistingIncidence.dynamicCast<Event>() );
02618 compareIncidences( event, mExistingIncidence );
02619 return !mChanges.isEmpty();
02620 }
02621 bool visit( Todo::Ptr todo )
02622 {
02623 compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() );
02624 compareIncidences( todo, mExistingIncidence );
02625 return !mChanges.isEmpty();
02626 }
02627 bool visit( Journal::Ptr journal )
02628 {
02629 compareIncidences( journal, mExistingIncidence );
02630 return !mChanges.isEmpty();
02631 }
02632 bool visit( FreeBusy::Ptr fb )
02633 {
02634 Q_UNUSED( fb );
02635 return !mChanges.isEmpty();
02636 }
02637
02638 private:
02639 void compareEvents( const Event::Ptr &newEvent,
02640 const Event::Ptr &oldEvent )
02641 {
02642 if ( !oldEvent || !newEvent ) {
02643 return;
02644 }
02645 if ( oldEvent->dtStart() != newEvent->dtStart() ||
02646 oldEvent->allDay() != newEvent->allDay() ) {
02647 mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
02648 eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
02649 }
02650 if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
02651 oldEvent->allDay() != newEvent->allDay() ) {
02652 mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
02653 eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
02654 }
02655 }
02656
02657 void compareTodos( const Todo::Ptr &newTodo,
02658 const Todo::Ptr &oldTodo )
02659 {
02660 if ( !oldTodo || !newTodo ) {
02661 return;
02662 }
02663
02664 if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
02665 mChanges += i18n( "The to-do has been completed" );
02666 }
02667 if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
02668 mChanges += i18n( "The to-do is no longer completed" );
02669 }
02670 if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
02671 const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
02672 const QString newPer = i18n( "%1%", newTodo->percentComplete() );
02673 mChanges += i18n( "The task completed percentage has changed from %1 to %2",
02674 oldPer, newPer );
02675 }
02676
02677 if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
02678 mChanges += i18n( "A to-do starting time has been added" );
02679 }
02680 if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
02681 mChanges += i18n( "The to-do starting time has been removed" );
02682 }
02683 if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
02684 oldTodo->dtStart() != newTodo->dtStart() ) {
02685 mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
02686 dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
02687 dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
02688 }
02689
02690 if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
02691 mChanges += i18n( "A to-do due time has been added" );
02692 }
02693 if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
02694 mChanges += i18n( "The to-do due time has been removed" );
02695 }
02696 if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
02697 oldTodo->dtDue() != newTodo->dtDue() ) {
02698 mChanges += i18n( "The to-do due time has been changed from %1 to %2",
02699 dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
02700 dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
02701 }
02702 }
02703
02704 void compareIncidences( const Incidence::Ptr &newInc,
02705 const Incidence::Ptr &oldInc )
02706 {
02707 if ( !oldInc || !newInc ) {
02708 return;
02709 }
02710
02711 if ( oldInc->summary() != newInc->summary() ) {
02712 mChanges += i18n( "The summary has been changed to: \"%1\"",
02713 newInc->richSummary() );
02714 }
02715
02716 if ( oldInc->location() != newInc->location() ) {
02717 mChanges += i18n( "The location has been changed to: \"%1\"",
02718 newInc->richLocation() );
02719 }
02720
02721 if ( oldInc->description() != newInc->description() ) {
02722 mChanges += i18n( "The description has been changed to: \"%1\"",
02723 newInc->richDescription() );
02724 }
02725
02726 Attendee::List oldAttendees = oldInc->attendees();
02727 Attendee::List newAttendees = newInc->attendees();
02728 for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
02729 it != newAttendees.constEnd(); ++it ) {
02730 Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() );
02731 if ( !oldAtt ) {
02732 mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
02733 } else {
02734 if ( oldAtt->status() != (*it)->status() ) {
02735 mChanges += i18n( "The status of attendee %1 has been changed to: %2",
02736 (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) );
02737 }
02738 }
02739 }
02740
02741 for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
02742 it != oldAttendees.constEnd(); ++it ) {
02743 if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
02744 Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() );
02745 if ( !newAtt ) {
02746 mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
02747 }
02748 }
02749 }
02750 }
02751
02752 private:
02753 Incidence::Ptr mExistingIncidence;
02754 QStringList mChanges;
02755 };
02756
02757
02758 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
02759 {
02760 if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
02761 QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
02762 arg( generateLinkURL( id ), text );
02763 return res;
02764 } else {
02765
02766 QString res = QString( "<a href=\"%1\">%2</a>" ).
02767 arg( generateLinkURL( id ), text );
02768 return res;
02769 }
02770 }
02771
02772
02773
02774 static bool incidenceOwnedByMe( const Calendar::Ptr &calendar,
02775 const Incidence::Ptr &incidence )
02776 {
02777 Q_UNUSED( calendar );
02778 Q_UNUSED( incidence );
02779 return true;
02780 }
02781
02782
02783 static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
02784 static QString tdClose = "</td>";
02785
02786 static QString responseButtons( const Incidence::Ptr &inc,
02787 bool rsvpReq, bool rsvpRec,
02788 InvitationFormatterHelper *helper )
02789 {
02790 QString html;
02791 if ( !helper ) {
02792 return html;
02793 }
02794
02795 if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
02796
02797 html += tdOpen;
02798 html += helper->makeLink( "record", i18n( "[Record]" ) );
02799 html += tdClose;
02800
02801
02802 html += tdOpen;
02803 html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
02804 html += tdClose;
02805
02806 } else {
02807
02808
02809 html += tdOpen;
02810 html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
02811 html += tdClose;
02812
02813
02814 html += tdOpen;
02815 html += helper->makeLink( "accept_conditionally",
02816 i18nc( "Accept invitation conditionally", "Accept cond." ) );
02817 html += tdClose;
02818
02819
02820 html += tdOpen;
02821 html += helper->makeLink( "counter",
02822 i18nc( "invitation counter proposal", "Counter proposal" ) );
02823 html += tdClose;
02824
02825
02826 html += tdOpen;
02827 html += helper->makeLink( "decline",
02828 i18nc( "decline invitation", "Decline" ) );
02829 html += tdClose;
02830 }
02831
02832 if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
02833
02834 html += tdOpen;
02835 html += helper->makeLink( "delegate",
02836 i18nc( "delegate inviation to another", "Delegate" ) );
02837 html += tdClose;
02838
02839
02840 html += tdOpen;
02841 html += helper->makeLink( "forward",
02842 i18nc( "forward request to another", "Forward" ) );
02843 html += tdClose;
02844
02845
02846 if ( inc && inc->type() == Incidence::TypeEvent ) {
02847 html += tdOpen;
02848 html += helper->makeLink( "check_calendar",
02849 i18nc( "look for scheduling conflicts", "Check my calendar" ) );
02850 html += tdClose;
02851 }
02852 }
02853 return html;
02854 }
02855
02856 static QString counterButtons( const Incidence::Ptr &incidence,
02857 InvitationFormatterHelper *helper )
02858 {
02859 QString html;
02860 if ( !helper ) {
02861 return html;
02862 }
02863
02864
02865 html += tdOpen;
02866 html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
02867 html += tdClose;
02868
02869
02870 html += tdOpen;
02871 html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
02872 html += tdClose;
02873
02874
02875 if ( incidence && incidence->type() == Incidence::TypeEvent ) {
02876 html += tdOpen;
02877 html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
02878 html += tdClose;
02879 }
02880 return html;
02881 }
02882
02883 Calendar::Ptr InvitationFormatterHelper::calendar() const
02884 {
02885 return Calendar::Ptr();
02886 }
02887
02888 static QString formatICalInvitationHelper( QString invitation,
02889 const MemoryCalendar::Ptr &mCalendar,
02890 InvitationFormatterHelper *helper,
02891 bool noHtmlMode,
02892 KDateTime::Spec spec,
02893 const QString &sender,
02894 bool outlookCompareStyle )
02895 {
02896 if ( invitation.isEmpty() ) {
02897 return QString();
02898 }
02899
02900 ICalFormat format;
02901
02902
02903 ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation );
02904
02905 if( !msg ) {
02906 kDebug() << "Failed to parse the scheduling message";
02907 Q_ASSERT( format.exception() );
02908 kDebug() << Stringify::errorMessage( *format.exception() );
02909 return QString();
02910 }
02911
02912 IncidenceBase::Ptr incBase = msg->event();
02913
02914 incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
02915
02916
02917 Incidence::Ptr existingIncidence;
02918 if ( incBase && helper->calendar() ) {
02919 existingIncidence = helper->calendar()->incidence( incBase->uid() );
02920
02921 if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
02922 existingIncidence.clear();
02923 }
02924 if ( !existingIncidence ) {
02925 const Incidence::List list = helper->calendar()->incidences();
02926 for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
02927 if ( (*it)->schedulingID() == incBase->uid() &&
02928 incidenceOwnedByMe( helper->calendar(), *it ) ) {
02929 existingIncidence = *it;
02930 break;
02931 }
02932 }
02933 }
02934 }
02935
02936 Incidence::Ptr inc = incBase.staticCast<Incidence>();
02937
02938
02939 QString html;
02940 html += "<div align=\"center\" style=\"border:solid 1px;\">";
02941
02942 IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
02943
02944 if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
02945 return QString();
02946 }
02947 html += htmlAddTag( "h3", headerVisitor.result() );
02948
02949 if ( outlookCompareStyle ||
02950 msg->method() == iTIPDeclineCounter ) {
02951
02952 IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02953 bool bodyOk;
02954 if ( msg->method() == iTIPRequest || msg->method() == iTIPReply ||
02955 msg->method() == iTIPDeclineCounter ) {
02956 if ( inc && existingIncidence &&
02957 inc->revision() < existingIncidence->revision() ) {
02958 bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
02959 } else {
02960 bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
02961 }
02962 } else {
02963 bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender );
02964 }
02965 if ( bodyOk ) {
02966 html += bodyVisitor.result();
02967 } else {
02968 return QString();
02969 }
02970 } else {
02971
02972 InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
02973 if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) {
02974 return QString();
02975 }
02976 html += bodyVisitor.result();
02977
02978 if ( msg->method() == iTIPRequest ) {
02979 IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
02980 if ( compareVisitor.act( inc, existingIncidence ) ) {
02981 html += "<p align=\"left\">";
02982 if ( senderIsOrganizer( inc, sender ) ) {
02983 html += i18n( "The following changes have been made by the organizer:" );
02984 } else if ( !sender.isEmpty() ) {
02985 html += i18n( "The following changes have been made by %1:", sender );
02986 } else {
02987 html += i18n( "The following changes have been made:" );
02988 }
02989 html += "</p>";
02990 html += compareVisitor.result();
02991 }
02992 }
02993 if ( msg->method() == iTIPReply ) {
02994 IncidenceCompareVisitor compareVisitor;
02995 if ( compareVisitor.act( inc, existingIncidence ) ) {
02996 html += "<p align=\"left\">";
02997 if ( !sender.isEmpty() ) {
02998 html += i18n( "The following changes have been made by %1:", sender );
02999 } else {
03000 html += i18n( "The following changes have been made by an attendee:" );
03001 }
03002 html += "</p>";
03003 html += compareVisitor.result();
03004 }
03005 }
03006 }
03007
03008
03009 bool myInc = iamOrganizer( inc );
03010
03011
03012 bool rsvpRec = false;
03013 Attendee::Ptr ea;
03014 if ( !myInc ) {
03015 Incidence::Ptr rsvpIncidence = existingIncidence;
03016 if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
03017 rsvpIncidence = inc;
03018 }
03019 if ( rsvpIncidence ) {
03020 ea = findMyAttendee( rsvpIncidence );
03021 }
03022 if ( ea &&
03023 ( ea->status() == Attendee::Accepted ||
03024 ea->status() == Attendee::Declined ||
03025 ea->status() == Attendee::Tentative ) ) {
03026 rsvpRec = true;
03027 }
03028 }
03029
03030
03031 QString role;
03032 bool isDelegated = false;
03033 Attendee::Ptr a = findMyAttendee( inc );
03034 if ( !a && inc ) {
03035 if ( !inc->attendees().isEmpty() ) {
03036 a = inc->attendees().first();
03037 }
03038 }
03039 if ( a ) {
03040 isDelegated = ( a->status() == Attendee::Delegated );
03041 role = Stringify::attendeeRole( a->role() );
03042 }
03043
03044
03045 bool rsvpReq = rsvpRequested( inc );
03046 if ( !myInc && a ) {
03047 html += "<br/>";
03048 html += "<i><u>";
03049 if ( rsvpRec && inc ) {
03050 if ( inc->revision() == 0 ) {
03051 html += i18n( "Your <b>%1</b> response has been recorded",
03052 Stringify::attendeeStatus( ea->status() ) );
03053 } else {
03054 html += i18n( "Your status for this invitation is <b>%1</b>",
03055 Stringify::attendeeStatus( ea->status() ) );
03056 }
03057 rsvpReq = false;
03058 } else if ( msg->method() == iTIPCancel ) {
03059 html += i18n( "This invitation was canceled" );
03060 } else if ( msg->method() == iTIPAdd ) {
03061 html += i18n( "This invitation was accepted" );
03062 } else if ( msg->method() == iTIPDeclineCounter ) {
03063 rsvpReq = true;
03064 html += rsvpRequestedStr( rsvpReq, role );
03065 } else {
03066 if ( !isDelegated ) {
03067 html += rsvpRequestedStr( rsvpReq, role );
03068 } else {
03069 html += i18n( "Awaiting delegation response" );
03070 }
03071 }
03072 html += "</u></i>";
03073 }
03074
03075
03076 if ( !myInc ) {
03077 if ( inc && inc->revision() == 0 ) {
03078 QString statStr = myStatusStr( inc );
03079 if ( !statStr.isEmpty() ) {
03080 html += "<br/>";
03081 html += "<i>";
03082 html += statStr;
03083 html += "</i>";
03084 }
03085 }
03086 }
03087
03088
03089
03090 html += "<p>";
03091 html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
03092
03093 switch ( msg->method() ) {
03094 case iTIPPublish:
03095 case iTIPRequest:
03096 case iTIPRefresh:
03097 case iTIPAdd:
03098 {
03099 if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
03100 if ( inc->type() == Incidence::TypeTodo ) {
03101 html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
03102 } else {
03103 html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
03104 }
03105 }
03106
03107 if ( !myInc && a ) {
03108 html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03109 }
03110 break;
03111 }
03112
03113 case iTIPCancel:
03114
03115 if ( inc ) {
03116 html += tdOpen;
03117 if ( inc->type() == Incidence::TypeTodo ) {
03118 html += helper->makeLink( "cancel",
03119 i18n( "Remove invitation from my to-do list" ) );
03120 } else {
03121 html += helper->makeLink( "cancel",
03122 i18n( "Remove invitation from my calendar" ) );
03123 }
03124 html += tdClose;
03125 }
03126 break;
03127
03128 case iTIPReply:
03129 {
03130
03131 Attendee::Ptr a;
03132 Attendee::Ptr ea;
03133 if ( inc ) {
03134
03135 if ( replyMeansCounter( inc ) ) {
03136 html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
03137 break;
03138 }
03139
03140
03141
03142
03143
03144
03145 a = findDelegatedFromMyAttendee( inc );
03146 if ( a ) {
03147 if ( a->status() != Attendee::Accepted ||
03148 a->status() != Attendee::Tentative ) {
03149 html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03150 break;
03151 }
03152 }
03153
03154
03155 if ( !inc->attendees().isEmpty() ) {
03156 a = inc->attendees().first();
03157 }
03158 if ( a && helper->calendar() ) {
03159 ea = findAttendee( existingIncidence, a->email() );
03160 }
03161 }
03162 if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
03163 html += tdOpen;
03164 html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded",
03165 Stringify::attendeeStatus( ea->status() ) ) );
03166 html += tdClose;
03167 } else {
03168 if ( inc ) {
03169 if ( inc->type() == Incidence::TypeTodo ) {
03170 html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
03171 } else {
03172 html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
03173 }
03174 }
03175 }
03176 break;
03177 }
03178
03179 case iTIPCounter:
03180
03181 html += counterButtons( inc, helper );
03182 break;
03183
03184 case iTIPDeclineCounter:
03185 html += responseButtons( inc, rsvpReq, rsvpRec, helper );
03186 break;
03187
03188 case iTIPNoMethod:
03189 break;
03190 }
03191
03192
03193 html += "</tr></table>";
03194
03195
03196 if ( myInc ) {
03197 html += invitationRsvpList( existingIncidence, a );
03198 } else {
03199 html += invitationAttendeeList( inc );
03200 }
03201
03202
03203 html += "</div>";
03204
03205
03206 html += invitationAttachments( helper, inc );
03207
03208 return html;
03209 }
03210
03211
03212 QString IncidenceFormatter::formatICalInvitation( QString invitation,
03213 const MemoryCalendar::Ptr &calendar,
03214 InvitationFormatterHelper *helper,
03215 bool outlookCompareStyle )
03216 {
03217 return formatICalInvitationHelper( invitation, calendar, helper, false,
03218 KSystemTimeZones::local(), QString(),
03219 outlookCompareStyle );
03220 }
03221
03222 QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
03223 const MemoryCalendar::Ptr &calendar,
03224 InvitationFormatterHelper *helper,
03225 const QString &sender,
03226 bool outlookCompareStyle )
03227 {
03228 return formatICalInvitationHelper( invitation, calendar, helper, true,
03229 KSystemTimeZones::local(), sender,
03230 outlookCompareStyle );
03231 }
03232
03233
03234
03235
03236
03237
03238 class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor
03239 {
03240 public:
03241 ToolTipVisitor()
03242 : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
03243
03244 bool act( const MemoryCalendar::Ptr &calendar,
03245 const IncidenceBase::Ptr &incidence,
03246 const QDate &date=QDate(), bool richText=true,
03247 KDateTime::Spec spec=KDateTime::Spec() )
03248 {
03249 mCalendar = calendar;
03250 mLocation.clear();
03251 mDate = date;
03252 mRichText = richText;
03253 mSpec = spec;
03254 mResult = "";
03255 return incidence ? incidence->accept( *this, incidence ) : false;
03256 }
03257
03258 bool act( const QString &location, const IncidenceBase::Ptr &incidence,
03259 const QDate &date=QDate(), bool richText=true,
03260 KDateTime::Spec spec=KDateTime::Spec() )
03261 {
03262 mLocation = location;
03263 mDate = date;
03264 mRichText = richText;
03265 mSpec = spec;
03266 mResult = "";
03267 return incidence ? incidence->accept( *this, incidence ) : false;
03268 }
03269
03270 QString result() const { return mResult; }
03271
03272 protected:
03273 bool visit( Event::Ptr event );
03274 bool visit( Todo::Ptr todo );
03275 bool visit( Journal::Ptr journal );
03276 bool visit( FreeBusy::Ptr fb );
03277
03278 QString dateRangeText( const Event::Ptr &event, const QDate &date );
03279 QString dateRangeText( const Todo::Ptr &todo, const QDate &date );
03280 QString dateRangeText( const Journal::Ptr &journal );
03281 QString dateRangeText( const FreeBusy::Ptr &fb );
03282
03283 QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText );
03284
03285 protected:
03286 MemoryCalendar::Ptr mCalendar;
03287 QString mLocation;
03288 QDate mDate;
03289 bool mRichText;
03290 KDateTime::Spec mSpec;
03291 QString mResult;
03292 };
03293
03294 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event,
03295 const QDate &date )
03296 {
03297
03298 QString ret;
03299 QString tmp;
03300
03301 KDateTime startDt = event->dtStart();
03302 KDateTime endDt = event->dtEnd();
03303 if ( event->recurs() ) {
03304 if ( date.isValid() ) {
03305 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03306 int diffDays = startDt.daysTo( kdt );
03307 kdt = kdt.addSecs( -1 );
03308 startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
03309 if ( event->hasEndDate() ) {
03310 endDt = endDt.addDays( diffDays );
03311 if ( startDt > endDt ) {
03312 startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
03313 endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
03314 }
03315 }
03316 }
03317 }
03318
03319 if ( event->isMultiDay() ) {
03320 tmp = dateToString( startDt, true, mSpec );
03321 ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
03322
03323 tmp = dateToString( endDt, true, mSpec );
03324 ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
03325
03326 } else {
03327
03328 ret += "<br>" +
03329 i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
03330 if ( !event->allDay() ) {
03331 const QString dtStartTime = timeToString( startDt, true, mSpec );
03332 const QString dtEndTime = timeToString( endDt, true, mSpec );
03333 if ( dtStartTime == dtEndTime ) {
03334
03335 tmp = "<br>" +
03336 i18nc( "time for event", "<i>Time:</i> %1",
03337 dtStartTime );
03338 } else {
03339 tmp = "<br>" +
03340 i18nc( "time range for event",
03341 "<i>Time:</i> %1 - %2",
03342 dtStartTime, dtEndTime );
03343 }
03344 ret += tmp;
03345 }
03346 }
03347 return ret.replace( ' ', " " );
03348 }
03349
03350 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo,
03351 const QDate &date )
03352 {
03353
03354 QString ret;
03355 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03356 KDateTime startDt = todo->dtStart();
03357 if ( todo->recurs() ) {
03358 if ( date.isValid() ) {
03359 startDt.setDate( date );
03360 }
03361 }
03362 ret += "<br>" +
03363 i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
03364 }
03365
03366 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03367 KDateTime dueDt = todo->dtDue();
03368 if ( todo->recurs() ) {
03369 if ( date.isValid() ) {
03370 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
03371 kdt = kdt.addSecs( -1 );
03372 dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
03373 }
03374 }
03375 ret += "<br>" +
03376 i18n( "<i>Due:</i> %1",
03377 dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
03378 }
03379
03380
03381
03382 if ( todo->priority() > 0 ) {
03383 ret += "<br>";
03384 ret += "<i>" + i18n( "Priority:" ) + "</i>" + " ";
03385 ret += QString::number( todo->priority() );
03386 }
03387
03388 ret += "<br>";
03389 if ( todo->isCompleted() ) {
03390 ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + " ";
03391 ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', " " );
03392 } else {
03393 ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + " ";
03394 ret += i18n( "%1%", todo->percentComplete() );
03395 }
03396
03397 return ret.replace( ' ', " " );
03398 }
03399
03400 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal )
03401 {
03402
03403 QString ret;
03404 if ( journal->dtStart().isValid() ) {
03405 ret += "<br>" +
03406 i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
03407 }
03408 return ret.replace( ' ', " " );
03409 }
03410
03411 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb )
03412 {
03413
03414 QString ret;
03415 ret = "<br>" +
03416 i18n( "<i>Period start:</i> %1",
03417 KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
03418 ret += "<br>" +
03419 i18n( "<i>Period start:</i> %1",
03420 KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
03421 return ret.replace( ' ', " " );
03422 }
03423
03424 bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event )
03425 {
03426 mResult = generateToolTip( event, dateRangeText( event, mDate ) );
03427 return !mResult.isEmpty();
03428 }
03429
03430 bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo )
03431 {
03432 mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
03433 return !mResult.isEmpty();
03434 }
03435
03436 bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal )
03437 {
03438 mResult = generateToolTip( journal, dateRangeText( journal ) );
03439 return !mResult.isEmpty();
03440 }
03441
03442 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb )
03443 {
03444
03445 mResult = "<qt><b>" +
03446 i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) +
03447 "</b>";
03448 mResult += dateRangeText( fb );
03449 mResult += "</qt>";
03450 return !mResult.isEmpty();
03451 }
03452
03453 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
03454 {
03455
03456 const QString printName = searchName( email, name );
03457
03458
03459 const QString iconPath = rsvpStatusIconPath( status );
03460
03461
03462 QString personString;
03463 if ( !iconPath.isEmpty() ) {
03464 personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + " ";
03465 }
03466 if ( status != Attendee::None ) {
03467 personString += i18nc( "attendee name (attendee status)", "%1 (%2)",
03468 printName.isEmpty() ? email : printName,
03469 Stringify::attendeeStatus( status ) );
03470 } else {
03471 personString += i18n( "%1", printName.isEmpty() ? email : printName );
03472 }
03473 return personString;
03474 }
03475
03476 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
03477 {
03478
03479 const QString printName = searchName( email, name );
03480
03481
03482 const QString iconPath =
03483 KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
03484
03485
03486 QString personString;
03487 personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + " ";
03488 personString += ( printName.isEmpty() ? email : printName );
03489 return personString;
03490 }
03491
03492 static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence,
03493 Attendee::Role role, bool showStatus )
03494 {
03495 int maxNumAtts = 8;
03496 const QString etc = i18nc( "elipsis", "..." );
03497
03498 int i = 0;
03499 QString tmpStr;
03500 Attendee::List::ConstIterator it;
03501 Attendee::List attendees = incidence->attendees();
03502
03503 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
03504 Attendee::Ptr a = *it;
03505 if ( a->role() != role ) {
03506
03507 continue;
03508 }
03509 if ( attendeeIsOrganizer( incidence, a ) ) {
03510
03511 continue;
03512 }
03513 if ( i == maxNumAtts ) {
03514 tmpStr += " " + etc;
03515 break;
03516 }
03517 tmpStr += " " + tooltipPerson( a->email(), a->name(),
03518 showStatus ? a->status() : Attendee::None );
03519 if ( !a->delegator().isEmpty() ) {
03520 tmpStr += i18n( " (delegated by %1)", a->delegator() );
03521 }
03522 if ( !a->delegate().isEmpty() ) {
03523 tmpStr += i18n( " (delegated to %1)", a->delegate() );
03524 }
03525 tmpStr += "<br>";
03526 i++;
03527 }
03528 if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
03529 tmpStr.chop( 4 );
03530 }
03531 return tmpStr;
03532 }
03533
03534 static QString tooltipFormatAttendees( const Calendar::Ptr &calendar,
03535 const Incidence::Ptr &incidence )
03536 {
03537 QString tmpStr, str;
03538
03539
03540 int attendeeCount = incidence->attendees().count();
03541 if ( attendeeCount > 1 ||
03542 ( attendeeCount == 1 &&
03543 !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
03544 tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
03545 tmpStr += " " + tooltipFormatOrganizer( incidence->organizer()->email(),
03546 incidence->organizer()->name() );
03547 }
03548
03549
03550
03551 bool showStatus = incOrganizerOwnsCalendar( calendar, incidence );
03552
03553
03554 str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
03555 if ( !str.isEmpty() ) {
03556 tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
03557 tmpStr += str;
03558 }
03559
03560
03561 str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
03562 if ( !str.isEmpty() ) {
03563 tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
03564 tmpStr += str;
03565 }
03566
03567
03568 str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
03569 if ( !str.isEmpty() ) {
03570 tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
03571 tmpStr += str;
03572 }
03573
03574
03575 str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
03576 if ( !str.isEmpty() ) {
03577 tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
03578 tmpStr += str;
03579 }
03580
03581 return tmpStr;
03582 }
03583
03584 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence,
03585 QString dtRangeText )
03586 {
03587 int maxDescLen = 120;
03588
03589
03590 if ( !incidence ) {
03591 return QString();
03592 }
03593
03594 QString tmp = "<qt>";
03595
03596
03597 tmp += "<b>" + incidence->richSummary() + "</b>";
03598 tmp += "<hr>";
03599
03600 QString calStr = mLocation;
03601 if ( mCalendar ) {
03602 calStr = resourceString( mCalendar, incidence );
03603 }
03604 if ( !calStr.isEmpty() ) {
03605 tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + " ";
03606 tmp += calStr;
03607 }
03608
03609 tmp += dtRangeText;
03610
03611 if ( !incidence->location().isEmpty() ) {
03612 tmp += "<br>";
03613 tmp += "<i>" + i18n( "Location:" ) + "</i>" + " ";
03614 tmp += incidence->richLocation();
03615 }
03616
03617 QString durStr = durationString( incidence );
03618 if ( !durStr.isEmpty() ) {
03619 tmp += "<br>";
03620 tmp += "<i>" + i18n( "Duration:" ) + "</i>" + " ";
03621 tmp += durStr;
03622 }
03623
03624 if ( incidence->recurs() ) {
03625 tmp += "<br>";
03626 tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + " ";
03627 tmp += recurrenceString( incidence );
03628 }
03629
03630 if ( !incidence->description().isEmpty() ) {
03631 QString desc( incidence->description() );
03632 if ( !incidence->descriptionIsRich() ) {
03633 if ( desc.length() > maxDescLen ) {
03634 desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." );
03635 }
03636 desc = Qt::escape( desc ).replace( '\n', "<br>" );
03637 } else {
03638
03639 }
03640 tmp += "<hr>";
03641 tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
03642 tmp += desc;
03643 tmp += "<hr>";
03644 }
03645
03646 int reminderCount = incidence->alarms().count();
03647 if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) {
03648 tmp += "<br>";
03649 tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + " ";
03650 tmp += reminderStringList( incidence ).join( ", " );
03651 }
03652
03653 tmp += "<br>";
03654 tmp += tooltipFormatAttendees( mCalendar, incidence );
03655
03656 int categoryCount = incidence->categories().count();
03657 if ( categoryCount > 0 ) {
03658 tmp += "<br>";
03659 tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + " ";
03660 tmp += incidence->categories().join( ", " );
03661 }
03662
03663 tmp += "</qt>";
03664 return tmp;
03665 }
03666
03667
03668 QString IncidenceFormatter::toolTipStr( const QString &sourceName,
03669 const IncidenceBase::Ptr &incidence,
03670 const QDate &date,
03671 bool richText,
03672 KDateTime::Spec spec )
03673 {
03674 ToolTipVisitor v;
03675 if ( v.act( sourceName, incidence, date, richText, spec ) ) {
03676 return v.result();
03677 } else {
03678 return QString();
03679 }
03680 }
03681
03682
03683
03684
03685
03686
03687 static QString mailBodyIncidence( const Incidence::Ptr &incidence )
03688 {
03689 QString body;
03690 if ( !incidence->summary().isEmpty() ) {
03691 body += i18n( "Summary: %1\n", incidence->richSummary() );
03692 }
03693 if ( !incidence->organizer()->isEmpty() ) {
03694 body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() );
03695 }
03696 if ( !incidence->location().isEmpty() ) {
03697 body += i18n( "Location: %1\n", incidence->richLocation() );
03698 }
03699 return body;
03700 }
03701
03702
03703
03704 class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor
03705 {
03706 public:
03707 MailBodyVisitor()
03708 : mSpec( KDateTime::Spec() ), mResult( "" ) {}
03709
03710 bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() )
03711 {
03712 mSpec = spec;
03713 mResult = "";
03714 return incidence ? incidence->accept( *this, incidence ) : false;
03715 }
03716 QString result() const
03717 {
03718 return mResult;
03719 }
03720
03721 protected:
03722 bool visit( Event::Ptr event );
03723 bool visit( Todo::Ptr todo );
03724 bool visit( Journal::Ptr journal );
03725 bool visit( FreeBusy::Ptr )
03726 {
03727 mResult = i18n( "This is a Free Busy Object" );
03728 return !mResult.isEmpty();
03729 }
03730 protected:
03731 KDateTime::Spec mSpec;
03732 QString mResult;
03733 };
03734
03735 bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event )
03736 {
03737 QString recurrence[]= {
03738 i18nc( "no recurrence", "None" ),
03739 i18nc( "event recurs by minutes", "Minutely" ),
03740 i18nc( "event recurs by hours", "Hourly" ),
03741 i18nc( "event recurs by days", "Daily" ),
03742 i18nc( "event recurs by weeks", "Weekly" ),
03743 i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
03744 i18nc( "event recurs same day each month", "Monthly Same Day" ),
03745 i18nc( "event recurs same month each year", "Yearly Same Month" ),
03746 i18nc( "event recurs same day each year", "Yearly Same Day" ),
03747 i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
03748 };
03749
03750 mResult = mailBodyIncidence( event );
03751 mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
03752 if ( !event->allDay() ) {
03753 mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
03754 }
03755 if ( event->dtStart() != event->dtEnd() ) {
03756 mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
03757 }
03758 if ( !event->allDay() ) {
03759 mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
03760 }
03761 if ( event->recurs() ) {
03762 Recurrence *recur = event->recurrence();
03763
03764 mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
03765 mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
03766
03767 if ( recur->duration() > 0 ) {
03768 mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
03769 mResult += '\n';
03770 } else {
03771 if ( recur->duration() != -1 ) {
03772
03773 QString endstr;
03774 if ( event->allDay() ) {
03775 endstr = KGlobal::locale()->formatDate( recur->endDate() );
03776 } else {
03777 endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
03778 }
03779 mResult += i18n( "Repeat until: %1\n", endstr );
03780 } else {
03781 mResult += i18n( "Repeats forever\n" );
03782 }
03783 }
03784 }
03785
03786 QString details = event->richDescription();
03787 if ( !details.isEmpty() ) {
03788 mResult += i18n( "Details:\n%1\n", details );
03789 }
03790 return !mResult.isEmpty();
03791 }
03792
03793 bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo )
03794 {
03795 mResult = mailBodyIncidence( todo );
03796
03797 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
03798 mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
03799 if ( !todo->allDay() ) {
03800 mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
03801 }
03802 }
03803 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
03804 mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
03805 if ( !todo->allDay() ) {
03806 mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
03807 }
03808 }
03809 QString details = todo->richDescription();
03810 if ( !details.isEmpty() ) {
03811 mResult += i18n( "Details:\n%1\n", details );
03812 }
03813 return !mResult.isEmpty();
03814 }
03815
03816 bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal )
03817 {
03818 mResult = mailBodyIncidence( journal );
03819 mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
03820 if ( !journal->allDay() ) {
03821 mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
03822 }
03823 if ( !journal->description().isEmpty() ) {
03824 mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
03825 }
03826 return !mResult.isEmpty();
03827 }
03828
03829
03830 QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence,
03831 KDateTime::Spec spec )
03832 {
03833 if ( !incidence ) {
03834 return QString();
03835 }
03836
03837 MailBodyVisitor v;
03838 if ( v.act( incidence, spec ) ) {
03839 return v.result();
03840 }
03841 return QString();
03842 }
03843
03844
03845 static QString recurEnd( const Incidence::Ptr &incidence )
03846 {
03847 QString endstr;
03848 if ( incidence->allDay() ) {
03849 endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
03850 } else {
03851 endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
03852 }
03853 return endstr;
03854 }
03855
03856
03857
03858
03859
03860
03861 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence )
03862 {
03863 if ( !incidence->recurs() ) {
03864 return i18n( "No recurrence" );
03865 }
03866 QStringList dayList;
03867 dayList.append( i18n( "31st Last" ) );
03868 dayList.append( i18n( "30th Last" ) );
03869 dayList.append( i18n( "29th Last" ) );
03870 dayList.append( i18n( "28th Last" ) );
03871 dayList.append( i18n( "27th Last" ) );
03872 dayList.append( i18n( "26th Last" ) );
03873 dayList.append( i18n( "25th Last" ) );
03874 dayList.append( i18n( "24th Last" ) );
03875 dayList.append( i18n( "23rd Last" ) );
03876 dayList.append( i18n( "22nd Last" ) );
03877 dayList.append( i18n( "21st Last" ) );
03878 dayList.append( i18n( "20th Last" ) );
03879 dayList.append( i18n( "19th Last" ) );
03880 dayList.append( i18n( "18th Last" ) );
03881 dayList.append( i18n( "17th Last" ) );
03882 dayList.append( i18n( "16th Last" ) );
03883 dayList.append( i18n( "15th Last" ) );
03884 dayList.append( i18n( "14th Last" ) );
03885 dayList.append( i18n( "13th Last" ) );
03886 dayList.append( i18n( "12th Last" ) );
03887 dayList.append( i18n( "11th Last" ) );
03888 dayList.append( i18n( "10th Last" ) );
03889 dayList.append( i18n( "9th Last" ) );
03890 dayList.append( i18n( "8th Last" ) );
03891 dayList.append( i18n( "7th Last" ) );
03892 dayList.append( i18n( "6th Last" ) );
03893 dayList.append( i18n( "5th Last" ) );
03894 dayList.append( i18n( "4th Last" ) );
03895 dayList.append( i18n( "3rd Last" ) );
03896 dayList.append( i18n( "2nd Last" ) );
03897 dayList.append( i18nc( "last day of the month", "Last" ) );
03898 dayList.append( i18nc( "unknown day of the month", "unknown" ) );
03899 dayList.append( i18n( "1st" ) );
03900 dayList.append( i18n( "2nd" ) );
03901 dayList.append( i18n( "3rd" ) );
03902 dayList.append( i18n( "4th" ) );
03903 dayList.append( i18n( "5th" ) );
03904 dayList.append( i18n( "6th" ) );
03905 dayList.append( i18n( "7th" ) );
03906 dayList.append( i18n( "8th" ) );
03907 dayList.append( i18n( "9th" ) );
03908 dayList.append( i18n( "10th" ) );
03909 dayList.append( i18n( "11th" ) );
03910 dayList.append( i18n( "12th" ) );
03911 dayList.append( i18n( "13th" ) );
03912 dayList.append( i18n( "14th" ) );
03913 dayList.append( i18n( "15th" ) );
03914 dayList.append( i18n( "16th" ) );
03915 dayList.append( i18n( "17th" ) );
03916 dayList.append( i18n( "18th" ) );
03917 dayList.append( i18n( "19th" ) );
03918 dayList.append( i18n( "20th" ) );
03919 dayList.append( i18n( "21st" ) );
03920 dayList.append( i18n( "22nd" ) );
03921 dayList.append( i18n( "23rd" ) );
03922 dayList.append( i18n( "24th" ) );
03923 dayList.append( i18n( "25th" ) );
03924 dayList.append( i18n( "26th" ) );
03925 dayList.append( i18n( "27th" ) );
03926 dayList.append( i18n( "28th" ) );
03927 dayList.append( i18n( "29th" ) );
03928 dayList.append( i18n( "30th" ) );
03929 dayList.append( i18n( "31st" ) );
03930
03931 int weekStart = KGlobal::locale()->weekStartDay();
03932 QString dayNames;
03933 const KCalendarSystem *calSys = KGlobal::locale()->calendar();
03934
03935 Recurrence *recur = incidence->recurrence();
03936
03937 QString txt, recurStr;
03938 switch ( recur->recurrenceType() ) {
03939 case Recurrence::rNone:
03940 return i18n( "No recurrence" );
03941
03942 case Recurrence::rMinutely:
03943 if ( recur->duration() != -1 ) {
03944 recurStr = i18np( "Recurs every minute until %2",
03945 "Recurs every %1 minutes until %2",
03946 recur->frequency(), recurEnd( incidence ) );
03947 if ( recur->duration() > 0 ) {
03948 recurStr += i18nc( "number of occurrences",
03949 " (<numid>%1</numid> occurrences)",
03950 recur->duration() );
03951 }
03952 } else {
03953 recurStr = i18np( "Recurs every minute",
03954 "Recurs every %1 minutes", recur->frequency() );
03955 }
03956 break;
03957
03958 case Recurrence::rHourly:
03959 if ( recur->duration() != -1 ) {
03960 recurStr = i18np( "Recurs hourly until %2",
03961 "Recurs every %1 hours until %2",
03962 recur->frequency(), recurEnd( incidence ) );
03963 if ( recur->duration() > 0 ) {
03964 recurStr += i18nc( "number of occurrences",
03965 " (<numid>%1</numid> occurrences)",
03966 recur->duration() );
03967 }
03968 } else {
03969 recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
03970 }
03971 break;
03972
03973 case Recurrence::rDaily:
03974 if ( recur->duration() != -1 ) {
03975 recurStr = i18np( "Recurs daily until %2",
03976 "Recurs every %1 days until %2",
03977 recur->frequency(), recurEnd( incidence ) );
03978 if ( recur->duration() > 0 ) {
03979 recurStr += i18nc( "number of occurrences",
03980 " (<numid>%1</numid> occurrences)",
03981 recur->duration() );
03982 }
03983 } else {
03984 recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
03985 }
03986 break;
03987
03988 case Recurrence::rWeekly:
03989 {
03990 bool addSpace = false;
03991 for ( int i = 0; i < 7; ++i ) {
03992 if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
03993 if ( addSpace ) {
03994 dayNames.append( i18nc( "separator for list of days", ", " ) );
03995 }
03996 dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
03997 KCalendarSystem::ShortDayName ) );
03998 addSpace = true;
03999 }
04000 }
04001 if ( dayNames.isEmpty() ) {
04002 dayNames = i18nc( "Recurs weekly on no days", "no days" );
04003 }
04004 if ( recur->duration() != -1 ) {
04005 recurStr = i18ncp( "Recurs weekly on [list of days] until end-date",
04006 "Recurs weekly on %2 until %3",
04007 "Recurs every <numid>%1</numid> weeks on %2 until %3",
04008 recur->frequency(), dayNames, recurEnd( incidence ) );
04009 if ( recur->duration() > 0 ) {
04010 recurStr += i18nc( "number of occurrences",
04011 " (<numid>%1</numid> occurrences)",
04012 recur->duration() );
04013 }
04014 } else {
04015 recurStr = i18ncp( "Recurs weekly on [list of days]",
04016 "Recurs weekly on %2",
04017 "Recurs every <numid>%1</numid> weeks on %2",
04018 recur->frequency(), dayNames );
04019 }
04020 break;
04021 }
04022 case Recurrence::rMonthlyPos:
04023 {
04024 if ( !recur->monthPositions().isEmpty() ) {
04025 RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
04026 if ( recur->duration() != -1 ) {
04027 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
04028 " weekdayname until end-date",
04029 "Recurs every month on the %2 %3 until %4",
04030 "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
04031 recur->frequency(),
04032 dayList[rule.pos() + 31],
04033 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04034 recurEnd( incidence ) );
04035 if ( recur->duration() > 0 ) {
04036 recurStr += i18nc( "number of occurrences",
04037 " (<numid>%1</numid> occurrences)",
04038 recur->duration() );
04039 }
04040 } else {
04041 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
04042 "Recurs every month on the %2 %3",
04043 "Recurs every %1 months on the %2 %3",
04044 recur->frequency(),
04045 dayList[rule.pos() + 31],
04046 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
04047 }
04048 }
04049 break;
04050 }
04051 case Recurrence::rMonthlyDay:
04052 {
04053 if ( !recur->monthDays().isEmpty() ) {
04054 int days = recur->monthDays()[0];
04055 if ( recur->duration() != -1 ) {
04056 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
04057 "Recurs monthly on the %2 day until %3",
04058 "Recurs every %1 months on the %2 day until %3",
04059 recur->frequency(),
04060 dayList[days + 31],
04061 recurEnd( incidence ) );
04062 if ( recur->duration() > 0 ) {
04063 recurStr += i18nc( "number of occurrences",
04064 " (<numid>%1</numid> occurrences)",
04065 recur->duration() );
04066 }
04067 } else {
04068 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day",
04069 "Recurs monthly on the %2 day",
04070 "Recurs every <numid>%1</numid> month on the %2 day",
04071 recur->frequency(),
04072 dayList[days + 31] );
04073 }
04074 }
04075 break;
04076 }
04077 case Recurrence::rYearlyMonth:
04078 {
04079 if ( recur->duration() != -1 ) {
04080 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04081 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
04082 " until end-date",
04083 "Recurs yearly on %2 %3 until %4",
04084 "Recurs every %1 years on %2 %3 until %4",
04085 recur->frequency(),
04086 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04087 dayList[ recur->yearDates()[0] + 31 ],
04088 recurEnd( incidence ) );
04089 if ( recur->duration() > 0 ) {
04090 recurStr += i18nc( "number of occurrences",
04091 " (<numid>%1</numid> occurrences)",
04092 recur->duration() );
04093 }
04094 }
04095 } else {
04096 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
04097 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
04098 "Recurs yearly on %2 %3",
04099 "Recurs every %1 years on %2 %3",
04100 recur->frequency(),
04101 calSys->monthName( recur->yearMonths()[0],
04102 recur->startDate().year() ),
04103 dayList[ recur->yearDates()[0] + 31 ] );
04104 } else {
04105 if (!recur->yearMonths().isEmpty() ) {
04106 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04107 "Recurs yearly on %1 %2",
04108 calSys->monthName( recur->yearMonths()[0],
04109 recur->startDate().year() ),
04110 dayList[ recur->startDate().day() + 31 ] );
04111 } else {
04112 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
04113 "Recurs yearly on %1 %2",
04114 calSys->monthName( recur->startDate().month(),
04115 recur->startDate().year() ),
04116 dayList[ recur->startDate().day() + 31 ] );
04117 }
04118 }
04119 }
04120 break;
04121 }
04122 case Recurrence::rYearlyDay:
04123 if ( !recur->yearDays().isEmpty() ) {
04124 if ( recur->duration() != -1 ) {
04125 recurStr = i18ncp( "Recurs every N years on day N until end-date",
04126 "Recurs every year on day <numid>%2</numid> until %3",
04127 "Recurs every <numid>%1</numid> years"
04128 " on day <numid>%2</numid> until %3",
04129 recur->frequency(),
04130 recur->yearDays()[0],
04131 recurEnd( incidence ) );
04132 if ( recur->duration() > 0 ) {
04133 recurStr += i18nc( "number of occurrences",
04134 " (<numid>%1</numid> occurrences)",
04135 recur->duration() );
04136 }
04137 } else {
04138 recurStr = i18ncp( "Recurs every N YEAR[S] on day N",
04139 "Recurs every year on day <numid>%2</numid>",
04140 "Recurs every <numid>%1</numid> years"
04141 " on day <numid>%2</numid>",
04142 recur->frequency(), recur->yearDays()[0] );
04143 }
04144 }
04145 break;
04146 case Recurrence::rYearlyPos:
04147 {
04148 if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
04149 RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
04150 if ( recur->duration() != -1 ) {
04151 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04152 "of monthname until end-date",
04153 "Every year on the %2 %3 of %4 until %5",
04154 "Every <numid>%1</numid> years on the %2 %3 of %4"
04155 " until %5",
04156 recur->frequency(),
04157 dayList[rule.pos() + 31],
04158 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04159 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
04160 recurEnd( incidence ) );
04161 if ( recur->duration() > 0 ) {
04162 recurStr += i18nc( "number of occurrences",
04163 " (<numid>%1</numid> occurrences)",
04164 recur->duration() );
04165 }
04166 } else {
04167 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
04168 "of monthname",
04169 "Every year on the %2 %3 of %4",
04170 "Every <numid>%1</numid> years on the %2 %3 of %4",
04171 recur->frequency(),
04172 dayList[rule.pos() + 31],
04173 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
04174 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
04175 }
04176 }
04177 }
04178 break;
04179 }
04180
04181 if ( recurStr.isEmpty() ) {
04182 recurStr = i18n( "Incidence recurs" );
04183 }
04184
04185
04186 DateTimeList l = recur->exDateTimes();
04187 DateTimeList::ConstIterator il;
04188 QStringList exStr;
04189 for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
04190 switch ( recur->recurrenceType() ) {
04191 case Recurrence::rMinutely:
04192 exStr << i18n( "minute %1", (*il).time().minute() );
04193 break;
04194 case Recurrence::rHourly:
04195 exStr << KGlobal::locale()->formatTime( (*il).time() );
04196 break;
04197 case Recurrence::rDaily:
04198 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04199 break;
04200 case Recurrence::rWeekly:
04201 exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName );
04202 break;
04203 case Recurrence::rMonthlyPos:
04204 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04205 break;
04206 case Recurrence::rMonthlyDay:
04207 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04208 break;
04209 case Recurrence::rYearlyMonth:
04210 exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName );
04211 break;
04212 case Recurrence::rYearlyDay:
04213 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04214 break;
04215 case Recurrence::rYearlyPos:
04216 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
04217 break;
04218 }
04219 }
04220
04221 DateList d = recur->exDates();
04222 DateList::ConstIterator dl;
04223 for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
04224 switch ( recur->recurrenceType() ) {
04225 case Recurrence::rDaily:
04226 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04227 break;
04228 case Recurrence::rWeekly:
04229 exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName );
04230 break;
04231 case Recurrence::rMonthlyPos:
04232 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04233 break;
04234 case Recurrence::rMonthlyDay:
04235 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04236 break;
04237 case Recurrence::rYearlyMonth:
04238 exStr << calSys->monthName( (*dl), KCalendarSystem::LongName );
04239 break;
04240 case Recurrence::rYearlyDay:
04241 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04242 break;
04243 case Recurrence::rYearlyPos:
04244 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
04245 break;
04246 }
04247 }
04248
04249 if ( !exStr.isEmpty() ) {
04250 recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) );
04251 }
04252
04253 return recurStr;
04254 }
04255
04256 QString IncidenceFormatter::timeToString( const KDateTime &date,
04257 bool shortfmt,
04258 const KDateTime::Spec &spec )
04259 {
04260 if ( spec.isValid() ) {
04261
04262 QString timeZone;
04263 if ( spec.timeZone() != KSystemTimeZones::local() ) {
04264 timeZone = ' ' + spec.timeZone().name();
04265 }
04266
04267 return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
04268 } else {
04269 return KGlobal::locale()->formatTime( date.time(), !shortfmt );
04270 }
04271 }
04272
04273 QString IncidenceFormatter::dateToString( const KDateTime &date,
04274 bool shortfmt,
04275 const KDateTime::Spec &spec )
04276 {
04277 if ( spec.isValid() ) {
04278
04279 QString timeZone;
04280 if ( spec.timeZone() != KSystemTimeZones::local() ) {
04281 timeZone = ' ' + spec.timeZone().name();
04282 }
04283
04284 return
04285 KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
04286 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
04287 timeZone;
04288 } else {
04289 return
04290 KGlobal::locale()->formatDate( date.date(),
04291 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04292 }
04293 }
04294
04295 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
04296 bool allDay,
04297 bool shortfmt,
04298 const KDateTime::Spec &spec )
04299 {
04300 if ( allDay ) {
04301 return dateToString( date, shortfmt, spec );
04302 }
04303
04304 if ( spec.isValid() ) {
04305 QString timeZone;
04306 if ( spec.timeZone() != KSystemTimeZones::local() ) {
04307 timeZone = ' ' + spec.timeZone().name();
04308 }
04309
04310 return KGlobal::locale()->formatDateTime(
04311 date.toTimeSpec( spec ).dateTime(),
04312 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
04313 } else {
04314 return KGlobal::locale()->formatDateTime(
04315 date.dateTime(),
04316 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
04317 }
04318 }
04319
04320 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar,
04321 const Incidence::Ptr &incidence )
04322 {
04323 Q_UNUSED( calendar );
04324 Q_UNUSED( incidence );
04325 return QString();
04326 }
04327
04328 static QString secs2Duration( int secs )
04329 {
04330 QString tmp;
04331 int days = secs / 86400;
04332 if ( days > 0 ) {
04333 tmp += i18np( "1 day", "%1 days", days );
04334 tmp += ' ';
04335 secs -= ( days * 86400 );
04336 }
04337 int hours = secs / 3600;
04338 if ( hours > 0 ) {
04339 tmp += i18np( "1 hour", "%1 hours", hours );
04340 tmp += ' ';
04341 secs -= ( hours * 3600 );
04342 }
04343 int mins = secs / 60;
04344 if ( mins > 0 ) {
04345 tmp += i18np( "1 minute", "%1 minutes", mins );
04346 }
04347 return tmp;
04348 }
04349
04350 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence )
04351 {
04352 QString tmp;
04353 if ( incidence->type() == Incidence::TypeEvent ) {
04354 Event::Ptr event = incidence.staticCast<Event>();
04355 if ( event->hasEndDate() ) {
04356 if ( !event->allDay() ) {
04357 tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
04358 } else {
04359 tmp = i18np( "1 day", "%1 days",
04360 event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
04361 }
04362 } else {
04363 tmp = i18n( "forever" );
04364 }
04365 } else if ( incidence->type() == Incidence::TypeTodo ) {
04366 Todo::Ptr todo = incidence.staticCast<Todo>();
04367 if ( todo->hasDueDate() ) {
04368 if ( todo->hasStartDate() ) {
04369 if ( !todo->allDay() ) {
04370 tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
04371 } else {
04372 tmp = i18np( "1 day", "%1 days",
04373 todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
04374 }
04375 }
04376 }
04377 }
04378 return tmp;
04379 }
04380
04381 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence,
04382 bool shortfmt )
04383 {
04384
04385 Q_UNUSED( shortfmt );
04386
04387 QStringList reminderStringList;
04388
04389 if ( incidence ) {
04390 Alarm::List alarms = incidence->alarms();
04391 Alarm::List::ConstIterator it;
04392 for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
04393 Alarm::Ptr alarm = *it;
04394 int offset = 0;
04395 QString remStr, atStr, offsetStr;
04396 if ( alarm->hasTime() ) {
04397 offset = 0;
04398 if ( alarm->time().isValid() ) {
04399 atStr = KGlobal::locale()->formatDateTime( alarm->time() );
04400 }
04401 } else if ( alarm->hasStartOffset() ) {
04402 offset = alarm->startOffset().asSeconds();
04403 if ( offset < 0 ) {
04404 offset = -offset;
04405 offsetStr = i18nc( "N days/hours/minutes before the start datetime",
04406 "%1 before the start", secs2Duration( offset ) );
04407 } else if ( offset > 0 ) {
04408 offsetStr = i18nc( "N days/hours/minutes after the start datetime",
04409 "%1 after the start", secs2Duration( offset ) );
04410 } else {
04411 if ( incidence->dtStart().isValid() ) {
04412 atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
04413 }
04414 }
04415 } else if ( alarm->hasEndOffset() ) {
04416 offset = alarm->endOffset().asSeconds();
04417 if ( offset < 0 ) {
04418 offset = -offset;
04419 if ( incidence->type() == Incidence::TypeTodo ) {
04420 offsetStr = i18nc( "N days/hours/minutes before the due datetime",
04421 "%1 before the to-do is due", secs2Duration( offset ) );
04422 } else {
04423 offsetStr = i18nc( "N days/hours/minutes before the end datetime",
04424 "%1 before the end", secs2Duration( offset ) );
04425 }
04426 } else if ( offset > 0 ) {
04427 if ( incidence->type() == Incidence::TypeTodo ) {
04428 offsetStr = i18nc( "N days/hours/minutes after the due datetime",
04429 "%1 after the to-do is due", secs2Duration( offset ) );
04430 } else {
04431 offsetStr = i18nc( "N days/hours/minutes after the end datetime",
04432 "%1 after the end", secs2Duration( offset ) );
04433 }
04434 } else {
04435 if ( incidence->type() == Incidence::TypeTodo ) {
04436 Todo::Ptr t = incidence.staticCast<Todo>();
04437 if ( t->dtDue().isValid() ) {
04438 atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
04439 }
04440 } else {
04441 Event::Ptr e = incidence.staticCast<Event>();
04442 if ( e->dtEnd().isValid() ) {
04443 atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
04444 }
04445 }
04446 }
04447 }
04448 if ( offset == 0 ) {
04449 if ( !atStr.isEmpty() ) {
04450 remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
04451 }
04452 } else {
04453 remStr = offsetStr;
04454 }
04455
04456 if ( alarm->repeatCount() > 0 ) {
04457 QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
04458 QString intervalStr = i18nc( "interval is N days/hours/minutes",
04459 "interval is %1",
04460 secs2Duration( alarm->snoozeTime().asSeconds() ) );
04461 QString repeatStr = i18nc( "(repeat string, interval string)",
04462 "(%1, %2)", countStr, intervalStr );
04463 remStr = remStr + ' ' + repeatStr;
04464
04465 }
04466 reminderStringList << remStr;
04467 }
04468 }
04469
04470 return reminderStringList;
04471 }