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