KCalUtils Library
incidenceformatter.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the kcalutils library. 00003 00004 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> 00005 Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> 00006 Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> 00007 Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 00008 00009 This library is free software; you can redistribute it and/or 00010 modify it under the terms of the GNU Library General Public 00011 License as published by the Free Software Foundation; either 00012 version 2 of the License, or (at your option) any later version. 00013 00014 This library is distributed in the hope that it will be useful, 00015 but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 Library General Public License for more details. 00018 00019 You should have received a copy of the GNU Library General Public License 00020 along with this library; see the file COPYING.LIB. If not, write to 00021 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00022 Boston, MA 02110-1301, USA. 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 * General helpers 00068 *******************/ 00069 00070 //@cond PRIVATE 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 // There is a UID, so make a link to the addressbook 00103 if ( name.isEmpty() ) { 00104 // Use the email address for text 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 // Yes, this is a silly method now, but it's predecessor was quite useful in e35. 00141 // For now, please keep this sillyness until e35 is frozen to ease forward porting. 00142 // -Allen 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 // Check if I'm this attendee 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 // Check if I'm the organizer for this incidence 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 // Check if the specified sender is the organizer 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 // for this heuristic, we say the sender is the organizer if either the name or the email match. 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 //@endcond 00305 00306 /******************************************************************* 00307 * Helper functions for the extensive display (display viewer) 00308 *******************************************************************/ 00309 00310 //@cond PRIVATE 00311 static QString displayViewFormatPerson( const QString &email, const QString &name, 00312 const QString &uid, const QString &iconPath ) 00313 { 00314 // Search for new print name or uid, if needed. 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 // Make the uid link 00325 if ( !printUid.isEmpty() ) { 00326 personString += htmlAddUidLink( email, printName, printUid ); 00327 } else { 00328 // No UID, just show some text 00329 personString += ( printName.isEmpty() ? email : printName ); 00330 } 00331 00332 // Make the mailto link 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 //PORTME! Look at e35's CalHelper::incOrganizerOwnsCalendar 00350 00351 // For now, use iamOrganizer() which is only part of the check 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 // skip this role 00367 continue; 00368 } 00369 if ( attendeeIsOrganizer( incidence, a ) ) { 00370 // skip attendee that is also the organizer 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 // Add organizer link 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 // Show the attendee status if the incidence's organizer owns the resource calendar, 00413 // which means they are running the show and have all the up-to-date response info. 00414 bool showStatus = incOrganizerOwnsCalendar( calendar, incidence ); 00415 00416 // Add "chair" 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 // Add required participants 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 // Add optional participants 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 // Add observers 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 // We do not use Incidence::categoriesStr() since it does not have whitespace 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 // show icons 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 // TODO: print comments? 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 // In kdepim all recuring to-dos have due date. 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 // TODO: print comments? 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 //@endcond 01015 01016 //@cond PRIVATE 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 //@endcond 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 * Helper functions for the body part formatter of kmail (Invitations) 01112 ***********************************************************************/ 01113 01114 //@cond PRIVATE 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 // Color for printing comparison differences inside invitations. 01215 01216 // return "#DE8519"; // hard-coded color from Outlook2007 01217 return QColor( Qt::red ).name(); //krazy:exclude=qenums TODO make configurable 01218 } 01219 01220 static QString noteColor() 01221 { 01222 // Color for printing notes inside invitations. 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 // if 'value' is empty, then print nothing 01238 if ( value.isEmpty() ) { 01239 return QString(); 01240 } 01241 01242 // if 'value' is new or unchanged, then print normally 01243 if ( oldvalue.isEmpty() || value == oldvalue ) { 01244 return htmlRow( title, value ); 01245 } 01246 01247 // if 'value' has changed, then make a special print 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 // Return the first attendee that was delegated-from me 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 // Return the attendee for the incidence that is probably me 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 // Search for an attendee by email address 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 //use a heuristic to determine if a response is requested. 01342 01343 bool rsvp = true; // better send superfluously than not at all 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(); // use what the first one has 01349 } else { 01350 if ( (*it)->RSVP() != rsvp ) { 01351 rsvp = true; // they differ, default 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 // Make the uid link 01424 if ( !printUid.isEmpty() ) { 01425 personString = htmlAddUidLink( email, printName, printUid ); 01426 } else { 01427 // No UID, just show some text 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 // Make the mailto link 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 // if description and comment -> use both 01447 // if description, but no comment -> use the desc as the comment (and no desc) 01448 // if comment, but no description -> use the comment and no description 01449 01450 QString html; 01451 QString descr; 01452 QStringList comments; 01453 01454 if ( incidence->comments().isEmpty() ) { 01455 if ( !incidence->description().isEmpty() ) { 01456 // use description as comments 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 //else desc and comments are empty 01468 } else { 01469 // non-empty comments 01470 foreach ( const QString &c, incidence->comments() ) { 01471 if ( !c.isEmpty() ) { 01472 // kcalutils doesn't know about richtext comments, so we need to guess 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 // use description too 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 // Invitation details are formatted into an HTML table 01534 if ( !event ) { 01535 return QString(); 01536 } 01537 01538 QString html = htmlInvitationDetailsBegin(); 01539 html += htmlInvitationDetailsTableBegin(); 01540 01541 // Invitation summary & location rows 01542 html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) ); 01543 html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) ); 01544 01545 // If a 1 day event 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 // Invitation Duration Row 01574 html += htmlRow( i18n( "Duration:" ), durationString( event ) ); 01575 01576 // Invitation Recurrence Row 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 // Print extra info typically dependent on the iTIP 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 // If a 1 day event 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 // To-do details are formatted into an HTML table 01690 if ( !todo ) { 01691 return QString(); 01692 } 01693 01694 QString html = htmlInvitationDetailsBegin(); 01695 html += htmlInvitationDetailsTableBegin(); 01696 01697 // Invitation summary & location rows 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 // Invitation Duration Row 01717 html += htmlRow( i18n( "Duration:" ), durationString( todo ) ); 01718 01719 // Completeness 01720 if ( todo->percentComplete() > 0 ) { 01721 html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) ); 01722 } 01723 01724 // Invitation Recurrence Row 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 // Print extra info typically dependent on the iTIP 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 //@endcond 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 // use the attendee taken from the response incidence, 02425 // rather than the attendee from the calendar incidence. 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 // Attachment icon 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 //@cond PRIVATE 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 //@endcond 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 //@cond PRIVATE 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 //@endcond 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 // draw the attachment links in non-bold face 02782 QString res = QString( "<a href=\"%1\">%2</a>" ). 02783 arg( generateLinkURL( id ), text ); 02784 return res; 02785 } 02786 } 02787 02788 // Check if the given incidence is likely one that we own instead one from 02789 // a shared calendar (Kolab-specific) 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 // The open & close table cell tags for the invitation buttons 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 // Record only 02813 html += tdOpen; 02814 html += helper->makeLink( "record", i18n( "[Record]" ) ); 02815 html += tdClose; 02816 02817 // Move to trash 02818 html += tdOpen; 02819 html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) ); 02820 html += tdClose; 02821 02822 } else { 02823 02824 // Accept 02825 html += tdOpen; 02826 html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) ); 02827 html += tdClose; 02828 02829 // Tentative 02830 html += tdOpen; 02831 html += helper->makeLink( "accept_conditionally", 02832 i18nc( "Accept invitation conditionally", "Accept cond." ) ); 02833 html += tdClose; 02834 02835 // Counter proposal 02836 html += tdOpen; 02837 html += helper->makeLink( "counter", 02838 i18nc( "invitation counter proposal", "Counter proposal" ) ); 02839 html += tdClose; 02840 02841 // Decline 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 // Delegate 02850 html += tdOpen; 02851 html += helper->makeLink( "delegate", 02852 i18nc( "delegate inviation to another", "Delegate" ) ); 02853 html += tdClose; 02854 02855 // Forward 02856 html += tdOpen; 02857 html += helper->makeLink( "forward", 02858 i18nc( "forward request to another", "Forward" ) ); 02859 html += tdClose; 02860 02861 // Check calendar 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 // Accept proposal 02881 html += tdOpen; 02882 html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) ); 02883 html += tdClose; 02884 02885 // Decline proposal 02886 html += tdOpen; 02887 html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) ); 02888 html += tdClose; 02889 02890 // Check calendar 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 // parseScheduleMessage takes the tz from the calendar, 02918 // no need to set it manually here for the format! 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() ); //krazy:exclude=kdebug 02925 return QString(); 02926 } 02927 02928 IncidenceBase::Ptr incBase = msg->event(); 02929 02930 incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() ); 02931 02932 // Determine if this incidence is in my calendar (and owned by me) 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>(); // the incidence in the invitation email 02953 02954 // First make the text of the message 02955 QString html; 02956 html += "<div align=\"center\" style=\"border:solid 1px;\">"; 02957 02958 IncidenceFormatter::InvitationHeaderVisitor headerVisitor; 02959 // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled 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 ) { //use Outlook style for decline 02967 // use the Outlook 2007 Comparison Style 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 // use our "Classic" Comparison Style 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 // determine if I am the organizer for this invitation 03025 bool myInc = iamOrganizer( inc ); 03026 03027 // determine if the invitation response has already been recorded 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 // determine invitation role 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 // determine if RSVP needed, not-needed, or response already recorded 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 // Print if the organizer gave you a preset status 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 // Add groupware links 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 // Remove invitation 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 // Record invitation response 03147 Attendee::Ptr a; 03148 Attendee::Ptr ea; 03149 if ( inc ) { 03150 // First, determine if this reply is really a counter in disguise. 03151 if ( replyMeansCounter( inc ) ) { 03152 html += "<tr>" + counterButtons( inc, helper ) + "</tr>"; 03153 break; 03154 } 03155 03156 // Next, maybe this is a declined reply that was delegated from me? 03157 // find first attendee who is delegated-from me 03158 // look a their PARTSTAT response, if the response is declined, 03159 // then we need to start over which means putting all the action 03160 // buttons and NOT putting on the [Record response..] button 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 // Finally, simply allow a Record of the reply 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 // Counter proposal 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 // close the groupware table 03209 html += "</tr></table>"; 03210 03211 // Add the attendee list 03212 if ( myInc ) { 03213 html += invitationRsvpList( existingIncidence, a ); 03214 } else { 03215 html += invitationAttendeeList( inc ); 03216 } 03217 03218 // close the top-level table 03219 html += "</div>"; 03220 03221 // Add the attachment list 03222 html += invitationAttachments( helper, inc ); 03223 03224 return html; 03225 } 03226 //@endcond 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 * Helper functions for the Incidence tooltips 03251 *******************************************************************/ 03252 03253 //@cond PRIVATE 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 //FIXME: support mRichText==false 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 // to prevent 'Time: 17:00 - 17:00' 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 //FIXME: support mRichText==false 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 // Print priority and completed info here, for lack of a better place 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 //FIXME: support mRichText==false 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 //FIXME: support mRichText==false 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 //FIXME: support mRichText==false 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 // Search for a new print name, if needed. 03472 const QString printName = searchName( email, name ); 03473 03474 // Get the icon corresponding to the attendee participation status. 03475 const QString iconPath = rsvpStatusIconPath( status ); 03476 03477 // Make the return string. 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 // Search for a new print name, if needed 03495 const QString printName = searchName( email, name ); 03496 03497 // Get the icon for organizer 03498 const QString iconPath = 03499 KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small ); 03500 03501 // Make the return string. 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; // maximum number of people to print per attendee role 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 // skip not this role 03523 continue; 03524 } 03525 if ( attendeeIsOrganizer( incidence, a ) ) { 03526 // skip attendee that is also the organizer 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 // Add organizer link 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 // Show the attendee status if the incidence's organizer owns the resource calendar, 03566 // which means they are running the show and have all the up-to-date response info. 03567 bool showStatus = incOrganizerOwnsCalendar( calendar, incidence ); 03568 03569 // Add "chair" 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 // Add required participants 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 // Add optional participants 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 // Add observers 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; // maximum description chars to print (before elipsis) 03604 03605 //FIXME: support mRichText==false 03606 if ( !incidence ) { 03607 return QString(); 03608 } 03609 03610 QString tmp = "<qt>"; 03611 03612 // header 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 // TODO: truncate the description when it's rich text 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 //@endcond 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 * Helper functions for the Incidence tooltips 03700 *******************************************************************/ 03701 03702 //@cond PRIVATE 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 //@endcond 03718 03719 //@cond PRIVATE 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 // TODO: Merge these two to one of the form "Recurs every 3 days" 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 // TODO_Recurrence: What to do with all-day 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 //@endcond 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 //@cond PRIVATE 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 //@endcond 03872 03873 /************************************ 03874 * More static formatting functions 03875 ************************************/ 03876 03877 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence ) 03878 { 03879 if ( !incidence->recurs() ) { 03880 return i18n( "No recurrence" ); 03881 } 03882 QStringList dayList; 03883 dayList.append( i18n( "31st Last" ) ); 03884 dayList.append( i18n( "30th Last" ) ); 03885 dayList.append( i18n( "29th Last" ) ); 03886 dayList.append( i18n( "28th Last" ) ); 03887 dayList.append( i18n( "27th Last" ) ); 03888 dayList.append( i18n( "26th Last" ) ); 03889 dayList.append( i18n( "25th Last" ) ); 03890 dayList.append( i18n( "24th Last" ) ); 03891 dayList.append( i18n( "23rd Last" ) ); 03892 dayList.append( i18n( "22nd Last" ) ); 03893 dayList.append( i18n( "21st Last" ) ); 03894 dayList.append( i18n( "20th Last" ) ); 03895 dayList.append( i18n( "19th Last" ) ); 03896 dayList.append( i18n( "18th Last" ) ); 03897 dayList.append( i18n( "17th Last" ) ); 03898 dayList.append( i18n( "16th Last" ) ); 03899 dayList.append( i18n( "15th Last" ) ); 03900 dayList.append( i18n( "14th Last" ) ); 03901 dayList.append( i18n( "13th Last" ) ); 03902 dayList.append( i18n( "12th Last" ) ); 03903 dayList.append( i18n( "11th Last" ) ); 03904 dayList.append( i18n( "10th Last" ) ); 03905 dayList.append( i18n( "9th Last" ) ); 03906 dayList.append( i18n( "8th Last" ) ); 03907 dayList.append( i18n( "7th Last" ) ); 03908 dayList.append( i18n( "6th Last" ) ); 03909 dayList.append( i18n( "5th Last" ) ); 03910 dayList.append( i18n( "4th Last" ) ); 03911 dayList.append( i18n( "3rd Last" ) ); 03912 dayList.append( i18n( "2nd Last" ) ); 03913 dayList.append( i18nc( "last day of the month", "Last" ) ); 03914 dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI 03915 dayList.append( i18n( "1st" ) ); 03916 dayList.append( i18n( "2nd" ) ); 03917 dayList.append( i18n( "3rd" ) ); 03918 dayList.append( i18n( "4th" ) ); 03919 dayList.append( i18n( "5th" ) ); 03920 dayList.append( i18n( "6th" ) ); 03921 dayList.append( i18n( "7th" ) ); 03922 dayList.append( i18n( "8th" ) ); 03923 dayList.append( i18n( "9th" ) ); 03924 dayList.append( i18n( "10th" ) ); 03925 dayList.append( i18n( "11th" ) ); 03926 dayList.append( i18n( "12th" ) ); 03927 dayList.append( i18n( "13th" ) ); 03928 dayList.append( i18n( "14th" ) ); 03929 dayList.append( i18n( "15th" ) ); 03930 dayList.append( i18n( "16th" ) ); 03931 dayList.append( i18n( "17th" ) ); 03932 dayList.append( i18n( "18th" ) ); 03933 dayList.append( i18n( "19th" ) ); 03934 dayList.append( i18n( "20th" ) ); 03935 dayList.append( i18n( "21st" ) ); 03936 dayList.append( i18n( "22nd" ) ); 03937 dayList.append( i18n( "23rd" ) ); 03938 dayList.append( i18n( "24th" ) ); 03939 dayList.append( i18n( "25th" ) ); 03940 dayList.append( i18n( "26th" ) ); 03941 dayList.append( i18n( "27th" ) ); 03942 dayList.append( i18n( "28th" ) ); 03943 dayList.append( i18n( "29th" ) ); 03944 dayList.append( i18n( "30th" ) ); 03945 dayList.append( i18n( "31st" ) ); 03946 03947 int weekStart = KGlobal::locale()->weekStartDay(); 03948 QString dayNames; 03949 const KCalendarSystem *calSys = KGlobal::locale()->calendar(); 03950 03951 Recurrence *recur = incidence->recurrence(); 03952 03953 QString txt, recurStr; 03954 switch ( recur->recurrenceType() ) { 03955 case Recurrence::rNone: 03956 return i18n( "No recurrence" ); 03957 03958 case Recurrence::rMinutely: 03959 if ( recur->duration() != -1 ) { 03960 recurStr = i18np( "Recurs every minute until %2", 03961 "Recurs every %1 minutes until %2", 03962 recur->frequency(), recurEnd( incidence ) ); 03963 if ( recur->duration() > 0 ) { 03964 recurStr += i18nc( "number of occurrences", 03965 " (<numid>%1</numid> occurrences)", 03966 recur->duration() ); 03967 } 03968 } else { 03969 recurStr = i18np( "Recurs every minute", 03970 "Recurs every %1 minutes", recur->frequency() ); 03971 } 03972 break; 03973 03974 case Recurrence::rHourly: 03975 if ( recur->duration() != -1 ) { 03976 recurStr = i18np( "Recurs hourly until %2", 03977 "Recurs every %1 hours until %2", 03978 recur->frequency(), recurEnd( incidence ) ); 03979 if ( recur->duration() > 0 ) { 03980 recurStr += i18nc( "number of occurrences", 03981 " (<numid>%1</numid> occurrences)", 03982 recur->duration() ); 03983 } 03984 } else { 03985 recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() ); 03986 } 03987 break; 03988 03989 case Recurrence::rDaily: 03990 if ( recur->duration() != -1 ) { 03991 recurStr = i18np( "Recurs daily until %2", 03992 "Recurs every %1 days until %2", 03993 recur->frequency(), recurEnd( incidence ) ); 03994 if ( recur->duration() > 0 ) { 03995 recurStr += i18nc( "number of occurrences", 03996 " (<numid>%1</numid> occurrences)", 03997 recur->duration() ); 03998 } 03999 } else { 04000 recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() ); 04001 } 04002 break; 04003 04004 case Recurrence::rWeekly: 04005 { 04006 bool addSpace = false; 04007 for ( int i = 0; i < 7; ++i ) { 04008 if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { 04009 if ( addSpace ) { 04010 dayNames.append( i18nc( "separator for list of days", ", " ) ); 04011 } 04012 dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, 04013 KCalendarSystem::ShortDayName ) ); 04014 addSpace = true; 04015 } 04016 } 04017 if ( dayNames.isEmpty() ) { 04018 dayNames = i18nc( "Recurs weekly on no days", "no days" ); 04019 } 04020 if ( recur->duration() != -1 ) { 04021 recurStr = i18ncp( "Recurs weekly on [list of days] until end-date", 04022 "Recurs weekly on %2 until %3", 04023 "Recurs every <numid>%1</numid> weeks on %2 until %3", 04024 recur->frequency(), dayNames, recurEnd( incidence ) ); 04025 if ( recur->duration() > 0 ) { 04026 recurStr += i18nc( "number of occurrences", 04027 " (<numid>%1</numid> occurrences)", 04028 recur->duration() ); 04029 } 04030 } else { 04031 recurStr = i18ncp( "Recurs weekly on [list of days]", 04032 "Recurs weekly on %2", 04033 "Recurs every <numid>%1</numid> weeks on %2", 04034 recur->frequency(), dayNames ); 04035 } 04036 break; 04037 } 04038 case Recurrence::rMonthlyPos: 04039 { 04040 if ( !recur->monthPositions().isEmpty() ) { 04041 RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; 04042 if ( recur->duration() != -1 ) { 04043 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]" 04044 " weekdayname until end-date", 04045 "Recurs every month on the %2 %3 until %4", 04046 "Recurs every <numid>%1</numid> months on the %2 %3 until %4", 04047 recur->frequency(), 04048 dayList[rule.pos() + 31], 04049 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04050 recurEnd( incidence ) ); 04051 if ( recur->duration() > 0 ) { 04052 recurStr += i18nc( "number of occurrences", 04053 " (<numid>%1</numid> occurrences)", 04054 recur->duration() ); 04055 } 04056 } else { 04057 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname", 04058 "Recurs every month on the %2 %3", 04059 "Recurs every %1 months on the %2 %3", 04060 recur->frequency(), 04061 dayList[rule.pos() + 31], 04062 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) ); 04063 } 04064 } 04065 break; 04066 } 04067 case Recurrence::rMonthlyDay: 04068 { 04069 if ( !recur->monthDays().isEmpty() ) { 04070 int days = recur->monthDays()[0]; 04071 if ( recur->duration() != -1 ) { 04072 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date", 04073 "Recurs monthly on the %2 day until %3", 04074 "Recurs every %1 months on the %2 day until %3", 04075 recur->frequency(), 04076 dayList[days + 31], 04077 recurEnd( incidence ) ); 04078 if ( recur->duration() > 0 ) { 04079 recurStr += i18nc( "number of occurrences", 04080 " (<numid>%1</numid> occurrences)", 04081 recur->duration() ); 04082 } 04083 } else { 04084 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day", 04085 "Recurs monthly on the %2 day", 04086 "Recurs every <numid>%1</numid> month on the %2 day", 04087 recur->frequency(), 04088 dayList[days + 31] ); 04089 } 04090 } 04091 break; 04092 } 04093 case Recurrence::rYearlyMonth: 04094 { 04095 if ( recur->duration() != -1 ) { 04096 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { 04097 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" 04098 " until end-date", 04099 "Recurs yearly on %2 %3 until %4", 04100 "Recurs every %1 years on %2 %3 until %4", 04101 recur->frequency(), 04102 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), 04103 dayList[ recur->yearDates()[0] + 31 ], 04104 recurEnd( incidence ) ); 04105 if ( recur->duration() > 0 ) { 04106 recurStr += i18nc( "number of occurrences", 04107 " (<numid>%1</numid> occurrences)", 04108 recur->duration() ); 04109 } 04110 } 04111 } else { 04112 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { 04113 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]", 04114 "Recurs yearly on %2 %3", 04115 "Recurs every %1 years on %2 %3", 04116 recur->frequency(), 04117 calSys->monthName( recur->yearMonths()[0], 04118 recur->startDate().year() ), 04119 dayList[ recur->yearDates()[0] + 31 ] ); 04120 } else { 04121 if (!recur->yearMonths().isEmpty() ) { 04122 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]", 04123 "Recurs yearly on %1 %2", 04124 calSys->monthName( recur->yearMonths()[0], 04125 recur->startDate().year() ), 04126 dayList[ recur->startDate().day() + 31 ] ); 04127 } else { 04128 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]", 04129 "Recurs yearly on %1 %2", 04130 calSys->monthName( recur->startDate().month(), 04131 recur->startDate().year() ), 04132 dayList[ recur->startDate().day() + 31 ] ); 04133 } 04134 } 04135 } 04136 break; 04137 } 04138 case Recurrence::rYearlyDay: 04139 if ( !recur->yearDays().isEmpty() ) { 04140 if ( recur->duration() != -1 ) { 04141 recurStr = i18ncp( "Recurs every N years on day N until end-date", 04142 "Recurs every year on day <numid>%2</numid> until %3", 04143 "Recurs every <numid>%1</numid> years" 04144 " on day <numid>%2</numid> until %3", 04145 recur->frequency(), 04146 recur->yearDays()[0], 04147 recurEnd( incidence ) ); 04148 if ( recur->duration() > 0 ) { 04149 recurStr += i18nc( "number of occurrences", 04150 " (<numid>%1</numid> occurrences)", 04151 recur->duration() ); 04152 } 04153 } else { 04154 recurStr = i18ncp( "Recurs every N YEAR[S] on day N", 04155 "Recurs every year on day <numid>%2</numid>", 04156 "Recurs every <numid>%1</numid> years" 04157 " on day <numid>%2</numid>", 04158 recur->frequency(), recur->yearDays()[0] ); 04159 } 04160 } 04161 break; 04162 case Recurrence::rYearlyPos: 04163 { 04164 if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) { 04165 RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; 04166 if ( recur->duration() != -1 ) { 04167 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " 04168 "of monthname until end-date", 04169 "Every year on the %2 %3 of %4 until %5", 04170 "Every <numid>%1</numid> years on the %2 %3 of %4" 04171 " until %5", 04172 recur->frequency(), 04173 dayList[rule.pos() + 31], 04174 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04175 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), 04176 recurEnd( incidence ) ); 04177 if ( recur->duration() > 0 ) { 04178 recurStr += i18nc( "number of occurrences", 04179 " (<numid>%1</numid> occurrences)", 04180 recur->duration() ); 04181 } 04182 } else { 04183 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " 04184 "of monthname", 04185 "Every year on the %2 %3 of %4", 04186 "Every <numid>%1</numid> years on the %2 %3 of %4", 04187 recur->frequency(), 04188 dayList[rule.pos() + 31], 04189 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04190 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); 04191 } 04192 } 04193 } 04194 break; 04195 } 04196 04197 if ( recurStr.isEmpty() ) { 04198 recurStr = i18n( "Incidence recurs" ); 04199 } 04200 04201 // Now, append the EXDATEs 04202 DateTimeList l = recur->exDateTimes(); 04203 DateTimeList::ConstIterator il; 04204 QStringList exStr; 04205 for ( il = l.constBegin(); il != l.constEnd(); ++il ) { 04206 switch ( recur->recurrenceType() ) { 04207 case Recurrence::rMinutely: 04208 exStr << i18n( "minute %1", (*il).time().minute() ); 04209 break; 04210 case Recurrence::rHourly: 04211 exStr << KGlobal::locale()->formatTime( (*il).time() ); 04212 break; 04213 case Recurrence::rDaily: 04214 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04215 break; 04216 case Recurrence::rWeekly: 04217 exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName ); 04218 break; 04219 case Recurrence::rMonthlyPos: 04220 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04221 break; 04222 case Recurrence::rMonthlyDay: 04223 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04224 break; 04225 case Recurrence::rYearlyMonth: 04226 exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName ); 04227 break; 04228 case Recurrence::rYearlyDay: 04229 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04230 break; 04231 case Recurrence::rYearlyPos: 04232 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04233 break; 04234 } 04235 } 04236 04237 DateList d = recur->exDates(); 04238 DateList::ConstIterator dl; 04239 for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) { 04240 switch ( recur->recurrenceType() ) { 04241 case Recurrence::rDaily: 04242 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04243 break; 04244 case Recurrence::rWeekly: 04245 exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName ); 04246 break; 04247 case Recurrence::rMonthlyPos: 04248 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04249 break; 04250 case Recurrence::rMonthlyDay: 04251 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04252 break; 04253 case Recurrence::rYearlyMonth: 04254 exStr << calSys->monthName( (*dl), KCalendarSystem::LongName ); 04255 break; 04256 case Recurrence::rYearlyDay: 04257 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04258 break; 04259 case Recurrence::rYearlyPos: 04260 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04261 break; 04262 } 04263 } 04264 04265 if ( !exStr.isEmpty() ) { 04266 recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) ); 04267 } 04268 04269 return recurStr; 04270 } 04271 04272 QString IncidenceFormatter::timeToString( const KDateTime &date, 04273 bool shortfmt, 04274 const KDateTime::Spec &spec ) 04275 { 04276 if ( spec.isValid() ) { 04277 04278 QString timeZone; 04279 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04280 timeZone = ' ' + spec.timeZone().name(); 04281 } 04282 04283 return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone; 04284 } else { 04285 return KGlobal::locale()->formatTime( date.time(), !shortfmt ); 04286 } 04287 } 04288 04289 QString IncidenceFormatter::dateToString( const KDateTime &date, 04290 bool shortfmt, 04291 const KDateTime::Spec &spec ) 04292 { 04293 if ( spec.isValid() ) { 04294 04295 QString timeZone; 04296 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04297 timeZone = ' ' + spec.timeZone().name(); 04298 } 04299 04300 return 04301 KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(), 04302 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + 04303 timeZone; 04304 } else { 04305 return 04306 KGlobal::locale()->formatDate( date.date(), 04307 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); 04308 } 04309 } 04310 04311 QString IncidenceFormatter::dateTimeToString( const KDateTime &date, 04312 bool allDay, 04313 bool shortfmt, 04314 const KDateTime::Spec &spec ) 04315 { 04316 if ( allDay ) { 04317 return dateToString( date, shortfmt, spec ); 04318 } 04319 04320 if ( spec.isValid() ) { 04321 QString timeZone; 04322 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04323 timeZone = ' ' + spec.timeZone().name(); 04324 } 04325 04326 return KGlobal::locale()->formatDateTime( 04327 date.toTimeSpec( spec ).dateTime(), 04328 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; 04329 } else { 04330 return KGlobal::locale()->formatDateTime( 04331 date.dateTime(), 04332 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); 04333 } 04334 } 04335 04336 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar, 04337 const Incidence::Ptr &incidence ) 04338 { 04339 Q_UNUSED( calendar ); 04340 Q_UNUSED( incidence ); 04341 return QString(); 04342 } 04343 04344 static QString secs2Duration( int secs ) 04345 { 04346 QString tmp; 04347 int days = secs / 86400; 04348 if ( days > 0 ) { 04349 tmp += i18np( "1 day", "%1 days", days ); 04350 tmp += ' '; 04351 secs -= ( days * 86400 ); 04352 } 04353 int hours = secs / 3600; 04354 if ( hours > 0 ) { 04355 tmp += i18np( "1 hour", "%1 hours", hours ); 04356 tmp += ' '; 04357 secs -= ( hours * 3600 ); 04358 } 04359 int mins = secs / 60; 04360 if ( mins > 0 ) { 04361 tmp += i18np( "1 minute", "%1 minutes", mins ); 04362 } 04363 return tmp; 04364 } 04365 04366 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence ) 04367 { 04368 QString tmp; 04369 if ( incidence->type() == Incidence::TypeEvent ) { 04370 Event::Ptr event = incidence.staticCast<Event>(); 04371 if ( event->hasEndDate() ) { 04372 if ( !event->allDay() ) { 04373 tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) ); 04374 } else { 04375 tmp = i18np( "1 day", "%1 days", 04376 event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 ); 04377 } 04378 } else { 04379 tmp = i18n( "forever" ); 04380 } 04381 } else if ( incidence->type() == Incidence::TypeTodo ) { 04382 Todo::Ptr todo = incidence.staticCast<Todo>(); 04383 if ( todo->hasDueDate() ) { 04384 if ( todo->hasStartDate() ) { 04385 if ( !todo->allDay() ) { 04386 tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) ); 04387 } else { 04388 tmp = i18np( "1 day", "%1 days", 04389 todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 ); 04390 } 04391 } 04392 } 04393 } 04394 return tmp; 04395 } 04396 04397 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence, 04398 bool shortfmt ) 04399 { 04400 //TODO: implement shortfmt=false 04401 Q_UNUSED( shortfmt ); 04402 04403 QStringList reminderStringList; 04404 04405 if ( incidence ) { 04406 Alarm::List alarms = incidence->alarms(); 04407 Alarm::List::ConstIterator it; 04408 for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) { 04409 Alarm::Ptr alarm = *it; 04410 int offset = 0; 04411 QString remStr, atStr, offsetStr; 04412 if ( alarm->hasTime() ) { 04413 offset = 0; 04414 if ( alarm->time().isValid() ) { 04415 atStr = KGlobal::locale()->formatDateTime( alarm->time() ); 04416 } 04417 } else if ( alarm->hasStartOffset() ) { 04418 offset = alarm->startOffset().asSeconds(); 04419 if ( offset < 0 ) { 04420 offset = -offset; 04421 offsetStr = i18nc( "N days/hours/minutes before the start datetime", 04422 "%1 before the start", secs2Duration( offset ) ); 04423 } else if ( offset > 0 ) { 04424 offsetStr = i18nc( "N days/hours/minutes after the start datetime", 04425 "%1 after the start", secs2Duration( offset ) ); 04426 } else { //offset is 0 04427 if ( incidence->dtStart().isValid() ) { 04428 atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() ); 04429 } 04430 } 04431 } else if ( alarm->hasEndOffset() ) { 04432 offset = alarm->endOffset().asSeconds(); 04433 if ( offset < 0 ) { 04434 offset = -offset; 04435 if ( incidence->type() == Incidence::TypeTodo ) { 04436 offsetStr = i18nc( "N days/hours/minutes before the due datetime", 04437 "%1 before the to-do is due", secs2Duration( offset ) ); 04438 } else { 04439 offsetStr = i18nc( "N days/hours/minutes before the end datetime", 04440 "%1 before the end", secs2Duration( offset ) ); 04441 } 04442 } else if ( offset > 0 ) { 04443 if ( incidence->type() == Incidence::TypeTodo ) { 04444 offsetStr = i18nc( "N days/hours/minutes after the due datetime", 04445 "%1 after the to-do is due", secs2Duration( offset ) ); 04446 } else { 04447 offsetStr = i18nc( "N days/hours/minutes after the end datetime", 04448 "%1 after the end", secs2Duration( offset ) ); 04449 } 04450 } else { //offset is 0 04451 if ( incidence->type() == Incidence::TypeTodo ) { 04452 Todo::Ptr t = incidence.staticCast<Todo>(); 04453 if ( t->dtDue().isValid() ) { 04454 atStr = KGlobal::locale()->formatDateTime( t->dtDue() ); 04455 } 04456 } else { 04457 Event::Ptr e = incidence.staticCast<Event>(); 04458 if ( e->dtEnd().isValid() ) { 04459 atStr = KGlobal::locale()->formatDateTime( e->dtEnd() ); 04460 } 04461 } 04462 } 04463 } 04464 if ( offset == 0 ) { 04465 if ( !atStr.isEmpty() ) { 04466 remStr = i18nc( "reminder occurs at datetime", "at %1", atStr ); 04467 } 04468 } else { 04469 remStr = offsetStr; 04470 } 04471 04472 if ( alarm->repeatCount() > 0 ) { 04473 QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() ); 04474 QString intervalStr = i18nc( "interval is N days/hours/minutes", 04475 "interval is %1", 04476 secs2Duration( alarm->snoozeTime().asSeconds() ) ); 04477 QString repeatStr = i18nc( "(repeat string, interval string)", 04478 "(%1, %2)", countStr, intervalStr ); 04479 remStr = remStr + ' ' + repeatStr; 04480 04481 } 04482 reminderStringList << remStr; 04483 } 04484 } 04485 04486 return reminderStringList; 04487 }