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