KCalUtils Library
incidenceformatter.cpp
Go to the documentation of this file.
00001 /* 00002 This file is part of the kcalutils library. 00003 00004 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org> 00005 Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com> 00006 Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net> 00007 Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net> 00008 00009 This library is free software; you can redistribute it and/or 00010 modify it under the terms of the GNU Library General Public 00011 License as published by the Free Software Foundation; either 00012 version 2 of the License, or (at your option) any later version. 00013 00014 This library is distributed in the hope that it will be useful, 00015 but WITHOUT ANY WARRANTY; without even the implied warranty of 00016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00017 Library General Public License for more details. 00018 00019 You should have received a copy of the GNU Library General Public License 00020 along with this library; see the file COPYING.LIB. If not, write to 00021 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00022 Boston, MA 02110-1301, USA. 00023 */ 00036 #include "incidenceformatter.h" 00037 #include "stringify.h" 00038 00039 #include <kcalcore/event.h> 00040 #include <kcalcore/freebusy.h> 00041 #include <kcalcore/icalformat.h> 00042 #include <kcalcore/journal.h> 00043 #include <kcalcore/memorycalendar.h> 00044 #include <kcalcore/todo.h> 00045 #include <kcalcore/visitor.h> 00046 using namespace KCalCore; 00047 00048 #include <kpimutils/email.h> 00049 00050 #include <KCalendarSystem> 00051 #include <KDebug> 00052 #include <KEMailSettings> 00053 #include <KIconLoader> 00054 #include <KLocale> 00055 #include <KMimeType> 00056 #include <KSystemTimeZone> 00057 00058 #include <QtCore/QBitArray> 00059 #include <QtGui/QApplication> 00060 #include <QtGui/QPalette> 00061 #include <QtGui/QTextDocument> 00062 00063 using namespace KCalUtils; 00064 using namespace IncidenceFormatter; 00065 00066 /******************* 00067 * General helpers 00068 *******************/ 00069 00070 //@cond PRIVATE 00071 static QString htmlAddLink( const QString &ref, const QString &text, 00072 bool newline = true ) 00073 { 00074 QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" ); 00075 if ( newline ) { 00076 tmpStr += '\n'; 00077 } 00078 return tmpStr; 00079 } 00080 00081 static QString htmlAddMailtoLink( const QString &email, const QString &name ) 00082 { 00083 QString str; 00084 00085 if ( !email.isEmpty() ) { 00086 Person person( name, email ); 00087 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 !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) { 01467 comments << string2HTML( incidence->description() ); 01468 } else { 01469 if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) { 01470 comments << incidence->richDescription(); 01471 } else { 01472 comments << incidence->description(); 01473 } 01474 if ( noHtmlMode ) { 01475 comments[0] = cleanHtml( comments[0] ); 01476 } 01477 comments[0] = htmlAddTag( "p", comments[0] ); 01478 } 01479 } 01480 //else desc and comments are empty 01481 } else { 01482 // non-empty comments 01483 foreach ( const QString &c, incidence->comments() ) { 01484 if ( !c.isEmpty() ) { 01485 // kcalutils doesn't know about richtext comments, so we need to guess 01486 if ( !Qt::mightBeRichText( c ) ) { 01487 comments << string2HTML( c ); 01488 } else { 01489 if ( noHtmlMode ) { 01490 comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) ); 01491 } else { 01492 comments << c; 01493 } 01494 } 01495 } 01496 } 01497 if ( !incidence->description().isEmpty() ) { 01498 // use description too 01499 if ( !incidence->descriptionIsRich() && 01500 !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) { 01501 descr = string2HTML( incidence->description() ); 01502 } else { 01503 if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) { 01504 descr = incidence->richDescription(); 01505 } else { 01506 descr = incidence->description(); 01507 } 01508 if ( noHtmlMode ) { 01509 descr = cleanHtml( descr ); 01510 } 01511 descr = htmlAddTag( "p", descr ); 01512 } 01513 } 01514 } 01515 01516 if( !descr.isEmpty() ) { 01517 html += "<p>"; 01518 html += "<table border=\"0\" style=\"margin-top:4px;\">"; 01519 html += "<tr><td><center>" + 01520 htmlAddTag( "u", i18n( "Description:" ) ) + 01521 "</center></td></tr>"; 01522 html += "<tr><td>" + descr + "</td></tr>"; 01523 html += "</table>"; 01524 } 01525 01526 if ( !comments.isEmpty() ) { 01527 html += "<p>"; 01528 html += "<table border=\"0\" style=\"margin-top:4px;\">"; 01529 html += "<tr><td><center>" + 01530 htmlAddTag( "u", i18n( "Comments:" ) ) + 01531 "</center></td></tr>"; 01532 html += "<tr><td>"; 01533 if ( comments.count() > 1 ) { 01534 html += "<ul>"; 01535 for ( int i=0; i < comments.count(); ++i ) { 01536 html += "<li>" + comments[i] + "</li>"; 01537 } 01538 html += "</ul>"; 01539 } else { 01540 html += comments[0]; 01541 } 01542 html += "</td></tr>"; 01543 html += "</table>"; 01544 } 01545 return html; 01546 } 01547 01548 static QString invitationDetailsEvent( const Event::Ptr &event, bool noHtmlMode, 01549 KDateTime::Spec spec ) 01550 { 01551 // Invitation details are formatted into an HTML table 01552 if ( !event ) { 01553 return QString(); 01554 } 01555 01556 QString html = htmlInvitationDetailsBegin(); 01557 html += htmlInvitationDetailsTableBegin(); 01558 01559 // Invitation summary & location rows 01560 html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) ); 01561 html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) ); 01562 01563 // If a 1 day event 01564 if ( event->dtStart().date() == event->dtEnd().date() ) { 01565 html += htmlRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) ); 01566 if ( !event->allDay() ) { 01567 html += htmlRow( i18n( "Time:" ), 01568 timeToString( event->dtStart(), true, spec ) + 01569 " - " + 01570 timeToString( event->dtEnd(), true, spec ) ); 01571 } 01572 } else { 01573 html += htmlRow( i18nc( "starting date", "From:" ), 01574 dateToString( event->dtStart(), false, spec ) ); 01575 if ( !event->allDay() ) { 01576 html += htmlRow( i18nc( "starting time", "At:" ), 01577 timeToString( event->dtStart(), true, spec ) ); 01578 } 01579 if ( event->hasEndDate() ) { 01580 html += htmlRow( i18nc( "ending date", "To:" ), 01581 dateToString( event->dtEnd(), false, spec ) ); 01582 if ( !event->allDay() ) { 01583 html += htmlRow( i18nc( "ending time", "At:" ), 01584 timeToString( event->dtEnd(), true, spec ) ); 01585 } 01586 } else { 01587 html += htmlRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) ); 01588 } 01589 } 01590 01591 // Invitation Duration Row 01592 html += htmlRow( i18n( "Duration:" ), durationString( event ) ); 01593 01594 // Invitation Recurrence Row 01595 if ( event->recurs() ) { 01596 html += htmlRow( i18n( "Recurrence:" ), recurrenceString( event ) ); 01597 } 01598 01599 html += htmlInvitationDetailsTableEnd(); 01600 html += invitationDetailsIncidence( event, noHtmlMode ); 01601 html += htmlInvitationDetailsEnd(); 01602 01603 return html; 01604 } 01605 01606 static QString invitationDetailsEvent( const Event::Ptr &event, const Event::Ptr &oldevent, 01607 const ScheduleMessage::Ptr message, bool noHtmlMode, 01608 KDateTime::Spec spec ) 01609 { 01610 if ( !oldevent ) { 01611 return invitationDetailsEvent( event, noHtmlMode, spec ); 01612 } 01613 01614 QString html; 01615 01616 // Print extra info typically dependent on the iTIP 01617 if ( message->method() == iTIPDeclineCounter ) { 01618 html += "<br>"; 01619 html += invitationNote( QString(), 01620 i18n( "Please respond again to the original proposal." ), 01621 QString(), noteColor() ); 01622 } 01623 01624 html += htmlInvitationDetailsBegin(); 01625 html += htmlInvitationDetailsTableBegin(); 01626 01627 html += htmlRow( i18n( "What:" ), 01628 invitationSummary( event, noHtmlMode ), 01629 invitationSummary( oldevent, noHtmlMode ) ); 01630 01631 html += htmlRow( i18n( "Where:" ), 01632 invitationLocation( event, noHtmlMode ), 01633 invitationLocation( oldevent, noHtmlMode ) ); 01634 01635 // If a 1 day event 01636 if ( event->dtStart().date() == event->dtEnd().date() ) { 01637 html += htmlRow( i18n( "Date:" ), 01638 dateToString( event->dtStart(), false ), 01639 dateToString( oldevent->dtStart(), false ) ); 01640 QString spanStr, oldspanStr; 01641 if ( !event->allDay() ) { 01642 spanStr = timeToString( event->dtStart(), true ) + 01643 " - " + 01644 timeToString( event->dtEnd(), true ); 01645 } 01646 if ( !oldevent->allDay() ) { 01647 oldspanStr = timeToString( oldevent->dtStart(), true ) + 01648 " - " + 01649 timeToString( oldevent->dtEnd(), true ); 01650 } 01651 html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr ); 01652 } else { 01653 html += htmlRow( i18nc( "Starting date of an event", "From:" ), 01654 dateToString( event->dtStart(), false ), 01655 dateToString( oldevent->dtStart(), false ) ); 01656 QString startStr, oldstartStr; 01657 if ( !event->allDay() ) { 01658 startStr = timeToString( event->dtStart(), true ); 01659 } 01660 if ( !oldevent->allDay() ) { 01661 oldstartStr = timeToString( oldevent->dtStart(), true ); 01662 } 01663 html += htmlRow( i18nc( "Starting time of an event", "At:" ), startStr, oldstartStr ); 01664 if ( event->hasEndDate() ) { 01665 html += htmlRow( i18nc( "Ending date of an event", "To:" ), 01666 dateToString( event->dtEnd(), false ), 01667 dateToString( oldevent->dtEnd(), false ) ); 01668 QString endStr, oldendStr; 01669 if ( !event->allDay() ) { 01670 endStr = timeToString( event->dtEnd(), true ); 01671 } 01672 if ( !oldevent->allDay() ) { 01673 oldendStr = timeToString( oldevent->dtEnd(), true ); 01674 } 01675 html += htmlRow( i18nc( "Starting time of an event", "At:" ), endStr, oldendStr ); 01676 } else { 01677 QString endStr = i18n( "no end date specified" ); 01678 QString oldendStr; 01679 if ( !oldevent->hasEndDate() ) { 01680 oldendStr = i18n( "no end date specified" ); 01681 } else { 01682 oldendStr = dateTimeToString( oldevent->dtEnd(), oldevent->allDay(), false ); 01683 } 01684 html += htmlRow( i18nc( "Ending date of an event", "To:" ), endStr, oldendStr ); 01685 } 01686 } 01687 01688 html += htmlRow( i18n( "Duration:" ), durationString( event ), durationString( oldevent ) ); 01689 01690 QString recurStr, oldrecurStr; 01691 if ( event->recurs() || oldevent->recurs() ) { 01692 recurStr = recurrenceString( event ); 01693 oldrecurStr = recurrenceString( oldevent ); 01694 } 01695 html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr ); 01696 01697 html += htmlInvitationDetailsTableEnd(); 01698 html += invitationDetailsIncidence( event, noHtmlMode ); 01699 html += htmlInvitationDetailsEnd(); 01700 01701 return html; 01702 } 01703 01704 static QString invitationDetailsTodo( const Todo::Ptr &todo, bool noHtmlMode, 01705 KDateTime::Spec spec ) 01706 { 01707 // To-do details are formatted into an HTML table 01708 if ( !todo ) { 01709 return QString(); 01710 } 01711 01712 QString html = htmlInvitationDetailsBegin(); 01713 html += htmlInvitationDetailsTableBegin(); 01714 01715 // Invitation summary & location rows 01716 html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) ); 01717 html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) ); 01718 01719 if ( todo->hasStartDate() && todo->dtStart().isValid() ) { 01720 html += htmlRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) ); 01721 if ( !todo->allDay() ) { 01722 html += htmlRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) ); 01723 } 01724 } 01725 if ( todo->hasDueDate() && todo->dtDue().isValid() ) { 01726 html += htmlRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) ); 01727 if ( !todo->allDay() ) { 01728 html += htmlRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) ); 01729 } 01730 } else { 01731 html += htmlRow( i18n( "Due Date:" ), i18nc( "Due Date: None", "None" ) ); 01732 } 01733 01734 // Invitation Duration Row 01735 html += htmlRow( i18n( "Duration:" ), durationString( todo ) ); 01736 01737 // Completeness 01738 if ( todo->percentComplete() > 0 ) { 01739 html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) ); 01740 } 01741 01742 // Invitation Recurrence Row 01743 if ( todo->recurs() ) { 01744 html += htmlRow( i18n( "Recurrence:" ), recurrenceString( todo ) ); 01745 } 01746 01747 html += htmlInvitationDetailsTableEnd(); 01748 html += invitationDetailsIncidence( todo, noHtmlMode ); 01749 html += htmlInvitationDetailsEnd(); 01750 01751 return html; 01752 } 01753 01754 static QString invitationDetailsTodo( const Todo::Ptr &todo, const Todo::Ptr &oldtodo, 01755 const ScheduleMessage::Ptr message, bool noHtmlMode, 01756 KDateTime::Spec spec ) 01757 { 01758 if ( !oldtodo ) { 01759 return invitationDetailsTodo( todo, noHtmlMode, spec ); 01760 } 01761 01762 QString html; 01763 01764 // Print extra info typically dependent on the iTIP 01765 if ( message->method() == iTIPDeclineCounter ) { 01766 html += "<br>"; 01767 html += invitationNote( QString(), 01768 i18n( "Please respond again to the original proposal." ), 01769 QString(), noteColor() ); 01770 } 01771 01772 html += htmlInvitationDetailsBegin(); 01773 html += htmlInvitationDetailsTableBegin(); 01774 01775 html += htmlRow( i18n( "What:" ), 01776 invitationSummary( todo, noHtmlMode ), 01777 invitationSummary( todo, noHtmlMode ) ); 01778 01779 html += htmlRow( i18n( "Where:" ), 01780 invitationLocation( todo, noHtmlMode ), 01781 invitationLocation( oldtodo, noHtmlMode ) ); 01782 01783 if ( todo->hasStartDate() && todo->dtStart().isValid() ) { 01784 html += htmlRow( i18n( "Start Date:" ), 01785 dateToString( todo->dtStart(), false ), 01786 dateToString( oldtodo->dtStart(), false ) ); 01787 QString startTimeStr, oldstartTimeStr; 01788 if ( !todo->allDay() || !oldtodo->allDay() ) { 01789 startTimeStr = todo->allDay() ? 01790 i18n( "All day" ) : timeToString( todo->dtStart(), false ); 01791 oldstartTimeStr = oldtodo->allDay() ? 01792 i18n( "All day" ) : timeToString( oldtodo->dtStart(), false ); 01793 } 01794 html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr ); 01795 } 01796 if ( todo->hasDueDate() && todo->dtDue().isValid() ) { 01797 html += htmlRow( i18n( "Due Date:" ), 01798 dateToString( todo->dtDue(), false ), 01799 dateToString( oldtodo->dtDue(), false ) ); 01800 QString endTimeStr, oldendTimeStr; 01801 if ( !todo->allDay() || !oldtodo->allDay() ) { 01802 endTimeStr = todo->allDay() ? 01803 i18n( "All day" ) : timeToString( todo->dtDue(), false ); 01804 oldendTimeStr = oldtodo->allDay() ? 01805 i18n( "All day" ) : timeToString( oldtodo->dtDue(), false ); 01806 } 01807 html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr ); 01808 } else { 01809 QString dueStr = i18nc( "Due Date: None", "None" ); 01810 QString olddueStr; 01811 if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) { 01812 olddueStr = i18nc( "Due Date: None", "None" ); 01813 } else { 01814 olddueStr = dateTimeToString( oldtodo->dtDue(), oldtodo->allDay(), false ); 01815 } 01816 html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr ); 01817 } 01818 01819 html += htmlRow( i18n( "Duration:" ), durationString( todo ), durationString( oldtodo ) ); 01820 01821 QString completionStr, oldcompletionStr; 01822 if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) { 01823 completionStr = i18n( "%1%", todo->percentComplete() ); 01824 oldcompletionStr = i18n( "%1%", oldtodo->percentComplete() ); 01825 } 01826 html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr ); 01827 01828 QString recurStr, oldrecurStr; 01829 if ( todo->recurs() || oldtodo->recurs() ) { 01830 recurStr = recurrenceString( todo ); 01831 oldrecurStr = recurrenceString( oldtodo ); 01832 } 01833 html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr ); 01834 01835 html += htmlInvitationDetailsTableEnd(); 01836 html += invitationDetailsIncidence( todo, noHtmlMode ); 01837 01838 html += htmlInvitationDetailsEnd(); 01839 01840 return html; 01841 } 01842 01843 static QString invitationDetailsJournal( const Journal::Ptr &journal, bool noHtmlMode, 01844 KDateTime::Spec spec ) 01845 { 01846 if ( !journal ) { 01847 return QString(); 01848 } 01849 01850 QString html = htmlInvitationDetailsBegin(); 01851 html += htmlInvitationDetailsTableBegin(); 01852 01853 html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) ); 01854 html += htmlRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) ); 01855 01856 html += htmlInvitationDetailsTableEnd(); 01857 html += invitationDetailsIncidence( journal, noHtmlMode ); 01858 html += htmlInvitationDetailsEnd(); 01859 01860 return html; 01861 } 01862 01863 static QString invitationDetailsJournal( const Journal::Ptr &journal, 01864 const Journal::Ptr &oldjournal, 01865 bool noHtmlMode, KDateTime::Spec spec ) 01866 { 01867 if ( !oldjournal ) { 01868 return invitationDetailsJournal( journal, noHtmlMode, spec ); 01869 } 01870 01871 QString html = htmlInvitationDetailsBegin(); 01872 html += htmlInvitationDetailsTableBegin(); 01873 01874 html += htmlRow( i18n( "What:" ), 01875 invitationSummary( journal, noHtmlMode ), 01876 invitationSummary( oldjournal, noHtmlMode ) ); 01877 01878 html += htmlRow( i18n( "Date:" ), 01879 dateToString( journal->dtStart(), false, spec ), 01880 dateToString( oldjournal->dtStart(), false, spec ) ); 01881 01882 html += htmlInvitationDetailsTableEnd(); 01883 html += invitationDetailsIncidence( journal, noHtmlMode ); 01884 html += htmlInvitationDetailsEnd(); 01885 01886 return html; 01887 } 01888 01889 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, bool noHtmlMode, 01890 KDateTime::Spec spec ) 01891 { 01892 Q_UNUSED( noHtmlMode ); 01893 01894 if ( !fb ) { 01895 return QString(); 01896 } 01897 01898 QString html = htmlInvitationDetailsTableBegin(); 01899 01900 html += htmlRow( i18n( "Person:" ), fb->organizer()->fullName() ); 01901 html += htmlRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) ); 01902 html += htmlRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) ); 01903 01904 html += "<tr><td colspan=2><hr></td></tr>\n"; 01905 html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n"; 01906 01907 Period::List periods = fb->busyPeriods(); 01908 Period::List::iterator it; 01909 for ( it = periods.begin(); it != periods.end(); ++it ) { 01910 Period per = *it; 01911 if ( per.hasDuration() ) { 01912 int dur = per.duration().asSeconds(); 01913 QString cont; 01914 if ( dur >= 3600 ) { 01915 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 ); 01916 dur %= 3600; 01917 } 01918 if ( dur >= 60 ) { 01919 cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 ); 01920 dur %= 60; 01921 } 01922 if ( dur > 0 ) { 01923 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur ); 01924 } 01925 html += htmlRow( QString(), 01926 i18nc( "startDate for duration", "%1 for %2", 01927 KGlobal::locale()->formatDateTime( 01928 per.start().dateTime(), KLocale::LongDate ), 01929 cont ) ); 01930 } else { 01931 QString cont; 01932 if ( per.start().date() == per.end().date() ) { 01933 cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3", 01934 KGlobal::locale()->formatDate( per.start().date() ), 01935 KGlobal::locale()->formatTime( per.start().time() ), 01936 KGlobal::locale()->formatTime( per.end().time() ) ); 01937 } else { 01938 cont = i18nc( "fromDateTime - toDateTime", "%1 - %2", 01939 KGlobal::locale()->formatDateTime( 01940 per.start().dateTime(), KLocale::LongDate ), 01941 KGlobal::locale()->formatDateTime( 01942 per.end().dateTime(), KLocale::LongDate ) ); 01943 } 01944 01945 html += htmlRow( QString(), cont ); 01946 } 01947 } 01948 01949 html += htmlInvitationDetailsTableEnd(); 01950 return html; 01951 } 01952 01953 static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, const FreeBusy::Ptr &oldfb, 01954 bool noHtmlMode, KDateTime::Spec spec ) 01955 { 01956 Q_UNUSED( oldfb ); 01957 return invitationDetailsFreeBusy( fb, noHtmlMode, spec ); 01958 } 01959 01960 static bool replyMeansCounter( const Incidence::Ptr &incidence ) 01961 { 01962 Q_UNUSED( incidence ); 01963 return false; 01978 } 01979 01980 static QString invitationHeaderEvent( const Event::Ptr &event, 01981 const Incidence::Ptr &existingIncidence, 01982 ScheduleMessage::Ptr msg, const QString &sender ) 01983 { 01984 if ( !msg || !event ) { 01985 return QString(); 01986 } 01987 01988 switch ( msg->method() ) { 01989 case iTIPPublish: 01990 return i18n( "This invitation has been published" ); 01991 case iTIPRequest: 01992 if ( existingIncidence && event->revision() > 0 ) { 01993 QString orgStr = organizerName( event, sender ); 01994 if ( senderIsOrganizer( event, sender ) ) { 01995 return i18n( "This invitation has been updated by the organizer %1", orgStr ); 01996 } else { 01997 return i18n( "This invitation has been updated by %1 as a representative of %2", 01998 sender, orgStr ); 01999 } 02000 } 02001 if ( iamOrganizer( event ) ) { 02002 return i18n( "I created this invitation" ); 02003 } else { 02004 QString orgStr = organizerName( event, sender ); 02005 if ( senderIsOrganizer( event, sender ) ) { 02006 return i18n( "You received an invitation from %1", orgStr ); 02007 } else { 02008 return i18n( "You received an invitation from %1 as a representative of %2", 02009 sender, orgStr ); 02010 } 02011 } 02012 case iTIPRefresh: 02013 return i18n( "This invitation was refreshed" ); 02014 case iTIPCancel: 02015 if ( iamOrganizer( event ) ) { 02016 return i18n( "This invitation has been canceled" ); 02017 } else { 02018 return i18n( "The organizer has revoked the invitation" ); 02019 } 02020 case iTIPAdd: 02021 return i18n( "Addition to the invitation" ); 02022 case iTIPReply: 02023 { 02024 if ( replyMeansCounter( event ) ) { 02025 return i18n( "%1 makes this counter proposal", firstAttendeeName( event, sender ) ); 02026 } 02027 02028 Attendee::List attendees = event->attendees(); 02029 if( attendees.count() == 0 ) { 02030 kDebug() << "No attendees in the iCal reply!"; 02031 return QString(); 02032 } 02033 if ( attendees.count() != 1 ) { 02034 kDebug() << "Warning: attendeecount in the reply should be 1" 02035 << "but is" << attendees.count(); 02036 } 02037 QString attendeeName = firstAttendeeName( event, sender ); 02038 02039 QString delegatorName, dummy; 02040 Attendee::Ptr attendee = *attendees.begin(); 02041 KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName ); 02042 if ( delegatorName.isEmpty() ) { 02043 delegatorName = attendee->delegator(); 02044 } 02045 02046 switch( attendee->status() ) { 02047 case Attendee::NeedsAction: 02048 return i18n( "%1 indicates this invitation still needs some action", attendeeName ); 02049 case Attendee::Accepted: 02050 if ( event->revision() > 0 ) { 02051 if ( !sender.isEmpty() ) { 02052 return i18n( "This invitation has been updated by attendee %1", sender ); 02053 } else { 02054 return i18n( "This invitation has been updated by an attendee" ); 02055 } 02056 } else { 02057 if ( delegatorName.isEmpty() ) { 02058 return i18n( "%1 accepts this invitation", attendeeName ); 02059 } else { 02060 return i18n( "%1 accepts this invitation on behalf of %2", 02061 attendeeName, delegatorName ); 02062 } 02063 } 02064 case Attendee::Tentative: 02065 if ( delegatorName.isEmpty() ) { 02066 return i18n( "%1 tentatively accepts this invitation", attendeeName ); 02067 } else { 02068 return i18n( "%1 tentatively accepts this invitation on behalf of %2", 02069 attendeeName, delegatorName ); 02070 } 02071 case Attendee::Declined: 02072 if ( delegatorName.isEmpty() ) { 02073 return i18n( "%1 declines this invitation", attendeeName ); 02074 } else { 02075 return i18n( "%1 declines this invitation on behalf of %2", 02076 attendeeName, delegatorName ); 02077 } 02078 case Attendee::Delegated: 02079 { 02080 QString delegate, dummy; 02081 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); 02082 if ( delegate.isEmpty() ) { 02083 delegate = attendee->delegate(); 02084 } 02085 if ( !delegate.isEmpty() ) { 02086 return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate ); 02087 } else { 02088 return i18n( "%1 has delegated this invitation", attendeeName ); 02089 } 02090 } 02091 case Attendee::Completed: 02092 return i18n( "This invitation is now completed" ); 02093 case Attendee::InProcess: 02094 return i18n( "%1 is still processing the invitation", attendeeName ); 02095 case Attendee::None: 02096 return i18n( "Unknown response to this invitation" ); 02097 } 02098 break; 02099 } 02100 case iTIPCounter: 02101 return i18n( "%1 makes this counter proposal", 02102 firstAttendeeName( event, i18n( "Sender" ) ) ); 02103 02104 case iTIPDeclineCounter: 02105 { 02106 QString orgStr = organizerName( event, sender ); 02107 if ( senderIsOrganizer( event, sender ) ) { 02108 return i18n( "%1 declines your counter proposal", orgStr ); 02109 } else { 02110 return i18n( "%1 declines your counter proposal on behalf of %2", sender, orgStr ); 02111 } 02112 } 02113 02114 case iTIPNoMethod: 02115 return i18n( "Error: Event iTIP message with unknown method" ); 02116 } 02117 kError() << "encountered an iTIP method that we do not support"; 02118 return QString(); 02119 } 02120 02121 static QString invitationHeaderTodo( const Todo::Ptr &todo, 02122 const Incidence::Ptr &existingIncidence, 02123 ScheduleMessage::Ptr msg, const QString &sender ) 02124 { 02125 if ( !msg || !todo ) { 02126 return QString(); 02127 } 02128 02129 switch ( msg->method() ) { 02130 case iTIPPublish: 02131 return i18n( "This to-do has been published" ); 02132 case iTIPRequest: 02133 if ( existingIncidence && todo->revision() > 0 ) { 02134 QString orgStr = organizerName( todo, sender ); 02135 if ( senderIsOrganizer( todo, sender ) ) { 02136 return i18n( "This to-do has been updated by the organizer %1", orgStr ); 02137 } else { 02138 return i18n( "This to-do has been updated by %1 as a representative of %2", 02139 sender, orgStr ); 02140 } 02141 } else { 02142 if ( iamOrganizer( todo ) ) { 02143 return i18n( "I created this to-do" ); 02144 } else { 02145 QString orgStr = organizerName( todo, sender ); 02146 if ( senderIsOrganizer( todo, sender ) ) { 02147 return i18n( "You have been assigned this to-do by %1", orgStr ); 02148 } else { 02149 return i18n( "You have been assigned this to-do by %1 as a representative of %2", 02150 sender, orgStr ); 02151 } 02152 } 02153 } 02154 case iTIPRefresh: 02155 return i18n( "This to-do was refreshed" ); 02156 case iTIPCancel: 02157 if ( iamOrganizer( todo ) ) { 02158 return i18n( "This to-do was canceled" ); 02159 } else { 02160 return i18n( "The organizer has revoked this to-do" ); 02161 } 02162 case iTIPAdd: 02163 return i18n( "Addition to the to-do" ); 02164 case iTIPReply: 02165 { 02166 if ( replyMeansCounter( todo ) ) { 02167 return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) ); 02168 } 02169 02170 Attendee::List attendees = todo->attendees(); 02171 if ( attendees.count() == 0 ) { 02172 kDebug() << "No attendees in the iCal reply!"; 02173 return QString(); 02174 } 02175 if ( attendees.count() != 1 ) { 02176 kDebug() << "Warning: attendeecount in the reply should be 1" 02177 << "but is" << attendees.count(); 02178 } 02179 QString attendeeName = firstAttendeeName( todo, sender ); 02180 02181 QString delegatorName, dummy; 02182 Attendee::Ptr attendee = *attendees.begin(); 02183 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName ); 02184 if ( delegatorName.isEmpty() ) { 02185 delegatorName = attendee->delegator(); 02186 } 02187 02188 switch( attendee->status() ) { 02189 case Attendee::NeedsAction: 02190 return i18n( "%1 indicates this to-do assignment still needs some action", 02191 attendeeName ); 02192 case Attendee::Accepted: 02193 if ( todo->revision() > 0 ) { 02194 if ( !sender.isEmpty() ) { 02195 if ( todo->isCompleted() ) { 02196 return i18n( "This to-do has been completed by assignee %1", sender ); 02197 } else { 02198 return i18n( "This to-do has been updated by assignee %1", sender ); 02199 } 02200 } else { 02201 if ( todo->isCompleted() ) { 02202 return i18n( "This to-do has been completed by an assignee" ); 02203 } else { 02204 return i18n( "This to-do has been updated by an assignee" ); 02205 } 02206 } 02207 } else { 02208 if ( delegatorName.isEmpty() ) { 02209 return i18n( "%1 accepts this to-do", attendeeName ); 02210 } else { 02211 return i18n( "%1 accepts this to-do on behalf of %2", 02212 attendeeName, delegatorName ); 02213 } 02214 } 02215 case Attendee::Tentative: 02216 if ( delegatorName.isEmpty() ) { 02217 return i18n( "%1 tentatively accepts this to-do", attendeeName ); 02218 } else { 02219 return i18n( "%1 tentatively accepts this to-do on behalf of %2", 02220 attendeeName, delegatorName ); 02221 } 02222 case Attendee::Declined: 02223 if ( delegatorName.isEmpty() ) { 02224 return i18n( "%1 declines this to-do", attendeeName ); 02225 } else { 02226 return i18n( "%1 declines this to-do on behalf of %2", 02227 attendeeName, delegatorName ); 02228 } 02229 case Attendee::Delegated: 02230 { 02231 QString delegate, dummy; 02232 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate ); 02233 if ( delegate.isEmpty() ) { 02234 delegate = attendee->delegate(); 02235 } 02236 if ( !delegate.isEmpty() ) { 02237 return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate ); 02238 } else { 02239 return i18n( "%1 has delegated this to-do", attendeeName ); 02240 } 02241 } 02242 case Attendee::Completed: 02243 return i18n( "The request for this to-do is now completed" ); 02244 case Attendee::InProcess: 02245 return i18n( "%1 is still processing the to-do", attendeeName ); 02246 case Attendee::None: 02247 return i18n( "Unknown response to this to-do" ); 02248 } 02249 break; 02250 } 02251 case iTIPCounter: 02252 return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) ); 02253 02254 case iTIPDeclineCounter: 02255 { 02256 QString orgStr = organizerName( todo, sender ); 02257 if ( senderIsOrganizer( todo, sender ) ) { 02258 return i18n( "%1 declines the counter proposal", orgStr ); 02259 } else { 02260 return i18n( "%1 declines the counter proposal on behalf of %2", sender, orgStr ); 02261 } 02262 } 02263 02264 case iTIPNoMethod: 02265 return i18n( "Error: To-do iTIP message with unknown method" ); 02266 } 02267 kError() << "encountered an iTIP method that we do not support"; 02268 return QString(); 02269 } 02270 02271 static QString invitationHeaderJournal( const Journal::Ptr &journal, 02272 ScheduleMessage::Ptr msg ) 02273 { 02274 if ( !msg || !journal ) { 02275 return QString(); 02276 } 02277 02278 switch ( msg->method() ) { 02279 case iTIPPublish: 02280 return i18n( "This journal has been published" ); 02281 case iTIPRequest: 02282 return i18n( "You have been assigned this journal" ); 02283 case iTIPRefresh: 02284 return i18n( "This journal was refreshed" ); 02285 case iTIPCancel: 02286 return i18n( "This journal was canceled" ); 02287 case iTIPAdd: 02288 return i18n( "Addition to the journal" ); 02289 case iTIPReply: 02290 { 02291 if ( replyMeansCounter( journal ) ) { 02292 return i18n( "Sender makes this counter proposal" ); 02293 } 02294 02295 Attendee::List attendees = journal->attendees(); 02296 if ( attendees.count() == 0 ) { 02297 kDebug() << "No attendees in the iCal reply!"; 02298 return QString(); 02299 } 02300 if( attendees.count() != 1 ) { 02301 kDebug() << "Warning: attendeecount in the reply should be 1 " 02302 << "but is " << attendees.count(); 02303 } 02304 Attendee::Ptr attendee = *attendees.begin(); 02305 02306 switch( attendee->status() ) { 02307 case Attendee::NeedsAction: 02308 return i18n( "Sender indicates this journal assignment still needs some action" ); 02309 case Attendee::Accepted: 02310 return i18n( "Sender accepts this journal" ); 02311 case Attendee::Tentative: 02312 return i18n( "Sender tentatively accepts this journal" ); 02313 case Attendee::Declined: 02314 return i18n( "Sender declines this journal" ); 02315 case Attendee::Delegated: 02316 return i18n( "Sender has delegated this request for the journal" ); 02317 case Attendee::Completed: 02318 return i18n( "The request for this journal is now completed" ); 02319 case Attendee::InProcess: 02320 return i18n( "Sender is still processing the invitation" ); 02321 case Attendee::None: 02322 return i18n( "Unknown response to this journal" ); 02323 } 02324 break; 02325 } 02326 case iTIPCounter: 02327 return i18n( "Sender makes this counter proposal" ); 02328 case iTIPDeclineCounter: 02329 return i18n( "Sender declines the counter proposal" ); 02330 case iTIPNoMethod: 02331 return i18n( "Error: Journal iTIP message with unknown method" ); 02332 } 02333 kError() << "encountered an iTIP method that we do not support"; 02334 return QString(); 02335 } 02336 02337 static QString invitationHeaderFreeBusy( const FreeBusy::Ptr &fb, 02338 ScheduleMessage::Ptr msg ) 02339 { 02340 if ( !msg || !fb ) { 02341 return QString(); 02342 } 02343 02344 switch ( msg->method() ) { 02345 case iTIPPublish: 02346 return i18n( "This free/busy list has been published" ); 02347 case iTIPRequest: 02348 return i18n( "The free/busy list has been requested" ); 02349 case iTIPRefresh: 02350 return i18n( "This free/busy list was refreshed" ); 02351 case iTIPCancel: 02352 return i18n( "This free/busy list was canceled" ); 02353 case iTIPAdd: 02354 return i18n( "Addition to the free/busy list" ); 02355 case iTIPReply: 02356 return i18n( "Reply to the free/busy list" ); 02357 case iTIPCounter: 02358 return i18n( "Sender makes this counter proposal" ); 02359 case iTIPDeclineCounter: 02360 return i18n( "Sender declines the counter proposal" ); 02361 case iTIPNoMethod: 02362 return i18n( "Error: Free/Busy iTIP message with unknown method" ); 02363 } 02364 kError() << "encountered an iTIP method that we do not support"; 02365 return QString(); 02366 } 02367 //@endcond 02368 02369 static QString invitationAttendeeList( const Incidence::Ptr &incidence ) 02370 { 02371 QString tmpStr; 02372 if ( !incidence ) { 02373 return tmpStr; 02374 } 02375 if ( incidence->type() == Incidence::TypeTodo ) { 02376 tmpStr += i18n( "Assignees" ); 02377 } else { 02378 tmpStr += i18n( "Invitation List" ); 02379 } 02380 02381 int count=0; 02382 Attendee::List attendees = incidence->attendees(); 02383 if ( !attendees.isEmpty() ) { 02384 QStringList comments; 02385 Attendee::List::ConstIterator it; 02386 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { 02387 Attendee::Ptr a = *it; 02388 if ( !iamAttendee( a ) ) { 02389 count++; 02390 if ( count == 1 ) { 02391 tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">"; 02392 } 02393 tmpStr += "<tr>"; 02394 tmpStr += "<td>"; 02395 comments.clear(); 02396 if ( attendeeIsOrganizer( incidence, a ) ) { 02397 comments << i18n( "organizer" ); 02398 } 02399 if ( !a->delegator().isEmpty() ) { 02400 comments << i18n( " (delegated by %1)", a->delegator() ); 02401 } 02402 if ( !a->delegate().isEmpty() ) { 02403 comments << i18n( " (delegated to %1)", a->delegate() ); 02404 } 02405 tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) ); 02406 tmpStr += "</td>"; 02407 tmpStr += "</tr>"; 02408 } 02409 } 02410 } 02411 if ( count ) { 02412 tmpStr += "</table>"; 02413 } else { 02414 tmpStr.clear(); 02415 } 02416 02417 return tmpStr; 02418 } 02419 02420 static QString invitationRsvpList( const Incidence::Ptr &incidence, const Attendee::Ptr &sender ) 02421 { 02422 QString tmpStr; 02423 if ( !incidence ) { 02424 return tmpStr; 02425 } 02426 if ( incidence->type() == Incidence::TypeTodo ) { 02427 tmpStr += i18n( "Assignees" ); 02428 } else { 02429 tmpStr += i18n( "Invitation List" ); 02430 } 02431 02432 int count=0; 02433 Attendee::List attendees = incidence->attendees(); 02434 if ( !attendees.isEmpty() ) { 02435 QStringList comments; 02436 Attendee::List::ConstIterator it; 02437 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { 02438 Attendee::Ptr a = *it; 02439 if ( !attendeeIsOrganizer( incidence, a ) ) { 02440 QString statusStr = Stringify::attendeeStatus( a->status () ); 02441 if ( sender && ( a->email() == sender->email() ) ) { 02442 // use the attendee taken from the response incidence, 02443 // rather than the attendee from the calendar incidence. 02444 if ( a->status() != sender->status() ) { 02445 statusStr = i18n( "%1 (<i>unrecorded</i>)", 02446 Stringify::attendeeStatus( sender->status() ) ); 02447 } 02448 a = sender; 02449 } 02450 count++; 02451 if ( count == 1 ) { 02452 tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">"; 02453 } 02454 tmpStr += "<tr>"; 02455 tmpStr += "<td>"; 02456 comments.clear(); 02457 if ( iamAttendee( a ) ) { 02458 comments << i18n( "myself" ); 02459 } 02460 if ( !a->delegator().isEmpty() ) { 02461 comments << i18n( " (delegated by %1)", a->delegator() ); 02462 } 02463 if ( !a->delegate().isEmpty() ) { 02464 comments << i18n( " (delegated to %1)", a->delegate() ); 02465 } 02466 tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) ); 02467 tmpStr += "</td>"; 02468 tmpStr += "<td>" + statusStr + "</td>"; 02469 tmpStr += "</tr>"; 02470 } 02471 } 02472 } 02473 if ( count ) { 02474 tmpStr += "</table>"; 02475 } else { 02476 tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>"; 02477 } 02478 02479 return tmpStr; 02480 } 02481 02482 static QString invitationAttachments( InvitationFormatterHelper *helper, 02483 const Incidence::Ptr &incidence ) 02484 { 02485 QString tmpStr; 02486 if ( !incidence ) { 02487 return tmpStr; 02488 } 02489 02490 Attachment::List attachments = incidence->attachments(); 02491 if ( !attachments.isEmpty() ) { 02492 tmpStr += i18n( "Attached Documents:" ) + "<ol>"; 02493 02494 Attachment::List::ConstIterator it; 02495 for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) { 02496 Attachment::Ptr a = *it; 02497 tmpStr += "<li>"; 02498 // Attachment icon 02499 KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() ); 02500 const QString iconStr = ( mimeType ? 02501 mimeType->iconName( a->uri() ) : 02502 QString( "application-octet-stream" ) ); 02503 const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small ); 02504 if ( !iconPath.isEmpty() ) { 02505 tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">"; 02506 } 02507 tmpStr += helper->makeLink( "ATTACH:" + a->label().toUtf8().toBase64(), a->label() ); 02508 tmpStr += "</li>"; 02509 } 02510 tmpStr += "</ol>"; 02511 } 02512 02513 return tmpStr; 02514 } 02515 02516 //@cond PRIVATE 02517 class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor 02518 { 02519 public: 02520 ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; } 02521 bool act( const IncidenceBase::Ptr &incidence, 02522 const Incidence::Ptr &existingIncidence, 02523 ScheduleMessage::Ptr msg, const QString &sender ) 02524 { 02525 mExistingIncidence = existingIncidence; 02526 mMessage = msg; 02527 mSender = sender; 02528 return incidence->accept( *this, incidence ); 02529 } 02530 QString result() const { return mResult; } 02531 02532 protected: 02533 QString mResult; 02534 Incidence::Ptr mExistingIncidence; 02535 ScheduleMessage::Ptr mMessage; 02536 QString mSender; 02537 }; 02538 02539 class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor : 02540 public IncidenceFormatter::ScheduleMessageVisitor 02541 { 02542 protected: 02543 bool visit( Event::Ptr event ) 02544 { 02545 mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender ); 02546 return !mResult.isEmpty(); 02547 } 02548 bool visit( Todo::Ptr todo ) 02549 { 02550 mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender ); 02551 return !mResult.isEmpty(); 02552 } 02553 bool visit( Journal::Ptr journal ) 02554 { 02555 mResult = invitationHeaderJournal( journal, mMessage ); 02556 return !mResult.isEmpty(); 02557 } 02558 bool visit( FreeBusy::Ptr fb ) 02559 { 02560 mResult = invitationHeaderFreeBusy( fb, mMessage ); 02561 return !mResult.isEmpty(); 02562 } 02563 }; 02564 02565 class KCalUtils::IncidenceFormatter::InvitationBodyVisitor 02566 : public IncidenceFormatter::ScheduleMessageVisitor 02567 { 02568 public: 02569 InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec ) 02570 : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {} 02571 02572 protected: 02573 bool visit( Event::Ptr event ) 02574 { 02575 Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>(); 02576 mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec ); 02577 return !mResult.isEmpty(); 02578 } 02579 bool visit( Todo::Ptr todo ) 02580 { 02581 Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>(); 02582 mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec ); 02583 return !mResult.isEmpty(); 02584 } 02585 bool visit( Journal::Ptr journal ) 02586 { 02587 Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>(); 02588 mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec ); 02589 return !mResult.isEmpty(); 02590 } 02591 bool visit( FreeBusy::Ptr fb ) 02592 { 02593 mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec ); 02594 return !mResult.isEmpty(); 02595 } 02596 02597 private: 02598 bool mNoHtmlMode; 02599 KDateTime::Spec mSpec; 02600 }; 02601 //@endcond 02602 02603 InvitationFormatterHelper::InvitationFormatterHelper() 02604 : d( 0 ) 02605 { 02606 } 02607 02608 InvitationFormatterHelper::~InvitationFormatterHelper() 02609 { 02610 } 02611 02612 QString InvitationFormatterHelper::generateLinkURL( const QString &id ) 02613 { 02614 return id; 02615 } 02616 02617 //@cond PRIVATE 02618 class IncidenceFormatter::IncidenceCompareVisitor : public Visitor 02619 { 02620 public: 02621 IncidenceCompareVisitor() {} 02622 bool act( const IncidenceBase::Ptr &incidence, 02623 const Incidence::Ptr &existingIncidence ) 02624 { 02625 if ( !existingIncidence ) { 02626 return false; 02627 } 02628 Incidence::Ptr inc = incidence.staticCast<Incidence>(); 02629 if ( !inc || !existingIncidence || 02630 inc->revision() <= existingIncidence->revision() ) { 02631 return false; 02632 } 02633 mExistingIncidence = existingIncidence; 02634 return incidence->accept( *this, incidence ); 02635 } 02636 02637 QString result() const 02638 { 02639 if ( mChanges.isEmpty() ) { 02640 return QString(); 02641 } 02642 QString html = "<div align=\"left\"><ul><li>"; 02643 html += mChanges.join( "</li><li>" ); 02644 html += "</li><ul></div>"; 02645 return html; 02646 } 02647 02648 protected: 02649 bool visit( Event::Ptr event ) 02650 { 02651 compareEvents( event, mExistingIncidence.dynamicCast<Event>() ); 02652 compareIncidences( event, mExistingIncidence ); 02653 return !mChanges.isEmpty(); 02654 } 02655 bool visit( Todo::Ptr todo ) 02656 { 02657 compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() ); 02658 compareIncidences( todo, mExistingIncidence ); 02659 return !mChanges.isEmpty(); 02660 } 02661 bool visit( Journal::Ptr journal ) 02662 { 02663 compareIncidences( journal, mExistingIncidence ); 02664 return !mChanges.isEmpty(); 02665 } 02666 bool visit( FreeBusy::Ptr fb ) 02667 { 02668 Q_UNUSED( fb ); 02669 return !mChanges.isEmpty(); 02670 } 02671 02672 private: 02673 void compareEvents( const Event::Ptr &newEvent, 02674 const Event::Ptr &oldEvent ) 02675 { 02676 if ( !oldEvent || !newEvent ) { 02677 return; 02678 } 02679 if ( oldEvent->dtStart() != newEvent->dtStart() || 02680 oldEvent->allDay() != newEvent->allDay() ) { 02681 mChanges += i18n( "The invitation starting time has been changed from %1 to %2", 02682 eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) ); 02683 } 02684 if ( oldEvent->dtEnd() != newEvent->dtEnd() || 02685 oldEvent->allDay() != newEvent->allDay() ) { 02686 mChanges += i18n( "The invitation ending time has been changed from %1 to %2", 02687 eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) ); 02688 } 02689 } 02690 02691 void compareTodos( const Todo::Ptr &newTodo, 02692 const Todo::Ptr &oldTodo ) 02693 { 02694 if ( !oldTodo || !newTodo ) { 02695 return; 02696 } 02697 02698 if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) { 02699 mChanges += i18n( "The to-do has been completed" ); 02700 } 02701 if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) { 02702 mChanges += i18n( "The to-do is no longer completed" ); 02703 } 02704 if ( oldTodo->percentComplete() != newTodo->percentComplete() ) { 02705 const QString oldPer = i18n( "%1%", oldTodo->percentComplete() ); 02706 const QString newPer = i18n( "%1%", newTodo->percentComplete() ); 02707 mChanges += i18n( "The task completed percentage has changed from %1 to %2", 02708 oldPer, newPer ); 02709 } 02710 02711 if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) { 02712 mChanges += i18n( "A to-do starting time has been added" ); 02713 } 02714 if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) { 02715 mChanges += i18n( "The to-do starting time has been removed" ); 02716 } 02717 if ( oldTodo->hasStartDate() && newTodo->hasStartDate() && 02718 oldTodo->dtStart() != newTodo->dtStart() ) { 02719 mChanges += i18n( "The to-do starting time has been changed from %1 to %2", 02720 dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ), 02721 dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) ); 02722 } 02723 02724 if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) { 02725 mChanges += i18n( "A to-do due time has been added" ); 02726 } 02727 if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) { 02728 mChanges += i18n( "The to-do due time has been removed" ); 02729 } 02730 if ( oldTodo->hasDueDate() && newTodo->hasDueDate() && 02731 oldTodo->dtDue() != newTodo->dtDue() ) { 02732 mChanges += i18n( "The to-do due time has been changed from %1 to %2", 02733 dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ), 02734 dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) ); 02735 } 02736 } 02737 02738 void compareIncidences( const Incidence::Ptr &newInc, 02739 const Incidence::Ptr &oldInc ) 02740 { 02741 if ( !oldInc || !newInc ) { 02742 return; 02743 } 02744 02745 if ( oldInc->summary() != newInc->summary() ) { 02746 mChanges += i18n( "The summary has been changed to: \"%1\"", 02747 newInc->richSummary() ); 02748 } 02749 02750 if ( oldInc->location() != newInc->location() ) { 02751 mChanges += i18n( "The location has been changed to: \"%1\"", 02752 newInc->richLocation() ); 02753 } 02754 02755 if ( oldInc->description() != newInc->description() ) { 02756 mChanges += i18n( "The description has been changed to: \"%1\"", 02757 newInc->richDescription() ); 02758 } 02759 02760 Attendee::List oldAttendees = oldInc->attendees(); 02761 Attendee::List newAttendees = newInc->attendees(); 02762 for ( Attendee::List::ConstIterator it = newAttendees.constBegin(); 02763 it != newAttendees.constEnd(); ++it ) { 02764 Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() ); 02765 if ( !oldAtt ) { 02766 mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() ); 02767 } else { 02768 if ( oldAtt->status() != (*it)->status() ) { 02769 mChanges += i18n( "The status of attendee %1 has been changed to: %2", 02770 (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) ); 02771 } 02772 } 02773 } 02774 02775 for ( Attendee::List::ConstIterator it = oldAttendees.constBegin(); 02776 it != oldAttendees.constEnd(); ++it ) { 02777 if ( !attendeeIsOrganizer( oldInc, (*it) ) ) { 02778 Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() ); 02779 if ( !newAtt ) { 02780 mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() ); 02781 } 02782 } 02783 } 02784 } 02785 02786 private: 02787 Incidence::Ptr mExistingIncidence; 02788 QStringList mChanges; 02789 }; 02790 //@endcond 02791 02792 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text ) 02793 { 02794 if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) { 02795 QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ). 02796 arg( generateLinkURL( id ), text ); 02797 return res; 02798 } else { 02799 // draw the attachment links in non-bold face 02800 QString res = QString( "<a href=\"%1\">%2</a>" ). 02801 arg( generateLinkURL( id ), text ); 02802 return res; 02803 } 02804 } 02805 02806 // Check if the given incidence is likely one that we own instead one from 02807 // a shared calendar (Kolab-specific) 02808 static bool incidenceOwnedByMe( const Calendar::Ptr &calendar, 02809 const Incidence::Ptr &incidence ) 02810 { 02811 Q_UNUSED( calendar ); 02812 Q_UNUSED( incidence ); 02813 return true; 02814 } 02815 02816 // The open & close table cell tags for the invitation buttons 02817 static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">"; 02818 static QString tdClose = "</td>"; 02819 02820 static QString responseButtons( const Incidence::Ptr &inc, 02821 bool rsvpReq, bool rsvpRec, 02822 InvitationFormatterHelper *helper ) 02823 { 02824 QString html; 02825 if ( !helper ) { 02826 return html; 02827 } 02828 02829 if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) { 02830 // Record only 02831 html += tdOpen; 02832 html += helper->makeLink( "record", i18n( "[Record]" ) ); 02833 html += tdClose; 02834 02835 // Move to trash 02836 html += tdOpen; 02837 html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) ); 02838 html += tdClose; 02839 02840 } else { 02841 02842 // Accept 02843 html += tdOpen; 02844 html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) ); 02845 html += tdClose; 02846 02847 // Tentative 02848 html += tdOpen; 02849 html += helper->makeLink( "accept_conditionally", 02850 i18nc( "Accept invitation conditionally", "Accept cond." ) ); 02851 html += tdClose; 02852 02853 // Counter proposal 02854 html += tdOpen; 02855 html += helper->makeLink( "counter", 02856 i18nc( "invitation counter proposal", "Counter proposal" ) ); 02857 html += tdClose; 02858 02859 // Decline 02860 html += tdOpen; 02861 html += helper->makeLink( "decline", 02862 i18nc( "decline invitation", "Decline" ) ); 02863 html += tdClose; 02864 } 02865 02866 if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) { 02867 // Delegate 02868 html += tdOpen; 02869 html += helper->makeLink( "delegate", 02870 i18nc( "delegate inviation to another", "Delegate" ) ); 02871 html += tdClose; 02872 02873 // Forward 02874 html += tdOpen; 02875 html += helper->makeLink( "forward", 02876 i18nc( "forward request to another", "Forward" ) ); 02877 html += tdClose; 02878 02879 // Check calendar 02880 if ( inc && inc->type() == Incidence::TypeEvent ) { 02881 html += tdOpen; 02882 html += helper->makeLink( "check_calendar", 02883 i18nc( "look for scheduling conflicts", "Check my calendar" ) ); 02884 html += tdClose; 02885 } 02886 } 02887 return html; 02888 } 02889 02890 static QString counterButtons( const Incidence::Ptr &incidence, 02891 InvitationFormatterHelper *helper ) 02892 { 02893 QString html; 02894 if ( !helper ) { 02895 return html; 02896 } 02897 02898 // Accept proposal 02899 html += tdOpen; 02900 html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) ); 02901 html += tdClose; 02902 02903 // Decline proposal 02904 html += tdOpen; 02905 html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) ); 02906 html += tdClose; 02907 02908 // Check calendar 02909 if ( incidence && incidence->type() == Incidence::TypeEvent ) { 02910 html += tdOpen; 02911 html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) ); 02912 html += tdClose; 02913 } 02914 return html; 02915 } 02916 02917 Calendar::Ptr InvitationFormatterHelper::calendar() const 02918 { 02919 return Calendar::Ptr(); 02920 } 02921 02922 static QString formatICalInvitationHelper( QString invitation, 02923 const MemoryCalendar::Ptr &mCalendar, 02924 InvitationFormatterHelper *helper, 02925 bool noHtmlMode, 02926 KDateTime::Spec spec, 02927 const QString &sender, 02928 bool outlookCompareStyle ) 02929 { 02930 if ( invitation.isEmpty() ) { 02931 return QString(); 02932 } 02933 02934 ICalFormat format; 02935 // parseScheduleMessage takes the tz from the calendar, 02936 // no need to set it manually here for the format! 02937 ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation ); 02938 02939 if( !msg ) { 02940 kDebug() << "Failed to parse the scheduling message"; 02941 Q_ASSERT( format.exception() ); 02942 kDebug() << Stringify::errorMessage( *format.exception() ); //krazy:exclude=kdebug 02943 return QString(); 02944 } 02945 02946 IncidenceBase::Ptr incBase = msg->event(); 02947 02948 incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() ); 02949 02950 // Determine if this incidence is in my calendar (and owned by me) 02951 Incidence::Ptr existingIncidence; 02952 if ( incBase && helper->calendar() ) { 02953 existingIncidence = helper->calendar()->incidence( incBase->uid() ); 02954 02955 if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) { 02956 existingIncidence.clear(); 02957 } 02958 if ( !existingIncidence ) { 02959 const Incidence::List list = helper->calendar()->incidences(); 02960 for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) { 02961 if ( (*it)->schedulingID() == incBase->uid() && 02962 incidenceOwnedByMe( helper->calendar(), *it ) ) { 02963 existingIncidence = *it; 02964 break; 02965 } 02966 } 02967 } 02968 } 02969 02970 Incidence::Ptr inc = incBase.staticCast<Incidence>(); // the incidence in the invitation email 02971 02972 // First make the text of the message 02973 QString html; 02974 html += "<div align=\"center\" style=\"border:solid 1px;\">"; 02975 02976 IncidenceFormatter::InvitationHeaderVisitor headerVisitor; 02977 // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled 02978 if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) { 02979 return QString(); 02980 } 02981 html += htmlAddTag( "h3", headerVisitor.result() ); 02982 02983 if ( outlookCompareStyle || 02984 msg->method() == iTIPDeclineCounter ) { //use Outlook style for decline 02985 // use the Outlook 2007 Comparison Style 02986 IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec ); 02987 bool bodyOk; 02988 if ( msg->method() == iTIPRequest || msg->method() == iTIPReply || 02989 msg->method() == iTIPDeclineCounter ) { 02990 if ( inc && existingIncidence && 02991 inc->revision() < existingIncidence->revision() ) { 02992 bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender ); 02993 } else { 02994 bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender ); 02995 } 02996 } else { 02997 bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ); 02998 } 02999 if ( bodyOk ) { 03000 html += bodyVisitor.result(); 03001 } else { 03002 return QString(); 03003 } 03004 } else { 03005 // use our "Classic" Comparison Style 03006 InvitationBodyVisitor bodyVisitor( noHtmlMode, spec ); 03007 if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) { 03008 return QString(); 03009 } 03010 html += bodyVisitor.result(); 03011 03012 if ( msg->method() == iTIPRequest ) { 03013 IncidenceFormatter::IncidenceCompareVisitor compareVisitor; 03014 if ( compareVisitor.act( inc, existingIncidence ) ) { 03015 html += "<p align=\"left\">"; 03016 if ( senderIsOrganizer( inc, sender ) ) { 03017 html += i18n( "The following changes have been made by the organizer:" ); 03018 } else if ( !sender.isEmpty() ) { 03019 html += i18n( "The following changes have been made by %1:", sender ); 03020 } else { 03021 html += i18n( "The following changes have been made:" ); 03022 } 03023 html += "</p>"; 03024 html += compareVisitor.result(); 03025 } 03026 } 03027 if ( msg->method() == iTIPReply ) { 03028 IncidenceCompareVisitor compareVisitor; 03029 if ( compareVisitor.act( inc, existingIncidence ) ) { 03030 html += "<p align=\"left\">"; 03031 if ( !sender.isEmpty() ) { 03032 html += i18n( "The following changes have been made by %1:", sender ); 03033 } else { 03034 html += i18n( "The following changes have been made by an attendee:" ); 03035 } 03036 html += "</p>"; 03037 html += compareVisitor.result(); 03038 } 03039 } 03040 } 03041 03042 // determine if I am the organizer for this invitation 03043 bool myInc = iamOrganizer( inc ); 03044 03045 // determine if the invitation response has already been recorded 03046 bool rsvpRec = false; 03047 Attendee::Ptr ea; 03048 if ( !myInc ) { 03049 Incidence::Ptr rsvpIncidence = existingIncidence; 03050 if ( !rsvpIncidence && inc && inc->revision() > 0 ) { 03051 rsvpIncidence = inc; 03052 } 03053 if ( rsvpIncidence ) { 03054 ea = findMyAttendee( rsvpIncidence ); 03055 } 03056 if ( ea && 03057 ( ea->status() == Attendee::Accepted || 03058 ea->status() == Attendee::Declined || 03059 ea->status() == Attendee::Tentative ) ) { 03060 rsvpRec = true; 03061 } 03062 } 03063 03064 // determine invitation role 03065 QString role; 03066 bool isDelegated = false; 03067 Attendee::Ptr a = findMyAttendee( inc ); 03068 if ( !a && inc ) { 03069 if ( !inc->attendees().isEmpty() ) { 03070 a = inc->attendees().first(); 03071 } 03072 } 03073 if ( a ) { 03074 isDelegated = ( a->status() == Attendee::Delegated ); 03075 role = Stringify::attendeeRole( a->role() ); 03076 } 03077 03078 // determine if RSVP needed, not-needed, or response already recorded 03079 bool rsvpReq = rsvpRequested( inc ); 03080 if ( !myInc && a ) { 03081 html += "<br/>"; 03082 html += "<i><u>"; 03083 if ( rsvpRec && inc ) { 03084 if ( inc->revision() == 0 ) { 03085 html += i18n( "Your <b>%1</b> response has been recorded", 03086 Stringify::attendeeStatus( ea->status() ) ); 03087 } else { 03088 html += i18n( "Your status for this invitation is <b>%1</b>", 03089 Stringify::attendeeStatus( ea->status() ) ); 03090 } 03091 rsvpReq = false; 03092 } else if ( msg->method() == iTIPCancel ) { 03093 html += i18n( "This invitation was canceled" ); 03094 } else if ( msg->method() == iTIPAdd ) { 03095 html += i18n( "This invitation was accepted" ); 03096 } else if ( msg->method() == iTIPDeclineCounter ) { 03097 rsvpReq = true; 03098 html += rsvpRequestedStr( rsvpReq, role ); 03099 } else { 03100 if ( !isDelegated ) { 03101 html += rsvpRequestedStr( rsvpReq, role ); 03102 } else { 03103 html += i18n( "Awaiting delegation response" ); 03104 } 03105 } 03106 html += "</u></i>"; 03107 } 03108 03109 // Print if the organizer gave you a preset status 03110 if ( !myInc ) { 03111 if ( inc && inc->revision() == 0 ) { 03112 QString statStr = myStatusStr( inc ); 03113 if ( !statStr.isEmpty() ) { 03114 html += "<br/>"; 03115 html += "<i>"; 03116 html += statStr; 03117 html += "</i>"; 03118 } 03119 } 03120 } 03121 03122 // Add groupware links 03123 03124 html += "<p>"; 03125 html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>"; 03126 03127 switch ( msg->method() ) { 03128 case iTIPPublish: 03129 case iTIPRequest: 03130 case iTIPRefresh: 03131 case iTIPAdd: 03132 { 03133 if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) { 03134 if ( inc->type() == Incidence::TypeTodo ) { 03135 html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) ); 03136 } else { 03137 html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) ); 03138 } 03139 } 03140 03141 if ( !myInc && a ) { 03142 html += responseButtons( inc, rsvpReq, rsvpRec, helper ); 03143 } 03144 break; 03145 } 03146 03147 case iTIPCancel: 03148 // Remove invitation 03149 if ( inc ) { 03150 html += tdOpen; 03151 if ( inc->type() == Incidence::TypeTodo ) { 03152 html += helper->makeLink( "cancel", 03153 i18n( "Remove invitation from my to-do list" ) ); 03154 } else { 03155 html += helper->makeLink( "cancel", 03156 i18n( "Remove invitation from my calendar" ) ); 03157 } 03158 html += tdClose; 03159 } 03160 break; 03161 03162 case iTIPReply: 03163 { 03164 // Record invitation response 03165 Attendee::Ptr a; 03166 Attendee::Ptr ea; 03167 if ( inc ) { 03168 // First, determine if this reply is really a counter in disguise. 03169 if ( replyMeansCounter( inc ) ) { 03170 html += "<tr>" + counterButtons( inc, helper ) + "</tr>"; 03171 break; 03172 } 03173 03174 // Next, maybe this is a declined reply that was delegated from me? 03175 // find first attendee who is delegated-from me 03176 // look a their PARTSTAT response, if the response is declined, 03177 // then we need to start over which means putting all the action 03178 // buttons and NOT putting on the [Record response..] button 03179 a = findDelegatedFromMyAttendee( inc ); 03180 if ( a ) { 03181 if ( a->status() != Attendee::Accepted || 03182 a->status() != Attendee::Tentative ) { 03183 html += responseButtons( inc, rsvpReq, rsvpRec, helper ); 03184 break; 03185 } 03186 } 03187 03188 // Finally, simply allow a Record of the reply 03189 if ( !inc->attendees().isEmpty() ) { 03190 a = inc->attendees().first(); 03191 } 03192 if ( a && helper->calendar() ) { 03193 ea = findAttendee( existingIncidence, a->email() ); 03194 } 03195 } 03196 if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) { 03197 html += tdOpen; 03198 html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded", 03199 Stringify::attendeeStatus( ea->status() ) ) ); 03200 html += tdClose; 03201 } else { 03202 if ( inc ) { 03203 if ( inc->type() == Incidence::TypeTodo ) { 03204 html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) ); 03205 } else { 03206 html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) ); 03207 } 03208 } 03209 } 03210 break; 03211 } 03212 03213 case iTIPCounter: 03214 // Counter proposal 03215 html += counterButtons( inc, helper ); 03216 break; 03217 03218 case iTIPDeclineCounter: 03219 html += responseButtons( inc, rsvpReq, rsvpRec, helper ); 03220 break; 03221 03222 case iTIPNoMethod: 03223 break; 03224 } 03225 03226 // close the groupware table 03227 html += "</tr></table>"; 03228 03229 // Add the attendee list 03230 if ( myInc ) { 03231 html += invitationRsvpList( existingIncidence, a ); 03232 } else { 03233 html += invitationAttendeeList( inc ); 03234 } 03235 03236 // close the top-level table 03237 html += "</div>"; 03238 03239 // Add the attachment list 03240 html += invitationAttachments( helper, inc ); 03241 03242 return html; 03243 } 03244 //@endcond 03245 03246 QString IncidenceFormatter::formatICalInvitation( QString invitation, 03247 const MemoryCalendar::Ptr &calendar, 03248 InvitationFormatterHelper *helper, 03249 bool outlookCompareStyle ) 03250 { 03251 return formatICalInvitationHelper( invitation, calendar, helper, false, 03252 KSystemTimeZones::local(), QString(), 03253 outlookCompareStyle ); 03254 } 03255 03256 QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation, 03257 const MemoryCalendar::Ptr &calendar, 03258 InvitationFormatterHelper *helper, 03259 const QString &sender, 03260 bool outlookCompareStyle ) 03261 { 03262 return formatICalInvitationHelper( invitation, calendar, helper, true, 03263 KSystemTimeZones::local(), sender, 03264 outlookCompareStyle ); 03265 } 03266 03267 /******************************************************************* 03268 * Helper functions for the Incidence tooltips 03269 *******************************************************************/ 03270 03271 //@cond PRIVATE 03272 class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor 03273 { 03274 public: 03275 ToolTipVisitor() 03276 : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {} 03277 03278 bool act( const MemoryCalendar::Ptr &calendar, 03279 const IncidenceBase::Ptr &incidence, 03280 const QDate &date=QDate(), bool richText=true, 03281 KDateTime::Spec spec=KDateTime::Spec() ) 03282 { 03283 mCalendar = calendar; 03284 mLocation.clear(); 03285 mDate = date; 03286 mRichText = richText; 03287 mSpec = spec; 03288 mResult = ""; 03289 return incidence ? incidence->accept( *this, incidence ) : false; 03290 } 03291 03292 bool act( const QString &location, const IncidenceBase::Ptr &incidence, 03293 const QDate &date=QDate(), bool richText=true, 03294 KDateTime::Spec spec=KDateTime::Spec() ) 03295 { 03296 mLocation = location; 03297 mDate = date; 03298 mRichText = richText; 03299 mSpec = spec; 03300 mResult = ""; 03301 return incidence ? incidence->accept( *this, incidence ) : false; 03302 } 03303 03304 QString result() const { return mResult; } 03305 03306 protected: 03307 bool visit( Event::Ptr event ); 03308 bool visit( Todo::Ptr todo ); 03309 bool visit( Journal::Ptr journal ); 03310 bool visit( FreeBusy::Ptr fb ); 03311 03312 QString dateRangeText( const Event::Ptr &event, const QDate &date ); 03313 QString dateRangeText( const Todo::Ptr &todo, const QDate &date ); 03314 QString dateRangeText( const Journal::Ptr &journal ); 03315 QString dateRangeText( const FreeBusy::Ptr &fb ); 03316 03317 QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText ); 03318 03319 protected: 03320 MemoryCalendar::Ptr mCalendar; 03321 QString mLocation; 03322 QDate mDate; 03323 bool mRichText; 03324 KDateTime::Spec mSpec; 03325 QString mResult; 03326 }; 03327 03328 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event, 03329 const QDate &date ) 03330 { 03331 //FIXME: support mRichText==false 03332 QString ret; 03333 QString tmp; 03334 03335 KDateTime startDt = event->dtStart(); 03336 KDateTime endDt = event->dtEnd(); 03337 if ( event->recurs() ) { 03338 if ( date.isValid() ) { 03339 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); 03340 int diffDays = startDt.daysTo( kdt ); 03341 kdt = kdt.addSecs( -1 ); 03342 startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() ); 03343 if ( event->hasEndDate() ) { 03344 endDt = endDt.addDays( diffDays ); 03345 if ( startDt > endDt ) { 03346 startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() ); 03347 endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) ); 03348 } 03349 } 03350 } 03351 } 03352 03353 if ( event->isMultiDay() ) { 03354 tmp = dateToString( startDt, true, mSpec ); 03355 ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp ); 03356 03357 tmp = dateToString( endDt, true, mSpec ); 03358 ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp ); 03359 03360 } else { 03361 03362 ret += "<br>" + 03363 i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) ); 03364 if ( !event->allDay() ) { 03365 const QString dtStartTime = timeToString( startDt, true, mSpec ); 03366 const QString dtEndTime = timeToString( endDt, true, mSpec ); 03367 if ( dtStartTime == dtEndTime ) { 03368 // to prevent 'Time: 17:00 - 17:00' 03369 tmp = "<br>" + 03370 i18nc( "time for event", "<i>Time:</i> %1", 03371 dtStartTime ); 03372 } else { 03373 tmp = "<br>" + 03374 i18nc( "time range for event", 03375 "<i>Time:</i> %1 - %2", 03376 dtStartTime, dtEndTime ); 03377 } 03378 ret += tmp; 03379 } 03380 } 03381 return ret.replace( ' ', " " ); 03382 } 03383 03384 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo, 03385 const QDate &date ) 03386 { 03387 //FIXME: support mRichText==false 03388 QString ret; 03389 if ( todo->hasStartDate() && todo->dtStart().isValid() ) { 03390 KDateTime startDt = todo->dtStart(); 03391 if ( todo->recurs() ) { 03392 if ( date.isValid() ) { 03393 startDt.setDate( date ); 03394 } 03395 } 03396 ret += "<br>" + 03397 i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) ); 03398 } 03399 03400 if ( todo->hasDueDate() && todo->dtDue().isValid() ) { 03401 KDateTime dueDt = todo->dtDue(); 03402 if ( todo->recurs() ) { 03403 if ( date.isValid() ) { 03404 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() ); 03405 kdt = kdt.addSecs( -1 ); 03406 dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() ); 03407 } 03408 } 03409 ret += "<br>" + 03410 i18n( "<i>Due:</i> %1", 03411 dateTimeToString( dueDt, todo->allDay(), false, mSpec ) ); 03412 } 03413 03414 // Print priority and completed info here, for lack of a better place 03415 03416 if ( todo->priority() > 0 ) { 03417 ret += "<br>"; 03418 ret += "<i>" + i18n( "Priority:" ) + "</i>" + " "; 03419 ret += QString::number( todo->priority() ); 03420 } 03421 03422 ret += "<br>"; 03423 if ( todo->isCompleted() ) { 03424 ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + " "; 03425 ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', " " ); 03426 } else { 03427 ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + " "; 03428 ret += i18n( "%1%", todo->percentComplete() ); 03429 } 03430 03431 return ret.replace( ' ', " " ); 03432 } 03433 03434 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal ) 03435 { 03436 //FIXME: support mRichText==false 03437 QString ret; 03438 if ( journal->dtStart().isValid() ) { 03439 ret += "<br>" + 03440 i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) ); 03441 } 03442 return ret.replace( ' ', " " ); 03443 } 03444 03445 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb ) 03446 { 03447 //FIXME: support mRichText==false 03448 QString ret; 03449 ret = "<br>" + 03450 i18n( "<i>Period start:</i> %1", 03451 KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) ); 03452 ret += "<br>" + 03453 i18n( "<i>Period start:</i> %1", 03454 KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) ); 03455 return ret.replace( ' ', " " ); 03456 } 03457 03458 bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event ) 03459 { 03460 mResult = generateToolTip( event, dateRangeText( event, mDate ) ); 03461 return !mResult.isEmpty(); 03462 } 03463 03464 bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo ) 03465 { 03466 mResult = generateToolTip( todo, dateRangeText( todo, mDate ) ); 03467 return !mResult.isEmpty(); 03468 } 03469 03470 bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal ) 03471 { 03472 mResult = generateToolTip( journal, dateRangeText( journal ) ); 03473 return !mResult.isEmpty(); 03474 } 03475 03476 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb ) 03477 { 03478 //FIXME: support mRichText==false 03479 mResult = "<qt><b>" + 03480 i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) + 03481 "</b>"; 03482 mResult += dateRangeText( fb ); 03483 mResult += "</qt>"; 03484 return !mResult.isEmpty(); 03485 } 03486 03487 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status ) 03488 { 03489 // Search for a new print name, if needed. 03490 const QString printName = searchName( email, name ); 03491 03492 // Get the icon corresponding to the attendee participation status. 03493 const QString iconPath = rsvpStatusIconPath( status ); 03494 03495 // Make the return string. 03496 QString personString; 03497 if ( !iconPath.isEmpty() ) { 03498 personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + " "; 03499 } 03500 if ( status != Attendee::None ) { 03501 personString += i18nc( "attendee name (attendee status)", "%1 (%2)", 03502 printName.isEmpty() ? email : printName, 03503 Stringify::attendeeStatus( status ) ); 03504 } else { 03505 personString += i18n( "%1", printName.isEmpty() ? email : printName ); 03506 } 03507 return personString; 03508 } 03509 03510 static QString tooltipFormatOrganizer( const QString &email, const QString &name ) 03511 { 03512 // Search for a new print name, if needed 03513 const QString printName = searchName( email, name ); 03514 03515 // Get the icon for organizer 03516 const QString iconPath = 03517 KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small ); 03518 03519 // Make the return string. 03520 QString personString; 03521 personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + " "; 03522 personString += ( printName.isEmpty() ? email : printName ); 03523 return personString; 03524 } 03525 03526 static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence, 03527 Attendee::Role role, bool showStatus ) 03528 { 03529 int maxNumAtts = 8; // maximum number of people to print per attendee role 03530 const QString etc = i18nc( "elipsis", "..." ); 03531 03532 int i = 0; 03533 QString tmpStr; 03534 Attendee::List::ConstIterator it; 03535 Attendee::List attendees = incidence->attendees(); 03536 03537 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) { 03538 Attendee::Ptr a = *it; 03539 if ( a->role() != role ) { 03540 // skip not this role 03541 continue; 03542 } 03543 if ( attendeeIsOrganizer( incidence, a ) ) { 03544 // skip attendee that is also the organizer 03545 continue; 03546 } 03547 if ( i == maxNumAtts ) { 03548 tmpStr += " " + etc; 03549 break; 03550 } 03551 tmpStr += " " + tooltipPerson( a->email(), a->name(), 03552 showStatus ? a->status() : Attendee::None ); 03553 if ( !a->delegator().isEmpty() ) { 03554 tmpStr += i18n( " (delegated by %1)", a->delegator() ); 03555 } 03556 if ( !a->delegate().isEmpty() ) { 03557 tmpStr += i18n( " (delegated to %1)", a->delegate() ); 03558 } 03559 tmpStr += "<br>"; 03560 i++; 03561 } 03562 if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) { 03563 tmpStr.chop( 4 ); 03564 } 03565 return tmpStr; 03566 } 03567 03568 static QString tooltipFormatAttendees( const Calendar::Ptr &calendar, 03569 const Incidence::Ptr &incidence ) 03570 { 03571 QString tmpStr, str; 03572 03573 // Add organizer link 03574 int attendeeCount = incidence->attendees().count(); 03575 if ( attendeeCount > 1 || 03576 ( attendeeCount == 1 && 03577 !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) { 03578 tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>"; 03579 tmpStr += " " + tooltipFormatOrganizer( incidence->organizer()->email(), 03580 incidence->organizer()->name() ); 03581 } 03582 03583 // Show the attendee status if the incidence's organizer owns the resource calendar, 03584 // which means they are running the show and have all the up-to-date response info. 03585 const bool showStatus = attendeeCount > 0 && incOrganizerOwnsCalendar( calendar, incidence ); 03586 03587 // Add "chair" 03588 str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus ); 03589 if ( !str.isEmpty() ) { 03590 tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>"; 03591 tmpStr += str; 03592 } 03593 03594 // Add required participants 03595 str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus ); 03596 if ( !str.isEmpty() ) { 03597 tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>"; 03598 tmpStr += str; 03599 } 03600 03601 // Add optional participants 03602 str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus ); 03603 if ( !str.isEmpty() ) { 03604 tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>"; 03605 tmpStr += str; 03606 } 03607 03608 // Add observers 03609 str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus ); 03610 if ( !str.isEmpty() ) { 03611 tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>"; 03612 tmpStr += str; 03613 } 03614 03615 return tmpStr; 03616 } 03617 03618 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence, 03619 QString dtRangeText ) 03620 { 03621 int maxDescLen = 120; // maximum description chars to print (before elipsis) 03622 03623 //FIXME: support mRichText==false 03624 if ( !incidence ) { 03625 return QString(); 03626 } 03627 03628 QString tmp = "<qt>"; 03629 03630 // header 03631 tmp += "<b>" + incidence->richSummary() + "</b>"; 03632 tmp += "<hr>"; 03633 03634 QString calStr = mLocation; 03635 if ( mCalendar ) { 03636 calStr = resourceString( mCalendar, incidence ); 03637 } 03638 if ( !calStr.isEmpty() ) { 03639 tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + " "; 03640 tmp += calStr; 03641 } 03642 03643 tmp += dtRangeText; 03644 03645 if ( !incidence->location().isEmpty() ) { 03646 tmp += "<br>"; 03647 tmp += "<i>" + i18n( "Location:" ) + "</i>" + " "; 03648 tmp += incidence->richLocation(); 03649 } 03650 03651 QString durStr = durationString( incidence ); 03652 if ( !durStr.isEmpty() ) { 03653 tmp += "<br>"; 03654 tmp += "<i>" + i18n( "Duration:" ) + "</i>" + " "; 03655 tmp += durStr; 03656 } 03657 03658 if ( incidence->recurs() ) { 03659 tmp += "<br>"; 03660 tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + " "; 03661 tmp += recurrenceString( incidence ); 03662 } 03663 03664 if ( !incidence->description().isEmpty() ) { 03665 QString desc( incidence->description() ); 03666 if ( !incidence->descriptionIsRich() ) { 03667 if ( desc.length() > maxDescLen ) { 03668 desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." ); 03669 } 03670 desc = Qt::escape( desc ).replace( '\n', "<br>" ); 03671 } else { 03672 // TODO: truncate the description when it's rich text 03673 } 03674 tmp += "<hr>"; 03675 tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>"; 03676 tmp += desc; 03677 tmp += "<hr>"; 03678 } 03679 03680 int reminderCount = incidence->alarms().count(); 03681 if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) { 03682 tmp += "<br>"; 03683 tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + " "; 03684 tmp += reminderStringList( incidence ).join( ", " ); 03685 } 03686 03687 tmp += "<br>"; 03688 tmp += tooltipFormatAttendees( mCalendar, incidence ); 03689 03690 int categoryCount = incidence->categories().count(); 03691 if ( categoryCount > 0 ) { 03692 tmp += "<br>"; 03693 tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + " "; 03694 tmp += incidence->categories().join( ", " ); 03695 } 03696 03697 tmp += "</qt>"; 03698 return tmp; 03699 } 03700 //@endcond 03701 03702 QString IncidenceFormatter::toolTipStr( const QString &sourceName, 03703 const IncidenceBase::Ptr &incidence, 03704 const QDate &date, 03705 bool richText, 03706 KDateTime::Spec spec ) 03707 { 03708 ToolTipVisitor v; 03709 if ( v.act( sourceName, incidence, date, richText, spec ) ) { 03710 return v.result(); 03711 } else { 03712 return QString(); 03713 } 03714 } 03715 03716 /******************************************************************* 03717 * Helper functions for the Incidence tooltips 03718 *******************************************************************/ 03719 03720 //@cond PRIVATE 03721 static QString mailBodyIncidence( const Incidence::Ptr &incidence ) 03722 { 03723 QString body; 03724 if ( !incidence->summary().isEmpty() ) { 03725 body += i18n( "Summary: %1\n", incidence->richSummary() ); 03726 } 03727 if ( !incidence->organizer()->isEmpty() ) { 03728 body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() ); 03729 } 03730 if ( !incidence->location().isEmpty() ) { 03731 body += i18n( "Location: %1\n", incidence->richLocation() ); 03732 } 03733 return body; 03734 } 03735 //@endcond 03736 03737 //@cond PRIVATE 03738 class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor 03739 { 03740 public: 03741 MailBodyVisitor() 03742 : mSpec( KDateTime::Spec() ), mResult( "" ) {} 03743 03744 bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() ) 03745 { 03746 mSpec = spec; 03747 mResult = ""; 03748 return incidence ? incidence->accept( *this, incidence ) : false; 03749 } 03750 QString result() const 03751 { 03752 return mResult; 03753 } 03754 03755 protected: 03756 bool visit( Event::Ptr event ); 03757 bool visit( Todo::Ptr todo ); 03758 bool visit( Journal::Ptr journal ); 03759 bool visit( FreeBusy::Ptr ) 03760 { 03761 mResult = i18n( "This is a Free Busy Object" ); 03762 return !mResult.isEmpty(); 03763 } 03764 protected: 03765 KDateTime::Spec mSpec; 03766 QString mResult; 03767 }; 03768 03769 bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event ) 03770 { 03771 QString recurrence[]= { 03772 i18nc( "no recurrence", "None" ), 03773 i18nc( "event recurs by minutes", "Minutely" ), 03774 i18nc( "event recurs by hours", "Hourly" ), 03775 i18nc( "event recurs by days", "Daily" ), 03776 i18nc( "event recurs by weeks", "Weekly" ), 03777 i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ), 03778 i18nc( "event recurs same day each month", "Monthly Same Day" ), 03779 i18nc( "event recurs same month each year", "Yearly Same Month" ), 03780 i18nc( "event recurs same day each year", "Yearly Same Day" ), 03781 i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" ) 03782 }; 03783 03784 mResult = mailBodyIncidence( event ); 03785 mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) ); 03786 if ( !event->allDay() ) { 03787 mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) ); 03788 } 03789 if ( event->dtStart() != event->dtEnd() ) { 03790 mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) ); 03791 } 03792 if ( !event->allDay() ) { 03793 mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) ); 03794 } 03795 if ( event->recurs() ) { 03796 Recurrence *recur = event->recurrence(); 03797 // TODO: Merge these two to one of the form "Recurs every 3 days" 03798 mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] ); 03799 mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() ); 03800 03801 if ( recur->duration() > 0 ) { 03802 mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() ); 03803 mResult += '\n'; 03804 } else { 03805 if ( recur->duration() != -1 ) { 03806 // TODO_Recurrence: What to do with all-day 03807 QString endstr; 03808 if ( event->allDay() ) { 03809 endstr = KGlobal::locale()->formatDate( recur->endDate() ); 03810 } else { 03811 endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() ); 03812 } 03813 mResult += i18n( "Repeat until: %1\n", endstr ); 03814 } else { 03815 mResult += i18n( "Repeats forever\n" ); 03816 } 03817 } 03818 } 03819 03820 QString details = event->richDescription(); 03821 if ( !details.isEmpty() ) { 03822 mResult += i18n( "Details:\n%1\n", details ); 03823 } 03824 return !mResult.isEmpty(); 03825 } 03826 03827 bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo ) 03828 { 03829 mResult = mailBodyIncidence( todo ); 03830 03831 if ( todo->hasStartDate() && todo->dtStart().isValid() ) { 03832 mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) ); 03833 if ( !todo->allDay() ) { 03834 mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) ); 03835 } 03836 } 03837 if ( todo->hasDueDate() && todo->dtDue().isValid() ) { 03838 mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) ); 03839 if ( !todo->allDay() ) { 03840 mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) ); 03841 } 03842 } 03843 QString details = todo->richDescription(); 03844 if ( !details.isEmpty() ) { 03845 mResult += i18n( "Details:\n%1\n", details ); 03846 } 03847 return !mResult.isEmpty(); 03848 } 03849 03850 bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal ) 03851 { 03852 mResult = mailBodyIncidence( journal ); 03853 mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) ); 03854 if ( !journal->allDay() ) { 03855 mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) ); 03856 } 03857 if ( !journal->description().isEmpty() ) { 03858 mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() ); 03859 } 03860 return !mResult.isEmpty(); 03861 } 03862 //@endcond 03863 03864 QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence, 03865 KDateTime::Spec spec ) 03866 { 03867 if ( !incidence ) { 03868 return QString(); 03869 } 03870 03871 MailBodyVisitor v; 03872 if ( v.act( incidence, spec ) ) { 03873 return v.result(); 03874 } 03875 return QString(); 03876 } 03877 03878 //@cond PRIVATE 03879 static QString recurEnd( const Incidence::Ptr &incidence ) 03880 { 03881 QString endstr; 03882 if ( incidence->allDay() ) { 03883 endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() ); 03884 } else { 03885 endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() ); 03886 } 03887 return endstr; 03888 } 03889 //@endcond 03890 03891 /************************************ 03892 * More static formatting functions 03893 ************************************/ 03894 03895 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence ) 03896 { 03897 if ( !incidence->recurs() ) { 03898 return i18n( "No recurrence" ); 03899 } 03900 static QStringList dayList; 03901 if ( dayList.isEmpty() ) { 03902 dayList.append( i18n( "31st Last" ) ); 03903 dayList.append( i18n( "30th Last" ) ); 03904 dayList.append( i18n( "29th Last" ) ); 03905 dayList.append( i18n( "28th Last" ) ); 03906 dayList.append( i18n( "27th Last" ) ); 03907 dayList.append( i18n( "26th Last" ) ); 03908 dayList.append( i18n( "25th Last" ) ); 03909 dayList.append( i18n( "24th Last" ) ); 03910 dayList.append( i18n( "23rd Last" ) ); 03911 dayList.append( i18n( "22nd Last" ) ); 03912 dayList.append( i18n( "21st Last" ) ); 03913 dayList.append( i18n( "20th Last" ) ); 03914 dayList.append( i18n( "19th Last" ) ); 03915 dayList.append( i18n( "18th Last" ) ); 03916 dayList.append( i18n( "17th Last" ) ); 03917 dayList.append( i18n( "16th Last" ) ); 03918 dayList.append( i18n( "15th Last" ) ); 03919 dayList.append( i18n( "14th Last" ) ); 03920 dayList.append( i18n( "13th Last" ) ); 03921 dayList.append( i18n( "12th Last" ) ); 03922 dayList.append( i18n( "11th Last" ) ); 03923 dayList.append( i18n( "10th Last" ) ); 03924 dayList.append( i18n( "9th Last" ) ); 03925 dayList.append( i18n( "8th Last" ) ); 03926 dayList.append( i18n( "7th Last" ) ); 03927 dayList.append( i18n( "6th Last" ) ); 03928 dayList.append( i18n( "5th Last" ) ); 03929 dayList.append( i18n( "4th Last" ) ); 03930 dayList.append( i18n( "3rd Last" ) ); 03931 dayList.append( i18n( "2nd Last" ) ); 03932 dayList.append( i18nc( "last day of the month", "Last" ) ); 03933 dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI 03934 dayList.append( i18n( "1st" ) ); 03935 dayList.append( i18n( "2nd" ) ); 03936 dayList.append( i18n( "3rd" ) ); 03937 dayList.append( i18n( "4th" ) ); 03938 dayList.append( i18n( "5th" ) ); 03939 dayList.append( i18n( "6th" ) ); 03940 dayList.append( i18n( "7th" ) ); 03941 dayList.append( i18n( "8th" ) ); 03942 dayList.append( i18n( "9th" ) ); 03943 dayList.append( i18n( "10th" ) ); 03944 dayList.append( i18n( "11th" ) ); 03945 dayList.append( i18n( "12th" ) ); 03946 dayList.append( i18n( "13th" ) ); 03947 dayList.append( i18n( "14th" ) ); 03948 dayList.append( i18n( "15th" ) ); 03949 dayList.append( i18n( "16th" ) ); 03950 dayList.append( i18n( "17th" ) ); 03951 dayList.append( i18n( "18th" ) ); 03952 dayList.append( i18n( "19th" ) ); 03953 dayList.append( i18n( "20th" ) ); 03954 dayList.append( i18n( "21st" ) ); 03955 dayList.append( i18n( "22nd" ) ); 03956 dayList.append( i18n( "23rd" ) ); 03957 dayList.append( i18n( "24th" ) ); 03958 dayList.append( i18n( "25th" ) ); 03959 dayList.append( i18n( "26th" ) ); 03960 dayList.append( i18n( "27th" ) ); 03961 dayList.append( i18n( "28th" ) ); 03962 dayList.append( i18n( "29th" ) ); 03963 dayList.append( i18n( "30th" ) ); 03964 dayList.append( i18n( "31st" ) ); 03965 } 03966 03967 const int weekStart = KGlobal::locale()->weekStartDay(); 03968 QString dayNames; 03969 const KCalendarSystem *calSys = KGlobal::locale()->calendar(); 03970 03971 Recurrence *recur = incidence->recurrence(); 03972 03973 QString txt, recurStr; 03974 static QString noRecurrence = i18n( "No recurrence" ); 03975 switch ( recur->recurrenceType() ) { 03976 case Recurrence::rNone: 03977 return noRecurrence; 03978 03979 case Recurrence::rMinutely: 03980 if ( recur->duration() != -1 ) { 03981 recurStr = i18np( "Recurs every minute until %2", 03982 "Recurs every %1 minutes until %2", 03983 recur->frequency(), recurEnd( incidence ) ); 03984 if ( recur->duration() > 0 ) { 03985 recurStr += i18nc( "number of occurrences", 03986 " (<numid>%1</numid> occurrences)", 03987 recur->duration() ); 03988 } 03989 } else { 03990 recurStr = i18np( "Recurs every minute", 03991 "Recurs every %1 minutes", recur->frequency() ); 03992 } 03993 break; 03994 03995 case Recurrence::rHourly: 03996 if ( recur->duration() != -1 ) { 03997 recurStr = i18np( "Recurs hourly until %2", 03998 "Recurs every %1 hours until %2", 03999 recur->frequency(), recurEnd( incidence ) ); 04000 if ( recur->duration() > 0 ) { 04001 recurStr += i18nc( "number of occurrences", 04002 " (<numid>%1</numid> occurrences)", 04003 recur->duration() ); 04004 } 04005 } else { 04006 recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() ); 04007 } 04008 break; 04009 04010 case Recurrence::rDaily: 04011 if ( recur->duration() != -1 ) { 04012 recurStr = i18np( "Recurs daily until %2", 04013 "Recurs every %1 days until %2", 04014 recur->frequency(), recurEnd( incidence ) ); 04015 if ( recur->duration() > 0 ) { 04016 recurStr += i18nc( "number of occurrences", 04017 " (<numid>%1</numid> occurrences)", 04018 recur->duration() ); 04019 } 04020 } else { 04021 recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() ); 04022 } 04023 break; 04024 04025 case Recurrence::rWeekly: 04026 { 04027 bool addSpace = false; 04028 for ( int i = 0; i < 7; ++i ) { 04029 if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) { 04030 if ( addSpace ) { 04031 dayNames.append( i18nc( "separator for list of days", ", " ) ); 04032 } 04033 dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1, 04034 KCalendarSystem::ShortDayName ) ); 04035 addSpace = true; 04036 } 04037 } 04038 if ( dayNames.isEmpty() ) { 04039 dayNames = i18nc( "Recurs weekly on no days", "no days" ); 04040 } 04041 if ( recur->duration() != -1 ) { 04042 recurStr = i18ncp( "Recurs weekly on [list of days] until end-date", 04043 "Recurs weekly on %2 until %3", 04044 "Recurs every <numid>%1</numid> weeks on %2 until %3", 04045 recur->frequency(), dayNames, recurEnd( incidence ) ); 04046 if ( recur->duration() > 0 ) { 04047 recurStr += i18nc( "number of occurrences", 04048 " (<numid>%1</numid> occurrences)", 04049 recur->duration() ); 04050 } 04051 } else { 04052 recurStr = i18ncp( "Recurs weekly on [list of days]", 04053 "Recurs weekly on %2", 04054 "Recurs every <numid>%1</numid> weeks on %2", 04055 recur->frequency(), dayNames ); 04056 } 04057 break; 04058 } 04059 case Recurrence::rMonthlyPos: 04060 { 04061 if ( !recur->monthPositions().isEmpty() ) { 04062 RecurrenceRule::WDayPos rule = recur->monthPositions()[0]; 04063 if ( recur->duration() != -1 ) { 04064 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]" 04065 " weekdayname until end-date", 04066 "Recurs every month on the %2 %3 until %4", 04067 "Recurs every <numid>%1</numid> months on the %2 %3 until %4", 04068 recur->frequency(), 04069 dayList[rule.pos() + 31], 04070 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04071 recurEnd( incidence ) ); 04072 if ( recur->duration() > 0 ) { 04073 recurStr += i18nc( "number of occurrences", 04074 " (<numid>%1</numid> occurrences)", 04075 recur->duration() ); 04076 } 04077 } else { 04078 recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname", 04079 "Recurs every month on the %2 %3", 04080 "Recurs every %1 months on the %2 %3", 04081 recur->frequency(), 04082 dayList[rule.pos() + 31], 04083 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) ); 04084 } 04085 } 04086 break; 04087 } 04088 case Recurrence::rMonthlyDay: 04089 { 04090 if ( !recur->monthDays().isEmpty() ) { 04091 int days = recur->monthDays()[0]; 04092 if ( recur->duration() != -1 ) { 04093 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date", 04094 "Recurs monthly on the %2 day until %3", 04095 "Recurs every %1 months on the %2 day until %3", 04096 recur->frequency(), 04097 dayList[days + 31], 04098 recurEnd( incidence ) ); 04099 if ( recur->duration() > 0 ) { 04100 recurStr += i18nc( "number of occurrences", 04101 " (<numid>%1</numid> occurrences)", 04102 recur->duration() ); 04103 } 04104 } else { 04105 recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day", 04106 "Recurs monthly on the %2 day", 04107 "Recurs every <numid>%1</numid> month on the %2 day", 04108 recur->frequency(), 04109 dayList[days + 31] ); 04110 } 04111 } 04112 break; 04113 } 04114 case Recurrence::rYearlyMonth: 04115 { 04116 if ( recur->duration() != -1 ) { 04117 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { 04118 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]" 04119 " until end-date", 04120 "Recurs yearly on %2 %3 until %4", 04121 "Recurs every %1 years on %2 %3 until %4", 04122 recur->frequency(), 04123 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), 04124 dayList[ recur->yearDates()[0] + 31 ], 04125 recurEnd( incidence ) ); 04126 if ( recur->duration() > 0 ) { 04127 recurStr += i18nc( "number of occurrences", 04128 " (<numid>%1</numid> occurrences)", 04129 recur->duration() ); 04130 } 04131 } 04132 } else { 04133 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) { 04134 recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]", 04135 "Recurs yearly on %2 %3", 04136 "Recurs every %1 years on %2 %3", 04137 recur->frequency(), 04138 calSys->monthName( recur->yearMonths()[0], 04139 recur->startDate().year() ), 04140 dayList[ recur->yearDates()[0] + 31 ] ); 04141 } else { 04142 if (!recur->yearMonths().isEmpty() ) { 04143 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]", 04144 "Recurs yearly on %1 %2", 04145 calSys->monthName( recur->yearMonths()[0], 04146 recur->startDate().year() ), 04147 dayList[ recur->startDate().day() + 31 ] ); 04148 } else { 04149 recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]", 04150 "Recurs yearly on %1 %2", 04151 calSys->monthName( recur->startDate().month(), 04152 recur->startDate().year() ), 04153 dayList[ recur->startDate().day() + 31 ] ); 04154 } 04155 } 04156 } 04157 break; 04158 } 04159 case Recurrence::rYearlyDay: 04160 if ( !recur->yearDays().isEmpty() ) { 04161 if ( recur->duration() != -1 ) { 04162 recurStr = i18ncp( "Recurs every N years on day N until end-date", 04163 "Recurs every year on day <numid>%2</numid> until %3", 04164 "Recurs every <numid>%1</numid> years" 04165 " on day <numid>%2</numid> until %3", 04166 recur->frequency(), 04167 recur->yearDays()[0], 04168 recurEnd( incidence ) ); 04169 if ( recur->duration() > 0 ) { 04170 recurStr += i18nc( "number of occurrences", 04171 " (<numid>%1</numid> occurrences)", 04172 recur->duration() ); 04173 } 04174 } else { 04175 recurStr = i18ncp( "Recurs every N YEAR[S] on day N", 04176 "Recurs every year on day <numid>%2</numid>", 04177 "Recurs every <numid>%1</numid> years" 04178 " on day <numid>%2</numid>", 04179 recur->frequency(), recur->yearDays()[0] ); 04180 } 04181 } 04182 break; 04183 case Recurrence::rYearlyPos: 04184 { 04185 if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) { 04186 RecurrenceRule::WDayPos rule = recur->yearPositions()[0]; 04187 if ( recur->duration() != -1 ) { 04188 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " 04189 "of monthname until end-date", 04190 "Every year on the %2 %3 of %4 until %5", 04191 "Every <numid>%1</numid> years on the %2 %3 of %4" 04192 " until %5", 04193 recur->frequency(), 04194 dayList[rule.pos() + 31], 04195 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04196 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ), 04197 recurEnd( incidence ) ); 04198 if ( recur->duration() > 0 ) { 04199 recurStr += i18nc( "number of occurrences", 04200 " (<numid>%1</numid> occurrences)", 04201 recur->duration() ); 04202 } 04203 } else { 04204 recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname " 04205 "of monthname", 04206 "Every year on the %2 %3 of %4", 04207 "Every <numid>%1</numid> years on the %2 %3 of %4", 04208 recur->frequency(), 04209 dayList[rule.pos() + 31], 04210 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ), 04211 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) ); 04212 } 04213 } 04214 } 04215 break; 04216 } 04217 04218 if ( recurStr.isEmpty() ) { 04219 recurStr = i18n( "Incidence recurs" ); 04220 } 04221 04222 // Now, append the EXDATEs 04223 DateTimeList l = recur->exDateTimes(); 04224 DateTimeList::ConstIterator il; 04225 QStringList exStr; 04226 for ( il = l.constBegin(); il != l.constEnd(); ++il ) { 04227 switch ( recur->recurrenceType() ) { 04228 case Recurrence::rMinutely: 04229 exStr << i18n( "minute %1", (*il).time().minute() ); 04230 break; 04231 case Recurrence::rHourly: 04232 exStr << KGlobal::locale()->formatTime( (*il).time() ); 04233 break; 04234 case Recurrence::rDaily: 04235 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04236 break; 04237 case Recurrence::rWeekly: 04238 exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName ); 04239 break; 04240 case Recurrence::rMonthlyPos: 04241 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04242 break; 04243 case Recurrence::rMonthlyDay: 04244 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04245 break; 04246 case Recurrence::rYearlyMonth: 04247 exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName ); 04248 break; 04249 case Recurrence::rYearlyDay: 04250 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04251 break; 04252 case Recurrence::rYearlyPos: 04253 exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate ); 04254 break; 04255 } 04256 } 04257 04258 DateList d = recur->exDates(); 04259 DateList::ConstIterator dl; 04260 for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) { 04261 switch ( recur->recurrenceType() ) { 04262 case Recurrence::rDaily: 04263 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04264 break; 04265 case Recurrence::rWeekly: 04266 // exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName ); 04267 // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr ) 04268 if ( exStr.isEmpty() ) { 04269 exStr << i18np( "1 day", "%1 days", recur->exDates().count() ); 04270 } 04271 break; 04272 case Recurrence::rMonthlyPos: 04273 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04274 break; 04275 case Recurrence::rMonthlyDay: 04276 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04277 break; 04278 case Recurrence::rYearlyMonth: 04279 exStr << calSys->monthName( (*dl), KCalendarSystem::LongName ); 04280 break; 04281 case Recurrence::rYearlyDay: 04282 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04283 break; 04284 case Recurrence::rYearlyPos: 04285 exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate ); 04286 break; 04287 } 04288 } 04289 04290 if ( !exStr.isEmpty() ) { 04291 recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) ); 04292 } 04293 04294 return recurStr; 04295 } 04296 04297 QString IncidenceFormatter::timeToString( const KDateTime &date, 04298 bool shortfmt, 04299 const KDateTime::Spec &spec ) 04300 { 04301 if ( spec.isValid() ) { 04302 04303 QString timeZone; 04304 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04305 timeZone = ' ' + spec.timeZone().name(); 04306 } 04307 04308 return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone; 04309 } else { 04310 return KGlobal::locale()->formatTime( date.time(), !shortfmt ); 04311 } 04312 } 04313 04314 QString IncidenceFormatter::dateToString( const KDateTime &date, 04315 bool shortfmt, 04316 const KDateTime::Spec &spec ) 04317 { 04318 if ( spec.isValid() ) { 04319 04320 QString timeZone; 04321 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04322 timeZone = ' ' + spec.timeZone().name(); 04323 } 04324 04325 return 04326 KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(), 04327 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + 04328 timeZone; 04329 } else { 04330 return 04331 KGlobal::locale()->formatDate( date.date(), 04332 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); 04333 } 04334 } 04335 04336 QString IncidenceFormatter::dateTimeToString( const KDateTime &date, 04337 bool allDay, 04338 bool shortfmt, 04339 const KDateTime::Spec &spec ) 04340 { 04341 if ( allDay ) { 04342 return dateToString( date, shortfmt, spec ); 04343 } 04344 04345 if ( spec.isValid() ) { 04346 QString timeZone; 04347 if ( spec.timeZone() != KSystemTimeZones::local() ) { 04348 timeZone = ' ' + spec.timeZone().name(); 04349 } 04350 04351 return KGlobal::locale()->formatDateTime( 04352 date.toTimeSpec( spec ).dateTime(), 04353 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; 04354 } else { 04355 return KGlobal::locale()->formatDateTime( 04356 date.dateTime(), 04357 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); 04358 } 04359 } 04360 04361 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar, 04362 const Incidence::Ptr &incidence ) 04363 { 04364 Q_UNUSED( calendar ); 04365 Q_UNUSED( incidence ); 04366 return QString(); 04367 } 04368 04369 static QString secs2Duration( int secs ) 04370 { 04371 QString tmp; 04372 int days = secs / 86400; 04373 if ( days > 0 ) { 04374 tmp += i18np( "1 day", "%1 days", days ); 04375 tmp += ' '; 04376 secs -= ( days * 86400 ); 04377 } 04378 int hours = secs / 3600; 04379 if ( hours > 0 ) { 04380 tmp += i18np( "1 hour", "%1 hours", hours ); 04381 tmp += ' '; 04382 secs -= ( hours * 3600 ); 04383 } 04384 int mins = secs / 60; 04385 if ( mins > 0 ) { 04386 tmp += i18np( "1 minute", "%1 minutes", mins ); 04387 } 04388 return tmp; 04389 } 04390 04391 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence ) 04392 { 04393 QString tmp; 04394 if ( incidence->type() == Incidence::TypeEvent ) { 04395 Event::Ptr event = incidence.staticCast<Event>(); 04396 if ( event->hasEndDate() ) { 04397 if ( !event->allDay() ) { 04398 tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) ); 04399 } else { 04400 tmp = i18np( "1 day", "%1 days", 04401 event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 ); 04402 } 04403 } else { 04404 tmp = i18n( "forever" ); 04405 } 04406 } else if ( incidence->type() == Incidence::TypeTodo ) { 04407 Todo::Ptr todo = incidence.staticCast<Todo>(); 04408 if ( todo->hasDueDate() ) { 04409 if ( todo->hasStartDate() ) { 04410 if ( !todo->allDay() ) { 04411 tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) ); 04412 } else { 04413 tmp = i18np( "1 day", "%1 days", 04414 todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 ); 04415 } 04416 } 04417 } 04418 } 04419 return tmp; 04420 } 04421 04422 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence, 04423 bool shortfmt ) 04424 { 04425 //TODO: implement shortfmt=false 04426 Q_UNUSED( shortfmt ); 04427 04428 QStringList reminderStringList; 04429 04430 if ( incidence ) { 04431 Alarm::List alarms = incidence->alarms(); 04432 Alarm::List::ConstIterator it; 04433 for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) { 04434 Alarm::Ptr alarm = *it; 04435 int offset = 0; 04436 QString remStr, atStr, offsetStr; 04437 if ( alarm->hasTime() ) { 04438 offset = 0; 04439 if ( alarm->time().isValid() ) { 04440 atStr = KGlobal::locale()->formatDateTime( alarm->time() ); 04441 } 04442 } else if ( alarm->hasStartOffset() ) { 04443 offset = alarm->startOffset().asSeconds(); 04444 if ( offset < 0 ) { 04445 offset = -offset; 04446 offsetStr = i18nc( "N days/hours/minutes before the start datetime", 04447 "%1 before the start", secs2Duration( offset ) ); 04448 } else if ( offset > 0 ) { 04449 offsetStr = i18nc( "N days/hours/minutes after the start datetime", 04450 "%1 after the start", secs2Duration( offset ) ); 04451 } else { //offset is 0 04452 if ( incidence->dtStart().isValid() ) { 04453 atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() ); 04454 } 04455 } 04456 } else if ( alarm->hasEndOffset() ) { 04457 offset = alarm->endOffset().asSeconds(); 04458 if ( offset < 0 ) { 04459 offset = -offset; 04460 if ( incidence->type() == Incidence::TypeTodo ) { 04461 offsetStr = i18nc( "N days/hours/minutes before the due datetime", 04462 "%1 before the to-do is due", secs2Duration( offset ) ); 04463 } else { 04464 offsetStr = i18nc( "N days/hours/minutes before the end datetime", 04465 "%1 before the end", secs2Duration( offset ) ); 04466 } 04467 } else if ( offset > 0 ) { 04468 if ( incidence->type() == Incidence::TypeTodo ) { 04469 offsetStr = i18nc( "N days/hours/minutes after the due datetime", 04470 "%1 after the to-do is due", secs2Duration( offset ) ); 04471 } else { 04472 offsetStr = i18nc( "N days/hours/minutes after the end datetime", 04473 "%1 after the end", secs2Duration( offset ) ); 04474 } 04475 } else { //offset is 0 04476 if ( incidence->type() == Incidence::TypeTodo ) { 04477 Todo::Ptr t = incidence.staticCast<Todo>(); 04478 if ( t->dtDue().isValid() ) { 04479 atStr = KGlobal::locale()->formatDateTime( t->dtDue() ); 04480 } 04481 } else { 04482 Event::Ptr e = incidence.staticCast<Event>(); 04483 if ( e->dtEnd().isValid() ) { 04484 atStr = KGlobal::locale()->formatDateTime( e->dtEnd() ); 04485 } 04486 } 04487 } 04488 } 04489 if ( offset == 0 ) { 04490 if ( !atStr.isEmpty() ) { 04491 remStr = i18nc( "reminder occurs at datetime", "at %1", atStr ); 04492 } 04493 } else { 04494 remStr = offsetStr; 04495 } 04496 04497 if ( alarm->repeatCount() > 0 ) { 04498 QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() ); 04499 QString intervalStr = i18nc( "interval is N days/hours/minutes", 04500 "interval is %1", 04501 secs2Duration( alarm->snoozeTime().asSeconds() ) ); 04502 QString repeatStr = i18nc( "(repeat string, interval string)", 04503 "(%1, %2)", countStr, intervalStr ); 04504 remStr = remStr + ' ' + repeatStr; 04505 04506 } 04507 reminderStringList << remStr; 04508 } 04509 } 04510 04511 return reminderStringList; 04512 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:25:00 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:25:00 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.