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