mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-24 10:52:52 +00:00
622 lines
15 KiB
C++
622 lines
15 KiB
C++
/*
|
|
This file is part of the kcal library.
|
|
|
|
Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
|
|
Copyright (C) 2009 Allen Winter <winter@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
/**
|
|
@file
|
|
This file is part of the API for handling calendar data and
|
|
defines the Todo class.
|
|
|
|
@brief
|
|
Provides a To-do in the sense of RFC2445.
|
|
|
|
@author Cornelius Schumacher \<schumacher@kde.org\>
|
|
@author Allen Winter \<winter@kde.org\>
|
|
*/
|
|
|
|
#include "todo.h"
|
|
#include "incidenceformatter.h"
|
|
|
|
#include <kglobal.h>
|
|
#include <klocale.h>
|
|
#include <klocalizedstring.h>
|
|
#include <kdebug.h>
|
|
#include <ksystemtimezone.h>
|
|
|
|
using namespace KCal;
|
|
|
|
/**
|
|
Private class that helps to provide binary compatibility between releases.
|
|
@internal
|
|
*/
|
|
//@cond PRIVATE
|
|
class KCal::Todo::Private
|
|
{
|
|
public:
|
|
Private()
|
|
: mPercentComplete( 0 ),
|
|
mHasDueDate( false ),
|
|
mHasStartDate( false ),
|
|
mHasCompletedDate( false )
|
|
{}
|
|
Private( const KCal::Todo::Private &other )
|
|
{ init( other ); }
|
|
|
|
void init( const KCal::Todo::Private &other );
|
|
|
|
KDateTime mDtDue; // to-do due date (if there is one)
|
|
// ALSO the first occurrence of a recurring to-do
|
|
KDateTime mDtRecurrence; // next occurrence (for recurring to-dos)
|
|
KDateTime mCompleted; // to-do completion date (if it has been completed)
|
|
int mPercentComplete; // to-do percent complete [0,100]
|
|
bool mHasDueDate; // true if the to-do has a due date
|
|
bool mHasStartDate; // true if the to-do has a starting date
|
|
bool mHasCompletedDate; // true if the to-do has a completion date
|
|
|
|
/**
|
|
Returns true if the todo got a new date, else false will be returned.
|
|
*/
|
|
bool recurTodo( Todo *todo );
|
|
};
|
|
|
|
void KCal::Todo::Private::init( const KCal::Todo::Private &other )
|
|
{
|
|
mDtDue = other.mDtDue;
|
|
mDtRecurrence = other.mDtRecurrence;
|
|
mCompleted = other.mCompleted;
|
|
mPercentComplete = other.mPercentComplete;
|
|
mHasDueDate = other.mHasDueDate;
|
|
mHasStartDate = other.mHasStartDate;
|
|
mHasCompletedDate = other.mHasCompletedDate;
|
|
}
|
|
|
|
//@endcond
|
|
|
|
Todo::Todo()
|
|
: d( new KCal::Todo::Private )
|
|
{
|
|
}
|
|
|
|
Todo::Todo( const Todo &other )
|
|
: Incidence( other ),
|
|
d( new KCal::Todo::Private( *other.d ) )
|
|
{
|
|
}
|
|
|
|
Todo::~Todo()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
Todo *Todo::clone()
|
|
{
|
|
return new Todo( *this );
|
|
}
|
|
|
|
Todo &Todo::operator=( const Todo &other )
|
|
{
|
|
// check for self assignment
|
|
if ( &other == this ) {
|
|
return *this;
|
|
}
|
|
|
|
Incidence::operator=( other );
|
|
d->init( *other.d );
|
|
return *this;
|
|
}
|
|
|
|
bool Todo::operator==( const Todo &todo ) const
|
|
{
|
|
return
|
|
Incidence::operator==( todo ) &&
|
|
dtDue() == todo.dtDue() &&
|
|
hasDueDate() == todo.hasDueDate() &&
|
|
hasStartDate() == todo.hasStartDate() &&
|
|
completed() == todo.completed() &&
|
|
hasCompletedDate() == todo.hasCompletedDate() &&
|
|
percentComplete() == todo.percentComplete();
|
|
}
|
|
|
|
QByteArray Todo::type() const
|
|
{
|
|
return "Todo";
|
|
}
|
|
|
|
//KDE5:
|
|
//QString Todo::typeStr() const
|
|
//{
|
|
// return i18nc( "incidence type is to-do/task", "to-do" );
|
|
//}
|
|
|
|
void Todo::setDtDue( const KDateTime &dtDue, bool first )
|
|
{
|
|
//int diffsecs = d->mDtDue.secsTo(dtDue);
|
|
|
|
/*if (mReadOnly) return;
|
|
const Alarm::List& alarms = alarms();
|
|
for (Alarm *alarm = alarms.first(); alarm; alarm = alarms.next()) {
|
|
if (alarm->enabled()) {
|
|
alarm->setTime(alarm->time().addSecs(diffsecs));
|
|
}
|
|
}*/
|
|
|
|
d->mHasDueDate = true;
|
|
if ( recurs() && !first ) {
|
|
d->mDtRecurrence = dtDue;
|
|
} else {
|
|
d->mDtDue = dtDue;
|
|
// TODO: This doesn't seem right...
|
|
recurrence()->setStartDateTime( dtDue );
|
|
recurrence()->setAllDay( allDay() );
|
|
}
|
|
|
|
if ( recurs() && dtDue < recurrence()->startDateTime() ) {
|
|
setDtStart( dtDue );
|
|
}
|
|
|
|
/*const Alarm::List& alarms = alarms();
|
|
for (Alarm *alarm = alarms.first(); alarm; alarm = alarms.next())
|
|
alarm->setAlarmStart(d->mDtDue);*/
|
|
|
|
updated();
|
|
}
|
|
|
|
KDateTime Todo::dtDue( bool first ) const
|
|
{
|
|
if ( !hasDueDate() ) {
|
|
return KDateTime();
|
|
}
|
|
if ( recurs() && !first && d->mDtRecurrence.isValid() ) {
|
|
return d->mDtRecurrence;
|
|
}
|
|
|
|
return d->mDtDue;
|
|
}
|
|
|
|
QString Todo::dtDueTimeStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatTime(
|
|
dtDue( !recurs() ).toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatTime(
|
|
dtDue( !recurs() ).time(), !shortfmt );
|
|
}
|
|
}
|
|
|
|
QString Todo::dtDueDateStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatDate(
|
|
dtDue( !recurs() ).toTimeSpec( spec ).date(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatDate(
|
|
dtDue( !recurs() ).date(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
}
|
|
|
|
QString Todo::dtDueStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( allDay() ) {
|
|
return IncidenceFormatter::dateToString( dtDue(), shortfmt, spec );
|
|
}
|
|
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtDue( !recurs() ).toTimeSpec( spec ).dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtDue( !recurs() ).dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
}
|
|
|
|
bool Todo::hasDueDate() const
|
|
{
|
|
return d->mHasDueDate;
|
|
}
|
|
|
|
void Todo::setHasDueDate( bool f )
|
|
{
|
|
if ( mReadOnly ) {
|
|
return;
|
|
}
|
|
d->mHasDueDate = f;
|
|
updated();
|
|
}
|
|
|
|
bool Todo::hasStartDate() const
|
|
{
|
|
return d->mHasStartDate;
|
|
}
|
|
|
|
void Todo::setHasStartDate( bool f )
|
|
{
|
|
if ( mReadOnly ) {
|
|
return;
|
|
}
|
|
|
|
if ( recurs() && !f ) {
|
|
if ( !comments().filter( "NoStartDate" ).count() ) {
|
|
addComment( "NoStartDate" ); //TODO: --> custom flag?
|
|
}
|
|
} else {
|
|
QString s( "NoStartDate" );
|
|
removeComment( s );
|
|
}
|
|
d->mHasStartDate = f;
|
|
updated();
|
|
}
|
|
|
|
KDateTime Todo::dtStart() const
|
|
{
|
|
return dtStart( false );
|
|
}
|
|
|
|
KDateTime Todo::dtStart( bool first ) const
|
|
{
|
|
if ( !hasStartDate() ) {
|
|
return KDateTime();
|
|
}
|
|
if ( recurs() && !first ) {
|
|
KDateTime dt = d->mDtRecurrence.addDays( dtDue( true ).daysTo( IncidenceBase::dtStart() ) );
|
|
dt.setTime( IncidenceBase::dtStart().time() );
|
|
return dt;
|
|
} else {
|
|
return IncidenceBase::dtStart();
|
|
}
|
|
}
|
|
|
|
void Todo::setDtStart( const KDateTime &dtStart )
|
|
{
|
|
// TODO: This doesn't seem right (rfc 2445/6 says, recurrence is calculated from the dtstart...)
|
|
if ( recurs() ) {
|
|
recurrence()->setStartDateTime( d->mDtDue );
|
|
recurrence()->setAllDay( allDay() );
|
|
}
|
|
d->mHasStartDate = true;
|
|
IncidenceBase::setDtStart( dtStart );
|
|
}
|
|
|
|
QString Todo::dtStartTimeStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatTime(
|
|
dtStart( first ).toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatTime(
|
|
dtStart( first ).time(), !shortfmt );
|
|
}
|
|
}
|
|
|
|
QString Todo::dtStartTimeStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
return IncidenceFormatter::timeToString( dtStart(), shortfmt, spec );
|
|
}
|
|
|
|
QString Todo::dtStartDateStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatDate(
|
|
dtStart( first ).toTimeSpec( spec ).date(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatDate(
|
|
dtStart( first ).date(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
}
|
|
|
|
QString Todo::dtStartDateStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec );
|
|
}
|
|
|
|
QString Todo::dtStartStr( bool shortfmt, bool first, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( allDay() ) {
|
|
return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec );
|
|
}
|
|
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtStart( first ).toTimeSpec( spec ).dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtStart( first ).dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
}
|
|
|
|
QString Todo::dtStartStr( bool shortfmt, const KDateTime::Spec &spec ) const
|
|
{
|
|
if ( allDay() ) {
|
|
return IncidenceFormatter::dateToString( dtStart(), shortfmt, spec );
|
|
}
|
|
|
|
if ( spec.isValid() ) {
|
|
|
|
QString timeZone;
|
|
if ( spec.timeZone() != KSystemTimeZones::local() ) {
|
|
timeZone = ' ' + spec.timeZone().name();
|
|
}
|
|
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtStart().toTimeSpec( spec ).dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
|
|
} else {
|
|
return KGlobal::locale()->formatDateTime(
|
|
dtStart().dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
}
|
|
|
|
bool Todo::isCompleted() const
|
|
{
|
|
if ( d->mPercentComplete == 100 ) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Todo::setCompleted( bool completed )
|
|
{
|
|
if ( completed ) {
|
|
d->mPercentComplete = 100;
|
|
} else {
|
|
d->mPercentComplete = 0;
|
|
d->mHasCompletedDate = false;
|
|
d->mCompleted = KDateTime();
|
|
}
|
|
updated();
|
|
}
|
|
|
|
KDateTime Todo::completed() const
|
|
{
|
|
if ( hasCompletedDate() ) {
|
|
return d->mCompleted;
|
|
} else {
|
|
return KDateTime();
|
|
}
|
|
}
|
|
|
|
QString Todo::completedStr( bool shortfmt ) const
|
|
{
|
|
return
|
|
KGlobal::locale()->formatDateTime( d->mCompleted.dateTime(),
|
|
( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
|
|
}
|
|
|
|
void Todo::setCompleted( const KDateTime &completed )
|
|
{
|
|
if ( !d->recurTodo( this ) ) {
|
|
d->mHasCompletedDate = true;
|
|
d->mPercentComplete = 100;
|
|
d->mCompleted = completed.toUtc();
|
|
}
|
|
updated();
|
|
}
|
|
|
|
bool Todo::hasCompletedDate() const
|
|
{
|
|
return d->mHasCompletedDate;
|
|
}
|
|
|
|
int Todo::percentComplete() const
|
|
{
|
|
return d->mPercentComplete;
|
|
}
|
|
|
|
void Todo::setPercentComplete( int percent )
|
|
{
|
|
//TODO: (?) assert percent between 0 and 100, inclusive
|
|
d->mPercentComplete = percent;
|
|
if ( percent != 100 ) {
|
|
d->mHasCompletedDate = false;
|
|
}
|
|
updated();
|
|
}
|
|
|
|
bool Todo::isInProgress( bool first ) const
|
|
{
|
|
if ( isOverdue() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( d->mPercentComplete > 0 ) {
|
|
return true;
|
|
}
|
|
|
|
if ( d->mHasStartDate && d->mHasDueDate ) {
|
|
if ( allDay() ) {
|
|
QDate currDate = QDate::currentDate();
|
|
if ( dtStart( first ).date() <= currDate && currDate < dtDue( first ).date() ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
KDateTime currDate = KDateTime::currentUtcDateTime();
|
|
if ( dtStart( first ) <= currDate && currDate < dtDue( first ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Todo::isOpenEnded() const
|
|
{
|
|
if ( !d->mHasDueDate && !isCompleted() ) {
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
bool Todo::isNotStarted( bool first ) const
|
|
{
|
|
if ( d->mPercentComplete > 0 ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !d->mHasStartDate ) {
|
|
return false;
|
|
}
|
|
|
|
if ( allDay() ) {
|
|
if ( dtStart( first ).date() >= QDate::currentDate() ) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if ( dtStart( first ) >= KDateTime::currentUtcDateTime() ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Todo::shiftTimes( const KDateTime::Spec &oldSpec,
|
|
const KDateTime::Spec &newSpec )
|
|
{
|
|
Incidence::shiftTimes( oldSpec, newSpec );
|
|
d->mDtDue = d->mDtDue.toTimeSpec( oldSpec );
|
|
d->mDtDue.setTimeSpec( newSpec );
|
|
if ( recurs() ) {
|
|
d->mDtRecurrence = d->mDtRecurrence.toTimeSpec( oldSpec );
|
|
d->mDtRecurrence.setTimeSpec( newSpec );
|
|
}
|
|
if ( d->mHasCompletedDate ) {
|
|
d->mCompleted = d->mCompleted.toTimeSpec( oldSpec );
|
|
d->mCompleted.setTimeSpec( newSpec );
|
|
}
|
|
}
|
|
|
|
void Todo::setDtRecurrence( const KDateTime &dt )
|
|
{
|
|
d->mDtRecurrence = dt;
|
|
}
|
|
|
|
KDateTime Todo::dtRecurrence() const
|
|
{
|
|
return d->mDtRecurrence.isValid() ? d->mDtRecurrence : d->mDtDue;
|
|
}
|
|
|
|
bool Todo::recursOn( const QDate &date, const KDateTime::Spec &timeSpec ) const
|
|
{
|
|
QDate today = QDate::currentDate();
|
|
return
|
|
Incidence::recursOn( date, timeSpec ) &&
|
|
!( date < today && d->mDtRecurrence.date() < today &&
|
|
d->mDtRecurrence > recurrence()->startDateTime() );
|
|
}
|
|
|
|
bool Todo::isOverdue() const
|
|
{
|
|
if ( !dtDue().isValid() ) {
|
|
return false; // if it's never due, it can't be overdue
|
|
}
|
|
|
|
bool inPast = allDay() ?
|
|
dtDue().date() < QDate::currentDate() :
|
|
dtDue() < KDateTime::currentUtcDateTime();
|
|
return inPast && !isCompleted();
|
|
}
|
|
|
|
KDateTime Todo::endDateRecurrenceBase() const
|
|
{
|
|
return dtDue();
|
|
}
|
|
|
|
//@cond PRIVATE
|
|
bool Todo::Private::recurTodo( Todo *todo )
|
|
{
|
|
if ( todo->recurs() ) {
|
|
Recurrence *r = todo->recurrence();
|
|
KDateTime endDateTime = r->endDateTime();
|
|
KDateTime nextDate = r->getNextDateTime( todo->dtDue() );
|
|
|
|
if ( ( r->duration() == -1 ||
|
|
( nextDate.isValid() && endDateTime.isValid() &&
|
|
nextDate <= endDateTime ) ) ) {
|
|
|
|
while ( !todo->recursAt( nextDate ) ||
|
|
nextDate <= KDateTime::currentUtcDateTime() ) {
|
|
|
|
if ( !nextDate.isValid() ||
|
|
( nextDate > endDateTime && r->duration() != -1 ) ) {
|
|
|
|
return false;
|
|
}
|
|
|
|
nextDate = r->getNextDateTime( nextDate );
|
|
}
|
|
|
|
todo->setDtDue( nextDate );
|
|
todo->setCompleted( false );
|
|
todo->setRevision( todo->revision() + 1 );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
//@endcond
|