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

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • 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