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