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