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

Skip menu "kdepimlibs-4.14.3 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