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