1 | /* |
2 | This file is part of the kcal library. |
3 | |
4 | Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org> |
5 | Copyright (C) 2009 Allen Winter <winter@kde.org> |
6 | |
7 | This library is free software; you can redistribute it and/or |
8 | modify it under the terms of the GNU Library General Public |
9 | License as published by the Free Software Foundation; either |
10 | version 2 of the License, or (at your option) any later version. |
11 | |
12 | This library is distributed in the hope that it will be useful, |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | Library General Public License for more details. |
16 | |
17 | You should have received a copy of the GNU Library General Public License |
18 | along with this library; see the file COPYING.LIB. If not, write to |
19 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
20 | Boston, MA 02110-1301, USA. |
21 | */ |
22 | /** |
23 | @file |
24 | This file is part of the API for handling calendar data and |
25 | defines the Todo class. |
26 | |
27 | @brief |
28 | Provides a To-do in the sense of RFC2445. |
29 | |
30 | @author Cornelius Schumacher \<schumacher@kde.org\> |
31 | @author Allen Winter \<winter@kde.org\> |
32 | */ |
33 | |
34 | #include "todo.h" |
35 | #include "incidenceformatter.h" |
36 | |
37 | #include <kglobal.h> |
38 | #include <klocale.h> |
39 | #include <klocalizedstring.h> |
40 | #include <kdebug.h> |
41 | #include <ksystemtimezone.h> |
42 | |
43 | using namespace KCal; |
44 | |
45 | /** |
46 | Private class that helps to provide binary compatibility between releases. |
47 | @internal |
48 | */ |
49 | //@cond PRIVATE |
50 | class KCal::Todo::Private |
51 | { |
52 | public: |
53 | Private() |
54 | : mPercentComplete( 0 ), |
55 | mHasDueDate( false ), |
56 | mHasStartDate( false ), |
57 | mHasCompletedDate( false ) |
58 | {} |
59 | Private( const KCal::Todo::Private &other ) |
60 | { init( other ); } |
61 | |
62 | void init( const KCal::Todo::Private &other ); |
63 | |
64 | KDateTime mDtDue; // to-do due date (if there is one) |
65 | // ALSO the first occurrence of a recurring to-do |
66 | KDateTime mDtRecurrence; // next occurrence (for recurring to-dos) |
67 | KDateTime mCompleted; // to-do completion date (if it has been completed) |
68 | int mPercentComplete; // to-do percent complete [0,100] |
69 | bool mHasDueDate; // true if the to-do has a due date |
70 | bool mHasStartDate; // true if the to-do has a starting date |
71 | bool mHasCompletedDate; // true if the to-do has a completion date |
72 | |
73 | /** |
74 | Returns true if the todo got a new date, else false will be returned. |
75 | */ |
76 | bool recurTodo( Todo *todo ); |
77 | }; |
78 | |
79 | void KCal::Todo::Private::init( const KCal::Todo::Private &other ) |
80 | { |
81 | mDtDue = other.mDtDue; |
82 | mDtRecurrence = other.mDtRecurrence; |
83 | mCompleted = other.mCompleted; |
84 | mPercentComplete = other.mPercentComplete; |
85 | mHasDueDate = other.mHasDueDate; |
86 | mHasStartDate = other.mHasStartDate; |
87 | mHasCompletedDate = other.mHasCompletedDate; |
88 | } |
89 | |
90 | //@endcond |
91 | |
92 | Todo::Todo() |
93 | : d( new KCal::Todo::Private ) |
94 | { |
95 | } |
96 | |
97 | Todo::Todo( const Todo &other ) |
98 | : Incidence( other ), |
99 | d( new KCal::Todo::Private( *other.d ) ) |
100 | { |
101 | } |
102 | |
103 | Todo::~Todo() |
104 | { |
105 | delete d; |
106 | } |
107 | |
108 | Todo *Todo::clone() |
109 | { |
110 | return new Todo( *this ); |
111 | } |
112 | |
113 | Todo &Todo::operator=( const Todo &other ) |
114 | { |
115 | // check for self assignment |
116 | if ( &other == this ) { |
117 | return *this; |
118 | } |
119 | |
120 | Incidence::operator=( other ); |
121 | d->init( *other.d ); |
122 | return *this; |
123 | } |
124 | |
125 | bool Todo::operator==( const Todo &todo ) const |
126 | { |
127 | return |
128 | Incidence::operator==( todo ) && |
129 | dtDue() == todo.dtDue() && |
130 | hasDueDate() == todo.hasDueDate() && |
131 | hasStartDate() == todo.hasStartDate() && |
132 | completed() == todo.completed() && |
133 | hasCompletedDate() == todo.hasCompletedDate() && |
134 | percentComplete() == todo.percentComplete(); |
135 | } |
136 | |
137 | QByteArray Todo::type() const |
138 | { |
139 | return "Todo" ; |
140 | } |
141 | |
142 | //KDE5: |
143 | //QString Todo::typeStr() const |
144 | //{ |
145 | // return i18nc( "incidence type is to-do/task", "to-do" ); |
146 | //} |
147 | |
148 | void Todo::setDtDue( const KDateTime &dtDue, bool first ) |
149 | { |
150 | //int diffsecs = d->mDtDue.secsTo(dtDue); |
151 | |
152 | /*if (mReadOnly) return; |
153 | const Alarm::List& alarms = alarms(); |
154 | for (Alarm *alarm = alarms.first(); alarm; alarm = alarms.next()) { |
155 | if (alarm->enabled()) { |
156 | alarm->setTime(alarm->time().addSecs(diffsecs)); |
157 | } |
158 | }*/ |
159 | |
160 | d->mHasDueDate = true; |
161 | if ( recurs() && !first ) { |
162 | d->mDtRecurrence = dtDue; |
163 | } else { |
164 | d->mDtDue = dtDue; |
165 | // TODO: This doesn't seem right... |
166 | recurrence()->setStartDateTime( dtDue ); |
167 | recurrence()->setAllDay( allDay() ); |
168 | } |
169 | |
170 | if ( recurs() && dtDue < recurrence()->startDateTime() ) { |
171 | setDtStart( dtDue ); |
172 | } |
173 | |
174 | /*const Alarm::List& alarms = alarms(); |
175 | for (Alarm *alarm = alarms.first(); alarm; alarm = alarms.next()) |
176 | alarm->setAlarmStart(d->mDtDue);*/ |
177 | |
178 | updated(); |
179 | } |
180 | |
181 | KDateTime Todo::dtDue( bool first ) const |
182 | { |
183 | if ( !hasDueDate() ) { |
184 | return KDateTime(); |
185 | } |
186 | if ( recurs() && !first && d->mDtRecurrence.isValid() ) { |
187 | return d->mDtRecurrence; |
188 | } |
189 | |
190 | return d->mDtDue; |
191 | } |
192 | |
193 | QString Todo::dtDueTimeStr( bool shortfmt, const KDateTime::Spec &spec ) const |
194 | { |
195 | if ( spec.isValid() ) { |
196 | |
197 | QString timeZone; |
198 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
199 | timeZone = ' ' + spec.timeZone().name(); |
200 | } |
201 | |
202 | return KGlobal::locale()->formatTime( |
203 | dtDue( !recurs() ).toTimeSpec( spec ).time(), !shortfmt ) + timeZone; |
204 | } else { |
205 | return KGlobal::locale()->formatTime( |
206 | dtDue( !recurs() ).time(), !shortfmt ); |
207 | } |
208 | } |
209 | |
210 | QString Todo::dtDueDateStr( bool shortfmt, const KDateTime::Spec &spec ) const |
211 | { |
212 | if ( spec.isValid() ) { |
213 | |
214 | QString timeZone; |
215 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
216 | timeZone = ' ' + spec.timeZone().name(); |
217 | } |
218 | |
219 | return KGlobal::locale()->formatDate( |
220 | dtDue( !recurs() ).toTimeSpec( spec ).date(), |
221 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; |
222 | } else { |
223 | return KGlobal::locale()->formatDate( |
224 | dtDue( !recurs() ).date(), |
225 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
226 | } |
227 | } |
228 | |
229 | QString Todo::dtDueStr( bool shortfmt, const KDateTime::Spec &spec ) const |
230 | { |
231 | if ( allDay() ) { |
232 | return IncidenceFormatter::dateToString( dtDue(), shortfmt, spec ); |
233 | } |
234 | |
235 | if ( spec.isValid() ) { |
236 | |
237 | QString timeZone; |
238 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
239 | timeZone = ' ' + spec.timeZone().name(); |
240 | } |
241 | |
242 | return KGlobal::locale()->formatDateTime( |
243 | dtDue( !recurs() ).toTimeSpec( spec ).dateTime(), |
244 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; |
245 | } else { |
246 | return KGlobal::locale()->formatDateTime( |
247 | dtDue( !recurs() ).dateTime(), |
248 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
249 | } |
250 | } |
251 | |
252 | bool Todo::hasDueDate() const |
253 | { |
254 | return d->mHasDueDate; |
255 | } |
256 | |
257 | void Todo::setHasDueDate( bool f ) |
258 | { |
259 | if ( mReadOnly ) { |
260 | return; |
261 | } |
262 | d->mHasDueDate = f; |
263 | updated(); |
264 | } |
265 | |
266 | bool Todo::hasStartDate() const |
267 | { |
268 | return d->mHasStartDate; |
269 | } |
270 | |
271 | void Todo::setHasStartDate( bool f ) |
272 | { |
273 | if ( mReadOnly ) { |
274 | return; |
275 | } |
276 | |
277 | if ( recurs() && !f ) { |
278 | if ( !comments().filter( "NoStartDate" ).count() ) { |
279 | addComment( "NoStartDate" ); //TODO: --> custom flag? |
280 | } |
281 | } else { |
282 | QString s( "NoStartDate" ); |
283 | removeComment( s ); |
284 | } |
285 | d->mHasStartDate = f; |
286 | updated(); |
287 | } |
288 | |
289 | KDateTime Todo::dtStart() const |
290 | { |
291 | return dtStart( false ); |
292 | } |
293 | |
294 | KDateTime Todo::dtStart( bool first ) const |
295 | { |
296 | if ( !hasStartDate() ) { |
297 | return KDateTime(); |
298 | } |
299 | if ( recurs() && !first ) { |
300 | KDateTime dt = d->mDtRecurrence.addDays( dtDue( true ).daysTo( IncidenceBase::dtStart() ) ); |
301 | dt.setTime( IncidenceBase::dtStart().time() ); |
302 | return dt; |
303 | } else { |
304 | return IncidenceBase::dtStart(); |
305 | } |
306 | } |
307 | |
308 | void Todo::setDtStart( const KDateTime &dtStart ) |
309 | { |
310 | // TODO: This doesn't seem right (rfc 2445/6 says, recurrence is calculated from the dtstart...) |
311 | if ( recurs() ) { |
312 | recurrence()->setStartDateTime( d->mDtDue ); |
313 | recurrence()->setAllDay( allDay() ); |
314 | } |
315 | d->mHasStartDate = true; |
316 | IncidenceBase::setDtStart( dtStart ); |
317 | } |
318 | |
319 | QString Todo::dtStartTimeStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const |
320 | { |
321 | if ( spec.isValid() ) { |
322 | |
323 | QString timeZone; |
324 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
325 | timeZone = ' ' + spec.timeZone().name(); |
326 | } |
327 | |
328 | return KGlobal::locale()->formatTime( |
329 | dtStart( first ).toTimeSpec( spec ).time(), !shortfmt ) + timeZone; |
330 | } else { |
331 | return KGlobal::locale()->formatTime( |
332 | dtStart( first ).time(), !shortfmt ); |
333 | } |
334 | } |
335 | |
336 | QString Todo::dtStartTimeStr( bool shortfmt, const KDateTime::Spec &spec ) const |
337 | { |
338 | return IncidenceFormatter::timeToString( dtStart(), shortfmt, spec ); |
339 | } |
340 | |
341 | QString Todo::dtStartDateStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const |
342 | { |
343 | if ( spec.isValid() ) { |
344 | |
345 | QString timeZone; |
346 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
347 | timeZone = ' ' + spec.timeZone().name(); |
348 | } |
349 | |
350 | return KGlobal::locale()->formatDate( |
351 | dtStart( first ).toTimeSpec( spec ).date(), |
352 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; |
353 | } else { |
354 | return KGlobal::locale()->formatDate( |
355 | dtStart( first ).date(), |
356 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
357 | } |
358 | } |
359 | |
360 | QString Todo::dtStartDateStr( bool shortfmt, const KDateTime::Spec &spec ) const |
361 | { |
362 | return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec ); |
363 | } |
364 | |
365 | QString Todo::dtStartStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const |
366 | { |
367 | if ( allDay() ) { |
368 | return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec ); |
369 | } |
370 | |
371 | if ( spec.isValid() ) { |
372 | |
373 | QString timeZone; |
374 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
375 | timeZone = ' ' + spec.timeZone().name(); |
376 | } |
377 | |
378 | return KGlobal::locale()->formatDateTime( |
379 | dtStart( first ).toTimeSpec( spec ).dateTime(), |
380 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; |
381 | } else { |
382 | return KGlobal::locale()->formatDateTime( |
383 | dtStart( first ).dateTime(), |
384 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
385 | } |
386 | } |
387 | |
388 | QString Todo::dtStartStr( bool shortfmt, const KDateTime::Spec &spec ) const |
389 | { |
390 | if ( allDay() ) { |
391 | return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec ); |
392 | } |
393 | |
394 | if ( spec.isValid() ) { |
395 | |
396 | QString timeZone; |
397 | if ( spec.timeZone() != KSystemTimeZones::local() ) { |
398 | timeZone = ' ' + spec.timeZone().name(); |
399 | } |
400 | |
401 | return KGlobal::locale()->formatDateTime( |
402 | dtStart().toTimeSpec( spec ).dateTime(), |
403 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone; |
404 | } else { |
405 | return KGlobal::locale()->formatDateTime( |
406 | dtStart().dateTime(), |
407 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
408 | } |
409 | } |
410 | |
411 | bool Todo::isCompleted() const |
412 | { |
413 | if ( d->mPercentComplete == 100 ) { |
414 | return true; |
415 | } else { |
416 | return false; |
417 | } |
418 | } |
419 | |
420 | void Todo::setCompleted( bool completed ) |
421 | { |
422 | if ( completed ) { |
423 | d->mPercentComplete = 100; |
424 | } else { |
425 | d->mPercentComplete = 0; |
426 | d->mHasCompletedDate = false; |
427 | d->mCompleted = KDateTime(); |
428 | } |
429 | updated(); |
430 | } |
431 | |
432 | KDateTime Todo::completed() const |
433 | { |
434 | if ( hasCompletedDate() ) { |
435 | return d->mCompleted; |
436 | } else { |
437 | return KDateTime(); |
438 | } |
439 | } |
440 | |
441 | QString Todo::completedStr( bool shortfmt ) const |
442 | { |
443 | return |
444 | KGlobal::locale()->formatDateTime( d->mCompleted.dateTime(), |
445 | ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ); |
446 | } |
447 | |
448 | void Todo::setCompleted( const KDateTime &completed ) |
449 | { |
450 | if ( !d->recurTodo( this ) ) { |
451 | d->mHasCompletedDate = true; |
452 | d->mPercentComplete = 100; |
453 | d->mCompleted = completed.toUtc(); |
454 | } |
455 | updated(); |
456 | } |
457 | |
458 | bool Todo::hasCompletedDate() const |
459 | { |
460 | return d->mHasCompletedDate; |
461 | } |
462 | |
463 | int Todo::percentComplete() const |
464 | { |
465 | return d->mPercentComplete; |
466 | } |
467 | |
468 | void Todo::setPercentComplete( int percent ) |
469 | { |
470 | //TODO: (?) assert percent between 0 and 100, inclusive |
471 | d->mPercentComplete = percent; |
472 | if ( percent != 100 ) { |
473 | d->mHasCompletedDate = false; |
474 | } |
475 | updated(); |
476 | } |
477 | |
478 | bool Todo::isInProgress( bool first ) const |
479 | { |
480 | if ( isOverdue() ) { |
481 | return false; |
482 | } |
483 | |
484 | if ( d->mPercentComplete > 0 ) { |
485 | return true; |
486 | } |
487 | |
488 | if ( d->mHasStartDate && d->mHasDueDate ) { |
489 | if ( allDay() ) { |
490 | QDate currDate = QDate::currentDate(); |
491 | if ( dtStart( first ).date() <= currDate && currDate < dtDue( first ).date() ) { |
492 | return true; |
493 | } |
494 | } else { |
495 | KDateTime currDate = KDateTime::currentUtcDateTime(); |
496 | if ( dtStart( first ) <= currDate && currDate < dtDue( first ) ) { |
497 | return true; |
498 | } |
499 | } |
500 | } |
501 | |
502 | return false; |
503 | } |
504 | |
505 | bool Todo::isOpenEnded() const |
506 | { |
507 | if ( !d->mHasDueDate && !isCompleted() ) { |
508 | return true; |
509 | } |
510 | return false; |
511 | |
512 | } |
513 | |
514 | bool Todo::isNotStarted( bool first ) const |
515 | { |
516 | if ( d->mPercentComplete > 0 ) { |
517 | return false; |
518 | } |
519 | |
520 | if ( !d->mHasStartDate ) { |
521 | return false; |
522 | } |
523 | |
524 | if ( allDay() ) { |
525 | if ( dtStart( first ).date() >= QDate::currentDate() ) { |
526 | return false; |
527 | } |
528 | } else { |
529 | if ( dtStart( first ) >= KDateTime::currentUtcDateTime() ) { |
530 | return false; |
531 | } |
532 | } |
533 | return true; |
534 | } |
535 | |
536 | void Todo::shiftTimes( const KDateTime::Spec &oldSpec, |
537 | const KDateTime::Spec &newSpec ) |
538 | { |
539 | Incidence::shiftTimes( oldSpec, newSpec ); |
540 | d->mDtDue = d->mDtDue.toTimeSpec( oldSpec ); |
541 | d->mDtDue.setTimeSpec( newSpec ); |
542 | if ( recurs() ) { |
543 | d->mDtRecurrence = d->mDtRecurrence.toTimeSpec( oldSpec ); |
544 | d->mDtRecurrence.setTimeSpec( newSpec ); |
545 | } |
546 | if ( d->mHasCompletedDate ) { |
547 | d->mCompleted = d->mCompleted.toTimeSpec( oldSpec ); |
548 | d->mCompleted.setTimeSpec( newSpec ); |
549 | } |
550 | } |
551 | |
552 | void Todo::setDtRecurrence( const KDateTime &dt ) |
553 | { |
554 | d->mDtRecurrence = dt; |
555 | } |
556 | |
557 | KDateTime Todo::dtRecurrence() const |
558 | { |
559 | return d->mDtRecurrence.isValid() ? d->mDtRecurrence : d->mDtDue; |
560 | } |
561 | |
562 | bool Todo::recursOn( const QDate &date, const KDateTime::Spec &timeSpec ) const |
563 | { |
564 | QDate today = QDate::currentDate(); |
565 | return |
566 | Incidence::recursOn( date, timeSpec ) && |
567 | !( date < today && d->mDtRecurrence.date() < today && |
568 | d->mDtRecurrence > recurrence()->startDateTime() ); |
569 | } |
570 | |
571 | bool Todo::isOverdue() const |
572 | { |
573 | if ( !dtDue().isValid() ) { |
574 | return false; // if it's never due, it can't be overdue |
575 | } |
576 | |
577 | bool inPast = allDay() ? |
578 | dtDue().date() < QDate::currentDate() : |
579 | dtDue() < KDateTime::currentUtcDateTime(); |
580 | return inPast && !isCompleted(); |
581 | } |
582 | |
583 | KDateTime Todo::endDateRecurrenceBase() const |
584 | { |
585 | return dtDue(); |
586 | } |
587 | |
588 | //@cond PRIVATE |
589 | bool Todo::Private::recurTodo( Todo *todo ) |
590 | { |
591 | if ( todo->recurs() ) { |
592 | Recurrence *r = todo->recurrence(); |
593 | KDateTime endDateTime = r->endDateTime(); |
594 | KDateTime nextDate = r->getNextDateTime( todo->dtDue() ); |
595 | |
596 | if ( ( r->duration() == -1 || |
597 | ( nextDate.isValid() && endDateTime.isValid() && |
598 | nextDate <= endDateTime ) ) ) { |
599 | |
600 | while ( !todo->recursAt( nextDate ) || |
601 | nextDate <= KDateTime::currentUtcDateTime() ) { |
602 | |
603 | if ( !nextDate.isValid() || |
604 | ( nextDate > endDateTime && r->duration() != -1 ) ) { |
605 | |
606 | return false; |
607 | } |
608 | |
609 | nextDate = r->getNextDateTime( nextDate ); |
610 | } |
611 | |
612 | todo->setDtDue( nextDate ); |
613 | todo->setCompleted( false ); |
614 | todo->setRevision( todo->revision() + 1 ); |
615 | |
616 | return true; |
617 | } |
618 | } |
619 | |
620 | return false; |
621 | } |
622 | //@endcond |
623 | |