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

KCalUtils Library

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

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

Search



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