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

KCalUtils Library

  • kcalutils
incidenceformatter.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the kcalutils library.
3 
4  Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5  Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6  Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
7  Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
8 
9  This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Library General Public
11  License as published by the Free Software Foundation; either
12  version 2 of the License, or (at your option) any later version.
13 
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  Library General Public License for more details.
18 
19  You should have received a copy of the GNU Library General Public License
20  along with this library; see the file COPYING.LIB. If not, write to
21  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22  Boston, MA 02110-1301, USA.
23 */
36 #include "incidenceformatter.h"
37 #include "stringify.h"
38 
39 #include <kcalcore/event.h>
40 #include <kcalcore/freebusy.h>
41 #include <kcalcore/icalformat.h>
42 #include <kcalcore/journal.h>
43 #include <kcalcore/memorycalendar.h>
44 #include <kcalcore/todo.h>
45 #include <kcalcore/visitor.h>
46 using namespace KCalCore;
47 
48 #include <kpimutils/email.h>
49 
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 <QApplication>
60 #include <QPalette>
61 #include <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  if ( incidence->type() == Incidence::TypeFreeBusy ) {
2491  // A FreeBusy does not have a valid attachment due to the static-cast from IncidenceBase
2492  return tmpStr;
2493  }
2494 
2495  Attachment::List attachments = incidence->attachments();
2496  if ( !attachments.isEmpty() ) {
2497  tmpStr += i18n( "Attached Documents:" ) + "<ol>";
2498 
2499  Attachment::List::ConstIterator it;
2500  for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
2501  Attachment::Ptr a = *it;
2502  tmpStr += "<li>";
2503  // Attachment icon
2504  KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
2505  const QString iconStr = ( mimeType ?
2506  mimeType->iconName( a->uri() ) :
2507  QString( "application-octet-stream" ) );
2508  const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
2509  if ( !iconPath.isEmpty() ) {
2510  tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
2511  }
2512  tmpStr += helper->makeLink( "ATTACH:" + a->label().toUtf8().toBase64(), a->label() );
2513  tmpStr += "</li>";
2514  }
2515  tmpStr += "</ol>";
2516  }
2517 
2518  return tmpStr;
2519 }
2520 
2521 //@cond PRIVATE
2522 class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor
2523 {
2524  public:
2525  ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; }
2526  bool act( const IncidenceBase::Ptr &incidence,
2527  const Incidence::Ptr &existingIncidence,
2528  ScheduleMessage::Ptr msg, const QString &sender )
2529  {
2530  mExistingIncidence = existingIncidence;
2531  mMessage = msg;
2532  mSender = sender;
2533  return incidence->accept( *this, incidence );
2534  }
2535  QString result() const { return mResult; }
2536 
2537  protected:
2538  QString mResult;
2539  Incidence::Ptr mExistingIncidence;
2540  ScheduleMessage::Ptr mMessage;
2541  QString mSender;
2542 };
2543 
2544 class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor :
2545  public IncidenceFormatter::ScheduleMessageVisitor
2546 {
2547  protected:
2548  bool visit( Event::Ptr event )
2549  {
2550  mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
2551  return !mResult.isEmpty();
2552  }
2553  bool visit( Todo::Ptr todo )
2554  {
2555  mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
2556  return !mResult.isEmpty();
2557  }
2558  bool visit( Journal::Ptr journal )
2559  {
2560  mResult = invitationHeaderJournal( journal, mMessage );
2561  return !mResult.isEmpty();
2562  }
2563  bool visit( FreeBusy::Ptr fb )
2564  {
2565  mResult = invitationHeaderFreeBusy( fb, mMessage );
2566  return !mResult.isEmpty();
2567  }
2568 };
2569 
2570 class KCalUtils::IncidenceFormatter::InvitationBodyVisitor
2571  : public IncidenceFormatter::ScheduleMessageVisitor
2572 {
2573  public:
2574  InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
2575  : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
2576 
2577  protected:
2578  bool visit( Event::Ptr event )
2579  {
2580  Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>();
2581  mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec );
2582  return !mResult.isEmpty();
2583  }
2584  bool visit( Todo::Ptr todo )
2585  {
2586  Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>();
2587  mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec );
2588  return !mResult.isEmpty();
2589  }
2590  bool visit( Journal::Ptr journal )
2591  {
2592  Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>();
2593  mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec );
2594  return !mResult.isEmpty();
2595  }
2596  bool visit( FreeBusy::Ptr fb )
2597  {
2598  mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec );
2599  return !mResult.isEmpty();
2600  }
2601 
2602  private:
2603  bool mNoHtmlMode;
2604  KDateTime::Spec mSpec;
2605 };
2606 //@endcond
2607 
2608 InvitationFormatterHelper::InvitationFormatterHelper()
2609  : d( 0 )
2610 {
2611 }
2612 
2613 InvitationFormatterHelper::~InvitationFormatterHelper()
2614 {
2615 }
2616 
2617 QString InvitationFormatterHelper::generateLinkURL( const QString &id )
2618 {
2619  return id;
2620 }
2621 
2622 //@cond PRIVATE
2623 class IncidenceFormatter::IncidenceCompareVisitor : public Visitor
2624 {
2625  public:
2626  IncidenceCompareVisitor() {}
2627  bool act( const IncidenceBase::Ptr &incidence,
2628  const Incidence::Ptr &existingIncidence )
2629  {
2630  if ( !existingIncidence ) {
2631  return false;
2632  }
2633  Incidence::Ptr inc = incidence.staticCast<Incidence>();
2634  if ( !inc || !existingIncidence ||
2635  inc->revision() <= existingIncidence->revision() ) {
2636  return false;
2637  }
2638  mExistingIncidence = existingIncidence;
2639  return incidence->accept( *this, incidence );
2640  }
2641 
2642  QString result() const
2643  {
2644  if ( mChanges.isEmpty() ) {
2645  return QString();
2646  }
2647  QString html = "<div align=\"left\"><ul><li>";
2648  html += mChanges.join( "</li><li>" );
2649  html += "</li><ul></div>";
2650  return html;
2651  }
2652 
2653  protected:
2654  bool visit( Event::Ptr event )
2655  {
2656  compareEvents( event, mExistingIncidence.dynamicCast<Event>() );
2657  compareIncidences( event, mExistingIncidence );
2658  return !mChanges.isEmpty();
2659  }
2660  bool visit( Todo::Ptr todo )
2661  {
2662  compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() );
2663  compareIncidences( todo, mExistingIncidence );
2664  return !mChanges.isEmpty();
2665  }
2666  bool visit( Journal::Ptr journal )
2667  {
2668  compareIncidences( journal, mExistingIncidence );
2669  return !mChanges.isEmpty();
2670  }
2671  bool visit( FreeBusy::Ptr fb )
2672  {
2673  Q_UNUSED( fb );
2674  return !mChanges.isEmpty();
2675  }
2676 
2677  private:
2678  void compareEvents( const Event::Ptr &newEvent,
2679  const Event::Ptr &oldEvent )
2680  {
2681  if ( !oldEvent || !newEvent ) {
2682  return;
2683  }
2684  if ( oldEvent->dtStart() != newEvent->dtStart() ||
2685  oldEvent->allDay() != newEvent->allDay() ) {
2686  mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
2687  eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
2688  }
2689  if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
2690  oldEvent->allDay() != newEvent->allDay() ) {
2691  mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
2692  eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
2693  }
2694  }
2695 
2696  void compareTodos( const Todo::Ptr &newTodo,
2697  const Todo::Ptr &oldTodo )
2698  {
2699  if ( !oldTodo || !newTodo ) {
2700  return;
2701  }
2702 
2703  if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
2704  mChanges += i18n( "The to-do has been completed" );
2705  }
2706  if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
2707  mChanges += i18n( "The to-do is no longer completed" );
2708  }
2709  if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
2710  const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
2711  const QString newPer = i18n( "%1%", newTodo->percentComplete() );
2712  mChanges += i18n( "The task completed percentage has changed from %1 to %2",
2713  oldPer, newPer );
2714  }
2715 
2716  if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
2717  mChanges += i18n( "A to-do starting time has been added" );
2718  }
2719  if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
2720  mChanges += i18n( "The to-do starting time has been removed" );
2721  }
2722  if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
2723  oldTodo->dtStart() != newTodo->dtStart() ) {
2724  mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
2725  dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
2726  dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
2727  }
2728 
2729  if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
2730  mChanges += i18n( "A to-do due time has been added" );
2731  }
2732  if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
2733  mChanges += i18n( "The to-do due time has been removed" );
2734  }
2735  if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
2736  oldTodo->dtDue() != newTodo->dtDue() ) {
2737  mChanges += i18n( "The to-do due time has been changed from %1 to %2",
2738  dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
2739  dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
2740  }
2741  }
2742 
2743  void compareIncidences( const Incidence::Ptr &newInc,
2744  const Incidence::Ptr &oldInc )
2745  {
2746  if ( !oldInc || !newInc ) {
2747  return;
2748  }
2749 
2750  if ( oldInc->summary() != newInc->summary() ) {
2751  mChanges += i18n( "The summary has been changed to: \"%1\"",
2752  newInc->richSummary() );
2753  }
2754 
2755  if ( oldInc->location() != newInc->location() ) {
2756  mChanges += i18n( "The location has been changed to: \"%1\"",
2757  newInc->richLocation() );
2758  }
2759 
2760  if ( oldInc->description() != newInc->description() ) {
2761  mChanges += i18n( "The description has been changed to: \"%1\"",
2762  newInc->richDescription() );
2763  }
2764 
2765  Attendee::List oldAttendees = oldInc->attendees();
2766  Attendee::List newAttendees = newInc->attendees();
2767  for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
2768  it != newAttendees.constEnd(); ++it ) {
2769  Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() );
2770  if ( !oldAtt ) {
2771  mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
2772  } else {
2773  if ( oldAtt->status() != (*it)->status() ) {
2774  mChanges += i18n( "The status of attendee %1 has been changed to: %2",
2775  (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) );
2776  }
2777  }
2778  }
2779 
2780  for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
2781  it != oldAttendees.constEnd(); ++it ) {
2782  if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
2783  Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() );
2784  if ( !newAtt ) {
2785  mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
2786  }
2787  }
2788  }
2789  }
2790 
2791  private:
2792  Incidence::Ptr mExistingIncidence;
2793  QStringList mChanges;
2794 };
2795 //@endcond
2796 
2797 QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
2798 {
2799  if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
2800  QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
2801  arg( generateLinkURL( id ), text );
2802  return res;
2803  } else {
2804  // draw the attachment links in non-bold face
2805  QString res = QString( "<a href=\"%1\">%2</a>" ).
2806  arg( generateLinkURL( id ), text );
2807  return res;
2808  }
2809 }
2810 
2811 // Check if the given incidence is likely one that we own instead one from
2812 // a shared calendar (Kolab-specific)
2813 static bool incidenceOwnedByMe( const Calendar::Ptr &calendar,
2814  const Incidence::Ptr &incidence )
2815 {
2816  Q_UNUSED( calendar );
2817  Q_UNUSED( incidence );
2818  return true;
2819 }
2820 
2821 // The open & close table cell tags for the invitation buttons
2822 static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
2823 static QString tdClose = "</td>";
2824 
2825 static QString responseButtons( const Incidence::Ptr &inc,
2826  bool rsvpReq, bool rsvpRec,
2827  InvitationFormatterHelper *helper )
2828 {
2829  QString html;
2830  if ( !helper ) {
2831  return html;
2832  }
2833 
2834  if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
2835  // Record only
2836  html += tdOpen;
2837  html += helper->makeLink( "record", i18n( "[Record]" ) );
2838  html += tdClose;
2839 
2840  // Move to trash
2841  html += tdOpen;
2842  html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
2843  html += tdClose;
2844 
2845  } else {
2846 
2847  // Accept
2848  html += tdOpen;
2849  html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
2850  html += tdClose;
2851 
2852  // Tentative
2853  html += tdOpen;
2854  html += helper->makeLink( "accept_conditionally",
2855  i18nc( "Accept invitation conditionally", "Accept cond." ) );
2856  html += tdClose;
2857 
2858  // Counter proposal
2859  html += tdOpen;
2860  html += helper->makeLink( "counter",
2861  i18nc( "invitation counter proposal", "Counter proposal" ) );
2862  html += tdClose;
2863 
2864  // Decline
2865  html += tdOpen;
2866  html += helper->makeLink( "decline",
2867  i18nc( "decline invitation", "Decline" ) );
2868  html += tdClose;
2869  }
2870 
2871  if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
2872  // Delegate
2873  html += tdOpen;
2874  html += helper->makeLink( "delegate",
2875  i18nc( "delegate inviation to another", "Delegate" ) );
2876  html += tdClose;
2877 
2878  // Forward
2879  html += tdOpen;
2880  html += helper->makeLink( "forward",
2881  i18nc( "forward request to another", "Forward" ) );
2882  html += tdClose;
2883 
2884  // Check calendar
2885  if ( inc && inc->type() == Incidence::TypeEvent ) {
2886  html += tdOpen;
2887  html += helper->makeLink( "check_calendar",
2888  i18nc( "look for scheduling conflicts", "Check my calendar" ) );
2889  html += tdClose;
2890  }
2891  }
2892  return html;
2893 }
2894 
2895 static QString counterButtons( const Incidence::Ptr &incidence,
2896  InvitationFormatterHelper *helper )
2897 {
2898  QString html;
2899  if ( !helper ) {
2900  return html;
2901  }
2902 
2903  // Accept proposal
2904  html += tdOpen;
2905  html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
2906  html += tdClose;
2907 
2908  // Decline proposal
2909  html += tdOpen;
2910  html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
2911  html += tdClose;
2912 
2913  // Check calendar
2914  if ( incidence && incidence->type() == Incidence::TypeEvent ) {
2915  html += tdOpen;
2916  html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
2917  html += tdClose;
2918  }
2919  return html;
2920 }
2921 
2922 Calendar::Ptr InvitationFormatterHelper::calendar() const
2923 {
2924  return Calendar::Ptr();
2925 }
2926 
2927 static QString formatICalInvitationHelper( QString invitation,
2928  const MemoryCalendar::Ptr &mCalendar,
2929  InvitationFormatterHelper *helper,
2930  bool noHtmlMode,
2931  KDateTime::Spec spec,
2932  const QString &sender,
2933  bool outlookCompareStyle )
2934 {
2935  if ( invitation.isEmpty() ) {
2936  return QString();
2937  }
2938 
2939  ICalFormat format;
2940  // parseScheduleMessage takes the tz from the calendar,
2941  // no need to set it manually here for the format!
2942  ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation );
2943 
2944  if( !msg ) {
2945  kDebug() << "Failed to parse the scheduling message";
2946  Q_ASSERT( format.exception() );
2947  kDebug() << Stringify::errorMessage( *format.exception() ); //krazy:exclude=kdebug
2948  return QString();
2949  }
2950 
2951  IncidenceBase::Ptr incBase = msg->event();
2952 
2953  incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
2954 
2955  // Determine if this incidence is in my calendar (and owned by me)
2956  Incidence::Ptr existingIncidence;
2957  if ( incBase && helper->calendar() ) {
2958  existingIncidence = helper->calendar()->incidence( incBase->uid() );
2959 
2960  if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
2961  existingIncidence.clear();
2962  }
2963  if ( !existingIncidence ) {
2964  const Incidence::List list = helper->calendar()->incidences();
2965  for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
2966  if ( (*it)->schedulingID() == incBase->uid() &&
2967  incidenceOwnedByMe( helper->calendar(), *it ) ) {
2968  existingIncidence = *it;
2969  break;
2970  }
2971  }
2972  }
2973  }
2974 
2975  Incidence::Ptr inc = incBase.staticCast<Incidence>(); // the incidence in the invitation email
2976 
2977  // If the IncidenceBase is a FreeBusy, then we cannot access the revision number in
2978  // the static-casted Incidence; so for sake of nothing better use 0 as the revision.
2979  int incRevision = 0;
2980  if ( inc && inc->type() != Incidence::TypeFreeBusy ) {
2981  incRevision = inc->revision();
2982  }
2983 
2984  // First make the text of the message
2985  QString html;
2986  html += "<div align=\"center\" style=\"border:solid 1px;\">";
2987 
2988  IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
2989  // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
2990  if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
2991  return QString();
2992  }
2993  html += htmlAddTag( "h3", headerVisitor.result() );
2994 
2995  if ( outlookCompareStyle ||
2996  msg->method() == iTIPDeclineCounter ) { //use Outlook style for decline
2997  // use the Outlook 2007 Comparison Style
2998  IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
2999  bool bodyOk;
3000  if ( msg->method() == iTIPRequest || msg->method() == iTIPReply ||
3001  msg->method() == iTIPDeclineCounter ) {
3002  if ( inc && existingIncidence &&
3003  incRevision < existingIncidence->revision() ) {
3004  bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
3005  } else {
3006  bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
3007  }
3008  } else {
3009  bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender );
3010  }
3011  if ( bodyOk ) {
3012  html += bodyVisitor.result();
3013  } else {
3014  return QString();
3015  }
3016  } else {
3017  // use our "Classic" Comparison Style
3018  InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
3019  if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) {
3020  return QString();
3021  }
3022  html += bodyVisitor.result();
3023 
3024  if ( msg->method() == iTIPRequest ) {
3025  IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
3026  if ( compareVisitor.act( inc, existingIncidence ) ) {
3027  html += "<p align=\"left\">";
3028  if ( senderIsOrganizer( inc, sender ) ) {
3029  html += i18n( "The following changes have been made by the organizer:" );
3030  } else if ( !sender.isEmpty() ) {
3031  html += i18n( "The following changes have been made by %1:", sender );
3032  } else {
3033  html += i18n( "The following changes have been made:" );
3034  }
3035  html += "</p>";
3036  html += compareVisitor.result();
3037  }
3038  }
3039  if ( msg->method() == iTIPReply ) {
3040  IncidenceCompareVisitor compareVisitor;
3041  if ( compareVisitor.act( inc, existingIncidence ) ) {
3042  html += "<p align=\"left\">";
3043  if ( !sender.isEmpty() ) {
3044  html += i18n( "The following changes have been made by %1:", sender );
3045  } else {
3046  html += i18n( "The following changes have been made by an attendee:" );
3047  }
3048  html += "</p>";
3049  html += compareVisitor.result();
3050  }
3051  }
3052  }
3053 
3054  // determine if I am the organizer for this invitation
3055  bool myInc = iamOrganizer( inc );
3056 
3057  // determine if the invitation response has already been recorded
3058  bool rsvpRec = false;
3059  Attendee::Ptr ea;
3060  if ( !myInc ) {
3061  Incidence::Ptr rsvpIncidence = existingIncidence;
3062  if ( !rsvpIncidence && inc && incRevision > 0 ) {
3063  rsvpIncidence = inc;
3064  }
3065  if ( rsvpIncidence ) {
3066  ea = findMyAttendee( rsvpIncidence );
3067  }
3068  if ( ea &&
3069  ( ea->status() == Attendee::Accepted ||
3070  ea->status() == Attendee::Declined ||
3071  ea->status() == Attendee::Tentative ) ) {
3072  rsvpRec = true;
3073  }
3074  }
3075 
3076  // determine invitation role
3077  QString role;
3078  bool isDelegated = false;
3079  Attendee::Ptr a = findMyAttendee( inc );
3080  if ( !a && inc ) {
3081  if ( !inc->attendees().isEmpty() ) {
3082  a = inc->attendees().first();
3083  }
3084  }
3085  if ( a ) {
3086  isDelegated = ( a->status() == Attendee::Delegated );
3087  role = Stringify::attendeeRole( a->role() );
3088  }
3089 
3090  // determine if RSVP needed, not-needed, or response already recorded
3091  bool rsvpReq = rsvpRequested( inc );
3092  if ( !myInc && a ) {
3093  html += "<br/>";
3094  html += "<i><u>";
3095  if ( rsvpRec && inc ) {
3096  if ( incRevision == 0 ) {
3097  html += i18n( "Your <b>%1</b> response has been recorded",
3098  Stringify::attendeeStatus( ea->status() ) );
3099  } else {
3100  html += i18n( "Your status for this invitation is <b>%1</b>",
3101  Stringify::attendeeStatus( ea->status() ) );
3102  }
3103  rsvpReq = false;
3104  } else if ( msg->method() == iTIPCancel ) {
3105  html += i18n( "This invitation was canceled" );
3106  } else if ( msg->method() == iTIPAdd ) {
3107  html += i18n( "This invitation was accepted" );
3108  } else if ( msg->method() == iTIPDeclineCounter ) {
3109  rsvpReq = true;
3110  html += rsvpRequestedStr( rsvpReq, role );
3111  } else {
3112  if ( !isDelegated ) {
3113  html += rsvpRequestedStr( rsvpReq, role );
3114  } else {
3115  html += i18n( "Awaiting delegation response" );
3116  }
3117  }
3118  html += "</u></i>";
3119  }
3120 
3121  // Print if the organizer gave you a preset status
3122  if ( !myInc ) {
3123  if ( inc && incRevision == 0 ) {
3124  QString statStr = myStatusStr( inc );
3125  if ( !statStr.isEmpty() ) {
3126  html += "<br/>";
3127  html += "<i>";
3128  html += statStr;
3129  html += "</i>";
3130  }
3131  }
3132  }
3133 
3134  // Add groupware links
3135 
3136  html += "<p>";
3137  html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
3138 
3139  switch ( msg->method() ) {
3140  case iTIPPublish:
3141  case iTIPRequest:
3142  case iTIPRefresh:
3143  case iTIPAdd:
3144  {
3145  if ( inc && incRevision > 0 && ( existingIncidence || !helper->calendar() ) ) {
3146  if ( inc->type() == Incidence::TypeTodo ) {
3147  html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
3148  } else {
3149  html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
3150  }
3151  }
3152 
3153  if ( !myInc && a ) {
3154  html += responseButtons( inc, rsvpReq, rsvpRec, helper );
3155  }
3156  break;
3157  }
3158 
3159  case iTIPCancel:
3160  // Remove invitation
3161  if ( inc ) {
3162  html += tdOpen;
3163  if ( inc->type() == Incidence::TypeTodo ) {
3164  html += helper->makeLink( "cancel",
3165  i18n( "Remove invitation from my to-do list" ) );
3166  } else {
3167  html += helper->makeLink( "cancel",
3168  i18n( "Remove invitation from my calendar" ) );
3169  }
3170  html += tdClose;
3171  }
3172  break;
3173 
3174  case iTIPReply:
3175  {
3176  // Record invitation response
3177  Attendee::Ptr a;
3178  Attendee::Ptr ea;
3179  if ( inc ) {
3180  // First, determine if this reply is really a counter in disguise.
3181  if ( replyMeansCounter( inc ) ) {
3182  html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
3183  break;
3184  }
3185 
3186  // Next, maybe this is a declined reply that was delegated from me?
3187  // find first attendee who is delegated-from me
3188  // look a their PARTSTAT response, if the response is declined,
3189  // then we need to start over which means putting all the action
3190  // buttons and NOT putting on the [Record response..] button
3191  a = findDelegatedFromMyAttendee( inc );
3192  if ( a ) {
3193  if ( a->status() != Attendee::Accepted ||
3194  a->status() != Attendee::Tentative ) {
3195  html += responseButtons( inc, rsvpReq, rsvpRec, helper );
3196  break;
3197  }
3198  }
3199 
3200  // Finally, simply allow a Record of the reply
3201  if ( !inc->attendees().isEmpty() ) {
3202  a = inc->attendees().first();
3203  }
3204  if ( a && helper->calendar() ) {
3205  ea = findAttendee( existingIncidence, a->email() );
3206  }
3207  }
3208  if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
3209  html += tdOpen;
3210  html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded",
3211  Stringify::attendeeStatus( ea->status() ) ) );
3212  html += tdClose;
3213  } else {
3214  if ( inc ) {
3215  if ( inc->type() == Incidence::TypeTodo ) {
3216  html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
3217  } else {
3218  html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
3219  }
3220  }
3221  }
3222  break;
3223  }
3224 
3225  case iTIPCounter:
3226  // Counter proposal
3227  html += counterButtons( inc, helper );
3228  break;
3229 
3230  case iTIPDeclineCounter:
3231  html += responseButtons( inc, rsvpReq, rsvpRec, helper );
3232  break;
3233 
3234  case iTIPNoMethod:
3235  break;
3236  }
3237 
3238  // close the groupware table
3239  html += "</tr></table>";
3240 
3241  // Add the attendee list
3242  if ( myInc ) {
3243  html += invitationRsvpList( existingIncidence, a );
3244  } else {
3245  html += invitationAttendeeList( inc );
3246  }
3247 
3248  // close the top-level table
3249  html += "</div>";
3250 
3251  // Add the attachment list
3252  html += invitationAttachments( helper, inc );
3253 
3254  return html;
3255 }
3256 //@endcond
3257 
3258 QString IncidenceFormatter::formatICalInvitation( QString invitation,
3259  const MemoryCalendar::Ptr &calendar,
3260  InvitationFormatterHelper *helper,
3261  bool outlookCompareStyle )
3262 {
3263  return formatICalInvitationHelper( invitation, calendar, helper, false,
3264  KSystemTimeZones::local(), QString(),
3265  outlookCompareStyle );
3266 }
3267 
3268 QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
3269  const MemoryCalendar::Ptr &calendar,
3270  InvitationFormatterHelper *helper,
3271  const QString &sender,
3272  bool outlookCompareStyle )
3273 {
3274  return formatICalInvitationHelper( invitation, calendar, helper, true,
3275  KSystemTimeZones::local(), sender,
3276  outlookCompareStyle );
3277 }
3278 
3279 /*******************************************************************
3280  * Helper functions for the Incidence tooltips
3281  *******************************************************************/
3282 
3283 //@cond PRIVATE
3284 class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor
3285 {
3286  public:
3287  ToolTipVisitor()
3288  : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
3289 
3290  bool act( const MemoryCalendar::Ptr &calendar,
3291  const IncidenceBase::Ptr &incidence,
3292  const QDate &date=QDate(), bool richText=true,
3293  KDateTime::Spec spec=KDateTime::Spec() )
3294  {
3295  mCalendar = calendar;
3296  mLocation.clear();
3297  mDate = date;
3298  mRichText = richText;
3299  mSpec = spec;
3300  mResult = "";
3301  return incidence ? incidence->accept( *this, incidence ) : false;
3302  }
3303 
3304  bool act( const QString &location, const IncidenceBase::Ptr &incidence,
3305  const QDate &date=QDate(), bool richText=true,
3306  KDateTime::Spec spec=KDateTime::Spec() )
3307  {
3308  mLocation = location;
3309  mDate = date;
3310  mRichText = richText;
3311  mSpec = spec;
3312  mResult = "";
3313  return incidence ? incidence->accept( *this, incidence ) : false;
3314  }
3315 
3316  QString result() const { return mResult; }
3317 
3318  protected:
3319  bool visit( Event::Ptr event );
3320  bool visit( Todo::Ptr todo );
3321  bool visit( Journal::Ptr journal );
3322  bool visit( FreeBusy::Ptr fb );
3323 
3324  QString dateRangeText( const Event::Ptr &event, const QDate &date );
3325  QString dateRangeText( const Todo::Ptr &todo, const QDate &date );
3326  QString dateRangeText( const Journal::Ptr &journal );
3327  QString dateRangeText( const FreeBusy::Ptr &fb );
3328 
3329  QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText );
3330 
3331  protected:
3332  MemoryCalendar::Ptr mCalendar;
3333  QString mLocation;
3334  QDate mDate;
3335  bool mRichText;
3336  KDateTime::Spec mSpec;
3337  QString mResult;
3338 };
3339 
3340 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event,
3341  const QDate &date )
3342 {
3343  //FIXME: support mRichText==false
3344  QString ret;
3345  QString tmp;
3346 
3347  KDateTime startDt = event->dtStart();
3348  KDateTime endDt = event->dtEnd();
3349  if ( event->recurs() ) {
3350  if ( date.isValid() ) {
3351  KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
3352  int diffDays = startDt.daysTo( kdt );
3353  kdt = kdt.addSecs( -1 );
3354  startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
3355  if ( event->hasEndDate() ) {
3356  endDt = endDt.addDays( diffDays );
3357  if ( startDt > endDt ) {
3358  startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
3359  endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
3360  }
3361  }
3362  }
3363  }
3364 
3365  if ( event->isMultiDay() ) {
3366  tmp = dateToString( startDt, true, mSpec );
3367  ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
3368 
3369  tmp = dateToString( endDt, true, mSpec );
3370  ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
3371 
3372  } else {
3373 
3374  ret += "<br>" +
3375  i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
3376  if ( !event->allDay() ) {
3377  const QString dtStartTime = timeToString( startDt, true, mSpec );
3378  const QString dtEndTime = timeToString( endDt, true, mSpec );
3379  if ( dtStartTime == dtEndTime ) {
3380  // to prevent 'Time: 17:00 - 17:00'
3381  tmp = "<br>" +
3382  i18nc( "time for event", "<i>Time:</i> %1",
3383  dtStartTime );
3384  } else {
3385  tmp = "<br>" +
3386  i18nc( "time range for event",
3387  "<i>Time:</i> %1 - %2",
3388  dtStartTime, dtEndTime );
3389  }
3390  ret += tmp;
3391  }
3392  }
3393  return ret.replace( ' ', "&nbsp;" );
3394 }
3395 
3396 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo,
3397  const QDate &date )
3398 {
3399  //FIXME: support mRichText==false
3400  QString ret;
3401  if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
3402  KDateTime startDt = todo->dtStart();
3403  if ( todo->recurs() ) {
3404  if ( date.isValid() ) {
3405  startDt.setDate( date );
3406  }
3407  }
3408  ret += "<br>" +
3409  i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
3410  }
3411 
3412  if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
3413  KDateTime dueDt = todo->dtDue();
3414  if ( todo->recurs() ) {
3415  if ( date.isValid() ) {
3416  KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
3417  kdt = kdt.addSecs( -1 );
3418  dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
3419  }
3420  }
3421  ret += "<br>" +
3422  i18n( "<i>Due:</i> %1",
3423  dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
3424  }
3425 
3426  // Print priority and completed info here, for lack of a better place
3427 
3428  if ( todo->priority() > 0 ) {
3429  ret += "<br>";
3430  ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
3431  ret += QString::number( todo->priority() );
3432  }
3433 
3434  ret += "<br>";
3435  if ( todo->isCompleted() ) {
3436  ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + "&nbsp;";
3437  ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', "&nbsp;" );
3438  } else {
3439  ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
3440  ret += i18n( "%1%", todo->percentComplete() );
3441  }
3442 
3443  return ret.replace( ' ', "&nbsp;" );
3444 }
3445 
3446 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal )
3447 {
3448  //FIXME: support mRichText==false
3449  QString ret;
3450  if ( journal->dtStart().isValid() ) {
3451  ret += "<br>" +
3452  i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
3453  }
3454  return ret.replace( ' ', "&nbsp;" );
3455 }
3456 
3457 QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb )
3458 {
3459  //FIXME: support mRichText==false
3460  QString ret;
3461  ret = "<br>" +
3462  i18n( "<i>Period start:</i> %1",
3463  KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
3464  ret += "<br>" +
3465  i18n( "<i>Period start:</i> %1",
3466  KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
3467  return ret.replace( ' ', "&nbsp;" );
3468 }
3469 
3470 bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event )
3471 {
3472  mResult = generateToolTip( event, dateRangeText( event, mDate ) );
3473  return !mResult.isEmpty();
3474 }
3475 
3476 bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo )
3477 {
3478  mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
3479  return !mResult.isEmpty();
3480 }
3481 
3482 bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal )
3483 {
3484  mResult = generateToolTip( journal, dateRangeText( journal ) );
3485  return !mResult.isEmpty();
3486 }
3487 
3488 bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb )
3489 {
3490  //FIXME: support mRichText==false
3491  mResult = "<qt><b>" +
3492  i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) +
3493  "</b>";
3494  mResult += dateRangeText( fb );
3495  mResult += "</qt>";
3496  return !mResult.isEmpty();
3497 }
3498 
3499 static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
3500 {
3501  // Search for a new print name, if needed.
3502  const QString printName = searchName( email, name );
3503 
3504  // Get the icon corresponding to the attendee participation status.
3505  const QString iconPath = rsvpStatusIconPath( status );
3506 
3507  // Make the return string.
3508  QString personString;
3509  if ( !iconPath.isEmpty() ) {
3510  personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
3511  }
3512  if ( status != Attendee::None ) {
3513  personString += i18nc( "attendee name (attendee status)", "%1 (%2)",
3514  printName.isEmpty() ? email : printName,
3515  Stringify::attendeeStatus( status ) );
3516  } else {
3517  personString += i18n( "%1", printName.isEmpty() ? email : printName );
3518  }
3519  return personString;
3520 }
3521 
3522 static QString tooltipFormatOrganizer( const QString &email, const QString &name )
3523 {
3524  // Search for a new print name, if needed
3525  const QString printName = searchName( email, name );
3526 
3527  // Get the icon for organizer
3528  const QString iconPath =
3529  KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
3530 
3531  // Make the return string.
3532  QString personString;
3533  personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
3534  personString += ( printName.isEmpty() ? email : printName );
3535  return personString;
3536 }
3537 
3538 static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence,
3539  Attendee::Role role, bool showStatus )
3540 {
3541  int maxNumAtts = 8; // maximum number of people to print per attendee role
3542  const QString etc = i18nc( "elipsis", "..." );
3543 
3544  int i = 0;
3545  QString tmpStr;
3546  Attendee::List::ConstIterator it;
3547  Attendee::List attendees = incidence->attendees();
3548 
3549  for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
3550  Attendee::Ptr a = *it;
3551  if ( a->role() != role ) {
3552  // skip not this role
3553  continue;
3554  }
3555  if ( attendeeIsOrganizer( incidence, a ) ) {
3556  // skip attendee that is also the organizer
3557  continue;
3558  }
3559  if ( i == maxNumAtts ) {
3560  tmpStr += "&nbsp;&nbsp;" + etc;
3561  break;
3562  }
3563  tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
3564  showStatus ? a->status() : Attendee::None );
3565  if ( !a->delegator().isEmpty() ) {
3566  tmpStr += i18n( " (delegated by %1)", a->delegator() );
3567  }
3568  if ( !a->delegate().isEmpty() ) {
3569  tmpStr += i18n( " (delegated to %1)", a->delegate() );
3570  }
3571  tmpStr += "<br>";
3572  i++;
3573  }
3574  if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
3575  tmpStr.chop( 4 );
3576  }
3577  return tmpStr;
3578 }
3579 
3580 static QString tooltipFormatAttendees( const Calendar::Ptr &calendar,
3581  const Incidence::Ptr &incidence )
3582 {
3583  QString tmpStr, str;
3584 
3585  // Add organizer link
3586  int attendeeCount = incidence->attendees().count();
3587  if ( attendeeCount > 1 ||
3588  ( attendeeCount == 1 &&
3589  !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
3590  tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
3591  tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer()->email(),
3592  incidence->organizer()->name() );
3593  }
3594 
3595  // Show the attendee status if the incidence's organizer owns the resource calendar,
3596  // which means they are running the show and have all the up-to-date response info.
3597  const bool showStatus = attendeeCount > 0 && incOrganizerOwnsCalendar( calendar, incidence );
3598 
3599  // Add "chair"
3600  str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
3601  if ( !str.isEmpty() ) {
3602  tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
3603  tmpStr += str;
3604  }
3605 
3606  // Add required participants
3607  str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
3608  if ( !str.isEmpty() ) {
3609  tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
3610  tmpStr += str;
3611  }
3612 
3613  // Add optional participants
3614  str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
3615  if ( !str.isEmpty() ) {
3616  tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
3617  tmpStr += str;
3618  }
3619 
3620  // Add observers
3621  str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
3622  if ( !str.isEmpty() ) {
3623  tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
3624  tmpStr += str;
3625  }
3626 
3627  return tmpStr;
3628 }
3629 
3630 QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence,
3631  QString dtRangeText )
3632 {
3633  int maxDescLen = 120; // maximum description chars to print (before elipsis)
3634 
3635  //FIXME: support mRichText==false
3636  if ( !incidence ) {
3637  return QString();
3638  }
3639 
3640  QString tmp = "<qt>";
3641 
3642  // header
3643  tmp += "<b>" + incidence->richSummary() + "</b>";
3644  tmp += "<hr>";
3645 
3646  QString calStr = mLocation;
3647  if ( mCalendar ) {
3648  calStr = resourceString( mCalendar, incidence );
3649  }
3650  if ( !calStr.isEmpty() ) {
3651  tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
3652  tmp += calStr;
3653  }
3654 
3655  tmp += dtRangeText;
3656 
3657  if ( !incidence->location().isEmpty() ) {
3658  tmp += "<br>";
3659  tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
3660  tmp += incidence->richLocation();
3661  }
3662 
3663  QString durStr = durationString( incidence );
3664  if ( !durStr.isEmpty() ) {
3665  tmp += "<br>";
3666  tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
3667  tmp += durStr;
3668  }
3669 
3670  if ( incidence->recurs() ) {
3671  tmp += "<br>";
3672  tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
3673  tmp += recurrenceString( incidence );
3674  }
3675 
3676  if ( !incidence->description().isEmpty() ) {
3677  QString desc( incidence->description() );
3678  if ( !incidence->descriptionIsRich() ) {
3679  if ( desc.length() > maxDescLen ) {
3680  desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." );
3681  }
3682  desc = Qt::escape( desc ).replace( '\n', "<br>" );
3683  } else {
3684  // TODO: truncate the description when it's rich text
3685  }
3686  tmp += "<hr>";
3687  tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
3688  tmp += desc;
3689  tmp += "<hr>";
3690  }
3691 
3692  int reminderCount = incidence->alarms().count();
3693  if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) {
3694  tmp += "<br>";
3695  tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + "&nbsp;";
3696  tmp += reminderStringList( incidence ).join( ", " );
3697  }
3698 
3699  tmp += "<br>";
3700  tmp += tooltipFormatAttendees( mCalendar, incidence );
3701 
3702  int categoryCount = incidence->categories().count();
3703  if ( categoryCount > 0 ) {
3704  tmp += "<br>";
3705  tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + "&nbsp;";
3706  tmp += incidence->categories().join( ", " );
3707  }
3708 
3709  tmp += "</qt>";
3710  return tmp;
3711 }
3712 //@endcond
3713 
3714 QString IncidenceFormatter::toolTipStr( const QString &sourceName,
3715  const IncidenceBase::Ptr &incidence,
3716  const QDate &date,
3717  bool richText,
3718  KDateTime::Spec spec )
3719 {
3720  ToolTipVisitor v;
3721  if ( v.act( sourceName, incidence, date, richText, spec ) ) {
3722  return v.result();
3723  } else {
3724  return QString();
3725  }
3726 }
3727 
3728 /*******************************************************************
3729  * Helper functions for the Incidence tooltips
3730  *******************************************************************/
3731 
3732 //@cond PRIVATE
3733 static QString mailBodyIncidence( const Incidence::Ptr &incidence )
3734 {
3735  QString body;
3736  if ( !incidence->summary().isEmpty() ) {
3737  body += i18n( "Summary: %1\n", incidence->richSummary() );
3738  }
3739  if ( !incidence->organizer()->isEmpty() ) {
3740  body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() );
3741  }
3742  if ( !incidence->location().isEmpty() ) {
3743  body += i18n( "Location: %1\n", incidence->richLocation() );
3744  }
3745  return body;
3746 }
3747 //@endcond
3748 
3749 //@cond PRIVATE
3750 class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor
3751 {
3752  public:
3753  MailBodyVisitor()
3754  : mSpec( KDateTime::Spec() ), mResult( "" ) {}
3755 
3756  bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() )
3757  {
3758  mSpec = spec;
3759  mResult = "";
3760  return incidence ? incidence->accept( *this, incidence ) : false;
3761  }
3762  QString result() const
3763  {
3764  return mResult;
3765  }
3766 
3767  protected:
3768  bool visit( Event::Ptr event );
3769  bool visit( Todo::Ptr todo );
3770  bool visit( Journal::Ptr journal );
3771  bool visit( FreeBusy::Ptr )
3772  {
3773  mResult = i18n( "This is a Free Busy Object" );
3774  return !mResult.isEmpty();
3775  }
3776  protected:
3777  KDateTime::Spec mSpec;
3778  QString mResult;
3779 };
3780 
3781 bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event )
3782 {
3783  QString recurrence[]= {
3784  i18nc( "no recurrence", "None" ),
3785  i18nc( "event recurs by minutes", "Minutely" ),
3786  i18nc( "event recurs by hours", "Hourly" ),
3787  i18nc( "event recurs by days", "Daily" ),
3788  i18nc( "event recurs by weeks", "Weekly" ),
3789  i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
3790  i18nc( "event recurs same day each month", "Monthly Same Day" ),
3791  i18nc( "event recurs same month each year", "Yearly Same Month" ),
3792  i18nc( "event recurs same day each year", "Yearly Same Day" ),
3793  i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
3794  };
3795 
3796  mResult = mailBodyIncidence( event );
3797  mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
3798  if ( !event->allDay() ) {
3799  mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
3800  }
3801  if ( event->dtStart() != event->dtEnd() ) {
3802  mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
3803  }
3804  if ( !event->allDay() ) {
3805  mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
3806  }
3807  if ( event->recurs() ) {
3808  Recurrence *recur = event->recurrence();
3809  // TODO: Merge these two to one of the form "Recurs every 3 days"
3810  mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
3811  mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
3812 
3813  if ( recur->duration() > 0 ) {
3814  mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
3815  mResult += '\n';
3816  } else {
3817  if ( recur->duration() != -1 ) {
3818 // TODO_Recurrence: What to do with all-day
3819  QString endstr;
3820  if ( event->allDay() ) {
3821  endstr = KGlobal::locale()->formatDate( recur->endDate() );
3822  } else {
3823  endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
3824  }
3825  mResult += i18n( "Repeat until: %1\n", endstr );
3826  } else {
3827  mResult += i18n( "Repeats forever\n" );
3828  }
3829  }
3830  }
3831 
3832  QString details = event->richDescription();
3833  if ( !details.isEmpty() ) {
3834  mResult += i18n( "Details:\n%1\n", details );
3835  }
3836  return !mResult.isEmpty();
3837 }
3838 
3839 bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo )
3840 {
3841  mResult = mailBodyIncidence( todo );
3842 
3843  if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
3844  mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
3845  if ( !todo->allDay() ) {
3846  mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
3847  }
3848  }
3849  if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
3850  mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
3851  if ( !todo->allDay() ) {
3852  mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
3853  }
3854  }
3855  QString details = todo->richDescription();
3856  if ( !details.isEmpty() ) {
3857  mResult += i18n( "Details:\n%1\n", details );
3858  }
3859  return !mResult.isEmpty();
3860 }
3861 
3862 bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal )
3863 {
3864  mResult = mailBodyIncidence( journal );
3865  mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
3866  if ( !journal->allDay() ) {
3867  mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
3868  }
3869  if ( !journal->description().isEmpty() ) {
3870  mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
3871  }
3872  return !mResult.isEmpty();
3873 }
3874 //@endcond
3875 
3876 QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence,
3877  KDateTime::Spec spec )
3878 {
3879  if ( !incidence ) {
3880  return QString();
3881  }
3882 
3883  MailBodyVisitor v;
3884  if ( v.act( incidence, spec ) ) {
3885  return v.result();
3886  }
3887  return QString();
3888 }
3889 
3890 //@cond PRIVATE
3891 static QString recurEnd( const Incidence::Ptr &incidence )
3892 {
3893  QString endstr;
3894  if ( incidence->allDay() ) {
3895  endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
3896  } else {
3897  endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
3898  }
3899  return endstr;
3900 }
3901 //@endcond
3902 
3903 /************************************
3904  * More static formatting functions
3905  ************************************/
3906 
3907 QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence )
3908 {
3909  if ( !incidence->recurs() ) {
3910  return i18n( "No recurrence" );
3911  }
3912  static QStringList dayList;
3913  if ( dayList.isEmpty() ) {
3914  dayList.append( i18n( "31st Last" ) );
3915  dayList.append( i18n( "30th Last" ) );
3916  dayList.append( i18n( "29th Last" ) );
3917  dayList.append( i18n( "28th Last" ) );
3918  dayList.append( i18n( "27th Last" ) );
3919  dayList.append( i18n( "26th Last" ) );
3920  dayList.append( i18n( "25th Last" ) );
3921  dayList.append( i18n( "24th Last" ) );
3922  dayList.append( i18n( "23rd Last" ) );
3923  dayList.append( i18n( "22nd Last" ) );
3924  dayList.append( i18n( "21st Last" ) );
3925  dayList.append( i18n( "20th Last" ) );
3926  dayList.append( i18n( "19th Last" ) );
3927  dayList.append( i18n( "18th Last" ) );
3928  dayList.append( i18n( "17th Last" ) );
3929  dayList.append( i18n( "16th Last" ) );
3930  dayList.append( i18n( "15th Last" ) );
3931  dayList.append( i18n( "14th Last" ) );
3932  dayList.append( i18n( "13th Last" ) );
3933  dayList.append( i18n( "12th Last" ) );
3934  dayList.append( i18n( "11th Last" ) );
3935  dayList.append( i18n( "10th Last" ) );
3936  dayList.append( i18n( "9th Last" ) );
3937  dayList.append( i18n( "8th Last" ) );
3938  dayList.append( i18n( "7th Last" ) );
3939  dayList.append( i18n( "6th Last" ) );
3940  dayList.append( i18n( "5th Last" ) );
3941  dayList.append( i18n( "4th Last" ) );
3942  dayList.append( i18n( "3rd Last" ) );
3943  dayList.append( i18n( "2nd Last" ) );
3944  dayList.append( i18nc( "last day of the month", "Last" ) );
3945  dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
3946  dayList.append( i18n( "1st" ) );
3947  dayList.append( i18n( "2nd" ) );
3948  dayList.append( i18n( "3rd" ) );
3949  dayList.append( i18n( "4th" ) );
3950  dayList.append( i18n( "5th" ) );
3951  dayList.append( i18n( "6th" ) );
3952  dayList.append( i18n( "7th" ) );
3953  dayList.append( i18n( "8th" ) );
3954  dayList.append( i18n( "9th" ) );
3955  dayList.append( i18n( "10th" ) );
3956  dayList.append( i18n( "11th" ) );
3957  dayList.append( i18n( "12th" ) );
3958  dayList.append( i18n( "13th" ) );
3959  dayList.append( i18n( "14th" ) );
3960  dayList.append( i18n( "15th" ) );
3961  dayList.append( i18n( "16th" ) );
3962  dayList.append( i18n( "17th" ) );
3963  dayList.append( i18n( "18th" ) );
3964  dayList.append( i18n( "19th" ) );
3965  dayList.append( i18n( "20th" ) );
3966  dayList.append( i18n( "21st" ) );
3967  dayList.append( i18n( "22nd" ) );
3968  dayList.append( i18n( "23rd" ) );
3969  dayList.append( i18n( "24th" ) );
3970  dayList.append( i18n( "25th" ) );
3971  dayList.append( i18n( "26th" ) );
3972  dayList.append( i18n( "27th" ) );
3973  dayList.append( i18n( "28th" ) );
3974  dayList.append( i18n( "29th" ) );
3975  dayList.append( i18n( "30th" ) );
3976  dayList.append( i18n( "31st" ) );
3977  }
3978 
3979  const int weekStart = KGlobal::locale()->weekStartDay();
3980  QString dayNames;
3981  const KCalendarSystem *calSys = KGlobal::locale()->calendar();
3982 
3983  Recurrence *recur = incidence->recurrence();
3984 
3985  QString txt, recurStr;
3986  static QString noRecurrence = i18n( "No recurrence" );
3987  switch ( recur->recurrenceType() ) {
3988  case Recurrence::rNone:
3989  return noRecurrence;
3990 
3991  case Recurrence::rMinutely:
3992  if ( recur->duration() != -1 ) {
3993  recurStr = i18np( "Recurs every minute until %2",
3994  "Recurs every %1 minutes until %2",
3995  recur->frequency(), recurEnd( incidence ) );
3996  if ( recur->duration() > 0 ) {
3997  recurStr += i18nc( "number of occurrences",
3998  " (<numid>%1</numid> occurrences)",
3999  recur->duration() );
4000  }
4001  } else {
4002  recurStr = i18np( "Recurs every minute",
4003  "Recurs every %1 minutes", recur->frequency() );
4004  }
4005  break;
4006 
4007  case Recurrence::rHourly:
4008  if ( recur->duration() != -1 ) {
4009  recurStr = i18np( "Recurs hourly until %2",
4010  "Recurs every %1 hours until %2",
4011  recur->frequency(), recurEnd( incidence ) );
4012  if ( recur->duration() > 0 ) {
4013  recurStr += i18nc( "number of occurrences",
4014  " (<numid>%1</numid> occurrences)",
4015  recur->duration() );
4016  }
4017  } else {
4018  recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
4019  }
4020  break;
4021 
4022  case Recurrence::rDaily:
4023  if ( recur->duration() != -1 ) {
4024  recurStr = i18np( "Recurs daily until %2",
4025  "Recurs every %1 days until %2",
4026  recur->frequency(), recurEnd( incidence ) );
4027  if ( recur->duration() > 0 ) {
4028  recurStr += i18nc( "number of occurrences",
4029  " (<numid>%1</numid> occurrences)",
4030  recur->duration() );
4031  }
4032  } else {
4033  recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
4034  }
4035  break;
4036 
4037  case Recurrence::rWeekly:
4038  {
4039  bool addSpace = false;
4040  for ( int i = 0; i < 7; ++i ) {
4041  if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
4042  if ( addSpace ) {
4043  dayNames.append( i18nc( "separator for list of days", ", " ) );
4044  }
4045  dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
4046  KCalendarSystem::ShortDayName ) );
4047  addSpace = true;
4048  }
4049  }
4050  if ( dayNames.isEmpty() ) {
4051  dayNames = i18nc( "Recurs weekly on no days", "no days" );
4052  }
4053  if ( recur->duration() != -1 ) {
4054  recurStr = i18ncp( "Recurs weekly on [list of days] until end-date",
4055  "Recurs weekly on %2 until %3",
4056  "Recurs every <numid>%1</numid> weeks on %2 until %3",
4057  recur->frequency(), dayNames, recurEnd( incidence ) );
4058  if ( recur->duration() > 0 ) {
4059  recurStr += i18nc( "number of occurrences",
4060  " (<numid>%1</numid> occurrences)",
4061  recur->duration() );
4062  }
4063  } else {
4064  recurStr = i18ncp( "Recurs weekly on [list of days]",
4065  "Recurs weekly on %2",
4066  "Recurs every <numid>%1</numid> weeks on %2",
4067  recur->frequency(), dayNames );
4068  }
4069  break;
4070  }
4071  case Recurrence::rMonthlyPos:
4072  {
4073  if ( !recur->monthPositions().isEmpty() ) {
4074  RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
4075  if ( recur->duration() != -1 ) {
4076  recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
4077  " weekdayname until end-date",
4078  "Recurs every month on the %2 %3 until %4",
4079  "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
4080  recur->frequency(),
4081  dayList[rule.pos() + 31],
4082  calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
4083  recurEnd( incidence ) );
4084  if ( recur->duration() > 0 ) {
4085  recurStr += i18nc( "number of occurrences",
4086  " (<numid>%1</numid> occurrences)",
4087  recur->duration() );
4088  }
4089  } else {
4090  recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
4091  "Recurs every month on the %2 %3",
4092  "Recurs every %1 months on the %2 %3",
4093  recur->frequency(),
4094  dayList[rule.pos() + 31],
4095  calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
4096  }
4097  }
4098  break;
4099  }
4100  case Recurrence::rMonthlyDay:
4101  {
4102  if ( !recur->monthDays().isEmpty() ) {
4103  int days = recur->monthDays()[0];
4104  if ( recur->duration() != -1 ) {
4105  recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
4106  "Recurs monthly on the %2 day until %3",
4107  "Recurs every %1 months on the %2 day until %3",
4108  recur->frequency(),
4109  dayList[days + 31],
4110  recurEnd( incidence ) );
4111  if ( recur->duration() > 0 ) {
4112  recurStr += i18nc( "number of occurrences",
4113  " (<numid>%1</numid> occurrences)",
4114  recur->duration() );
4115  }
4116  } else {
4117  recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day",
4118  "Recurs monthly on the %2 day",
4119  "Recurs every <numid>%1</numid> month on the %2 day",
4120  recur->frequency(),
4121  dayList[days + 31] );
4122  }
4123  }
4124  break;
4125  }
4126  case Recurrence::rYearlyMonth:
4127  {
4128  if ( recur->duration() != -1 ) {
4129  if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
4130  recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
4131  " until end-date",
4132  "Recurs yearly on %2 %3 until %4",
4133  "Recurs every %1 years on %2 %3 until %4",
4134  recur->frequency(),
4135  calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
4136  dayList[ recur->yearDates()[0] + 31 ],
4137  recurEnd( incidence ) );
4138  if ( recur->duration() > 0 ) {
4139  recurStr += i18nc( "number of occurrences",
4140  " (<numid>%1</numid> occurrences)",
4141  recur->duration() );
4142  }
4143  }
4144  } else {
4145  if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
4146  recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
4147  "Recurs yearly on %2 %3",
4148  "Recurs every %1 years on %2 %3",
4149  recur->frequency(),
4150  calSys->monthName( recur->yearMonths()[0],
4151  recur->startDate().year() ),
4152  dayList[ recur->yearDates()[0] + 31 ] );
4153  } else {
4154  if (!recur->yearMonths().isEmpty() ) {
4155  recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
4156  "Recurs yearly on %1 %2",
4157  calSys->monthName( recur->yearMonths()[0],
4158  recur->startDate().year() ),
4159  dayList[ recur->startDate().day() + 31 ] );
4160  } else {
4161  recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
4162  "Recurs yearly on %1 %2",
4163  calSys->monthName( recur->startDate().month(),
4164  recur->startDate().year() ),
4165  dayList[ recur->startDate().day() + 31 ] );
4166  }
4167  }
4168  }
4169  break;
4170  }
4171  case Recurrence::rYearlyDay:
4172  if ( !recur->yearDays().isEmpty() ) {
4173  if ( recur->duration() != -1 ) {
4174  recurStr = i18ncp( "Recurs every N years on day N until end-date",
4175  "Recurs every year on day <numid>%2</numid> until %3",
4176  "Recurs every <numid>%1</numid> years"
4177  " on day <numid>%2</numid> until %3",
4178  recur->frequency(),
4179  recur->yearDays()[0],
4180  recurEnd( incidence ) );
4181  if ( recur->duration() > 0 ) {
4182  recurStr += i18nc( "number of occurrences",
4183  " (<numid>%1</numid> occurrences)",
4184  recur->duration() );
4185  }
4186  } else {
4187  recurStr = i18ncp( "Recurs every N YEAR[S] on day N",
4188  "Recurs every year on day <numid>%2</numid>",
4189  "Recurs every <numid>%1</numid> years"
4190  " on day <numid>%2</numid>",
4191  recur->frequency(), recur->yearDays()[0] );
4192  }
4193  }
4194  break;
4195  case Recurrence::rYearlyPos:
4196  {
4197  if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
4198  RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
4199  if ( recur->duration() != -1 ) {
4200  recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
4201  "of monthname until end-date",
4202  "Every year on the %2 %3 of %4 until %5",
4203  "Every <numid>%1</numid> years on the %2 %3 of %4"
4204  " until %5",
4205  recur->frequency(),
4206  dayList[rule.pos() + 31],
4207  calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
4208  calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
4209  recurEnd( incidence ) );
4210  if ( recur->duration() > 0 ) {
4211  recurStr += i18nc( "number of occurrences",
4212  " (<numid>%1</numid> occurrences)",
4213  recur->duration() );
4214  }
4215  } else {
4216  recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
4217  "of monthname",
4218  "Every year on the %2 %3 of %4",
4219  "Every <numid>%1</numid> years on the %2 %3 of %4",
4220  recur->frequency(),
4221  dayList[rule.pos() + 31],
4222  calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
4223  calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
4224  }
4225  }
4226  }
4227  break;
4228  }
4229 
4230  if ( recurStr.isEmpty() ) {
4231  recurStr = i18n( "Incidence recurs" );
4232  }
4233 
4234  // Now, append the EXDATEs
4235  DateTimeList l = recur->exDateTimes();
4236  DateTimeList::ConstIterator il;
4237  QStringList exStr;
4238  for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
4239  switch ( recur->recurrenceType() ) {
4240  case Recurrence::rMinutely:
4241  exStr << i18n( "minute %1", (*il).time().minute() );
4242  break;
4243  case Recurrence::rHourly:
4244  exStr << KGlobal::locale()->formatTime( (*il).time() );
4245  break;
4246  case Recurrence::rDaily:
4247  exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
4248  break;
4249  case Recurrence::rWeekly:
4250  exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName );
4251  break;
4252  case Recurrence::rMonthlyPos:
4253  exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
4254  break;
4255  case Recurrence::rMonthlyDay:
4256  exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
4257  break;
4258  case Recurrence::rYearlyMonth:
4259  exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName );
4260  break;
4261  case Recurrence::rYearlyDay:
4262  exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
4263  break;
4264  case Recurrence::rYearlyPos:
4265  exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
4266  break;
4267  }
4268  }
4269 
4270  DateList d = recur->exDates();
4271  DateList::ConstIterator dl;
4272  for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
4273  switch ( recur->recurrenceType() ) {
4274  case Recurrence::rDaily:
4275  exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
4276  break;
4277  case Recurrence::rWeekly:
4278  // exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName );
4279  // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
4280  if ( exStr.isEmpty() ) {
4281  exStr << i18np( "1 day", "%1 days", recur->exDates().count() );
4282  }
4283  break;
4284  case Recurrence::rMonthlyPos:
4285  exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
4286  break;
4287  case Recurrence::rMonthlyDay:
4288  exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
4289  break;
4290  case Recurrence::rYearlyMonth:
4291  exStr << calSys->monthName( (*dl), KCalendarSystem::LongName );
4292  break;
4293  case Recurrence::rYearlyDay:
4294  exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
4295  break;
4296  case Recurrence::rYearlyPos:
4297  exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
4298  break;
4299  }
4300  }
4301 
4302  if ( !exStr.isEmpty() ) {
4303  recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) );
4304  }
4305 
4306  return recurStr;
4307 }
4308 
4309 QString IncidenceFormatter::timeToString( const KDateTime &date,
4310  bool shortfmt,
4311  const KDateTime::Spec &spec )
4312 {
4313  if ( spec.isValid() ) {
4314 
4315  QString timeZone;
4316  if ( spec.timeZone() != KSystemTimeZones::local() ) {
4317  timeZone = ' ' + spec.timeZone().name();
4318  }
4319 
4320  return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
4321  } else {
4322  return KGlobal::locale()->formatTime( date.time(), !shortfmt );
4323  }
4324 }
4325 
4326 QString IncidenceFormatter::dateToString( const KDateTime &date,
4327  bool shortfmt,
4328  const KDateTime::Spec &spec )
4329 {
4330  if ( spec.isValid() ) {
4331 
4332  QString timeZone;
4333  if ( spec.timeZone() != KSystemTimeZones::local() ) {
4334  timeZone = ' ' + spec.timeZone().name();
4335  }
4336 
4337  return
4338  KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
4339  ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
4340  timeZone;
4341  } else {
4342  return
4343  KGlobal::locale()->formatDate( date.date(),
4344  ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
4345  }
4346 }
4347 
4348 QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
4349  bool allDay,
4350  bool shortfmt,
4351  const KDateTime::Spec &spec )
4352 {
4353  if ( allDay ) {
4354  return dateToString( date, shortfmt, spec );
4355  }
4356 
4357  if ( spec.isValid() ) {
4358  QString timeZone;
4359  if ( spec.timeZone() != KSystemTimeZones::local() ) {
4360  timeZone = ' ' + spec.timeZone().name();
4361  }
4362 
4363  return KGlobal::locale()->formatDateTime(
4364  date.toTimeSpec( spec ).dateTime(),
4365  ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
4366  } else {
4367  return KGlobal::locale()->formatDateTime(
4368  date.dateTime(),
4369  ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
4370  }
4371 }
4372 
4373 QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar,
4374  const Incidence::Ptr &incidence )
4375 {
4376  Q_UNUSED( calendar );
4377  Q_UNUSED( incidence );
4378  return QString();
4379 }
4380 
4381 static QString secs2Duration( int secs )
4382 {
4383  QString tmp;
4384  int days = secs / 86400;
4385  if ( days > 0 ) {
4386  tmp += i18np( "1 day", "%1 days", days );
4387  tmp += ' ';
4388  secs -= ( days * 86400 );
4389  }
4390  int hours = secs / 3600;
4391  if ( hours > 0 ) {
4392  tmp += i18np( "1 hour", "%1 hours", hours );
4393  tmp += ' ';
4394  secs -= ( hours * 3600 );
4395  }
4396  int mins = secs / 60;
4397  if ( mins > 0 ) {
4398  tmp += i18np( "1 minute", "%1 minutes", mins );
4399  }
4400  return tmp;
4401 }
4402 
4403 QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence )
4404 {
4405  QString tmp;
4406  if ( incidence->type() == Incidence::TypeEvent ) {
4407  Event::Ptr event = incidence.staticCast<Event>();
4408  if ( event->hasEndDate() ) {
4409  if ( !event->allDay() ) {
4410  tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
4411  } else {
4412  tmp = i18np( "1 day", "%1 days",
4413  event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
4414  }
4415  } else {
4416  tmp = i18n( "forever" );
4417  }
4418  } else if ( incidence->type() == Incidence::TypeTodo ) {
4419  Todo::Ptr todo = incidence.staticCast<Todo>();
4420  if ( todo->hasDueDate() ) {
4421  if ( todo->hasStartDate() ) {
4422  if ( !todo->allDay() ) {
4423  tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
4424  } else {
4425  tmp = i18np( "1 day", "%1 days",
4426  todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
4427  }
4428  }
4429  }
4430  }
4431  return tmp;
4432 }
4433 
4434 QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence,
4435  bool shortfmt )
4436 {
4437  //TODO: implement shortfmt=false
4438  Q_UNUSED( shortfmt );
4439 
4440  QStringList reminderStringList;
4441 
4442  if ( incidence ) {
4443  Alarm::List alarms = incidence->alarms();
4444  Alarm::List::ConstIterator it;
4445  for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
4446  Alarm::Ptr alarm = *it;
4447  int offset = 0;
4448  QString remStr, atStr, offsetStr;
4449  if ( alarm->hasTime() ) {
4450  offset = 0;
4451  if ( alarm->time().isValid() ) {
4452  atStr = KGlobal::locale()->formatDateTime( alarm->time() );
4453  }
4454  } else if ( alarm->hasStartOffset() ) {
4455  offset = alarm->startOffset().asSeconds();
4456  if ( offset < 0 ) {
4457  offset = -offset;
4458  offsetStr = i18nc( "N days/hours/minutes before the start datetime",
4459  "%1 before the start", secs2Duration( offset ) );
4460  } else if ( offset > 0 ) {
4461  offsetStr = i18nc( "N days/hours/minutes after the start datetime",
4462  "%1 after the start", secs2Duration( offset ) );
4463  } else { //offset is 0
4464  if ( incidence->dtStart().isValid() ) {
4465  atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
4466  }
4467  }
4468  } else if ( alarm->hasEndOffset() ) {
4469  offset = alarm->endOffset().asSeconds();
4470  if ( offset < 0 ) {
4471  offset = -offset;
4472  if ( incidence->type() == Incidence::TypeTodo ) {
4473  offsetStr = i18nc( "N days/hours/minutes before the due datetime",
4474  "%1 before the to-do is due", secs2Duration( offset ) );
4475  } else {
4476  offsetStr = i18nc( "N days/hours/minutes before the end datetime",
4477  "%1 before the end", secs2Duration( offset ) );
4478  }
4479  } else if ( offset > 0 ) {
4480  if ( incidence->type() == Incidence::TypeTodo ) {
4481  offsetStr = i18nc( "N days/hours/minutes after the due datetime",
4482  "%1 after the to-do is due", secs2Duration( offset ) );
4483  } else {
4484  offsetStr = i18nc( "N days/hours/minutes after the end datetime",
4485  "%1 after the end", secs2Duration( offset ) );
4486  }
4487  } else { //offset is 0
4488  if ( incidence->type() == Incidence::TypeTodo ) {
4489  Todo::Ptr t = incidence.staticCast<Todo>();
4490  if ( t->dtDue().isValid() ) {
4491  atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
4492  }
4493  } else {
4494  Event::Ptr e = incidence.staticCast<Event>();
4495  if ( e->dtEnd().isValid() ) {
4496  atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
4497  }
4498  }
4499  }
4500  }
4501  if ( offset == 0 ) {
4502  if ( !atStr.isEmpty() ) {
4503  remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
4504  }
4505  } else {
4506  remStr = offsetStr;
4507  }
4508 
4509  if ( alarm->repeatCount() > 0 ) {
4510  QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
4511  QString intervalStr = i18nc( "interval is N days/hours/minutes",
4512  "interval is %1",
4513  secs2Duration( alarm->snoozeTime().asSeconds() ) );
4514  QString repeatStr = i18nc( "(repeat string, interval string)",
4515  "(%1, %2)", countStr, intervalStr );
4516  remStr = remStr + ' ' + repeatStr;
4517 
4518  }
4519  reminderStringList << remStr;
4520  }
4521  }
4522 
4523  return reminderStringList;
4524 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:26:46 by doxygen 1.8.3.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KCalUtils Library

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

kdepimlibs-4.10.5 API Reference

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

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