• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.14.10 API Reference
  • KDE Home
  • Contact Us
 

KCalUtils Library

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

KDE's Doxygen guidelines are available online.

KCalUtils Library

Skip menu "KCalUtils Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Members
  • File List
  • Related Pages

kdepimlibs-4.14.10 API Reference

Skip menu "kdepimlibs-4.14.10 API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal