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

KCalUtils Library

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

KDE's Doxygen guidelines are available online.

KCalUtils Library

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

kdepimlibs-4.11.3 API Reference

Skip menu "kdepimlibs-4.11.3 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