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