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