File indexing completed on 2024-11-17 04:49:33

0001 /*
0002  * Copyright (C) 1997 by Stephan Kulow <coolo@kde.org>
0003  * Copyright (C) 2019  Alexander Potashev <aspotashev@gmail.com>
0004  *
0005  *   This program is free software; you can redistribute it and/or modify
0006  *   it under the terms of the GNU General Public License as published by
0007  *   the Free Software Foundation; either version 2 of the License, or
0008  *   (at your option) any later version.
0009  *
0010  *   This program is distributed in the hope that it will be useful,
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  *   GNU General Public License for more details.
0014  *
0015  *   You should have received a copy of the GNU General Public License along
0016  *   with this program; if not, write to the
0017  *      Free Software Foundation, Inc.
0018  *      51 Franklin Street, Fifth Floor
0019  *      Boston, MA  02110-1301  USA.
0020  *
0021  */
0022 
0023 #include "task.h"
0024 
0025 #include <KCalendarCore/CalFormat>
0026 
0027 #include "ktimetracker.h"
0028 #include "ktimetrackerutility.h"
0029 #include "ktt_debug.h"
0030 #include "model/eventsmodel.h"
0031 #include "model/projectmodel.h"
0032 #include "model/tasksmodel.h"
0033 #include "timetrackerstorage.h"
0034 
0035 static const QByteArray eventAppName = QByteArray("ktimetracker");
0036 
0037 Task::Task(const QString &taskName,
0038            const QString &taskDescription,
0039            int64_t minutes,
0040            int64_t sessionTime,
0041            const DesktopList &desktops,
0042            ProjectModel *projectModel,
0043            Task *parentTask)
0044     : TasksModelItem(projectModel->tasksModel(), parentTask)
0045     , m_projectModel(projectModel)
0046 {
0047     if (parentTask) {
0048         parentTask->addChild(this);
0049     } else {
0050         m_projectModel->tasksModel()->addChild(this);
0051     }
0052 
0053     init(taskName, taskDescription, minutes, sessionTime, QStringLiteral(), desktops, 0, 0);
0054 
0055     m_uid = KCalendarCore::CalFormat::createUniqueId();
0056 }
0057 
0058 Task::Task(const KCalendarCore::Todo::Ptr &todo, ProjectModel *projectModel)
0059     : TasksModelItem(projectModel->tasksModel(), nullptr)
0060     , m_projectModel(projectModel)
0061 {
0062     projectModel->tasksModel()->addChild(this);
0063 
0064     int64_t minutes = 0;
0065     QString name;
0066     QString description;
0067     int64_t sessionTime = 0;
0068     QString sessionStartTiMe;
0069     int percent_complete = 0;
0070     int priority = 0;
0071     DesktopList desktops;
0072 
0073     parseIncidence(todo,
0074                    minutes,
0075                    sessionTime,
0076                    sessionStartTiMe,
0077                    name,
0078                    description,
0079                    desktops,
0080                    percent_complete,
0081                    priority);
0082     init(name, description, minutes, sessionTime, sessionStartTiMe, desktops, percent_complete, priority);
0083 }
0084 
0085 Task::~Task()
0086 {
0087     disconnectFromParent();
0088 }
0089 
0090 int Task::depth()
0091 {
0092     int res = 0;
0093     for (Task *t = parentTask(); t; t = t->parentTask()) {
0094         res++;
0095     }
0096 
0097     qCDebug(KTT_LOG) << "Leaving function. depth is:" << res;
0098     return res;
0099 }
0100 
0101 void Task::init(const QString &taskName,
0102                 const QString &taskDescription,
0103                 int64_t minutes,
0104                 int64_t sessionTime,
0105                 const QString &sessionStartTiMe,
0106                 const DesktopList &desktops,
0107                 int percent_complete,
0108                 int priority)
0109 {
0110     m_isRunning = false;
0111     m_name = taskName.trimmed();
0112     m_description = taskDescription.trimmed();
0113     m_lastStart = QDateTime::currentDateTime();
0114     m_totalTime = m_time = minutes;
0115     m_totalSessionTime = m_sessionTime = sessionTime;
0116     m_desktops = desktops;
0117 
0118     m_percentComplete = percent_complete;
0119     m_priority = priority;
0120     m_sessionStartTime = QDateTime::fromString(sessionStartTiMe);
0121 
0122     update();
0123     changeParentTotalTimes(m_sessionTime, m_time);
0124 }
0125 
0126 void Task::delete_recursive()
0127 {
0128     while (this->child(0)) {
0129         Task *t = (Task *)this->child(0);
0130         t->delete_recursive();
0131     }
0132     delete this;
0133 }
0134 
0135 // This is the back-end, the front-end is StartTimerFor()
0136 void Task::setRunning(bool on, const QDateTime &when)
0137 {
0138     if (on != m_isRunning) {
0139         m_isRunning = on;
0140         invalidateRunningState();
0141 
0142         if (on) {
0143             m_lastStart = when;
0144             qCDebug(KTT_LOG) << "task has been started for " << when;
0145 
0146 //            m_projectModel->eventsModel()->startTask(this, when);
0147             m_projectModel->eventsModel()->startTask(this);
0148         } else {
0149             m_projectModel->eventsModel()->stopTask(this, when);
0150         }
0151     }
0152 }
0153 
0154 // setRunning is the back-end, the front-end is StartTimerFor().
0155 // resumeRunning does the same as setRunning, but not add a new
0156 // start date to the storage.
0157 void Task::resumeRunning()
0158 {
0159     qCDebug(KTT_LOG) << "Entering function";
0160     if (!isRunning()) {
0161         m_isRunning = true;
0162         invalidateRunningState();
0163     }
0164 }
0165 
0166 bool Task::isRunning() const
0167 {
0168     return m_isRunning;
0169 }
0170 
0171 void Task::setName(const QString &name)
0172 {
0173     qCDebug(KTT_LOG) << "Entering function, name=" << name;
0174 
0175     QString oldname = m_name;
0176     if (oldname != name) {
0177         m_name = name;
0178         update();
0179     }
0180 }
0181 
0182 void Task::setDescription(const QString &description)
0183 {
0184     qCDebug(KTT_LOG) << "Entering function, description=" << description;
0185 
0186     QString olddescription = m_description;
0187     if (olddescription != description) {
0188         m_description = description;
0189         update();
0190     }
0191 }
0192 
0193 void Task::setPercentComplete(int percent)
0194 {
0195     qCDebug(KTT_LOG) << "Entering function(" << percent << "):" << m_uid;
0196 
0197     if (!percent) {
0198         m_percentComplete = 0;
0199     } else if (percent > 100) {
0200         m_percentComplete = 100;
0201     } else if (percent < 0) {
0202         m_percentComplete = 0;
0203     } else {
0204         m_percentComplete = percent;
0205     }
0206 
0207     if (isRunning() && m_percentComplete == 100) {
0208         Q_EMIT m_projectModel->tasksModel()->taskCompleted(this);
0209     }
0210 
0211     invalidateCompletedState();
0212 
0213     // When parent marked as complete, mark all children as complete as well.
0214     // This behavior is consistent with KOrganizer (as of 2003-09-24).
0215     if (m_percentComplete == 100) {
0216         for (int i = 0; i < childCount(); ++i) {
0217             Task *task = dynamic_cast<Task *>(child(i));
0218             task->setPercentComplete(m_percentComplete);
0219         }
0220     }
0221     // maybe there is a column "percent completed", so do a ...
0222     update();
0223 }
0224 
0225 void Task::setPriority(int priority)
0226 {
0227     if (priority < 0) {
0228         priority = 0;
0229     } else if (priority > 9) {
0230         priority = 9;
0231     }
0232 
0233     m_priority = priority;
0234     update();
0235 }
0236 
0237 bool Task::isComplete()
0238 {
0239     return m_percentComplete == 100;
0240 }
0241 
0242 void Task::setDesktopList(const DesktopList &desktopList)
0243 {
0244     m_desktops = desktopList;
0245 }
0246 
0247 void Task::addTime(int64_t minutes)
0248 {
0249     m_time += minutes;
0250     this->addTotalTime(minutes);
0251 }
0252 
0253 void Task::addTotalTime(int64_t minutes)
0254 {
0255     m_totalTime += minutes;
0256     if (parentTask()) {
0257         parentTask()->addTotalTime(minutes);
0258     }
0259 }
0260 
0261 void Task::addSessionTime(int64_t minutes)
0262 {
0263     m_sessionTime += minutes;
0264     this->addTotalSessionTime(minutes);
0265 }
0266 
0267 void Task::addTotalSessionTime(int64_t minutes)
0268 {
0269     m_totalSessionTime += minutes;
0270     if (parentTask()) {
0271         parentTask()->addTotalSessionTime(minutes);
0272     }
0273 }
0274 
0275 QString Task::setTime(int64_t minutes)
0276 {
0277     m_time = minutes;
0278     m_totalTime += minutes;
0279     return QString();
0280 }
0281 
0282 void Task::recalculateTotalTimesSubtree()
0283 {
0284     int64_t totalMinutes = time();
0285     int64_t totalSessionMinutes = sessionTime();
0286     for (int i = 0; i < this->childCount(); ++i) {
0287         Task *subTask = dynamic_cast<Task *>(child(i));
0288         subTask->recalculateTotalTimesSubtree();
0289 
0290         totalMinutes += subTask->totalTime();
0291         totalSessionMinutes += subTask->totalSessionTime();
0292     }
0293 
0294     setTotalTime(totalMinutes);
0295     setTotalSessionTime(totalSessionMinutes);
0296 }
0297 
0298 QString Task::setSessionTime(int64_t minutes)
0299 {
0300     m_sessionTime = minutes;
0301     m_totalSessionTime += minutes;
0302     return QString();
0303 }
0304 
0305 void Task::changeTimes(int64_t minutesSession, int64_t minutes, EventsModel *eventsModel)
0306 {
0307     qDebug() << "Task's sessionStartTiMe is " << m_sessionStartTime;
0308     if (minutesSession != 0 || minutes != 0) {
0309         m_sessionTime += minutesSession;
0310         m_time += minutes;
0311         if (eventsModel) {
0312             eventsModel->changeTime(this, minutes * secsPerMinute);
0313         }
0314         changeTotalTimes(minutesSession, minutes);
0315     }
0316 }
0317 
0318 void Task::changeTime(int64_t minutes, EventsModel *eventsModel)
0319 {
0320     changeTimes(minutes, minutes, eventsModel);
0321 }
0322 
0323 void Task::changeTotalTimes(int64_t minutesSession, int64_t minutes)
0324 {
0325     qCDebug(KTT_LOG) << "Task::changeTotalTimes(" << minutesSession << "," << minutes << ") for" << name();
0326     m_totalSessionTime += minutesSession;
0327     m_totalTime += minutes;
0328     update();
0329     changeParentTotalTimes(minutesSession, minutes);
0330 }
0331 
0332 void Task::resetTimes()
0333 {
0334     m_totalSessionTime -= m_sessionTime;
0335     m_totalTime -= m_time;
0336     changeParentTotalTimes(-m_sessionTime, -m_time);
0337     m_sessionTime = 0;
0338     m_time = 0;
0339     update();
0340 }
0341 
0342 void Task::changeParentTotalTimes(int64_t minutesSession, int64_t minutes)
0343 {
0344     if (parentTask()) {
0345         parentTask()->changeTotalTimes(minutesSession, minutes);
0346     }
0347 }
0348 
0349 bool Task::remove()
0350 {
0351     qCDebug(KTT_LOG) << "entering function" << m_name;
0352     bool ok = true;
0353 
0354     for (int i = 0; i < childCount(); ++i) {
0355         Task *task = dynamic_cast<Task *>(child(i));
0356         if (!task) {
0357             qFatal("Task::remove: task is nullptr");
0358         }
0359 
0360         task->remove();
0361     }
0362 
0363     setRunning(false);
0364 
0365     m_projectModel->eventsModel()->removeAllForTask(this);
0366 
0367     changeParentTotalTimes(-m_sessionTime, -m_time);
0368 
0369     return ok;
0370 }
0371 
0372 QString Task::fullName() const
0373 {
0374     if (isRoot()) {
0375         return name();
0376     } else {
0377         return parentTask()->fullName() + QString::fromLatin1("/") + name();
0378     }
0379 }
0380 
0381 KCalendarCore::Todo::Ptr Task::asTodo(const KCalendarCore::Todo::Ptr &todo) const
0382 {
0383     Q_ASSERT(todo != nullptr);
0384 
0385     qCDebug(KTT_LOG) << "Task::asTodo: name() = '" << name() << "'";
0386     todo->setUid(uid());
0387     todo->setSummary(name());
0388     todo->setDescription(description());
0389 
0390     // Note: if the date start is empty, the KOrganizer GUI will have the
0391     // checkbox blank, but will prefill the todo's starting datetime to the
0392     // time the file is opened.
0393     // todo->setDtStart( current );
0394 
0395     todo->setCustomProperty(eventAppName, QByteArray("totalTaskTime"), QString::number(m_time));
0396     todo->setCustomProperty(eventAppName, QByteArray("totalSessionTime"), QString::number(m_sessionTime));
0397     todo->setCustomProperty(eventAppName, QByteArray("sessionStartTiMe"), m_sessionStartTime.toString());
0398     qDebug() << "m_sessionStartTime=" << m_sessionStartTime.toString();
0399 
0400     if (getDesktopStr().isEmpty()) {
0401         todo->removeCustomProperty(eventAppName, QByteArray("desktopList"));
0402     } else {
0403         todo->setCustomProperty(eventAppName, QByteArray("desktopList"), getDesktopStr());
0404     }
0405 
0406     todo->setPercentComplete(m_percentComplete);
0407     todo->setPriority(m_priority);
0408 
0409     if (parentTask()) {
0410         todo->setRelatedTo(parentTask()->uid());
0411     }
0412 
0413     return todo;
0414 }
0415 
0416 bool Task::parseIncidence(const KCalendarCore::Incidence::Ptr &incident,
0417                           int64_t &minutes,
0418                           int64_t &sessionMinutes,
0419                           QString &sessionStartTiMe,
0420                           QString &name,
0421                           QString &description,
0422                           DesktopList &desktops,
0423                           int &percent_complete,
0424                           int &priority)
0425 {
0426     qCDebug(KTT_LOG) << "Entering function";
0427     bool ok;
0428     name = incident->summary();
0429     description = incident->description();
0430     m_uid = incident->uid();
0431 
0432     ok = false;
0433     minutes = getCustomProperty(incident, QStringLiteral("totalTaskTime")).toInt(&ok);
0434     if (!ok) {
0435         minutes = 0;
0436     }
0437 
0438     ok = false;
0439     sessionMinutes = getCustomProperty(incident, QStringLiteral("totalSessionTime")).toInt(&ok);
0440     if (!ok) {
0441         sessionMinutes = 0;
0442     }
0443 
0444     sessionStartTiMe = getCustomProperty(incident, QStringLiteral("sessionStartTiMe"));
0445 
0446     QString desktopList = getCustomProperty(incident, QStringLiteral("desktopList"));
0447     QStringList desktopStrList = desktopList.split(QStringLiteral(","), Qt::SkipEmptyParts);
0448     desktops.clear();
0449 
0450     for (const QString &desktopStr : desktopStrList) {
0451         int desktopInt = desktopStr.toInt(&ok);
0452         if (ok) {
0453             desktops.push_back(desktopInt);
0454         }
0455     }
0456     percent_complete = incident.staticCast<KCalendarCore::Todo>()->percentComplete();
0457     priority = incident->priority();
0458     return true;
0459 }
0460 
0461 QString Task::getDesktopStr() const
0462 {
0463     if (m_desktops.empty()) {
0464         return QString();
0465     }
0466 
0467     QString desktopsStr;
0468     for (const int desktop : m_desktops) {
0469         desktopsStr += QString::number(desktop) + QString::fromLatin1(",");
0470     }
0471     desktopsStr.remove(desktopsStr.length() - 1, 1);
0472     return desktopsStr;
0473 }
0474 
0475 // This is needed e.g. to move a task under its parent when loading.
0476 void Task::cut()
0477 {
0478     changeParentTotalTimes(-m_totalSessionTime, -m_totalTime);
0479     if (!parentTask()) {
0480         m_projectModel->tasksModel()->takeTopLevelItem(m_projectModel->tasksModel()->indexOfTopLevelItem(this));
0481     } else {
0482         parentTask()->takeChild(parentTask()->indexOfChild(this));
0483     }
0484 }
0485 
0486 // This is needed e.g. to move a task under its parent when loading.
0487 void Task::paste(TasksModelItem *destination)
0488 {
0489     destination->insertChild(0, this);
0490     changeParentTotalTimes(m_totalSessionTime, m_totalTime);
0491 }
0492 
0493 // This is used e.g. to move each task under its parent after loading.
0494 void Task::move(TasksModelItem *destination)
0495 {
0496     cut();
0497     paste(destination);
0498 }
0499 
0500 QVariant Task::data(int column, int role) const
0501 {
0502     switch (role) {
0503     case Qt::DisplayRole: {
0504         bool b = KTimeTrackerSettings::decimalFormat();
0505         switch (column) {
0506         case 0:
0507             return m_name;
0508         case 1:
0509             return formatTime(m_sessionTime, b);
0510         case 2:
0511             return formatTime(m_time, b);
0512         case 3:
0513             return formatTime(m_totalSessionTime, b);
0514         case 4:
0515             return formatTime(m_totalTime, b);
0516         case 5:
0517             return m_priority > 0 ? QString::number(m_priority) : QStringLiteral("--");
0518         case 6:
0519             return QString::number(m_percentComplete);
0520         default:
0521             return {};
0522         }
0523     }
0524     case SortRole: {
0525         // QSortFilterProxyModel::lessThan() supports comparison of a few data
0526         // types, here we use some of those: QString, qlonglong, int.
0527         switch (column) {
0528         case 0:
0529             return m_name;
0530         case 1:
0531             return QVariant::fromValue<qlonglong>(m_sessionTime);
0532         case 2:
0533             return QVariant::fromValue<qlonglong>(m_time);
0534         case 3:
0535             return QVariant::fromValue<qlonglong>(m_totalSessionTime);
0536         case 4:
0537             return QVariant::fromValue<qlonglong>(m_totalTime);
0538         case 5:
0539             return QVariant::fromValue<int>(m_priority);
0540         case 6:
0541             return QVariant::fromValue<int>(m_percentComplete);
0542         default:
0543             return {};
0544         }
0545     }
0546     default:
0547         return {};
0548     }
0549 }
0550 
0551 // Update a row, containing one task
0552 void Task::update()
0553 {
0554     QModelIndex first = m_projectModel->tasksModel()->index(this, 0);
0555     QModelIndex last = m_projectModel->tasksModel()->index(this, 6);
0556     Q_EMIT m_projectModel->tasksModel()->dataChanged(first, last, QVector<int>{Qt::DisplayRole});
0557 }
0558 
0559 void Task::startNewSession()
0560 {
0561     changeTimes(-m_sessionTime, 0, nullptr);
0562     m_sessionStartTime = QDateTime::currentDateTime();
0563 }
0564 
0565 //BEGIN Properties
0566 QString Task::uid() const
0567 {
0568     return m_uid;
0569 }
0570 
0571 int Task::percentComplete() const
0572 {
0573     return m_percentComplete;
0574 }
0575 
0576 int Task::priority() const
0577 {
0578     return m_priority;
0579 }
0580 
0581 QString Task::name() const
0582 {
0583     return m_name;
0584 }
0585 
0586 QString Task::description() const
0587 {
0588     return m_description;
0589 }
0590 
0591 QDateTime Task::startTime() const
0592 {
0593     return m_lastStart;
0594 }
0595 
0596 QDateTime Task::sessionStartTiMe() const
0597 {
0598     return m_sessionStartTime;
0599 }
0600 
0601 DesktopList Task::desktops() const
0602 {
0603     return m_desktops;
0604 }
0605 //END