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

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