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

KCalUtils Library

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

KDE's Doxygen guidelines are available online.

KCalUtils Library

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

kdepimlibs-4.11.5 API Reference

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

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