1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6 Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
7 Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
8
9 This library is free software; you can redistribute it and/or
10 modify it under the terms of the GNU Library General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License, or (at your option) any later version.
13
14 This library is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU Library General Public License
20 along with this library; see the file COPYING.LIB. If not, write to
21 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 Boston, MA 02110-1301, USA.
23*/
24/**
25 @file
26 This file is part of the API for handling calendar data and provides
27 static functions for formatting Incidences for various purposes.
28
29 @brief
30 Provides methods to format Incidences in various ways for display purposes.
31
32 @author Cornelius Schumacher \<schumacher@kde.org\>
33 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
34 @author Allen Winter \<allen@kdab.com\>
35*/
36
37#include "incidenceformatter.h"
38#include "attachment.h"
39#include "calendar.h"
40#include "calendarlocal.h"
41#ifndef KDEPIM_NO_KRESOURCES
42#include "calendarresources.h"
43#endif
44#include "event.h"
45#include "freebusy.h"
46#include "icalformat.h"
47#include "journal.h"
48#include "todo.h"
49
50#include "kpimutils/email.h"
51#include "kabc/phonenumber.h"
52#include "kabc/vcardconverter.h"
53#include "kabc/stdaddressbook.h"
54
55#include <kdatetime.h>
56#include <kemailsettings.h>
57
58#include <kglobal.h>
59#include <kiconloader.h>
60#include <klocalizedstring.h>
61#include <kcalendarsystem.h>
62#include <ksystemtimezone.h>
63#include <kmimetype.h>
64
65#include <QtCore/QBuffer>
66#include <QtCore/QList>
67#include <QTextDocument>
68#include <QApplication>
69
70using namespace KCal;
71using namespace IncidenceFormatter;
72
73/*******************
74 * General helpers
75 *******************/
76
77//@cond PRIVATE
78static QString htmlAddLink( const QString &ref, const QString &text,
79 bool newline = true )
80{
81 QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
82 if ( newline ) {
83 tmpStr += '\n';
84 }
85 return tmpStr;
86}
87
88static QString htmlAddTag( const QString &tag, const QString &text )
89{
90 int numLineBreaks = text.count( "\n" );
91 QString str = '<' + tag + '>';
92 QString tmpText = text;
93 QString tmpStr = str;
94 if( numLineBreaks >= 0 ) {
95 if ( numLineBreaks > 0 ) {
96 int pos = 0;
97 QString tmp;
98 for ( int i = 0; i <= numLineBreaks; ++i ) {
99 pos = tmpText.indexOf( "\n" );
100 tmp = tmpText.left( pos );
101 tmpText = tmpText.right( tmpText.length() - pos - 1 );
102 tmpStr += tmp + "<br>";
103 }
104 } else {
105 tmpStr += tmpText;
106 }
107 }
108 tmpStr += "</" + tag + '>';
109 return tmpStr;
110}
111
112static bool iamAttendee( Attendee *attendee )
113{
114 // Check if I'm this attendee
115
116 bool iam = false;
117 KEMailSettings settings;
118 QStringList profiles = settings.profiles();
119 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
120 settings.setProfile( *it );
121 if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
122 iam = true;
123 break;
124 }
125 }
126 return iam;
127}
128
129static bool iamOrganizer( Incidence *incidence )
130{
131 // Check if I'm the organizer for this incidence
132
133 if ( !incidence ) {
134 return false;
135 }
136
137 bool iam = false;
138 KEMailSettings settings;
139 QStringList profiles = settings.profiles();
140 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
141 settings.setProfile( *it );
142 if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer().email() ) {
143 iam = true;
144 break;
145 }
146 }
147 return iam;
148}
149
150static bool senderIsOrganizer( Incidence *incidence, const QString &sender )
151{
152 // Check if the specified sender is the organizer
153
154 if ( !incidence || sender.isEmpty() ) {
155 return true;
156 }
157
158 bool isorg = true;
159 QString senderName, senderEmail;
160 if ( KPIMUtils::extractEmailAddressAndName( sender, senderEmail, senderName ) ) {
161 // for this heuristic, we say the sender is the organizer if either the name or the email match.
162 if ( incidence->organizer().email() != senderEmail &&
163 incidence->organizer().name() != senderName ) {
164 isorg = false;
165 }
166 }
167 return isorg;
168}
169
170static QString firstAttendeeName( Incidence *incidence, const QString &defName )
171{
172 QString name;
173 if ( !incidence ) {
174 return name;
175 }
176
177 Attendee::List attendees = incidence->attendees();
178 if( attendees.count() > 0 ) {
179 Attendee *attendee = *attendees.begin();
180 name = attendee->name();
181 if ( name.isEmpty() ) {
182 name = attendee->email();
183 }
184 if ( name.isEmpty() ) {
185 name = defName;
186 }
187 }
188 return name;
189}
190//@endcond
191
192/*******************************************************************
193 * Helper functions for the extensive display (display viewer)
194 *******************************************************************/
195
196//@cond PRIVATE
197static QString displayViewLinkPerson( const QString &email, QString name,
198 QString uid, const QString &iconPath )
199{
200 // Make the search, if there is an email address to search on,
201 // and either name or uid is missing
202 if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
203#ifndef KDEPIM_NO_KRESOURCES
204 KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
205 KABC::Addressee::List addressList = add_book->findByEmail( email );
206 KABC::Addressee o = ( !addressList.isEmpty() ? addressList.first() : KABC::Addressee() );
207 if ( !o.isEmpty() && addressList.size() < 2 ) {
208 if ( name.isEmpty() ) {
209 // No name set, so use the one from the addressbook
210 name = o.formattedName();
211 }
212 uid = o.uid();
213 } else {
214 // Email not found in the addressbook. Don't make a link
215 uid.clear();
216 }
217#else
218 uid.clear();
219#endif
220 }
221
222 // Show the attendee
223 QString tmpString;
224 if ( !uid.isEmpty() ) {
225 // There is a UID, so make a link to the addressbook
226 if ( name.isEmpty() ) {
227 // Use the email address for text
228 tmpString += htmlAddLink( "uid:" + uid, email );
229 } else {
230 tmpString += htmlAddLink( "uid:" + uid, name );
231 }
232 } else {
233 // No UID, just show some text
234 tmpString += ( name.isEmpty() ? email : name );
235 }
236
237 // Make the mailto link
238 if ( !email.isEmpty() && !iconPath.isNull() ) {
239 KUrl mailto;
240 mailto.setProtocol( "mailto" );
241 mailto.setPath( email );
242 tmpString += htmlAddLink( mailto.url(),
243 "<img valign=\"top\" src=\"" + iconPath + "\">" );
244 }
245
246 return tmpString;
247}
248
249static QString displayViewFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
250{
251 QString tmpStr;
252 Attendee::List::ConstIterator it;
253 Attendee::List attendees = incidence->attendees();
254 KIconLoader *iconLoader = KIconLoader::global();
255 const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
256
257 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
258 Attendee *a = *it;
259 if ( a->role() != role ) {
260 // skip this role
261 continue;
262 }
263 if ( a->email() == incidence->organizer().email() ) {
264 // skip attendee that is also the organizer
265 continue;
266 }
267 tmpStr += displayViewLinkPerson( a->email(), a->name(), a->uid(), iconPath );
268 if ( !a->delegator().isEmpty() ) {
269 tmpStr += i18n( " (delegated by %1)", a->delegator() );
270 }
271 if ( !a->delegate().isEmpty() ) {
272 tmpStr += i18n( " (delegated to %1)", a->delegate() );
273 }
274 tmpStr += "<br>";
275 }
276 if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
277 tmpStr.chop( 4 );
278 }
279 return tmpStr;
280}
281
282static QString displayViewFormatAttendees( Incidence *incidence )
283{
284 QString tmpStr, str;
285
286 KIconLoader *iconLoader = KIconLoader::global();
287 const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
288
289 // Add organizer link
290 int attendeeCount = incidence->attendees().count();
291 if ( attendeeCount > 1 ||
292 ( attendeeCount == 1 &&
293 incidence->organizer().email() != incidence->attendees().first()->email() ) ) {
294 tmpStr += "<tr>";
295 tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
296 tmpStr += "<td>" +
297 displayViewLinkPerson( incidence->organizer().email(),
298 incidence->organizer().name(),
299 QString(), iconPath ) +
300 "</td>";
301 tmpStr += "</tr>";
302 }
303
304 // Add "chair"
305 str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair );
306 if ( !str.isEmpty() ) {
307 tmpStr += "<tr>";
308 tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
309 tmpStr += "<td>" + str + "</td>";
310 tmpStr += "</tr>";
311 }
312
313 // Add required participants
314 str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
315 if ( !str.isEmpty() ) {
316 tmpStr += "<tr>";
317 tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
318 tmpStr += "<td>" + str + "</td>";
319 tmpStr += "</tr>";
320 }
321
322 // Add optional participants
323 str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
324 if ( !str.isEmpty() ) {
325 tmpStr += "<tr>";
326 tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
327 tmpStr += "<td>" + str + "</td>";
328 tmpStr += "</tr>";
329 }
330
331 // Add observers
332 str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
333 if ( !str.isEmpty() ) {
334 tmpStr += "<tr>";
335 tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
336 tmpStr += "<td>" + str + "</td>";
337 tmpStr += "</tr>";
338 }
339
340 return tmpStr;
341}
342
343static QString displayViewFormatAttachments( Incidence *incidence )
344{
345 QString tmpStr;
346 Attachment::List as = incidence->attachments();
347 Attachment::List::ConstIterator it;
348 int count = 0;
349 for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
350 count++;
351 if ( (*it)->isUri() ) {
352 QString name;
353 if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) {
354 name = i18n( "Show mail" );
355 } else {
356 name = (*it)->label();
357 }
358 tmpStr += htmlAddLink( (*it)->uri(), name );
359 } else {
360 tmpStr += (*it)->label();
361 }
362 if ( count < as.count() ) {
363 tmpStr += "<br>";
364 }
365 }
366 return tmpStr;
367}
368
369static QString displayViewFormatCategories( Incidence *incidence )
370{
371 // We do not use Incidence::categoriesStr() since it does not have whitespace
372 return incidence->categories().join( ", " );
373}
374
375static QString displayViewFormatCreationDate( Incidence *incidence, KDateTime::Spec spec )
376{
377 KDateTime kdt = incidence->created().toTimeSpec( spec );
378 return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) );
379}
380
381static QString displayViewFormatBirthday( Event *event )
382{
383 if ( !event ) {
384 return QString();
385 }
386 if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" &&
387 event->customProperty( "KABC", "ANNIVERSARY" ) != "YES" ) {
388 return QString();
389 }
390
391 QString uid_1 = event->customProperty( "KABC", "UID-1" );
392 QString name_1 = event->customProperty( "KABC", "NAME-1" );
393 QString email_1= event->customProperty( "KABC", "EMAIL-1" );
394
395 KIconLoader *iconLoader = KIconLoader::global();
396 const QString iconPath = iconLoader->iconPath( "mail-message-new", KIconLoader::Small );
397 //TODO: add a birthday cake icon
398 QString tmpStr = displayViewLinkPerson( email_1, name_1, uid_1, iconPath );
399
400 return tmpStr;
401}
402
403static QString displayViewFormatHeader( Incidence *incidence )
404{
405 QString tmpStr = "<table><tr>";
406
407 // show icons
408 KIconLoader *iconLoader = KIconLoader::global();
409 tmpStr += "<td>";
410
411 // TODO: KDE5. Make the function QString Incidence::getPixmap() so we don't
412 // need downcasting.
413
414 if ( incidence->type() == "Todo" ) {
415 tmpStr += "<img valign=\"top\" src=\"";
416 Todo *todo = static_cast<Todo *>( incidence );
417 if ( !todo->isCompleted() ) {
418 tmpStr += iconLoader->iconPath( "view-calendar-tasks", KIconLoader::Small );
419 } else {
420 tmpStr += iconLoader->iconPath( "task-complete", KIconLoader::Small );
421 }
422 tmpStr += "\">";
423 }
424
425 if ( incidence->type() == "Event" ) {
426 QString iconPath;
427 if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
428 iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small );
429 } else if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
430 iconPath = iconLoader->iconPath( "view-calendar-wedding-anniversary", KIconLoader::Small );
431 } else {
432 iconPath = iconLoader->iconPath( "view-calendar-day", KIconLoader::Small );
433 }
434 tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
435 }
436
437 if ( incidence->type() == "Journal" ) {
438 tmpStr += "<img valign=\"top\" src=\"" +
439 iconLoader->iconPath( "view-pim-journal", KIconLoader::Small ) +
440 "\">";
441 }
442
443 if ( incidence->isAlarmEnabled() ) {
444 tmpStr += "<img valign=\"top\" src=\"" +
445 iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
446 "\">";
447 }
448 if ( incidence->recurs() ) {
449 tmpStr += "<img valign=\"top\" src=\"" +
450 iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
451 "\">";
452 }
453 if ( incidence->isReadOnly() ) {
454 tmpStr += "<img valign=\"top\" src=\"" +
455 iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
456 "\">";
457 }
458 tmpStr += "</td>";
459
460 tmpStr += "<td>";
461 tmpStr += "<b><u>" + incidence->richSummary() + "</u></b>";
462 tmpStr += "</td>";
463
464 tmpStr += "</tr></table>";
465
466 return tmpStr;
467}
468
469static QString displayViewFormatEvent( const QString &calStr, Event *event,
470 const QDate &date, KDateTime::Spec spec )
471{
472 if ( !event ) {
473 return QString();
474 }
475
476 QString tmpStr = displayViewFormatHeader( event );
477
478 tmpStr += "<table>";
479 tmpStr += "<col width=\"25%\"/>";
480 tmpStr += "<col width=\"75%\"/>";
481
482 if ( !calStr.isEmpty() ) {
483 tmpStr += "<tr>";
484 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
485 tmpStr += "<td>" + calStr + "</td>";
486 tmpStr += "</tr>";
487 }
488
489 if ( !event->location().isEmpty() ) {
490 tmpStr += "<tr>";
491 tmpStr += "<td><b>" + i18nc( "@title:column event location", "Location:" ) + "</b></td>";
492 tmpStr += "<td>" + event->richLocation() + "</td>";
493 tmpStr += "</tr>";
494 }
495
496 KDateTime startDt = event->dtStart();
497 KDateTime endDt = event->dtEnd();
498 if ( event->recurs() ) {
499 if ( date.isValid() ) {
500 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
501 int diffDays = startDt.daysTo( kdt );
502 kdt = kdt.addSecs( -1 );
503 startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
504 if ( event->hasEndDate() ) {
505 endDt = endDt.addDays( diffDays );
506 if ( startDt > endDt ) {
507 startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
508 endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
509 }
510 }
511 }
512 }
513
514 tmpStr += "<tr>";
515 if ( event->allDay() ) {
516 if ( event->isMultiDay() ) {
517 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
518 tmpStr += "<td>" +
519 i18nc( "<beginTime> - <endTime>","%1 - %2",
520 dateToString( startDt, false, spec ),
521 dateToString( endDt, false, spec ) ) +
522 "</td>";
523 } else {
524 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
525 tmpStr += "<td>" +
526 i18nc( "date as string","%1",
527 dateToString( startDt, false, spec ) ) +
528 "</td>";
529 }
530 } else {
531 if ( event->isMultiDay() ) {
532 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
533 tmpStr += "<td>" +
534 i18nc( "<beginTime> - <endTime>","%1 - %2",
535 dateToString( startDt, false, spec ),
536 dateToString( endDt, false, spec ) ) +
537 "</td>";
538 } else {
539 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
540 tmpStr += "<td>" +
541 i18nc( "date as string", "%1",
542 dateToString( startDt, false, spec ) ) +
543 "</td>";
544
545 tmpStr += "</tr><tr>";
546 tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
547 if ( event->hasEndDate() && startDt != endDt ) {
548 tmpStr += "<td>" +
549 i18nc( "<beginTime> - <endTime>","%1 - %2",
550 timeToString( startDt, true, spec ),
551 timeToString( endDt, true, spec ) ) +
552 "</td>";
553 } else {
554 tmpStr += "<td>" +
555 timeToString( startDt, true, spec ) +
556 "</td>";
557 }
558 }
559 }
560 tmpStr += "</tr>";
561
562 QString durStr = durationString( event );
563 if ( !durStr.isEmpty() ) {
564 tmpStr += "<tr>";
565 tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
566 tmpStr += "<td>" + durStr + "</td>";
567 tmpStr += "</tr>";
568 }
569
570 if ( event->recurs() ) {
571 tmpStr += "<tr>";
572 tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
573 tmpStr += "<td>" +
574 recurrenceString( event ) +
575 "</td>";
576 tmpStr += "</tr>";
577 }
578
579 const bool isBirthday = event->customProperty( "KABC", "BIRTHDAY" ) == "YES";
580 const bool isAnniversary = event->customProperty( "KABC", "ANNIVERSARY" ) == "YES";
581
582 if ( isBirthday || isAnniversary ) {
583 tmpStr += "<tr>";
584 if ( isAnniversary ) {
585 tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
586 } else {
587 tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
588 }
589 tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
590 tmpStr += "</tr>";
591 tmpStr += "</table>";
592 return tmpStr;
593 }
594
595 if ( !event->description().isEmpty() ) {
596 tmpStr += "<tr>";
597 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
598 tmpStr += "<td>" + event->richDescription() + "</td>";
599 tmpStr += "</tr>";
600 }
601
602 // TODO: print comments?
603
604 int reminderCount = event->alarms().count();
605 if ( reminderCount > 0 && event->isAlarmEnabled() ) {
606 tmpStr += "<tr>";
607 tmpStr += "<td><b>" +
608 i18np( "Reminder:", "Reminders:", reminderCount ) +
609 "</b></td>";
610 tmpStr += "<td>" + reminderStringList( event ).join( "<br>" ) + "</td>";
611 tmpStr += "</tr>";
612 }
613
614 tmpStr += displayViewFormatAttendees( event );
615
616 int categoryCount = event->categories().count();
617 if ( categoryCount > 0 ) {
618 tmpStr += "<tr>";
619 tmpStr += "<td><b>";
620 tmpStr += i18np( "Category:", "Categories:", categoryCount ) +
621 "</b></td>";
622 tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
623 tmpStr += "</tr>";
624 }
625
626 int attachmentCount = event->attachments().count();
627 if ( attachmentCount > 0 ) {
628 tmpStr += "<tr>";
629 tmpStr += "<td><b>" +
630 i18np( "Attachment:", "Attachments:", attachmentCount ) +
631 "</b></td>";
632 tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
633 tmpStr += "</tr>";
634 }
635 tmpStr += "</table>";
636
637 tmpStr += "<p><em>" + displayViewFormatCreationDate( event, spec ) + "</em>";
638
639 return tmpStr;
640}
641
642static QString displayViewFormatTodo( const QString &calStr, Todo *todo,
643 const QDate &date, KDateTime::Spec spec )
644{
645 if ( !todo ) {
646 return QString();
647 }
648
649 QString tmpStr = displayViewFormatHeader( todo );
650
651 tmpStr += "<table>";
652 tmpStr += "<col width=\"25%\"/>";
653 tmpStr += "<col width=\"75%\"/>";
654
655 if ( !calStr.isEmpty() ) {
656 tmpStr += "<tr>";
657 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
658 tmpStr += "<td>" + calStr + "</td>";
659 tmpStr += "</tr>";
660 }
661
662 if ( !todo->location().isEmpty() ) {
663 tmpStr += "<tr>";
664 tmpStr += "<td><b>" + i18nc( "@title:column to-do location", "Location:" ) + "</b></td>";
665 tmpStr += "<td>" + todo->richLocation() + "</td>";
666 tmpStr += "</tr>";
667 }
668
669 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
670 KDateTime startDt = todo->dtStart();
671 if ( todo->recurs() ) {
672 if ( date.isValid() ) {
673 startDt.setDate( date );
674 }
675 }
676 tmpStr += "<tr>";
677 tmpStr += "<td><b>" +
678 i18nc( "to-do start date/time", "Start:" ) +
679 "</b></td>";
680 tmpStr += "<td>" +
681 dateTimeToString( startDt, todo->allDay(), false, spec ) +
682 "</td>";
683 tmpStr += "</tr>";
684 }
685
686 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
687 KDateTime dueDt = todo->dtDue();
688 if ( todo->recurs() ) {
689 if ( date.isValid() ) {
690 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
691 kdt = kdt.addSecs( -1 );
692 dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
693 }
694 }
695 tmpStr += "<tr>";
696 tmpStr += "<td><b>" +
697 i18nc( "to-do due date/time", "Due:" ) +
698 "</b></td>";
699 tmpStr += "<td>" +
700 dateTimeToString( dueDt, todo->allDay(), false, spec ) +
701 "</td>";
702 tmpStr += "</tr>";
703 }
704
705 QString durStr = durationString( todo );
706 if ( !durStr.isEmpty() ) {
707 tmpStr += "<tr>";
708 tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
709 tmpStr += "<td>" + durStr + "</td>";
710 tmpStr += "</tr>";
711 }
712
713 if ( todo->recurs() ) {
714 tmpStr += "<tr>";
715 tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
716 tmpStr += "<td>" +
717 recurrenceString( todo ) +
718 "</td>";
719 tmpStr += "</tr>";
720 }
721
722 if ( !todo->description().isEmpty() ) {
723 tmpStr += "<tr>";
724 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
725 tmpStr += "<td>" + todo->richDescription() + "</td>";
726 tmpStr += "</tr>";
727 }
728
729 // TODO: print comments?
730
731 int reminderCount = todo->alarms().count();
732 if ( reminderCount > 0 && todo->isAlarmEnabled() ) {
733 tmpStr += "<tr>";
734 tmpStr += "<td><b>" +
735 i18np( "Reminder:", "Reminders:", reminderCount ) +
736 "</b></td>";
737 tmpStr += "<td>" + reminderStringList( todo ).join( "<br>" ) + "</td>";
738 tmpStr += "</tr>";
739 }
740
741 tmpStr += displayViewFormatAttendees( todo );
742
743 int categoryCount = todo->categories().count();
744 if ( categoryCount > 0 ) {
745 tmpStr += "<tr>";
746 tmpStr += "<td><b>" +
747 i18np( "Category:", "Categories:", categoryCount ) +
748 "</b></td>";
749 tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
750 tmpStr += "</tr>";
751 }
752
753 if ( todo->priority() > 0 ) {
754 tmpStr += "<tr>";
755 tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
756 tmpStr += "<td>";
757 tmpStr += QString::number( todo->priority() );
758 tmpStr += "</td>";
759 tmpStr += "</tr>";
760 }
761
762 tmpStr += "<tr>";
763 if ( todo->isCompleted() ) {
764 tmpStr += "<td><b>" + i18nc( "Completed: date", "Completed:" ) + "</b></td>";
765 tmpStr += "<td>";
766 tmpStr += todo->completedStr();
767 } else {
768 tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
769 tmpStr += "<td>";
770 tmpStr += i18n( "%1%", todo->percentComplete() );
771 }
772 tmpStr += "</td>";
773 tmpStr += "</tr>";
774
775 int attachmentCount = todo->attachments().count();
776 if ( attachmentCount > 0 ) {
777 tmpStr += "<tr>";
778 tmpStr += "<td><b>" +
779 i18np( "Attachment:", "Attachments:", attachmentCount ) +
780 "</b></td>";
781 tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
782 tmpStr += "</tr>";
783 }
784 tmpStr += "</table>";
785
786 tmpStr += "<p><em>" + displayViewFormatCreationDate( todo, spec ) + "</em>";
787
788 return tmpStr;
789}
790
791static QString displayViewFormatJournal( const QString &calStr, Journal *journal,
792 KDateTime::Spec spec )
793{
794 if ( !journal ) {
795 return QString();
796 }
797
798 QString tmpStr = displayViewFormatHeader( journal );
799
800 tmpStr += "<table>";
801 tmpStr += "<col width=\"25%\"/>";
802 tmpStr += "<col width=\"75%\"/>";
803
804 if ( !calStr.isEmpty() ) {
805 tmpStr += "<tr>";
806 tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
807 tmpStr += "<td>" + calStr + "</td>";
808 tmpStr += "</tr>";
809 }
810
811 tmpStr += "<tr>";
812 tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
813 tmpStr += "<td>" +
814 dateToString( journal->dtStart(), false, spec ) +
815 "</td>";
816 tmpStr += "</tr>";
817
818 if ( !journal->description().isEmpty() ) {
819 tmpStr += "<tr>";
820 tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
821 tmpStr += "<td>" + journal->richDescription() + "</td>";
822 tmpStr += "</tr>";
823 }
824
825 int categoryCount = journal->categories().count();
826 if ( categoryCount > 0 ) {
827 tmpStr += "<tr>";
828 tmpStr += "<td><b>" +
829 i18np( "Category:", "Categories:", categoryCount ) +
830 "</b></td>";
831 tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
832 tmpStr += "</tr>";
833 }
834
835 tmpStr += "</table>";
836
837 tmpStr += "<p><em>" + displayViewFormatCreationDate( journal, spec ) + "</em>";
838
839 return tmpStr;
840}
841
842static QString displayViewFormatFreeBusy( const QString &calStr, FreeBusy *fb,
843 KDateTime::Spec spec )
844{
845 Q_UNUSED( calStr );
846 if ( !fb ) {
847 return QString();
848 }
849
850 QString tmpStr(
851 htmlAddTag(
852 "h2", i18n( "Free/Busy information for %1", fb->organizer().fullName() ) ) );
853
854 tmpStr += htmlAddTag( "h4",
855 i18n( "Busy times in date range %1 - %2:",
856 dateToString( fb->dtStart(), true, spec ),
857 dateToString( fb->dtEnd(), true, spec ) ) );
858
859 QList<Period> periods = fb->busyPeriods();
860
861 QString text =
862 htmlAddTag( "em",
863 htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
864
865 QList<Period>::iterator it;
866 for ( it = periods.begin(); it != periods.end(); ++it ) {
867 Period per = *it;
868 if ( per.hasDuration() ) {
869 int dur = per.duration().asSeconds();
870 QString cont;
871 if ( dur >= 3600 ) {
872 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
873 dur %= 3600;
874 }
875 if ( dur >= 60 ) {
876 cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
877 dur %= 60;
878 }
879 if ( dur > 0 ) {
880 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
881 }
882 text += i18nc( "startDate for duration", "%1 for %2",
883 dateTimeToString( per.start(), false, true, spec ),
884 cont );
885 text += "<br>";
886 } else {
887 if ( per.start().date() == per.end().date() ) {
888 text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
889 dateToString( per.start(), true, spec ),
890 timeToString( per.start(), true, spec ),
891 timeToString( per.end(), true, spec ) );
892 } else {
893 text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
894 dateTimeToString( per.start(), false, true, spec ),
895 dateTimeToString( per.end(), false, true, spec ) );
896 }
897 text += "<br>";
898 }
899 }
900 tmpStr += htmlAddTag( "p", text );
901 return tmpStr;
902}
903//@endcond
904
905//@cond PRIVATE
906class KCal::IncidenceFormatter::EventViewerVisitor
907 : public IncidenceBase::Visitor
908{
909 public:
910 EventViewerVisitor()
911 : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
912
913 bool act( Calendar *calendar, IncidenceBase *incidence, const QDate &date,
914 KDateTime::Spec spec=KDateTime::Spec() )
915 {
916 mCalendar = calendar;
917 mSourceName.clear();
918 mDate = date;
919 mSpec = spec;
920 mResult = "";
921 return incidence->accept( *this );
922 }
923
924 bool act( const QString &sourceName, IncidenceBase *incidence, const QDate &date,
925 KDateTime::Spec spec=KDateTime::Spec() )
926 {
927 mCalendar = 0;
928 mSourceName = sourceName;
929 mDate = date;
930 mSpec = spec;
931 mResult = "";
932 return incidence->accept( *this );
933 }
934
935 QString result() const { return mResult; }
936
937 protected:
938 bool visit( Event *event )
939 {
940 const QString calStr = mCalendar ? resourceString( mCalendar, event ) : mSourceName;
941 mResult = displayViewFormatEvent( calStr, event, mDate, mSpec );
942 return !mResult.isEmpty();
943 }
944 bool visit( Todo *todo )
945 {
946 const QString calStr = mCalendar ? resourceString( mCalendar, todo ) : mSourceName;
947 mResult = displayViewFormatTodo( calStr, todo, mDate, mSpec );
948 return !mResult.isEmpty();
949 }
950 bool visit( Journal *journal )
951 {
952 const QString calStr = mCalendar ? resourceString( mCalendar, journal ) : mSourceName;
953 mResult = displayViewFormatJournal( calStr, journal, mSpec );
954 return !mResult.isEmpty();
955 }
956 bool visit( FreeBusy *fb )
957 {
958 mResult = displayViewFormatFreeBusy( mSourceName, fb, mSpec );
959 return !mResult.isEmpty();
960 }
961
962 protected:
963 Calendar *mCalendar;
964 QString mSourceName;
965 QDate mDate;
966 KDateTime::Spec mSpec;
967 QString mResult;
968};
969//@endcond
970
971QString IncidenceFormatter::extensiveDisplayString( IncidenceBase *incidence )
972{
973 return extensiveDisplayStr( 0, incidence, QDate(), KDateTime::Spec() );
974}
975
976QString IncidenceFormatter::extensiveDisplayStr( IncidenceBase *incidence,
977 KDateTime::Spec spec )
978{
979 if ( !incidence ) {
980 return QString();
981 }
982
983 EventViewerVisitor v;
984 if ( v.act( 0, incidence, QDate(), spec ) ) {
985 return v.result();
986 } else {
987 return QString();
988 }
989}
990
991QString IncidenceFormatter::extensiveDisplayStr( Calendar *calendar,
992 IncidenceBase *incidence,
993 const QDate &date,
994 KDateTime::Spec spec )
995{
996 if ( !incidence ) {
997 return QString();
998 }
999
1000 EventViewerVisitor v;
1001 if ( v.act( calendar, incidence, date, spec ) ) {
1002 return v.result();
1003 } else {
1004 return QString();
1005 }
1006}
1007
1008QString IncidenceFormatter::extensiveDisplayStr( const QString &sourceName,
1009 IncidenceBase *incidence,
1010 const QDate &date,
1011 KDateTime::Spec spec )
1012{
1013 if ( !incidence ) {
1014 return QString();
1015 }
1016
1017 EventViewerVisitor v;
1018 if ( v.act( sourceName, incidence, date, spec ) ) {
1019 return v.result();
1020 } else {
1021 return QString();
1022 }
1023}
1024/***********************************************************************
1025 * Helper functions for the body part formatter of kmail (Invitations)
1026 ***********************************************************************/
1027
1028//@cond PRIVATE
1029static QString string2HTML( const QString &str )
1030{
1031 return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
1032}
1033
1034static QString cleanHtml( const QString &html )
1035{
1036 QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
1037 rx.indexIn( html );
1038 QString body = rx.cap( 1 );
1039
1040 return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
1041}
1042
1043static QString eventStartTimeStr( Event *event )
1044{
1045 QString tmp;
1046 if ( !event->allDay() ) {
1047 tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
1048 dateToString( event->dtStart(), true, KSystemTimeZones::local() ),
1049 timeToString( event->dtStart(), true, KSystemTimeZones::local() ) );
1050 } else {
1051 tmp = i18nc( "%1: Start Date", "%1 (all day)",
1052 dateToString( event->dtStart(), true, KSystemTimeZones::local() ) );
1053 }
1054 return tmp;
1055}
1056
1057static QString eventEndTimeStr( Event *event )
1058{
1059 QString tmp;
1060 if ( event->hasEndDate() && event->dtEnd().isValid() ) {
1061 if ( !event->allDay() ) {
1062 tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2",
1063 dateToString( event->dtEnd(), true, KSystemTimeZones::local() ),
1064 timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
1065 } else {
1066 tmp = i18nc( "%1: End Date", "%1 (all day)",
1067 dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
1068 }
1069 }
1070 return tmp;
1071}
1072
1073static QString invitationRow( const QString &cell1, const QString &cell2 )
1074{
1075 return "<tr><td>" + cell1 + "</td><td>" + cell2 + "</td></tr>\n";
1076}
1077
1078static Attendee *findDelegatedFromMyAttendee( Incidence *incidence )
1079{
1080 // Return the first attendee that was delegated-from me
1081
1082 Attendee *attendee = 0;
1083 if ( !incidence ) {
1084 return attendee;
1085 }
1086
1087 KEMailSettings settings;
1088 QStringList profiles = settings.profiles();
1089 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
1090 settings.setProfile( *it );
1091
1092 QString delegatorName, delegatorEmail;
1093 Attendee::List attendees = incidence->attendees();
1094 Attendee::List::ConstIterator it2;
1095 for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
1096 Attendee *a = *it2;
1097 KPIMUtils::extractEmailAddressAndName( a->delegator(), delegatorEmail, delegatorName );
1098 if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
1099 attendee = a;
1100 break;
1101 }
1102 }
1103 }
1104 return attendee;
1105}
1106
1107static Attendee *findMyAttendee( Incidence *incidence )
1108{
1109 // Return the attendee for the incidence that is probably me
1110
1111 Attendee *attendee = 0;
1112 if ( !incidence ) {
1113 return attendee;
1114 }
1115
1116 KEMailSettings settings;
1117 QStringList profiles = settings.profiles();
1118 for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
1119 settings.setProfile( *it );
1120
1121 Attendee::List attendees = incidence->attendees();
1122 Attendee::List::ConstIterator it2;
1123 for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
1124 Attendee *a = *it2;
1125 if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
1126 attendee = a;
1127 break;
1128 }
1129 }
1130 }
1131 return attendee;
1132}
1133
1134static Attendee *findAttendee( Incidence *incidence, const QString &email )
1135{
1136 // Search for an attendee by email address
1137
1138 Attendee *attendee = 0;
1139 if ( !incidence ) {
1140 return attendee;
1141 }
1142
1143 Attendee::List attendees = incidence->attendees();
1144 Attendee::List::ConstIterator it;
1145 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
1146 Attendee *a = *it;
1147 if ( email == a->email() ) {
1148 attendee = a;
1149 break;
1150 }
1151 }
1152 return attendee;
1153}
1154
1155static bool rsvpRequested( Incidence *incidence )
1156{
1157 if ( !incidence ) {
1158 return false;
1159 }
1160
1161 //use a heuristic to determine if a response is requested.
1162
1163 bool rsvp = true; // better send superfluously than not at all
1164 Attendee::List attendees = incidence->attendees();
1165 Attendee::List::ConstIterator it;
1166 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
1167 if ( it == attendees.constBegin() ) {
1168 rsvp = (*it)->RSVP(); // use what the first one has
1169 } else {
1170 if ( (*it)->RSVP() != rsvp ) {
1171 rsvp = true; // they differ, default
1172 break;
1173 }
1174 }
1175 }
1176 return rsvp;
1177}
1178
1179static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
1180{
1181 if ( rsvpRequested ) {
1182 if ( role.isEmpty() ) {
1183 return i18n( "Your response is requested" );
1184 } else {
1185 return i18n( "Your response as <b>%1</b> is requested", role );
1186 }
1187 } else {
1188 if ( role.isEmpty() ) {
1189 return i18n( "No response is necessary" );
1190 } else {
1191 return i18n( "No response as <b>%1</b> is necessary", role );
1192 }
1193 }
1194}
1195
1196static QString myStatusStr( Incidence *incidence )
1197{
1198 QString ret;
1199 Attendee *a = findMyAttendee( incidence );
1200 if ( a &&
1201 a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
1202 ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)",
1203 Attendee::statusName( a->status() ) );
1204 }
1205 return ret;
1206}
1207
1208static QString invitationPerson( const QString &email, QString name, QString uid )
1209{
1210 // Make the search, if there is an email address to search on,
1211 // and either name or uid is missing
1212 if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
1213#ifndef KDEPIM_NO_KRESOURCES
1214 KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
1215 KABC::Addressee::List addressList = add_book->findByEmail( email );
1216 if ( !addressList.isEmpty() ) {
1217 KABC::Addressee o = addressList.first();
1218 if ( !o.isEmpty() && addressList.size() < 2 ) {
1219 if ( name.isEmpty() ) {
1220 // No name set, so use the one from the addressbook
1221 name = o.formattedName();
1222 }
1223 uid = o.uid();
1224 } else {
1225 // Email not found in the addressbook. Don't make a link
1226 uid.clear();
1227 }
1228 }
1229#else
1230 uid.clear();
1231#endif
1232 }
1233
1234 // Show the attendee
1235 QString tmpString;
1236 if ( !uid.isEmpty() ) {
1237 // There is a UID, so make a link to the addressbook
1238 if ( name.isEmpty() ) {
1239 // Use the email address for text
1240 tmpString += htmlAddLink( "uid:" + uid, email );
1241 } else {
1242 tmpString += htmlAddLink( "uid:" + uid, name );
1243 }
1244 } else {
1245 // No UID, just show some text
1246 tmpString += ( name.isEmpty() ? email : name );
1247 }
1248 tmpString += '\n';
1249
1250 // Make the mailto link
1251 if ( !email.isEmpty() ) {
1252 KCal::Person person( name, email );
1253 KUrl mailto;
1254 mailto.setProtocol( "mailto" );
1255 mailto.setPath( person.fullName() );
1256 const QString iconPath =
1257 KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small );
1258 tmpString += htmlAddLink( mailto.url(),
1259 "<img valign=\"top\" src=\"" + iconPath + "\">" );
1260 }
1261 tmpString += '\n';
1262
1263 return tmpString;
1264}
1265
1266static QString invitationsDetailsIncidence( Incidence *incidence, bool noHtmlMode )
1267{
1268 // if description and comment -> use both
1269 // if description, but no comment -> use the desc as the comment (and no desc)
1270 // if comment, but no description -> use the comment and no description
1271
1272 QString html;
1273 QString descr;
1274 QStringList comments;
1275
1276 if ( incidence->comments().isEmpty() ) {
1277 if ( !incidence->description().isEmpty() ) {
1278 // use description as comments
1279 if ( !incidence->descriptionIsRich() ) {
1280 comments << string2HTML( incidence->description() );
1281 } else {
1282 comments << incidence->richDescription();
1283 if ( noHtmlMode ) {
1284 comments[0] = cleanHtml( comments[0] );
1285 }
1286 comments[0] = htmlAddTag( "p", comments[0] );
1287 }
1288 }
1289 //else desc and comments are empty
1290 } else {
1291 // non-empty comments
1292 foreach ( const QString &c, incidence->comments() ) {
1293 if ( !c.isEmpty() ) {
1294 // kcal doesn't know about richtext comments, so we need to guess
1295 if ( !Qt::mightBeRichText( c ) ) {
1296 comments << string2HTML( c );
1297 } else {
1298 if ( noHtmlMode ) {
1299 comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) );
1300 } else {
1301 comments << c;
1302 }
1303 }
1304 }
1305 }
1306 if ( !incidence->description().isEmpty() ) {
1307 // use description too
1308 if ( !incidence->descriptionIsRich() ) {
1309 descr = string2HTML( incidence->description() );
1310 } else {
1311 descr = incidence->richDescription();
1312 if ( noHtmlMode ) {
1313 descr = cleanHtml( descr );
1314 }
1315 descr = htmlAddTag( "p", descr );
1316 }
1317 }
1318 }
1319
1320 if( !descr.isEmpty() ) {
1321 html += "<p>";
1322 html += "<table border=\"0\" style=\"margin-top:4px;\">";
1323 html += "<tr><td><center>" +
1324 htmlAddTag( "u", i18n( "Description:" ) ) +
1325 "</center></td></tr>";
1326 html += "<tr><td>" + descr + "</td></tr>";
1327 html += "</table>";
1328 }
1329
1330 if ( !comments.isEmpty() ) {
1331 html += "<p>";
1332 html += "<table border=\"0\" style=\"margin-top:4px;\">";
1333 html += "<tr><td><center>" +
1334 htmlAddTag( "u", i18n( "Comments:" ) ) +
1335 "</center></td></tr>";
1336 html += "<tr><td>";
1337 if ( comments.count() > 1 ) {
1338 html += "<ul>";
1339 for ( int i=0; i < comments.count(); ++i ) {
1340 html += "<li>" + comments[i] + "</li>";
1341 }
1342 html += "</ul>";
1343 } else {
1344 html += comments[0];
1345 }
1346 html += "</td></tr>";
1347 html += "</table>";
1348 }
1349 return html;
1350}
1351
1352static QString invitationDetailsEvent( Event *event, bool noHtmlMode, KDateTime::Spec spec )
1353{
1354 // Invitation details are formatted into an HTML table
1355 if ( !event ) {
1356 return QString();
1357 }
1358
1359 QString sSummary = i18n( "Summary unspecified" );
1360 if ( !event->summary().isEmpty() ) {
1361 if ( !event->summaryIsRich() ) {
1362 sSummary = Qt::escape( event->summary() );
1363 } else {
1364 sSummary = event->richSummary();
1365 if ( noHtmlMode ) {
1366 sSummary = cleanHtml( sSummary );
1367 }
1368 }
1369 }
1370
1371 QString sLocation = i18nc( "event location", "Location unspecified" );
1372 if ( !event->location().isEmpty() ) {
1373 if ( !event->locationIsRich() ) {
1374 sLocation = Qt::escape( event->location() );
1375 } else {
1376 sLocation = event->richLocation();
1377 if ( noHtmlMode ) {
1378 sLocation = cleanHtml( sLocation );
1379 }
1380 }
1381 }
1382
1383 QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
1384 QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
1385 html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
1386
1387 // Invitation summary & location rows
1388 html += invitationRow( i18n( "What:" ), sSummary );
1389 html += invitationRow( i18n( "Where:" ), sLocation );
1390
1391 // If a 1 day event
1392 if ( event->dtStart().date() == event->dtEnd().date() ) {
1393 html += invitationRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
1394 if ( !event->allDay() ) {
1395 html += invitationRow( i18n( "Time:" ),
1396 timeToString( event->dtStart(), true, spec ) +
1397 " - " +
1398 timeToString( event->dtEnd(), true, spec ) );
1399 }
1400 } else {
1401 html += invitationRow( i18nc( "starting date", "From:" ),
1402 dateToString( event->dtStart(), false, spec ) );
1403 if ( !event->allDay() ) {
1404 html += invitationRow( i18nc( "starting time", "At:" ),
1405 timeToString( event->dtStart(), true, spec ) );
1406 }
1407 if ( event->hasEndDate() ) {
1408 html += invitationRow( i18nc( "ending date", "To:" ),
1409 dateToString( event->dtEnd(), false, spec ) );
1410 if ( !event->allDay() ) {
1411 html += invitationRow( i18nc( "ending time", "At:" ),
1412 timeToString( event->dtEnd(), true, spec ) );
1413 }
1414 } else {
1415 html += invitationRow( i18nc( "ending date", "To:" ),
1416 i18n( "no end date specified" ) );
1417 }
1418 }
1419
1420 // Invitation Duration Row
1421 QString durStr = durationString( event );
1422 if ( !durStr.isEmpty() ) {
1423 html += invitationRow( i18n( "Duration:" ), durStr );
1424 }
1425
1426 if ( event->recurs() ) {
1427 html += invitationRow( i18n( "Recurrence:" ), recurrenceString( event ) );
1428 }
1429
1430 html += "</table></div>\n";
1431 html += invitationsDetailsIncidence( event, noHtmlMode );
1432
1433 return html;
1434}
1435
1436static QString invitationDetailsTodo( Todo *todo, bool noHtmlMode, KDateTime::Spec spec )
1437{
1438 // To-do details are formatted into an HTML table
1439 if ( !todo ) {
1440 return QString();
1441 }
1442
1443 QString sSummary = i18n( "Summary unspecified" );
1444 if ( !todo->summary().isEmpty() ) {
1445 if ( !todo->summaryIsRich() ) {
1446 sSummary = Qt::escape( todo->summary() );
1447 } else {
1448 sSummary = todo->richSummary();
1449 if ( noHtmlMode ) {
1450 sSummary = cleanHtml( sSummary );
1451 }
1452 }
1453 }
1454
1455 QString sLocation = i18nc( "todo location", "Location unspecified" );
1456 if ( !todo->location().isEmpty() ) {
1457 if ( !todo->locationIsRich() ) {
1458 sLocation = Qt::escape( todo->location() );
1459 } else {
1460 sLocation = todo->richLocation();
1461 if ( noHtmlMode ) {
1462 sLocation = cleanHtml( sLocation );
1463 }
1464 }
1465 }
1466
1467 QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
1468 QString html = QString( "<div dir=\"%1\">\n" ).arg( dir );
1469 html += "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
1470
1471 // Invitation summary & location rows
1472 html += invitationRow( i18n( "What:" ), sSummary );
1473 html += invitationRow( i18n( "Where:" ), sLocation );
1474
1475 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
1476 html += invitationRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
1477 if ( !todo->allDay() ) {
1478 html += invitationRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) );
1479 }
1480 }
1481 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
1482 html += invitationRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
1483 if ( !todo->allDay() ) {
1484 html += invitationRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) );
1485 }
1486 } else {
1487 html += invitationRow( i18n( "Due Date:" ), i18nc( "no to-do due date", "None" ) );
1488 }
1489
1490 html += "</table></div>\n";
1491 html += invitationsDetailsIncidence( todo, noHtmlMode );
1492
1493 return html;
1494}
1495
1496static QString invitationDetailsJournal( Journal *journal, bool noHtmlMode, KDateTime::Spec spec )
1497{
1498 if ( !journal ) {
1499 return QString();
1500 }
1501
1502 QString sSummary = i18n( "Summary unspecified" );
1503 QString sDescr = i18n( "Description unspecified" );
1504 if ( ! journal->summary().isEmpty() ) {
1505 sSummary = journal->richSummary();
1506 if ( noHtmlMode ) {
1507 sSummary = cleanHtml( sSummary );
1508 }
1509 }
1510 if ( ! journal->description().isEmpty() ) {
1511 sDescr = journal->richDescription();
1512 if ( noHtmlMode ) {
1513 sDescr = cleanHtml( sDescr );
1514 }
1515 }
1516 QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
1517 html += invitationRow( i18n( "Summary:" ), sSummary );
1518 html += invitationRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
1519 html += invitationRow( i18n( "Description:" ), sDescr );
1520 html += "</table>\n";
1521 html += invitationsDetailsIncidence( journal, noHtmlMode );
1522
1523 return html;
1524}
1525
1526static QString invitationDetailsFreeBusy( FreeBusy *fb, bool noHtmlMode, KDateTime::Spec spec )
1527{
1528 Q_UNUSED( noHtmlMode );
1529
1530 if ( !fb ) {
1531 return QString();
1532 }
1533
1534 QString html( "<table border=\"0\" cellpadding=\"1\" cellspacing=\"1\">\n" );
1535 html += invitationRow( i18n( "Person:" ), fb->organizer().fullName() );
1536 html += invitationRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
1537 html += invitationRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
1538
1539 html += "<tr><td colspan=2><hr></td></tr>\n";
1540 html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
1541
1542 QList<Period> periods = fb->busyPeriods();
1543 QList<Period>::iterator it;
1544 for ( it = periods.begin(); it != periods.end(); ++it ) {
1545 Period per = *it;
1546 if ( per.hasDuration() ) {
1547 int dur = per.duration().asSeconds();
1548 QString cont;
1549 if ( dur >= 3600 ) {
1550 cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
1551 dur %= 3600;
1552 }
1553 if ( dur >= 60 ) {
1554 cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
1555 dur %= 60;
1556 }
1557 if ( dur > 0 ) {
1558 cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
1559 }
1560 html += invitationRow(
1561 QString(), i18nc( "startDate for duration", "%1 for %2",
1562 KGlobal::locale()->formatDateTime(
1563 per.start().dateTime(), KLocale::LongDate ), cont ) );
1564 } else {
1565 QString cont;
1566 if ( per.start().date() == per.end().date() ) {
1567 cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
1568 KGlobal::locale()->formatDate( per.start().date() ),
1569 KGlobal::locale()->formatTime( per.start().time() ),
1570 KGlobal::locale()->formatTime( per.end().time() ) );
1571 } else {
1572 cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
1573 KGlobal::locale()->formatDateTime(
1574 per.start().dateTime(), KLocale::LongDate ),
1575 KGlobal::locale()->formatDateTime(
1576 per.end().dateTime(), KLocale::LongDate ) );
1577 }
1578
1579 html += invitationRow( QString(), cont );
1580 }
1581 }
1582
1583 html += "</table>\n";
1584 return html;
1585}
1586
1587static bool replyMeansCounter( Incidence */*incidence*/ )
1588{
1589 return false;
1590/**
1591 see kolab/issue 3665 for an example of when we might use this for something
1592
1593 bool status = false;
1594 if ( incidence ) {
1595 // put code here that looks at the incidence and determines that
1596 // the reply is meant to be a counter proposal. We think this happens
1597 // with Outlook counter proposals, but we aren't sure how yet.
1598 if ( condition ) {
1599 status = true;
1600 }
1601 }
1602 return status;
1603*/
1604}
1605
1606static QString invitationHeaderEvent( Event *event, Incidence *existingIncidence,
1607 ScheduleMessage *msg, const QString &sender )
1608{
1609 if ( !msg || !event ) {
1610 return QString();
1611 }
1612
1613 switch ( msg->method() ) {
1614 case iTIPPublish:
1615 return i18n( "This invitation has been published" );
1616 case iTIPRequest:
1617 if ( existingIncidence && event->revision() > 0 ) {
1618 return i18n( "This invitation has been updated by the organizer %1",
1619 event->organizer().fullName() );
1620 }
1621 if ( iamOrganizer( event ) ) {
1622 return i18n( "I created this invitation" );
1623 } else {
1624 if ( senderIsOrganizer( event, sender ) ) {
1625 if ( !event->organizer().fullName().isEmpty() ) {
1626 return i18n( "You received an invitation from %1",
1627 event->organizer().fullName() );
1628 } else {
1629 return i18n( "You received an invitation" );
1630 }
1631 } else {
1632 if ( !event->organizer().fullName().isEmpty() ) {
1633 return i18n( "You received an invitation from %1 as a representative of %2",
1634 sender, event->organizer().fullName() );
1635 } else {
1636 return i18n( "You received an invitation from %1 as the organizer's representative",
1637 sender );
1638 }
1639 }
1640 }
1641 case iTIPRefresh:
1642 return i18n( "This invitation was refreshed" );
1643 case iTIPCancel:
1644 return i18n( "This invitation has been canceled" );
1645 case iTIPAdd:
1646 return i18n( "Addition to the invitation" );
1647 case iTIPReply:
1648 {
1649 if ( replyMeansCounter( event ) ) {
1650 return i18n( "%1 makes this counter proposal",
1651 firstAttendeeName( event, i18n( "Sender" ) ) );
1652 }
1653
1654 Attendee::List attendees = event->attendees();
1655 if( attendees.count() == 0 ) {
1656 kDebug() << "No attendees in the iCal reply!";
1657 return QString();
1658 }
1659 if ( attendees.count() != 1 ) {
1660 kDebug() << "Warning: attendeecount in the reply should be 1"
1661 << "but is" << attendees.count();
1662 }
1663 QString attendeeName = firstAttendeeName( event, i18n( "Sender" ) );
1664
1665 QString delegatorName, dummy;
1666 Attendee *attendee = *attendees.begin();
1667 KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
1668 if ( delegatorName.isEmpty() ) {
1669 delegatorName = attendee->delegator();
1670 }
1671
1672 switch( attendee->status() ) {
1673 case Attendee::NeedsAction:
1674 return i18n( "%1 indicates this invitation still needs some action", attendeeName );
1675 case Attendee::Accepted:
1676 if ( event->revision() > 0 ) {
1677 if ( !sender.isEmpty() ) {
1678 return i18n( "This invitation has been updated by attendee %1", sender );
1679 } else {
1680 return i18n( "This invitation has been updated by an attendee" );
1681 }
1682 } else {
1683 if ( delegatorName.isEmpty() ) {
1684 return i18n( "%1 accepts this invitation", attendeeName );
1685 } else {
1686 return i18n( "%1 accepts this invitation on behalf of %2",
1687 attendeeName, delegatorName );
1688 }
1689 }
1690 case Attendee::Tentative:
1691 if ( delegatorName.isEmpty() ) {
1692 return i18n( "%1 tentatively accepts this invitation", attendeeName );
1693 } else {
1694 return i18n( "%1 tentatively accepts this invitation on behalf of %2",
1695 attendeeName, delegatorName );
1696 }
1697 case Attendee::Declined:
1698 if ( delegatorName.isEmpty() ) {
1699 return i18n( "%1 declines this invitation", attendeeName );
1700 } else {
1701 return i18n( "%1 declines this invitation on behalf of %2",
1702 attendeeName, delegatorName );
1703 }
1704 case Attendee::Delegated:
1705 {
1706 QString delegate, dummy;
1707 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
1708 if ( delegate.isEmpty() ) {
1709 delegate = attendee->delegate();
1710 }
1711 if ( !delegate.isEmpty() ) {
1712 return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
1713 } else {
1714 return i18n( "%1 has delegated this invitation", attendeeName );
1715 }
1716 }
1717 case Attendee::Completed:
1718 return i18n( "This invitation is now completed" );
1719 case Attendee::InProcess:
1720 return i18n( "%1 is still processing the invitation", attendeeName );
1721 case Attendee::None:
1722 return i18n( "Unknown response to this invitation" );
1723 }
1724 break;
1725 }
1726 case iTIPCounter:
1727 return i18n( "%1 makes this counter proposal",
1728 firstAttendeeName( event, i18n( "Sender" ) ) );
1729
1730 case iTIPDeclineCounter:
1731 return i18n( "%1 declines the counter proposal",
1732 firstAttendeeName( event, i18n( "Sender" ) ) );
1733
1734 case iTIPNoMethod:
1735 return i18n( "Error: Event iTIP message with unknown method" );
1736 }
1737 kError() << "encountered an iTIP method that we do not support";
1738 return QString();
1739}
1740
1741static QString invitationHeaderTodo( Todo *todo, Incidence *existingIncidence,
1742 ScheduleMessage *msg, const QString &sender )
1743{
1744 if ( !msg || !todo ) {
1745 return QString();
1746 }
1747
1748 switch ( msg->method() ) {
1749 case iTIPPublish:
1750 return i18n( "This to-do has been published" );
1751 case iTIPRequest:
1752 if ( existingIncidence && todo->revision() > 0 ) {
1753 return i18n( "This to-do has been updated by the organizer %1",
1754 todo->organizer().fullName() );
1755 } else {
1756 if ( iamOrganizer( todo ) ) {
1757 return i18n( "I created this to-do" );
1758 } else {
1759 if ( senderIsOrganizer( todo, sender ) ) {
1760 if ( !todo->organizer().fullName().isEmpty() ) {
1761 return i18n( "You have been assigned this to-do by %1", todo->organizer().fullName() );
1762 } else {
1763 return i18n( "You have been assigned this to-do" );
1764 }
1765 } else {
1766 if ( !todo->organizer().fullName().isEmpty() ) {
1767 return i18n( "You have been assigned this to-do by %1 as a representative of %2",
1768 sender, todo->organizer().fullName() );
1769 } else {
1770 return i18n( "You have been assigned this to-do by %1 as the "
1771 "organizer's representative", sender );
1772 }
1773 }
1774 }
1775 }
1776 case iTIPRefresh:
1777 return i18n( "This to-do was refreshed" );
1778 case iTIPCancel:
1779 return i18n( "This to-do was canceled" );
1780 case iTIPAdd:
1781 return i18n( "Addition to the to-do" );
1782 case iTIPReply:
1783 {
1784 if ( replyMeansCounter( todo ) ) {
1785 return i18n( "%1 makes this counter proposal",
1786 firstAttendeeName( todo, i18n( "Sender" ) ) );
1787 }
1788
1789 Attendee::List attendees = todo->attendees();
1790 if ( attendees.count() == 0 ) {
1791 kDebug() << "No attendees in the iCal reply!";
1792 return QString();
1793 }
1794 if ( attendees.count() != 1 ) {
1795 kDebug() << "Warning: attendeecount in the reply should be 1"
1796 << "but is" << attendees.count();
1797 }
1798 QString attendeeName = firstAttendeeName( todo, i18n( "Sender" ) );
1799
1800 QString delegatorName, dummy;
1801 Attendee *attendee = *attendees.begin();
1802 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName );
1803 if ( delegatorName.isEmpty() ) {
1804 delegatorName = attendee->delegator();
1805 }
1806
1807 switch( attendee->status() ) {
1808 case Attendee::NeedsAction:
1809 return i18n( "%1 indicates this to-do assignment still needs some action",
1810 attendeeName );
1811 case Attendee::Accepted:
1812 if ( todo->revision() > 0 ) {
1813 if ( !sender.isEmpty() ) {
1814 if ( todo->isCompleted() ) {
1815 return i18n( "This to-do has been completed by assignee %1", sender );
1816 } else {
1817 return i18n( "This to-do has been updated by assignee %1", sender );
1818 }
1819 } else {
1820 if ( todo->isCompleted() ) {
1821 return i18n( "This to-do has been completed by an assignee" );
1822 } else {
1823 return i18n( "This to-do has been updated by an assignee" );
1824 }
1825 }
1826 } else {
1827 if ( delegatorName.isEmpty() ) {
1828 return i18n( "%1 accepts this to-do", attendeeName );
1829 } else {
1830 return i18n( "%1 accepts this to-do on behalf of %2",
1831 attendeeName, delegatorName );
1832 }
1833 }
1834 case Attendee::Tentative:
1835 if ( delegatorName.isEmpty() ) {
1836 return i18n( "%1 tentatively accepts this to-do", attendeeName );
1837 } else {
1838 return i18n( "%1 tentatively accepts this to-do on behalf of %2",
1839 attendeeName, delegatorName );
1840 }
1841 case Attendee::Declined:
1842 if ( delegatorName.isEmpty() ) {
1843 return i18n( "%1 declines this to-do", attendeeName );
1844 } else {
1845 return i18n( "%1 declines this to-do on behalf of %2",
1846 attendeeName, delegatorName );
1847 }
1848 case Attendee::Delegated:
1849 {
1850 QString delegate, dummy;
1851 KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
1852 if ( delegate.isEmpty() ) {
1853 delegate = attendee->delegate();
1854 }
1855 if ( !delegate.isEmpty() ) {
1856 return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate );
1857 } else {
1858 return i18n( "%1 has delegated this to-do", attendeeName );
1859 }
1860 }
1861 case Attendee::Completed:
1862 return i18n( "The request for this to-do is now completed" );
1863 case Attendee::InProcess:
1864 return i18n( "%1 is still processing the to-do", attendeeName );
1865 case Attendee::None:
1866 return i18n( "Unknown response to this to-do" );
1867 }
1868 break;
1869 }
1870 case iTIPCounter:
1871 return i18n( "%1 makes this counter proposal",
1872 firstAttendeeName( todo, i18n( "Sender" ) ) );
1873
1874 case iTIPDeclineCounter:
1875 return i18n( "%1 declines the counter proposal",
1876 firstAttendeeName( todo, i18n( "Sender" ) ) );
1877
1878 case iTIPNoMethod:
1879 return i18n( "Error: To-do iTIP message with unknown method" );
1880 }
1881 kError() << "encountered an iTIP method that we do not support";
1882 return QString();
1883}
1884
1885static QString invitationHeaderJournal( Journal *journal, ScheduleMessage *msg )
1886{
1887 if ( !msg || !journal ) {
1888 return QString();
1889 }
1890
1891 switch ( msg->method() ) {
1892 case iTIPPublish:
1893 return i18n( "This journal has been published" );
1894 case iTIPRequest:
1895 return i18n( "You have been assigned this journal" );
1896 case iTIPRefresh:
1897 return i18n( "This journal was refreshed" );
1898 case iTIPCancel:
1899 return i18n( "This journal was canceled" );
1900 case iTIPAdd:
1901 return i18n( "Addition to the journal" );
1902 case iTIPReply:
1903 {
1904 if ( replyMeansCounter( journal ) ) {
1905 return i18n( "Sender makes this counter proposal" );
1906 }
1907
1908 Attendee::List attendees = journal->attendees();
1909 if ( attendees.count() == 0 ) {
1910 kDebug() << "No attendees in the iCal reply!";
1911 return QString();
1912 }
1913 if( attendees.count() != 1 ) {
1914 kDebug() << "Warning: attendeecount in the reply should be 1 "
1915 << "but is " << attendees.count();
1916 }
1917 Attendee *attendee = *attendees.begin();
1918
1919 switch( attendee->status() ) {
1920 case Attendee::NeedsAction:
1921 return i18n( "Sender indicates this journal assignment still needs some action" );
1922 case Attendee::Accepted:
1923 return i18n( "Sender accepts this journal" );
1924 case Attendee::Tentative:
1925 return i18n( "Sender tentatively accepts this journal" );
1926 case Attendee::Declined:
1927 return i18n( "Sender declines this journal" );
1928 case Attendee::Delegated:
1929 return i18n( "Sender has delegated this request for the journal" );
1930 case Attendee::Completed:
1931 return i18n( "The request for this journal is now completed" );
1932 case Attendee::InProcess:
1933 return i18n( "Sender is still processing the invitation" );
1934 case Attendee::None:
1935 return i18n( "Unknown response to this journal" );
1936 }
1937 break;
1938 }
1939 case iTIPCounter:
1940 return i18n( "Sender makes this counter proposal" );
1941 case iTIPDeclineCounter:
1942 return i18n( "Sender declines the counter proposal" );
1943 case iTIPNoMethod:
1944 return i18n( "Error: Journal iTIP message with unknown method" );
1945 }
1946 kError() << "encountered an iTIP method that we do not support";
1947 return QString();
1948}
1949
1950static QString invitationHeaderFreeBusy( FreeBusy *fb, ScheduleMessage *msg )
1951{
1952 if ( !msg || !fb ) {
1953 return QString();
1954 }
1955
1956 switch ( msg->method() ) {
1957 case iTIPPublish:
1958 return i18n( "This free/busy list has been published" );
1959 case iTIPRequest:
1960 return i18n( "The free/busy list has been requested" );
1961 case iTIPRefresh:
1962 return i18n( "This free/busy list was refreshed" );
1963 case iTIPCancel:
1964 return i18n( "This free/busy list was canceled" );
1965 case iTIPAdd:
1966 return i18n( "Addition to the free/busy list" );
1967 case iTIPReply:
1968 return i18n( "Reply to the free/busy list" );
1969 case iTIPCounter:
1970 return i18n( "Sender makes this counter proposal" );
1971 case iTIPDeclineCounter:
1972 return i18n( "Sender declines the counter proposal" );
1973 case iTIPNoMethod:
1974 return i18n( "Error: Free/Busy iTIP message with unknown method" );
1975 }
1976 kError() << "encountered an iTIP method that we do not support";
1977 return QString();
1978}
1979//@endcond
1980
1981static QString invitationAttendees( Incidence *incidence )
1982{
1983 QString tmpStr;
1984 if ( !incidence ) {
1985 return tmpStr;
1986 }
1987
1988 tmpStr += i18n( "Invitation List" );
1989
1990 int count=0;
1991 Attendee::List attendees = incidence->attendees();
1992 if ( !attendees.isEmpty() ) {
1993
1994 Attendee::List::ConstIterator it;
1995 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
1996 Attendee *a = *it;
1997 if ( !iamAttendee( a ) ) {
1998 count++;
1999 if ( count == 1 ) {
2000 tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
2001 }
2002 tmpStr += "<tr>";
2003 tmpStr += "<td>";
2004 tmpStr += invitationPerson( a->email(), a->name(), QString() );
2005 if ( !a->delegator().isEmpty() ) {
2006 tmpStr += i18n( " (delegated by %1)", a->delegator() );
2007 }
2008 if ( !a->delegate().isEmpty() ) {
2009 tmpStr += i18n( " (delegated to %1)", a->delegate() );
2010 }
2011 tmpStr += "</td>";
2012 tmpStr += "<td>" + a->statusStr() + "</td>";
2013 tmpStr += "</tr>";
2014 }
2015 }
2016 }
2017 if ( count ) {
2018 tmpStr += "</table>";
2019 } else {
2020 tmpStr += "<i> " + i18nc( "no attendees", "None" ) + "</i>";
2021 }
2022
2023 return tmpStr;
2024}
2025
2026static QString invitationAttachments( InvitationFormatterHelper *helper, Incidence *incidence )
2027{
2028 QString tmpStr;
2029 if ( !incidence ) {
2030 return tmpStr;
2031 }
2032
2033 Attachment::List attachments = incidence->attachments();
2034 if ( !attachments.isEmpty() ) {
2035 tmpStr += i18n( "Attached Documents:" ) + "<ol>";
2036
2037 Attachment::List::ConstIterator it;
2038 for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
2039 Attachment *a = *it;
2040 tmpStr += "<li>";
2041 // Attachment icon
2042 KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
2043 const QString iconStr = ( mimeType ?
2044 mimeType->iconName( a->uri() ) :
2045 QString( "application-octet-stream" ) );
2046 const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
2047 if ( !iconPath.isEmpty() ) {
2048 tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
2049 }
2050 tmpStr += helper->makeLink( "ATTACH:" + a->label(), a->label() );
2051 tmpStr += "</li>";
2052 }
2053 tmpStr += "</ol>";
2054 }
2055
2056 return tmpStr;
2057}
2058
2059//@cond PRIVATE
2060class KCal::IncidenceFormatter::ScheduleMessageVisitor
2061 : public IncidenceBase::Visitor
2062{
2063 public:
2064 ScheduleMessageVisitor() : mExistingIncidence( 0 ), mMessage( 0 ) { mResult = ""; }
2065 bool act( IncidenceBase *incidence, Incidence *existingIncidence,
2066 ScheduleMessage *msg, const QString &sender )
2067 {
2068 mExistingIncidence = existingIncidence;
2069 mMessage = msg;
2070 mSender = sender;
2071 return incidence->accept( *this );
2072 }
2073 QString result() const { return mResult; }
2074
2075 protected:
2076 QString mResult;
2077 Incidence *mExistingIncidence;
2078 ScheduleMessage *mMessage;
2079 QString mSender;
2080};
2081
2082class KCal::IncidenceFormatter::InvitationHeaderVisitor :
2083 public IncidenceFormatter::ScheduleMessageVisitor
2084{
2085 protected:
2086 bool visit( Event *event )
2087 {
2088 mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
2089 return !mResult.isEmpty();
2090 }
2091 bool visit( Todo *todo )
2092 {
2093 mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
2094 return !mResult.isEmpty();
2095 }
2096 bool visit( Journal *journal )
2097 {
2098 mResult = invitationHeaderJournal( journal, mMessage );
2099 return !mResult.isEmpty();
2100 }
2101 bool visit( FreeBusy *fb )
2102 {
2103 mResult = invitationHeaderFreeBusy( fb, mMessage );
2104 return !mResult.isEmpty();
2105 }
2106};
2107
2108class KCal::IncidenceFormatter::InvitationBodyVisitor
2109 : public IncidenceFormatter::ScheduleMessageVisitor
2110{
2111 public:
2112 InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
2113 : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
2114
2115 protected:
2116 bool visit( Event *event )
2117 {
2118 mResult = invitationDetailsEvent( event, mNoHtmlMode, mSpec );
2119 return !mResult.isEmpty();
2120 }
2121 bool visit( Todo *todo )
2122 {
2123 mResult = invitationDetailsTodo( todo, mNoHtmlMode, mSpec );
2124 return !mResult.isEmpty();
2125 }
2126 bool visit( Journal *journal )
2127 {
2128 mResult = invitationDetailsJournal( journal, mNoHtmlMode, mSpec );
2129 return !mResult.isEmpty();
2130 }
2131 bool visit( FreeBusy *fb )
2132 {
2133 mResult = invitationDetailsFreeBusy( fb, mNoHtmlMode, mSpec );
2134 return !mResult.isEmpty();
2135 }
2136
2137 private:
2138 bool mNoHtmlMode;
2139 KDateTime::Spec mSpec;
2140};
2141//@endcond
2142
2143QString InvitationFormatterHelper::generateLinkURL( const QString &id )
2144{
2145 return id;
2146}
2147
2148//@cond PRIVATE
2149class IncidenceFormatter::IncidenceCompareVisitor
2150 : public IncidenceBase::Visitor
2151{
2152 public:
2153 IncidenceCompareVisitor() : mExistingIncidence( 0 ) {}
2154 bool act( IncidenceBase *incidence, Incidence *existingIncidence, iTIPMethod method )
2155 {
2156 if ( !existingIncidence ) {
2157 return false;
2158 }
2159 Incidence *inc = dynamic_cast<Incidence *>( incidence );
2160 if ( !inc || !existingIncidence || inc->revision() <= existingIncidence->revision() ) {
2161 return false;
2162 }
2163 mExistingIncidence = existingIncidence;
2164 mMethod = method;
2165 return incidence->accept( *this );
2166 }
2167
2168 QString result() const
2169 {
2170 if ( mChanges.isEmpty() ) {
2171 return QString();
2172 }
2173 QString html = "<div align=\"left\"><ul><li>";
2174 html += mChanges.join( "</li><li>" );
2175 html += "</li><ul></div>";
2176 return html;
2177 }
2178
2179 protected:
2180 bool visit( Event *event )
2181 {
2182 compareEvents( event, dynamic_cast<Event*>( mExistingIncidence ) );
2183 compareIncidences( event, mExistingIncidence, mMethod );
2184 return !mChanges.isEmpty();
2185 }
2186 bool visit( Todo *todo )
2187 {
2188 compareTodos( todo, dynamic_cast<Todo*>( mExistingIncidence ) );
2189 compareIncidences( todo, mExistingIncidence, mMethod );
2190 return !mChanges.isEmpty();
2191 }
2192 bool visit( Journal *journal )
2193 {
2194 compareIncidences( journal, mExistingIncidence, mMethod );
2195 return !mChanges.isEmpty();
2196 }
2197 bool visit( FreeBusy *fb )
2198 {
2199 Q_UNUSED( fb );
2200 return !mChanges.isEmpty();
2201 }
2202
2203 private:
2204 void compareEvents( Event *newEvent, Event *oldEvent )
2205 {
2206 if ( !oldEvent || !newEvent ) {
2207 return;
2208 }
2209 if ( oldEvent->dtStart() != newEvent->dtStart() ||
2210 oldEvent->allDay() != newEvent->allDay() ) {
2211 mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
2212 eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
2213 }
2214 if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
2215 oldEvent->allDay() != newEvent->allDay() ) {
2216 mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
2217 eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
2218 }
2219 }
2220
2221 void compareTodos( Todo *newTodo, Todo *oldTodo )
2222 {
2223 if ( !oldTodo || !newTodo ) {
2224 return;
2225 }
2226
2227 if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
2228 mChanges += i18n( "The to-do has been completed" );
2229 }
2230 if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
2231 mChanges += i18n( "The to-do is no longer completed" );
2232 }
2233 if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
2234 const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
2235 const QString newPer = i18n( "%1%", newTodo->percentComplete() );
2236 mChanges += i18n( "The task completed percentage has changed from %1 to %2",
2237 oldPer, newPer );
2238 }
2239
2240 if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
2241 mChanges += i18n( "A to-do starting time has been added" );
2242 }
2243 if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
2244 mChanges += i18n( "The to-do starting time has been removed" );
2245 }
2246 if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
2247 oldTodo->dtStart() != newTodo->dtStart() ) {
2248 mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
2249 dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
2250 dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
2251 }
2252
2253 if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
2254 mChanges += i18n( "A to-do due time has been added" );
2255 }
2256 if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
2257 mChanges += i18n( "The to-do due time has been removed" );
2258 }
2259 if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
2260 oldTodo->dtDue() != newTodo->dtDue() ) {
2261 mChanges += i18n( "The to-do due time has been changed from %1 to %2",
2262 dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
2263 dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
2264 }
2265 }
2266
2267 void compareIncidences( Incidence *newInc, Incidence *oldInc, iTIPMethod method )
2268 {
2269 if ( !oldInc || !newInc ) {
2270 return;
2271 }
2272
2273 if ( oldInc->summary() != newInc->summary() ) {
2274 mChanges += i18n( "The summary has been changed to: \"%1\"",
2275 newInc->richSummary() );
2276 }
2277
2278 if ( oldInc->location() != newInc->location() ) {
2279 mChanges += i18nc( "event/todo location", "The location has been changed to: \"%1\"",
2280 newInc->richLocation() );
2281 }
2282
2283 if ( oldInc->description() != newInc->description() ) {
2284 mChanges += i18n( "The description has been changed to: \"%1\"",
2285 newInc->richDescription() );
2286 }
2287
2288 Attendee::List oldAttendees = oldInc->attendees();
2289 Attendee::List newAttendees = newInc->attendees();
2290 for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
2291 it != newAttendees.constEnd(); ++it ) {
2292 Attendee *oldAtt = oldInc->attendeeByMail( (*it)->email() );
2293 if ( !oldAtt ) {
2294 mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
2295 } else {
2296 if ( oldAtt->status() != (*it)->status() ) {
2297 mChanges += i18n( "The status of attendee %1 has been changed to: %2",
2298 (*it)->fullName(), (*it)->statusStr() );
2299 }
2300 }
2301 }
2302
2303 if ( method == iTIPRequest ) {
2304 for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
2305 it != oldAttendees.constEnd(); ++it ) {
2306 if ( (*it)->email() != oldInc->organizer().email() ) {
2307 Attendee *newAtt = newInc->attendeeByMail( (*it)->email() );
2308 if ( !newAtt ) {
2309 mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
2310 }
2311 }
2312 }
2313 }
2314 }
2315
2316 private:
2317 Incidence *mExistingIncidence;
2318 iTIPMethod mMethod;
2319 QStringList mChanges;
2320};
2321//@endcond
2322
2323QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
2324{
2325 if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
2326 QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
2327 arg( generateLinkURL( id ), text );
2328 return res;
2329 } else {
2330 // draw the attachment links in non-bold face
2331 QString res = QString( "<a href=\"%1\">%2</a>" ).
2332 arg( generateLinkURL( id ), text );
2333 return res;
2334 }
2335}
2336
2337// Check if the given incidence is likely one that we own instead one from
2338// a shared calendar (Kolab-specific)
2339static bool incidenceOwnedByMe( Calendar *calendar, Incidence *incidence )
2340{
2341#ifndef KDEPIM_NO_KRESOURCES
2342 CalendarResources *cal = dynamic_cast<CalendarResources*>( calendar );
2343 if ( !cal || !incidence ) {
2344 return true;
2345 }
2346 ResourceCalendar *res = cal->resource( incidence );
2347 if ( !res ) {
2348 return true;
2349 }
2350 const QString subRes = res->subresourceIdentifier( incidence );
2351 if ( !subRes.contains( "/.INBOX.directory/" ) ) {
2352 return false;
2353 }
2354#endif
2355 return true;
2356}
2357
2358// The open & close table cell tags for the invitation buttons
2359static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
2360static QString tdClose = "</td>";
2361
2362static QString responseButtons( Incidence *inc, bool rsvpReq, bool rsvpRec,
2363 InvitationFormatterHelper *helper )
2364{
2365 QString html;
2366 if ( !helper ) {
2367 return html;
2368 }
2369
2370 if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
2371 // Record only
2372 html += tdOpen;
2373 html += helper->makeLink( "record", i18n( "[Record]" ) );
2374 html += tdClose;
2375
2376 // Move to trash
2377 html += tdOpen;
2378 html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
2379 html += tdClose;
2380
2381 } else {
2382
2383 // Accept
2384 html += tdOpen;
2385 html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
2386 html += tdClose;
2387
2388 // Tentative
2389 html += tdOpen;
2390 html += helper->makeLink( "accept_conditionally",
2391 i18nc( "Accept invitation conditionally", "Accept cond." ) );
2392 html += tdClose;
2393
2394 // Counter proposal
2395 html += tdOpen;
2396 html += helper->makeLink( "counter",
2397 i18nc( "invitation counter proposal", "Counter proposal" ) );
2398 html += tdClose;
2399
2400 // Decline
2401 html += tdOpen;
2402 html += helper->makeLink( "decline",
2403 i18nc( "decline invitation", "Decline" ) );
2404 html += tdClose;
2405 }
2406
2407 if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
2408 // Delegate
2409 html += tdOpen;
2410 html += helper->makeLink( "delegate",
2411 i18nc( "delegate inviation to another", "Delegate" ) );
2412 html += tdClose;
2413
2414 // Forward
2415 html += tdOpen;
2416 html += helper->makeLink( "forward",
2417 i18nc( "forward request to another", "Forward" ) );
2418 html += tdClose;
2419
2420 // Check calendar
2421 if ( inc && inc->type() == "Event" ) {
2422 html += tdOpen;
2423 html += helper->makeLink( "check_calendar",
2424 i18nc( "look for scheduling conflicts", "Check my calendar" ) );
2425 html += tdClose;
2426 }
2427 }
2428 return html;
2429}
2430
2431static QString counterButtons( Incidence *incidence,
2432 InvitationFormatterHelper *helper )
2433{
2434 QString html;
2435 if ( !helper ) {
2436 return html;
2437 }
2438
2439 // Accept proposal
2440 html += tdOpen;
2441 html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
2442 html += tdClose;
2443
2444 // Decline proposal
2445 html += tdOpen;
2446 html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
2447 html += tdClose;
2448
2449 // Check calendar
2450 if ( incidence && incidence->type() == "Event" ) {
2451 html += tdOpen;
2452 html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
2453 html += tdClose;
2454 }
2455 return html;
2456}
2457
2458Calendar *InvitationFormatterHelper::calendar() const
2459{
2460 return 0;
2461}
2462
2463static QString formatICalInvitationHelper( QString invitation,
2464 Calendar *mCalendar,
2465 InvitationFormatterHelper *helper,
2466 bool noHtmlMode,
2467 KDateTime::Spec spec,
2468 const QString &sender )
2469{
2470 if ( invitation.isEmpty() ) {
2471 return QString();
2472 }
2473
2474 ICalFormat format;
2475 // parseScheduleMessage takes the tz from the calendar,
2476 // no need to set it manually here for the format!
2477 ScheduleMessage *msg = format.parseScheduleMessage( mCalendar, invitation );
2478
2479 if( !msg ) {
2480 kDebug() << "Failed to parse the scheduling message";
2481 Q_ASSERT( format.exception() );
2482 kDebug() << format.exception()->message();
2483 return QString();
2484 }
2485
2486 IncidenceBase *incBase = msg->event();
2487 incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
2488
2489 // Determine if this incidence is in my calendar (and owned by me)
2490 Incidence *existingIncidence = 0;
2491 if ( incBase && helper->calendar() ) {
2492 existingIncidence = helper->calendar()->incidence( incBase->uid() );
2493 if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
2494 existingIncidence = 0;
2495 }
2496 if ( !existingIncidence ) {
2497 const Incidence::List list = helper->calendar()->incidences();
2498 for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
2499 if ( (*it)->schedulingID() == incBase->uid() &&
2500 incidenceOwnedByMe( helper->calendar(), *it ) ) {
2501 existingIncidence = *it;
2502 break;
2503 }
2504 }
2505 }
2506 }
2507
2508 // First make the text of the message
2509 QString html;
2510 html += "<div align=\"center\" style=\"border:solid 1px;\">";
2511
2512 IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
2513 // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
2514 if ( !headerVisitor.act( incBase, existingIncidence, msg, sender ) ) {
2515 return QString();
2516 }
2517 html += htmlAddTag( "h3", headerVisitor.result() );
2518
2519 IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
2520 if ( !bodyVisitor.act( incBase, existingIncidence, msg, sender ) ) {
2521 return QString();
2522 }
2523 html += bodyVisitor.result();
2524
2525 if ( msg->method() == iTIPRequest ) {
2526 IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
2527 if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
2528 html += "<p align=\"left\">";
2529 html += i18n( "The following changes have been made by the organizer:" );
2530 html += "</p>";
2531 html += compareVisitor.result();
2532 }
2533 }
2534 if ( msg->method() == iTIPReply ) {
2535 IncidenceCompareVisitor compareVisitor;
2536 if ( compareVisitor.act( incBase, existingIncidence, msg->method() ) ) {
2537 html += "<p align=\"left\">";
2538 if ( !sender.isEmpty() ) {
2539 html += i18n( "The following changes have been made by %1:", sender );
2540 } else {
2541 html += i18n( "The following changes have been made by an attendee:" );
2542 }
2543 html += "</p>";
2544 html += compareVisitor.result();
2545 }
2546 }
2547
2548 Incidence *inc = dynamic_cast<Incidence*>( incBase );
2549
2550 // determine if I am the organizer for this invitation
2551 bool myInc = iamOrganizer( inc );
2552
2553 // determine if the invitation response has already been recorded
2554 bool rsvpRec = false;
2555 Attendee *ea = 0;
2556 if ( !myInc ) {
2557 Incidence *rsvpIncidence = existingIncidence;
2558 if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
2559 rsvpIncidence = inc;
2560 }
2561 if ( rsvpIncidence ) {
2562 ea = findMyAttendee( rsvpIncidence );
2563 }
2564 if ( ea &&
2565 ( ea->status() == Attendee::Accepted ||
2566 ea->status() == Attendee::Declined ||
2567 ea->status() == Attendee::Tentative ) ) {
2568 rsvpRec = true;
2569 }
2570 }
2571
2572 // determine invitation role
2573 QString role;
2574 bool isDelegated = false;
2575 Attendee *a = findMyAttendee( inc );
2576 if ( !a && inc ) {
2577 if ( !inc->attendees().isEmpty() ) {
2578 a = inc->attendees().first();
2579 }
2580 }
2581 if ( a ) {
2582 isDelegated = ( a->status() == Attendee::Delegated );
2583 role = Attendee::roleName( a->role() );
2584 }
2585
2586 // Print if RSVP needed, not-needed, or response already recorded
2587 bool rsvpReq = rsvpRequested( inc );
2588 if ( !myInc && a ) {
2589 html += "<br/>";
2590 html += "<i><u>";
2591 if ( rsvpRec && inc ) {
2592 if ( inc->revision() == 0 ) {
2593 html += i18n( "Your <b>%1</b> response has already been recorded", ea->statusStr() );
2594 } else {
2595 html += i18n( "Your status for this invitation is <b>%1</b>", ea->statusStr() );
2596 }
2597 rsvpReq = false;
2598 } else if ( msg->method() == iTIPCancel ) {
2599 html += i18n( "This invitation was declined" );
2600 } else if ( msg->method() == iTIPAdd ) {
2601 html += i18n( "This invitation was accepted" );
2602 } else {
2603 if ( !isDelegated ) {
2604 html += rsvpRequestedStr( rsvpReq, role );
2605 } else {
2606 html += i18n( "Awaiting delegation response" );
2607 }
2608 }
2609 html += "</u></i>";
2610 }
2611
2612 // Print if the organizer gave you a preset status
2613 if ( !myInc ) {
2614 if ( inc && inc->revision() == 0 ) {
2615 QString statStr = myStatusStr( inc );
2616 if ( !statStr.isEmpty() ) {
2617 html += "<br/>";
2618 html += "<i>";
2619 html += statStr;
2620 html += "</i>";
2621 }
2622 }
2623 }
2624
2625 // Add groupware links
2626
2627 html += "<p>";
2628 html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
2629
2630 switch ( msg->method() ) {
2631 case iTIPPublish:
2632 case iTIPRequest:
2633 case iTIPRefresh:
2634 case iTIPAdd:
2635 {
2636 if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
2637 if ( inc->type() == "Todo" ) {
2638 html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
2639 } else {
2640 html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
2641 }
2642 }
2643
2644 if ( !myInc && a ) {
2645 html += responseButtons( inc, rsvpReq, rsvpRec, helper );
2646 }
2647 break;
2648 }
2649
2650 case iTIPCancel:
2651 // Remove invitation
2652 if ( inc ) {
2653 html += tdOpen;
2654 if ( inc->type() == "Todo" ) {
2655 html += helper->makeLink( "cancel",
2656 i18n( "Remove invitation from my to-do list" ) );
2657 } else {
2658 html += helper->makeLink( "cancel",
2659 i18n( "Remove invitation from my calendar" ) );
2660 }
2661 html += tdClose;
2662 }
2663 break;
2664
2665 case iTIPReply:
2666 {
2667 // Record invitation response
2668 Attendee *a = 0;
2669 Attendee *ea = 0;
2670 if ( inc ) {
2671 // First, determine if this reply is really a counter in disguise.
2672 if ( replyMeansCounter( inc ) ) {
2673 html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
2674 break;
2675 }
2676
2677 // Next, maybe this is a declined reply that was delegated from me?
2678 // find first attendee who is delegated-from me
2679 // look a their PARTSTAT response, if the response is declined,
2680 // then we need to start over which means putting all the action
2681 // buttons and NOT putting on the [Record response..] button
2682 a = findDelegatedFromMyAttendee( inc );
2683 if ( a ) {
2684 if ( a->status() != Attendee::Accepted ||
2685 a->status() != Attendee::Tentative ) {
2686 html += responseButtons( inc, rsvpReq, rsvpRec, helper );
2687 break;
2688 }
2689 }
2690
2691 // Finally, simply allow a Record of the reply
2692 if ( !inc->attendees().isEmpty() ) {
2693 a = inc->attendees().first();
2694 }
2695 if ( a && helper->calendar() ) {
2696 ea = findAttendee( existingIncidence, a->email() );
2697 }
2698 }
2699 if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
2700 html += tdOpen;
2701 html += htmlAddTag( "i", i18n( "The response has already been recorded" ) );
2702 html += tdClose;
2703 } else {
2704 if ( inc ) {
2705 if ( inc->type() == "Todo" ) {
2706 html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
2707 } else {
2708 html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
2709 }
2710 }
2711 }
2712 break;
2713 }
2714
2715 case iTIPCounter:
2716 // Counter proposal
2717 html += counterButtons( inc, helper );
2718 break;
2719
2720 case iTIPDeclineCounter:
2721 case iTIPNoMethod:
2722 break;
2723 }
2724
2725 // close the groupware table
2726 html += "</tr></table>";
2727
2728 // Add the attendee list if I am the organizer
2729 if ( myInc && helper->calendar() ) {
2730 html += invitationAttendees( helper->calendar()->incidence( inc->uid() ) );
2731 }
2732
2733 // close the top-level
2734 html += "</div>";
2735
2736 // Add the attachment list
2737 html += invitationAttachments( helper, inc );
2738
2739 return html;
2740}
2741//@endcond
2742
2743QString IncidenceFormatter::formatICalInvitation( QString invitation,
2744 Calendar *calendar,
2745 InvitationFormatterHelper *helper )
2746{
2747 return formatICalInvitationHelper( invitation, calendar, helper, false,
2748 KSystemTimeZones::local(), QString() );
2749}
2750
2751QString IncidenceFormatter::formatICalInvitationNoHtml( QString invitation,
2752 Calendar *calendar,
2753 InvitationFormatterHelper *helper )
2754{
2755 return formatICalInvitationHelper( invitation, calendar, helper, true,
2756 KSystemTimeZones::local(), QString() );
2757}
2758
2759QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
2760 Calendar *calendar,
2761 InvitationFormatterHelper *helper,
2762 const QString &sender )
2763{
2764 return formatICalInvitationHelper( invitation, calendar, helper, true,
2765 KSystemTimeZones::local(), sender );
2766}
2767
2768/*******************************************************************
2769 * Helper functions for the Incidence tooltips
2770 *******************************************************************/
2771
2772//@cond PRIVATE
2773class KCal::IncidenceFormatter::ToolTipVisitor
2774 : public IncidenceBase::Visitor
2775{
2776 public:
2777 ToolTipVisitor()
2778 : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
2779
2780 bool act( Calendar *calendar, IncidenceBase *incidence,
2781 const QDate &date=QDate(), bool richText=true,
2782 KDateTime::Spec spec=KDateTime::Spec() )
2783 {
2784 mCalendar = calendar;
2785 mLocation.clear();
2786 mDate = date;
2787 mRichText = richText;
2788 mSpec = spec;
2789 mResult = "";
2790 return incidence ? incidence->accept( *this ) : false;
2791 }
2792
2793 bool act( const QString &location, IncidenceBase *incidence,
2794 const QDate &date=QDate(), bool richText=true,
2795 KDateTime::Spec spec=KDateTime::Spec() )
2796 {
2797 mCalendar = 0;
2798 mLocation = location;
2799 mDate = date;
2800 mRichText = richText;
2801 mSpec = spec;
2802 mResult = "";
2803 return incidence ? incidence->accept( *this ) : false;
2804 }
2805
2806 QString result() const { return mResult; }
2807
2808 protected:
2809 bool visit( Event *event );
2810 bool visit( Todo *todo );
2811 bool visit( Journal *journal );
2812 bool visit( FreeBusy *fb );
2813
2814 QString dateRangeText( Event *event, const QDate &date );
2815 QString dateRangeText( Todo *todo, const QDate &date );
2816 QString dateRangeText( Journal *journal );
2817 QString dateRangeText( FreeBusy *fb );
2818
2819 QString generateToolTip( Incidence *incidence, QString dtRangeText );
2820
2821 protected:
2822 Calendar *mCalendar;
2823 QString mLocation;
2824 QDate mDate;
2825 bool mRichText;
2826 KDateTime::Spec mSpec;
2827 QString mResult;
2828};
2829
2830QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Event *event, const QDate &date )
2831{
2832 //FIXME: support mRichText==false
2833 QString ret;
2834 QString tmp;
2835
2836 KDateTime startDt = event->dtStart();
2837 KDateTime endDt = event->dtEnd();
2838 if ( event->recurs() ) {
2839 if ( date.isValid() ) {
2840 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
2841 int diffDays = startDt.daysTo( kdt );
2842 kdt = kdt.addSecs( -1 );
2843 startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
2844 if ( event->hasEndDate() ) {
2845 endDt = endDt.addDays( diffDays );
2846 if ( startDt > endDt ) {
2847 startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
2848 endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
2849 }
2850 }
2851 }
2852 }
2853
2854 if ( event->isMultiDay() ) {
2855 tmp = dateToString( startDt, true, mSpec );
2856 ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
2857
2858 tmp = dateToString( endDt, true, mSpec );
2859 ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
2860
2861 } else {
2862
2863 ret += "<br>" +
2864 i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
2865 if ( !event->allDay() ) {
2866 const QString dtStartTime = timeToString( startDt, true, mSpec );
2867 const QString dtEndTime = timeToString( endDt, true, mSpec );
2868 if ( dtStartTime == dtEndTime ) {
2869 // to prevent 'Time: 17:00 - 17:00'
2870 tmp = "<br>" +
2871 i18nc( "time for event", "<i>Time:</i> %1",
2872 dtStartTime );
2873 } else {
2874 tmp = "<br>" +
2875 i18nc( "time range for event",
2876 "<i>Time:</i> %1 - %2",
2877 dtStartTime, dtEndTime );
2878 }
2879 ret += tmp;
2880 }
2881 }
2882 return ret.replace( ' ', "&nbsp;" );
2883}
2884
2885QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Todo *todo, const QDate &date )
2886{
2887 //FIXME: support mRichText==false
2888 QString ret;
2889 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
2890 KDateTime startDt = todo->dtStart();
2891 if ( todo->recurs() ) {
2892 if ( date.isValid() ) {
2893 startDt.setDate( date );
2894 }
2895 }
2896 ret += "<br>" +
2897 i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
2898 }
2899
2900 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
2901 KDateTime dueDt = todo->dtDue();
2902 if ( todo->recurs() ) {
2903 if ( date.isValid() ) {
2904 KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
2905 kdt = kdt.addSecs( -1 );
2906 dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
2907 }
2908 }
2909 ret += "<br>" +
2910 i18n( "<i>Due:</i> %1",
2911 dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
2912 }
2913
2914 // Print priority and completed info here, for lack of a better place
2915
2916 if ( todo->priority() > 0 ) {
2917 ret += "<br>";
2918 ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
2919 ret += QString::number( todo->priority() );
2920 }
2921
2922 ret += "<br>";
2923 if ( todo->isCompleted() ) {
2924 ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + "&nbsp;";
2925 ret += todo->completedStr().replace( ' ', "&nbsp;" );
2926 } else {
2927 ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
2928 ret += i18n( "%1%", todo->percentComplete() );
2929 }
2930
2931 return ret.replace( ' ', "&nbsp;" );
2932}
2933
2934QString IncidenceFormatter::ToolTipVisitor::dateRangeText( Journal *journal )
2935{
2936 //FIXME: support mRichText==false
2937 QString ret;
2938 if ( journal->dtStart().isValid() ) {
2939 ret += "<br>" +
2940 i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
2941 }
2942 return ret.replace( ' ', "&nbsp;" );
2943}
2944
2945QString IncidenceFormatter::ToolTipVisitor::dateRangeText( FreeBusy *fb )
2946{
2947 //FIXME: support mRichText==false
2948 QString ret;
2949 ret = "<br>" +
2950 i18n( "<i>Period start:</i> %1",
2951 KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
2952 ret += "<br>" +
2953 i18n( "<i>Period start:</i> %1",
2954 KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
2955 return ret.replace( ' ', "&nbsp;" );
2956}
2957
2958bool IncidenceFormatter::ToolTipVisitor::visit( Event *event )
2959{
2960 mResult = generateToolTip( event, dateRangeText( event, mDate ) );
2961 return !mResult.isEmpty();
2962}
2963
2964bool IncidenceFormatter::ToolTipVisitor::visit( Todo *todo )
2965{
2966 mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
2967 return !mResult.isEmpty();
2968}
2969
2970bool IncidenceFormatter::ToolTipVisitor::visit( Journal *journal )
2971{
2972 mResult = generateToolTip( journal, dateRangeText( journal ) );
2973 return !mResult.isEmpty();
2974}
2975
2976bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy *fb )
2977{
2978 //FIXME: support mRichText==false
2979 mResult = "<qt><b>" + i18n( "Free/Busy information for %1", fb->organizer().fullName() ) + "</b>";
2980 mResult += dateRangeText( fb );
2981 mResult += "</qt>";
2982 return !mResult.isEmpty();
2983}
2984
2985static QString tooltipPerson( const QString &email, QString name )
2986{
2987 // Make the search, if there is an email address to search on,
2988 // and name is missing
2989 if ( name.isEmpty() && !email.isEmpty() ) {
2990#ifndef KDEPIM_NO_KRESOURCES
2991 KABC::AddressBook *add_book = KABC::StdAddressBook::self( true );
2992 KABC::Addressee::List addressList = add_book->findByEmail( email );
2993 if ( !addressList.isEmpty() ) {
2994 KABC::Addressee o = addressList.first();
2995 if ( !o.isEmpty() && addressList.size() < 2 ) {
2996 // use the name from the addressbook
2997 name = o.formattedName();
2998 }
2999 }
3000#endif
3001 }
3002
3003 // Show the attendee
3004 QString tmpString = ( name.isEmpty() ? email : name );
3005
3006 return tmpString;
3007}
3008
3009static QString tooltipFormatAttendeeRoleList( Incidence *incidence, Attendee::Role role )
3010{
3011 int maxNumAtts = 8; // maximum number of people to print per attendee role
3012 QString sep = i18nc( "separator for lists of people names", ", " );
3013 int sepLen = sep.length();
3014
3015 int i = 0;
3016 QString tmpStr;
3017 Attendee::List::ConstIterator it;
3018 Attendee::List attendees = incidence->attendees();
3019
3020 for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
3021 Attendee *a = *it;
3022 if ( a->role() != role ) {
3023 // skip not this role
3024 continue;
3025 }
3026 if ( a->email() == incidence->organizer().email() ) {
3027 // skip attendee that is also the organizer
3028 continue;
3029 }
3030 if ( i == maxNumAtts ) {
3031 static QString etc = i18nc( "elipsis", "..." );
3032 tmpStr += etc;
3033 break;
3034 }
3035 tmpStr += tooltipPerson( a->email(), a->name() );
3036 if ( !a->delegator().isEmpty() ) {
3037 tmpStr += i18n( " (delegated by %1)", a->delegator() );
3038 }
3039 if ( !a->delegate().isEmpty() ) {
3040 tmpStr += i18n( " (delegated to %1)", a->delegate() );
3041 }
3042 tmpStr += sep;
3043 i++;
3044 }
3045 if ( tmpStr.endsWith( sep ) ) {
3046 tmpStr.truncate( tmpStr.length() - sepLen );
3047 }
3048 return tmpStr;
3049}
3050
3051static QString tooltipFormatAttendees( Incidence *incidence )
3052{
3053 QString tmpStr, str;
3054
3055 // Add organizer link
3056 int attendeeCount = incidence->attendees().count();
3057 if ( attendeeCount > 1 ||
3058 ( attendeeCount == 1 &&
3059 incidence->organizer().email() != incidence->attendees().first()->email() ) ) {
3060 tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "&nbsp;";
3061 tmpStr += tooltipPerson( incidence->organizer().email(),
3062 incidence->organizer().name() );
3063 }
3064
3065 // Add "chair"
3066 str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair );
3067 if ( !str.isEmpty() ) {
3068 tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "&nbsp;";
3069 tmpStr += str;
3070 }
3071
3072 // Add required participants
3073 str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant );
3074 if ( !str.isEmpty() ) {
3075 tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "&nbsp;";
3076 tmpStr += str;
3077 }
3078
3079 // Add optional participants
3080 str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant );
3081 if ( !str.isEmpty() ) {
3082 tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "&nbsp;";
3083 tmpStr += str;
3084 }
3085
3086 // Add observers
3087 str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant );
3088 if ( !str.isEmpty() ) {
3089 tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "&nbsp;";
3090 tmpStr += str;
3091 }
3092
3093 return tmpStr;
3094}
3095
3096QString IncidenceFormatter::ToolTipVisitor::generateToolTip( Incidence *incidence,
3097 QString dtRangeText )
3098{
3099 int maxDescLen = 120; // maximum description chars to print (before elipsis)
3100
3101 //FIXME: support mRichText==false
3102 if ( !incidence ) {
3103 return QString();
3104 }
3105
3106 QString tmp = "<qt>";
3107
3108 // header
3109 tmp += "<b>" + incidence->richSummary() + "</b>";
3110 tmp += "<hr>";
3111
3112 QString calStr = mLocation;
3113 if ( mCalendar ) {
3114 calStr = resourceString( mCalendar, incidence );
3115 }
3116 if ( !calStr.isEmpty() ) {
3117 tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
3118 tmp += calStr;
3119 }
3120
3121 tmp += dtRangeText;
3122
3123 if ( !incidence->location().isEmpty() ) {
3124 tmp += "<br>";
3125 tmp += "<i>" + i18nc( "event/todo location", "Location:" ) + "</i>" + "&nbsp;";
3126 tmp += incidence->richLocation();
3127 }
3128
3129 QString durStr = durationString( incidence );
3130 if ( !durStr.isEmpty() ) {
3131 tmp += "<br>";
3132 tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
3133 tmp += durStr;
3134 }
3135
3136 if ( incidence->recurs() ) {
3137 tmp += "<br>";
3138 tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
3139 tmp += recurrenceString( incidence );
3140 }
3141
3142 if ( !incidence->description().isEmpty() ) {
3143 QString desc( incidence->description() );
3144 if ( !incidence->descriptionIsRich() ) {
3145 if ( desc.length() > maxDescLen ) {
3146 static QString etc = i18nc( "elipsis", "..." );
3147 desc = desc.left( maxDescLen ) + etc;
3148 }
3149 desc = Qt::escape( desc ).replace( '\n', "<br>" );
3150 } else {
3151 // TODO: truncate the description when it's rich text
3152 }
3153 tmp += "<hr>";
3154 tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
3155 tmp += desc;
3156 tmp += "<hr>";
3157 }
3158
3159 int reminderCount = incidence->alarms().count();
3160 if ( reminderCount > 0 && incidence->isAlarmEnabled() ) {
3161 tmp += "<br>";
3162 tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + "&nbsp;";
3163 tmp += reminderStringList( incidence ).join( ", " );
3164 }
3165
3166 tmp += "<br>";
3167 tmp += tooltipFormatAttendees( incidence );
3168
3169 int categoryCount = incidence->categories().count();
3170 if ( categoryCount > 0 ) {
3171 tmp += "<br>";
3172 tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + "&nbsp;";
3173 tmp += incidence->categories().join( ", " );
3174 }
3175
3176 tmp += "</qt>";
3177 return tmp;
3178}
3179//@endcond
3180
3181QString IncidenceFormatter::toolTipString( IncidenceBase *incidence,
3182 bool richText )
3183{
3184 return toolTipStr( 0, incidence, QDate(), richText, KDateTime::Spec() );
3185}
3186
3187QString IncidenceFormatter::toolTipStr( IncidenceBase *incidence,
3188 bool richText, KDateTime::Spec spec )
3189{
3190 ToolTipVisitor v;
3191 if ( v.act( 0, incidence, QDate(), richText, spec ) ) {
3192 return v.result();
3193 } else {
3194 return QString();
3195 }
3196}
3197
3198QString IncidenceFormatter::toolTipStr( Calendar *calendar,
3199 IncidenceBase *incidence,
3200 const QDate &date,
3201 bool richText, KDateTime::Spec spec )
3202{
3203 ToolTipVisitor v;
3204 if ( v.act( calendar, incidence, date, richText, spec ) ) {
3205 return v.result();
3206 } else {
3207 return QString();
3208 }
3209}
3210
3211QString IncidenceFormatter::toolTipStr( const QString &sourceName,
3212 IncidenceBase *incidence,
3213 const QDate &date,
3214 bool richText, KDateTime::Spec spec )
3215{
3216 ToolTipVisitor v;
3217 if ( v.act( sourceName, incidence, date, richText, spec ) ) {
3218 return v.result();
3219 } else {
3220 return QString();
3221 }
3222}
3223
3224/*******************************************************************
3225 * Helper functions for the Incidence tooltips
3226 *******************************************************************/
3227
3228//@cond PRIVATE
3229static QString mailBodyIncidence( Incidence *incidence )
3230{
3231 QString body;
3232 if ( !incidence->summary().isEmpty() ) {
3233 body += i18n( "Summary: %1\n", incidence->richSummary() );
3234 }
3235 if ( !incidence->organizer().isEmpty() ) {
3236 body += i18n( "Organizer: %1\n", incidence->organizer().fullName() );
3237 }
3238 if ( !incidence->location().isEmpty() ) {
3239 body += i18nc( "event/todo location", "Location: %1\n", incidence->richLocation() );
3240 }
3241 return body;
3242}
3243//@endcond
3244
3245//@cond PRIVATE
3246class KCal::IncidenceFormatter::MailBodyVisitor
3247 : public IncidenceBase::Visitor
3248{
3249 public:
3250 MailBodyVisitor()
3251 : mSpec( KDateTime::Spec() ), mResult( "" ) {}
3252
3253 bool act( IncidenceBase *incidence, KDateTime::Spec spec=KDateTime::Spec() )
3254 {
3255 mSpec = spec;
3256 mResult = "";
3257 return incidence ? incidence->accept( *this ) : false;
3258 }
3259 QString result() const
3260 {
3261 return mResult;
3262 }
3263
3264 protected:
3265 bool visit( Event *event );
3266 bool visit( Todo *todo );
3267 bool visit( Journal *journal );
3268 bool visit( FreeBusy * )
3269 {
3270 mResult = i18n( "This is a Free Busy Object" );
3271 return !mResult.isEmpty();
3272 }
3273 protected:
3274 KDateTime::Spec mSpec;
3275 QString mResult;
3276};
3277
3278bool IncidenceFormatter::MailBodyVisitor::visit( Event *event )
3279{
3280 QString recurrence[]= {
3281 i18nc( "no recurrence", "None" ),
3282 i18nc( "event recurs by minutes", "Minutely" ),
3283 i18nc( "event recurs by hours", "Hourly" ),
3284 i18nc( "event recurs by days", "Daily" ),
3285 i18nc( "event recurs by weeks", "Weekly" ),
3286 i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
3287 i18nc( "event recurs same day each month", "Monthly Same Day" ),
3288 i18nc( "event recurs same month each year", "Yearly Same Month" ),
3289 i18nc( "event recurs same day each year", "Yearly Same Day" ),
3290 i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
3291 };
3292
3293 mResult = mailBodyIncidence( event );
3294 mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
3295 if ( !event->allDay() ) {
3296 mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
3297 }
3298 if ( event->dtStart() != event->dtEnd() ) {
3299 mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
3300 }
3301 if ( !event->allDay() ) {
3302 mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
3303 }
3304 if ( event->recurs() ) {
3305 Recurrence *recur = event->recurrence();
3306 // TODO: Merge these two to one of the form "Recurs every 3 days"
3307 mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
3308 mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
3309
3310 if ( recur->duration() > 0 ) {
3311 mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
3312 mResult += '\n';
3313 } else {
3314 if ( recur->duration() != -1 ) {
3315// TODO_Recurrence: What to do with all-day
3316 QString endstr;
3317 if ( event->allDay() ) {
3318 endstr = KGlobal::locale()->formatDate( recur->endDate() );
3319 } else {
3320 endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
3321 }
3322 mResult += i18n( "Repeat until: %1\n", endstr );
3323 } else {
3324 mResult += i18n( "Repeats forever\n" );
3325 }
3326 }
3327 }
3328
3329 QString details = event->richDescription();
3330 if ( !details.isEmpty() ) {
3331 mResult += i18n( "Details:\n%1\n", details );
3332 }
3333 return !mResult.isEmpty();
3334}
3335
3336bool IncidenceFormatter::MailBodyVisitor::visit( Todo *todo )
3337{
3338 mResult = mailBodyIncidence( todo );
3339
3340 if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
3341 mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
3342 if ( !todo->allDay() ) {
3343 mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
3344 }
3345 }
3346 if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
3347 mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
3348 if ( !todo->allDay() ) {
3349 mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
3350 }
3351 }
3352 QString details = todo->richDescription();
3353 if ( !details.isEmpty() ) {
3354 mResult += i18n( "Details:\n%1\n", details );
3355 }
3356 return !mResult.isEmpty();
3357}
3358
3359bool IncidenceFormatter::MailBodyVisitor::visit( Journal *journal )
3360{
3361 mResult = mailBodyIncidence( journal );
3362 mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
3363 if ( !journal->allDay() ) {
3364 mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
3365 }
3366 if ( !journal->description().isEmpty() ) {
3367 mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
3368 }
3369 return !mResult.isEmpty();
3370}
3371//@endcond
3372
3373QString IncidenceFormatter::mailBodyString( IncidenceBase *incidence )
3374{
3375 return mailBodyStr( incidence, KDateTime::Spec() );
3376}
3377
3378QString IncidenceFormatter::mailBodyStr( IncidenceBase *incidence,
3379 KDateTime::Spec spec )
3380{
3381 if ( !incidence ) {
3382 return QString();
3383 }
3384
3385 MailBodyVisitor v;
3386 if ( v.act( incidence, spec ) ) {
3387 return v.result();
3388 }
3389 return QString();
3390}
3391
3392//@cond PRIVATE
3393static QString recurEnd( Incidence *incidence )
3394{
3395 QString endstr;
3396 if ( incidence->allDay() ) {
3397 endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
3398 } else {
3399 endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
3400 }
3401 return endstr;
3402}
3403//@endcond
3404
3405/************************************
3406 * More static formatting functions
3407 ************************************/
3408
3409QString IncidenceFormatter::recurrenceString( Incidence *incidence )
3410{
3411 if ( !incidence->recurs() ) {
3412 return i18n( "No recurrence" );
3413 }
3414 QStringList dayList;
3415 dayList.append( i18n( "31st Last" ) );
3416 dayList.append( i18n( "30th Last" ) );
3417 dayList.append( i18n( "29th Last" ) );
3418 dayList.append( i18n( "28th Last" ) );
3419 dayList.append( i18n( "27th Last" ) );
3420 dayList.append( i18n( "26th Last" ) );
3421 dayList.append( i18n( "25th Last" ) );
3422 dayList.append( i18n( "24th Last" ) );
3423 dayList.append( i18n( "23rd Last" ) );
3424 dayList.append( i18n( "22nd Last" ) );
3425 dayList.append( i18n( "21st Last" ) );
3426 dayList.append( i18n( "20th Last" ) );
3427 dayList.append( i18n( "19th Last" ) );
3428 dayList.append( i18n( "18th Last" ) );
3429 dayList.append( i18n( "17th Last" ) );
3430 dayList.append( i18n( "16th Last" ) );
3431 dayList.append( i18n( "15th Last" ) );
3432 dayList.append( i18n( "14th Last" ) );
3433 dayList.append( i18n( "13th Last" ) );
3434 dayList.append( i18n( "12th Last" ) );
3435 dayList.append( i18n( "11th Last" ) );
3436 dayList.append( i18n( "10th Last" ) );
3437 dayList.append( i18n( "9th Last" ) );
3438 dayList.append( i18n( "8th Last" ) );
3439 dayList.append( i18n( "7th Last" ) );
3440 dayList.append( i18n( "6th Last" ) );
3441 dayList.append( i18n( "5th Last" ) );
3442 dayList.append( i18n( "4th Last" ) );
3443 dayList.append( i18n( "3rd Last" ) );
3444 dayList.append( i18n( "2nd Last" ) );
3445 dayList.append( i18nc( "last day of the month", "Last" ) );
3446 dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
3447 dayList.append( i18n( "1st" ) );
3448 dayList.append( i18n( "2nd" ) );
3449 dayList.append( i18n( "3rd" ) );
3450 dayList.append( i18n( "4th" ) );
3451 dayList.append( i18n( "5th" ) );
3452 dayList.append( i18n( "6th" ) );
3453 dayList.append( i18n( "7th" ) );
3454 dayList.append( i18n( "8th" ) );
3455 dayList.append( i18n( "9th" ) );
3456 dayList.append( i18n( "10th" ) );
3457 dayList.append( i18n( "11th" ) );
3458 dayList.append( i18n( "12th" ) );
3459 dayList.append( i18n( "13th" ) );
3460 dayList.append( i18n( "14th" ) );
3461 dayList.append( i18n( "15th" ) );
3462 dayList.append( i18n( "16th" ) );
3463 dayList.append( i18n( "17th" ) );
3464 dayList.append( i18n( "18th" ) );
3465 dayList.append( i18n( "19th" ) );
3466 dayList.append( i18n( "20th" ) );
3467 dayList.append( i18n( "21st" ) );
3468 dayList.append( i18n( "22nd" ) );
3469 dayList.append( i18n( "23rd" ) );
3470 dayList.append( i18n( "24th" ) );
3471 dayList.append( i18n( "25th" ) );
3472 dayList.append( i18n( "26th" ) );
3473 dayList.append( i18n( "27th" ) );
3474 dayList.append( i18n( "28th" ) );
3475 dayList.append( i18n( "29th" ) );
3476 dayList.append( i18n( "30th" ) );
3477 dayList.append( i18n( "31st" ) );
3478 int weekStart = KGlobal::locale()->weekStartDay();
3479 QString dayNames;
3480 QString txt;
3481 const KCalendarSystem *calSys = KGlobal::locale()->calendar();
3482 Recurrence *recur = incidence->recurrence();
3483 switch ( recur->recurrenceType() ) {
3484 case Recurrence::rNone:
3485 return i18n( "No recurrence" );
3486 case Recurrence::rMinutely:
3487 if ( recur->duration() != -1 ) {
3488 txt = i18np( "Recurs every minute until %2",
3489 "Recurs every %1 minutes until %2",
3490 recur->frequency(), recurEnd( incidence ) );
3491 if ( recur->duration() > 0 ) {
3492 txt += i18nc( "number of occurrences",
3493 " (<numid>%1</numid> occurrences)",
3494 recur->duration() );
3495 }
3496 return txt;
3497 }
3498 return i18np( "Recurs every minute",
3499 "Recurs every %1 minutes", recur->frequency() );
3500 case Recurrence::rHourly:
3501 if ( recur->duration() != -1 ) {
3502 txt = i18np( "Recurs hourly until %2",
3503 "Recurs every %1 hours until %2",
3504 recur->frequency(), recurEnd( incidence ) );
3505 if ( recur->duration() > 0 ) {
3506 txt += i18nc( "number of occurrences",
3507 " (<numid>%1</numid> occurrences)",
3508 recur->duration() );
3509 }
3510 return txt;
3511 }
3512 return i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
3513 case Recurrence::rDaily:
3514 if ( recur->duration() != -1 ) {
3515 txt = i18np( "Recurs daily until %2",
3516 "Recurs every %1 days until %2",
3517 recur->frequency(), recurEnd( incidence ) );
3518 if ( recur->duration() > 0 ) {
3519 txt += i18nc( "number of occurrences",
3520 " (<numid>%1</numid> occurrences)",
3521 recur->duration() );
3522 }
3523 return txt;
3524 }
3525 return i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
3526 case Recurrence::rWeekly:
3527 {
3528 bool addSpace = false;
3529 for ( int i = 0; i < 7; ++i ) {
3530 if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
3531 if ( addSpace ) {
3532 dayNames.append( i18nc( "separator for list of days", ", " ) );
3533 }
3534 dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
3535 KCalendarSystem::ShortDayName ) );
3536 addSpace = true;
3537 }
3538 }
3539 if ( dayNames.isEmpty() ) {
3540 dayNames = i18nc( "Recurs weekly on no days", "no days" );
3541 }
3542 if ( recur->duration() != -1 ) {
3543 txt = i18ncp( "Recurs weekly on [list of days] until end-date",
3544 "Recurs weekly on %2 until %3",
3545 "Recurs every <numid>%1</numid> weeks on %2 until %3",
3546 recur->frequency(), dayNames, recurEnd( incidence ) );
3547 if ( recur->duration() > 0 ) {
3548 txt += i18nc( "number of occurrences",
3549 " (<numid>%1</numid> occurrences)",
3550 recur->duration() );
3551 }
3552 return txt;
3553 }
3554 return i18ncp( "Recurs weekly on [list of days]",
3555 "Recurs weekly on %2",
3556 "Recurs every <numid>%1</numid> weeks on %2",
3557 recur->frequency(), dayNames );
3558 }
3559 case Recurrence::rMonthlyPos:
3560 {
3561 if ( !recur->monthPositions().isEmpty() ) {
3562 KCal::RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
3563 if ( recur->duration() != -1 ) {
3564 txt = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
3565 " weekdayname until end-date",
3566 "Recurs every month on the %2 %3 until %4",
3567 "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
3568 recur->frequency(),
3569 dayList[rule.pos() + 31],
3570 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
3571 recurEnd( incidence ) );
3572 if ( recur->duration() > 0 ) {
3573 txt += i18nc( "number of occurrences",
3574 " (<numid>%1</numid> occurrences)",
3575 recur->duration() );
3576 }
3577 return txt;
3578 }
3579 return i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
3580 "Recurs every month on the %2 %3",
3581 "Recurs every %1 months on the %2 %3",
3582 recur->frequency(),
3583 dayList[rule.pos() + 31],
3584 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
3585 }
3586 break;
3587 }
3588 case Recurrence::rMonthlyDay:
3589 {
3590 if ( !recur->monthDays().isEmpty() ) {
3591 int days = recur->monthDays()[0];
3592 if ( recur->duration() != -1 ) {
3593 txt = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
3594 "Recurs monthly on the %2 day until %3",
3595 "Recurs every %1 months on the %2 day until %3",
3596 recur->frequency(),
3597 dayList[days + 31],
3598 recurEnd( incidence ) );
3599 if ( recur->duration() > 0 ) {
3600 txt += i18nc( "number of occurrences",
3601 " (<numid>%1</numid> occurrences)",
3602 recur->duration() );
3603 }
3604 return txt;
3605 }
3606 return i18ncp( "Recurs monthly on the [1st|2nd|...] day",
3607 "Recurs monthly on the %2 day",
3608 "Recurs every <numid>%1</numid> month on the %2 day",
3609 recur->frequency(),
3610 dayList[days + 31] );
3611 }
3612 break;
3613 }
3614 case Recurrence::rYearlyMonth:
3615 {
3616 if ( recur->duration() != -1 ) {
3617 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
3618 txt = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
3619 " until end-date",
3620 "Recurs yearly on %2 %3 until %4",
3621 "Recurs every %1 years on %2 %3 until %4",
3622 recur->frequency(),
3623 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
3624 dayList[ recur->yearDates()[0] + 31 ],
3625 recurEnd( incidence ) );
3626 if ( recur->duration() > 0 ) {
3627 txt += i18nc( "number of occurrences",
3628 " (<numid>%1</numid> occurrences)",
3629 recur->duration() );
3630 }
3631 return txt;
3632 }
3633 }
3634 if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
3635 return i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
3636 "Recurs yearly on %2 %3",
3637 "Recurs every %1 years on %2 %3",
3638 recur->frequency(),
3639 calSys->monthName( recur->yearMonths()[0],
3640 recur->startDate().year() ),
3641 dayList[ recur->yearDates()[0] + 31 ] );
3642 } else {
3643 if (!recur->yearMonths().isEmpty() ) {
3644 return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
3645 "Recurs yearly on %1 %2",
3646 calSys->monthName( recur->yearMonths()[0],
3647 recur->startDate().year() ),
3648 dayList[ recur->startDate().day() + 31 ] );
3649 } else {
3650 return i18nc( "Recurs Every year on month-name [1st|2nd|...]",
3651 "Recurs yearly on %1 %2",
3652 calSys->monthName( recur->startDate().month(),
3653 recur->startDate().year() ),
3654 dayList[ recur->startDate().day() + 31 ] );
3655 }
3656 }
3657 break;
3658 }
3659 case Recurrence::rYearlyDay:
3660 if ( !recur->yearDays().isEmpty() ) {
3661 if ( recur->duration() != -1 ) {
3662 txt = i18ncp( "Recurs every N years on day N until end-date",
3663 "Recurs every year on day <numid>%2</numid> until %3",
3664 "Recurs every <numid>%1</numid> years"
3665 " on day <numid>%2</numid> until %3",
3666 recur->frequency(),
3667 recur->yearDays()[0],
3668 recurEnd( incidence ) );
3669 if ( recur->duration() > 0 ) {
3670 txt += i18nc( "number of occurrences",
3671 " (<numid>%1</numid> occurrences)",
3672 recur->duration() );
3673 }
3674 return txt;
3675 }
3676 return i18ncp( "Recurs every N YEAR[S] on day N",
3677 "Recurs every year on day <numid>%2</numid>",
3678 "Recurs every <numid>%1</numid> years"
3679 " on day <numid>%2</numid>",
3680 recur->frequency(), recur->yearDays()[0] );
3681 }
3682 break;
3683 case Recurrence::rYearlyPos:
3684 {
3685 if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
3686 KCal::RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
3687 if ( recur->duration() != -1 ) {
3688 txt = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
3689 "of monthname until end-date",
3690 "Every year on the %2 %3 of %4 until %5",
3691 "Every <numid>%1</numid> years on the %2 %3 of %4"
3692 " until %5",
3693 recur->frequency(),
3694 dayList[rule.pos() + 31],
3695 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
3696 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
3697 recurEnd( incidence ) );
3698 if ( recur->duration() > 0 ) {
3699 txt += i18nc( "number of occurrences",
3700 " (<numid>%1</numid> occurrences)",
3701 recur->duration() );
3702 }
3703 return txt;
3704 }
3705 return i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
3706 "of monthname",
3707 "Every year on the %2 %3 of %4",
3708 "Every <numid>%1</numid> years on the %2 %3 of %4",
3709 recur->frequency(),
3710 dayList[rule.pos() + 31],
3711 calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
3712 calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
3713 }
3714 }
3715 break;
3716 }
3717 return i18n( "Incidence recurs" );
3718}
3719
3720QString IncidenceFormatter::timeToString( const KDateTime &date,
3721 bool shortfmt,
3722 const KDateTime::Spec &spec )
3723{
3724 if ( spec.isValid() ) {
3725
3726 QString timeZone;
3727 if ( spec.timeZone() != KSystemTimeZones::local() ) {
3728 timeZone = ' ' + spec.timeZone().name();
3729 }
3730
3731 return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
3732 } else {
3733 return KGlobal::locale()->formatTime( date.time(), !shortfmt );
3734 }
3735}
3736
3737QString IncidenceFormatter::dateToString( const KDateTime &date,
3738 bool shortfmt,
3739 const KDateTime::Spec &spec )
3740{
3741 if ( spec.isValid() ) {
3742
3743 QString timeZone;
3744 if ( spec.timeZone() != KSystemTimeZones::local() ) {
3745 timeZone = ' ' + spec.timeZone().name();
3746 }
3747
3748 return
3749 KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
3750 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
3751 timeZone;
3752 } else {
3753 return
3754 KGlobal::locale()->formatDate( date.date(),
3755 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
3756 }
3757}
3758
3759QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
3760 bool allDay,
3761 bool shortfmt,
3762 const KDateTime::Spec &spec )
3763{
3764 if ( allDay ) {
3765 return dateToString( date, shortfmt, spec );
3766 }
3767
3768 if ( spec.isValid() ) {
3769 QString timeZone;
3770 if ( spec.timeZone() != KSystemTimeZones::local() ) {
3771 timeZone = ' ' + spec.timeZone().name();
3772 }
3773
3774 return KGlobal::locale()->formatDateTime(
3775 date.toTimeSpec( spec ).dateTime(),
3776 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
3777 } else {
3778 return KGlobal::locale()->formatDateTime(
3779 date.dateTime(),
3780 ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
3781 }
3782}
3783
3784QString IncidenceFormatter::resourceString( Calendar *calendar, Incidence *incidence )
3785{
3786#ifndef KDEPIM_NO_KRESOURCES
3787 if ( !calendar || !incidence ) {
3788 return QString();
3789 }
3790
3791 CalendarResources *calendarResource = dynamic_cast<CalendarResources*>( calendar );
3792 if ( !calendarResource ) {
3793 return QString();
3794 }
3795
3796 ResourceCalendar *resourceCalendar = calendarResource->resource( incidence );
3797 if ( resourceCalendar ) {
3798 if ( !resourceCalendar->subresources().isEmpty() ) {
3799 QString subRes = resourceCalendar->subresourceIdentifier( incidence );
3800 if ( subRes.isEmpty() ) {
3801 return resourceCalendar->resourceName();
3802 } else {
3803 return resourceCalendar->labelForSubresource( subRes );
3804 }
3805 }
3806 return resourceCalendar->resourceName();
3807 }
3808#endif
3809 return QString();
3810}
3811
3812static QString secs2Duration( int secs )
3813{
3814 QString tmp;
3815 int days = secs / 86400;
3816 if ( days > 0 ) {
3817 tmp += i18np( "1 day", "%1 days", days );
3818 tmp += ' ';
3819 secs -= ( days * 86400 );
3820 }
3821 int hours = secs / 3600;
3822 if ( hours > 0 ) {
3823 tmp += i18np( "1 hour", "%1 hours", hours );
3824 tmp += ' ';
3825 secs -= ( hours * 3600 );
3826 }
3827 int mins = secs / 60;
3828 if ( mins > 0 ) {
3829 tmp += i18np( "1 minute", "%1 minutes", mins );
3830 }
3831 return tmp;
3832}
3833
3834QString IncidenceFormatter::durationString( Incidence *incidence )
3835{
3836 QString tmp;
3837 if ( incidence->type() == "Event" ) {
3838 Event *event = static_cast<Event *>( incidence );
3839 if ( event->hasEndDate() ) {
3840 if ( !event->allDay() ) {
3841 tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
3842 } else {
3843 tmp = i18np( "1 day", "%1 days",
3844 event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
3845 }
3846 } else {
3847 tmp = i18n( "forever" );
3848 }
3849 } else if ( incidence->type() == "Todo" ) {
3850 Todo *todo = static_cast<Todo *>( incidence );
3851 if ( todo->hasDueDate() ) {
3852 if ( todo->hasStartDate() ) {
3853 if ( !todo->allDay() ) {
3854 tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
3855 } else {
3856 tmp = i18np( "1 day", "%1 days",
3857 todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
3858 }
3859 }
3860 }
3861 }
3862 return tmp;
3863}
3864
3865QStringList IncidenceFormatter::reminderStringList( Incidence *incidence, bool shortfmt )
3866{
3867 //TODO: implement shortfmt=false
3868 Q_UNUSED( shortfmt );
3869
3870 QStringList reminderStringList;
3871
3872 if ( incidence ) {
3873 Alarm::List alarms = incidence->alarms();
3874 Alarm::List::ConstIterator it;
3875 for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
3876 Alarm *alarm = *it;
3877 int offset = 0;
3878 QString remStr, atStr, offsetStr;
3879 if ( alarm->hasTime() ) {
3880 offset = 0;
3881 if ( alarm->time().isValid() ) {
3882 atStr = KGlobal::locale()->formatDateTime( alarm->time() );
3883 }
3884 } else if ( alarm->hasStartOffset() ) {
3885 offset = alarm->startOffset().asSeconds();
3886 if ( offset < 0 ) {
3887 offset = -offset;
3888 offsetStr = i18nc( "N days/hours/minutes before the start datetime",
3889 "%1 before the start", secs2Duration( offset ) );
3890 } else if ( offset > 0 ) {
3891 offsetStr = i18nc( "N days/hours/minutes after the start datetime",
3892 "%1 after the start", secs2Duration( offset ) );
3893 } else { //offset is 0
3894 if ( incidence->dtStart().isValid() ) {
3895 atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
3896 }
3897 }
3898 } else if ( alarm->hasEndOffset() ) {
3899 offset = alarm->endOffset().asSeconds();
3900 if ( offset < 0 ) {
3901 offset = -offset;
3902 if ( incidence->type() == "Todo" ) {
3903 offsetStr = i18nc( "N days/hours/minutes before the due datetime",
3904 "%1 before the to-do is due", secs2Duration( offset ) );
3905 } else {
3906 offsetStr = i18nc( "N days/hours/minutes before the end datetime",
3907 "%1 before the end", secs2Duration( offset ) );
3908 }
3909 } else if ( offset > 0 ) {
3910 if ( incidence->type() == "Todo" ) {
3911 offsetStr = i18nc( "N days/hours/minutes after the due datetime",
3912 "%1 after the to-do is due", secs2Duration( offset ) );
3913 } else {
3914 offsetStr = i18nc( "N days/hours/minutes after the end datetime",
3915 "%1 after the end", secs2Duration( offset ) );
3916 }
3917 } else { //offset is 0
3918 if ( incidence->type() == "Todo" ) {
3919 Todo *t = static_cast<Todo *>( incidence );
3920 if ( t->dtDue().isValid() ) {
3921 atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
3922 }
3923 } else {
3924 Event *e = static_cast<Event *>( incidence );
3925 if ( e->dtEnd().isValid() ) {
3926 atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
3927 }
3928 }
3929 }
3930 }
3931 if ( offset == 0 ) {
3932 if ( !atStr.isEmpty() ) {
3933 remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
3934 }
3935 } else {
3936 remStr = offsetStr;
3937 }
3938
3939 if ( alarm->repeatCount() > 0 ) {
3940 QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
3941 QString intervalStr = i18nc( "interval is N days/hours/minutes",
3942 "interval is %1",
3943 secs2Duration( alarm->snoozeTime().asSeconds() ) );
3944 QString repeatStr = i18nc( "(repeat string, interval string)",
3945 "(%1, %2)", countStr, intervalStr );
3946 remStr = remStr + ' ' + repeatStr;
3947
3948 }
3949 reminderStringList << remStr;
3950 }
3951 }
3952
3953 return reminderStringList;
3954}
3955