diff --git a/src/model/task.cpp b/src/model/task.cpp index 1de98bc..11a4298 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -1,606 +1,589 @@ /* * Copyright (C) 1997 by Stephan Kulow * Copyright (C) 2019 Alexander Potashev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA. * */ #include "task.h" #include #include "ktimetracker.h" #include "ktimetrackerutility.h" #include "ktt_debug.h" #include "model/eventsmodel.h" #include "model/projectmodel.h" #include "model/tasksmodel.h" #include "timetrackerstorage.h" static const QByteArray eventAppName = QByteArray("ktimetracker"); Task::Task(const QString& taskName, const QString& taskDescription, int64_t minutes, int64_t sessionTime, const DesktopList& desktops, ProjectModel *projectModel, Task *parentTask) : TasksModelItem(projectModel->tasksModel(), parentTask) , m_projectModel(projectModel) { if (parentTask) { parentTask->addChild(this); } else { m_projectModel->tasksModel()->addChild(this); } init(taskName, taskDescription, minutes, sessionTime, nullptr, desktops, 0, 0); m_uid = KCalendarCore::CalFormat::createUniqueId(); } Task::Task(const KCalendarCore::Todo::Ptr &todo, ProjectModel *projectModel) : TasksModelItem(projectModel->tasksModel(), nullptr) , m_projectModel(projectModel) { projectModel->tasksModel()->addChild(this); int64_t minutes = 0; QString name; QString description; int64_t sessionTime = 0; QString sessionStartTiMe; int percent_complete = 0; int priority = 0; DesktopList desktops; parseIncidence( todo, minutes, sessionTime, sessionStartTiMe, name, description, desktops, percent_complete, priority ); init( name, description, minutes, sessionTime, sessionStartTiMe, desktops, percent_complete, priority); } Task::~Task() { disconnectFromParent(); } int Task::depth() { int res = 0; for (Task* t = parentTask(); t; t = t->parentTask()) { res++; } qCDebug(KTT_LOG) << "Leaving function. depth is:" << res; return res; } void Task::init( const QString& taskName, const QString& taskDescription, int64_t minutes, int64_t sessionTime, const QString& sessionStartTiMe, const DesktopList& desktops, int percent_complete, int priority) { m_isRunning = false; m_name = taskName.trimmed(); m_description = taskDescription.trimmed(); m_lastStart = QDateTime::currentDateTime(); m_totalTime = m_time = minutes; m_totalSessionTime = m_sessionTime = sessionTime; m_desktops = desktops; m_percentComplete = percent_complete; m_priority = priority; m_sessionStartTime = QDateTime::fromString(sessionStartTiMe); update(); changeParentTotalTimes(m_sessionTime, m_time); } void Task::delete_recursive() { while (this->child(0)) { Task* t = (Task*) this->child(0); t->delete_recursive(); } delete this; } // This is the back-end, the front-end is StartTimerFor() void Task::setRunning(bool on, const QDateTime& when) { if (on != m_isRunning) { m_isRunning = on; invalidateRunningState(); if (on) { m_lastStart = when; qCDebug(KTT_LOG) << "task has been started for " << when; // m_projectModel->eventsModel()->startTask(this, when); m_projectModel->eventsModel()->startTask(this); } else { m_projectModel->eventsModel()->stopTask(this, when); } } } // setRunning is the back-end, the front-end is StartTimerFor(). // resumeRunning does the same as setRunning, but not add a new // start date to the storage. void Task::resumeRunning() { qCDebug(KTT_LOG) << "Entering function"; if (!isRunning()) { m_isRunning = true; invalidateRunningState(); } } bool Task::isRunning() const { return m_isRunning; } void Task::setName(const QString& name) { qCDebug(KTT_LOG) << "Entering function, name=" << name; QString oldname = m_name; if (oldname != name) { m_name = name; update(); } } void Task::setDescription(const QString& description) { qCDebug(KTT_LOG) << "Entering function, description=" << description; QString olddescription = m_description; if (olddescription != description) { m_description = description; update(); } } void Task::setPercentComplete(int percent) { qCDebug(KTT_LOG) << "Entering function(" << percent << "):" << m_uid; if (!percent) { m_percentComplete = 0; } else if (percent > 100) { m_percentComplete = 100; } else if (percent < 0) { m_percentComplete = 0; } else { m_percentComplete = percent; } if (isRunning() && m_percentComplete == 100) { emit m_projectModel->tasksModel()->taskCompleted(this); } invalidateCompletedState(); // When parent marked as complete, mark all children as complete as well. // This behavior is consistent with KOrganizer (as of 2003-09-24). if (m_percentComplete == 100) { for (int i = 0; i < childCount(); ++i) { Task *task = dynamic_cast(child(i)); task->setPercentComplete(m_percentComplete); } } // maybe there is a column "percent completed", so do a ... update(); } void Task::setPriority(int priority) { if (priority < 0) { priority = 0; } else if (priority > 9) { priority = 9; } m_priority = priority; update(); } bool Task::isComplete() { return m_percentComplete == 100; } void Task::setDesktopList(const DesktopList& desktopList) { m_desktops = desktopList; } void Task::addTime(int64_t minutes) { m_time += minutes; this->addTotalTime(minutes); } void Task::addTotalTime(int64_t minutes) { m_totalTime += minutes; if (parentTask()) { parentTask()->addTotalTime(minutes); } } void Task::addSessionTime(int64_t minutes) { m_sessionTime += minutes; this->addTotalSessionTime(minutes); } void Task::addTotalSessionTime(int64_t minutes) { m_totalSessionTime += minutes; if (parentTask()) { parentTask()->addTotalSessionTime(minutes); } } QString Task::setTime(int64_t minutes) { m_time = minutes; m_totalTime += minutes; return QString(); } void Task::recalculateTotalTimesSubtree() { int64_t totalMinutes = time(); int64_t totalSessionMinutes = sessionTime(); for (int i = 0; i < this->childCount(); ++i) { Task *subTask = dynamic_cast(child(i)); subTask->recalculateTotalTimesSubtree(); totalMinutes += subTask->totalTime(); totalSessionMinutes += subTask->totalSessionTime(); } setTotalTime(totalMinutes); setTotalSessionTime(totalSessionMinutes); } QString Task::setSessionTime(int64_t minutes) { m_sessionTime = minutes; m_totalSessionTime += minutes; return QString(); } void Task::changeTimes(int64_t minutesSession, int64_t minutes, EventsModel *eventsModel) { qDebug() << "Task's sessionStartTiMe is " << m_sessionStartTime; if (minutesSession != 0 || minutes != 0) { m_sessionTime += minutesSession; m_time += minutes; if (eventsModel) { eventsModel->changeTime(this, minutes * secsPerMinute); } changeTotalTimes(minutesSession, minutes); } } void Task::changeTime(int64_t minutes, EventsModel *eventsModel) { changeTimes(minutes, minutes, eventsModel); } void Task::changeTotalTimes(int64_t minutesSession, int64_t minutes) { qCDebug(KTT_LOG) << "Task::changeTotalTimes(" << minutesSession << "," << minutes << ") for" << name(); m_totalSessionTime += minutesSession; m_totalTime += minutes; update(); changeParentTotalTimes(minutesSession, minutes); } void Task::resetTimes() { m_totalSessionTime -= m_sessionTime; m_totalTime -= m_time; changeParentTotalTimes(-m_sessionTime, -m_time); m_sessionTime = 0; m_time = 0; update(); } void Task::changeParentTotalTimes(int64_t minutesSession, int64_t minutes) { if (parentTask()) { parentTask()->changeTotalTimes(minutesSession, minutes); } } bool Task::remove(TimeTrackerStorage* storage) { qCDebug(KTT_LOG) << "entering function" << m_name; bool ok = true; for (int i = 0; i < childCount(); ++i) { Task* task = dynamic_cast(child(i)); if (!task) { qFatal("Task::remove: task is nullptr"); } task->remove(storage); } setRunning(false); m_projectModel->eventsModel()->removeAllForTask(this); changeParentTotalTimes(-m_sessionTime, -m_time); // TODO check return value storage->save(); return ok; } QString Task::fullName() const { if (isRoot()) { return name(); } else { return parentTask()->fullName() + QString::fromLatin1("/") + name(); } } KCalendarCore::Todo::Ptr Task::asTodo(const KCalendarCore::Todo::Ptr& todo) const { Q_ASSERT(todo != nullptr); qCDebug(KTT_LOG) <<"Task::asTodo: name() = '" << name() << "'"; todo->setUid(uid()); todo->setSummary(name()); todo->setDescription(description()); // Note: if the date start is empty, the KOrganizer GUI will have the // checkbox blank, but will prefill the todo's starting datetime to the // time the file is opened. // todo->setDtStart( current ); todo->setCustomProperty(eventAppName, QByteArray("totalTaskTime"), QString::number(m_time)); todo->setCustomProperty(eventAppName, QByteArray("totalSessionTime"), QString::number(m_sessionTime)); todo->setCustomProperty(eventAppName, QByteArray("sessionStartTiMe"), m_sessionStartTime.toString()); qDebug() << "m_sessionStartTime=" << m_sessionStartTime.toString(); if (getDesktopStr().isEmpty()) { todo->removeCustomProperty(eventAppName, QByteArray("desktopList")); } else { todo->setCustomProperty(eventAppName, QByteArray("desktopList"), getDesktopStr()); } todo->setPercentComplete(m_percentComplete); todo->setPriority( m_priority ); if (parentTask()) { todo->setRelatedTo(parentTask()->uid()); } return todo; } bool Task::parseIncidence( const KCalendarCore::Incidence::Ptr &incident, int64_t& minutes, int64_t& sessionMinutes, QString& sessionStartTiMe, QString& name, QString& description, DesktopList& desktops, int& percent_complete, int& priority) { qCDebug(KTT_LOG) << "Entering function"; bool ok; name = incident->summary(); description = incident->description(); m_uid = incident->uid(); - m_comment = incident->description(); ok = false; minutes = getCustomProperty(incident, QStringLiteral("totalTaskTime")).toInt(&ok); if (!ok) { minutes = 0; } ok = false; sessionMinutes = getCustomProperty(incident, QStringLiteral("totalSessionTime")).toInt(&ok); if (!ok) { sessionMinutes = 0; } sessionStartTiMe = getCustomProperty(incident, QStringLiteral("sessionStartTiMe")); QString desktopList = getCustomProperty(incident, QStringLiteral("desktopList")); QStringList desktopStrList = desktopList.split(QStringLiteral(","), QString::SkipEmptyParts); desktops.clear(); for (const QString& desktopStr : desktopStrList) { int desktopInt = desktopStr.toInt(&ok); if (ok) { desktops.push_back(desktopInt); } } percent_complete = incident.staticCast()->percentComplete(); priority = incident->priority(); return true; } QString Task::getDesktopStr() const { if (m_desktops.empty()) { return QString(); } QString desktopsStr; for (const int desktop : m_desktops) { desktopsStr += QString::number(desktop) + QString::fromLatin1(","); } desktopsStr.remove(desktopsStr.length() - 1, 1); return desktopsStr; } // This is needed e.g. to move a task under its parent when loading. void Task::cut() { changeParentTotalTimes(-m_totalSessionTime, -m_totalTime); if (!parentTask()) { m_projectModel->tasksModel()->takeTopLevelItem(m_projectModel->tasksModel()->indexOfTopLevelItem(this)); } else { parentTask()->takeChild(parentTask()->indexOfChild(this)); } } // This is needed e.g. to move a task under its parent when loading. void Task::paste(TasksModelItem* destination) { destination->insertChild(0, this); changeParentTotalTimes(m_totalSessionTime, m_totalTime); } // This is used e.g. to move each task under its parent after loading. void Task::move(TasksModelItem* destination) { cut(); paste(destination); } QVariant Task::data(int column, int role) const { switch (role) { case Qt::DisplayRole: { bool b = KTimeTrackerSettings::decimalFormat(); switch (column) { case 0: return m_name; case 1: return formatTime(m_sessionTime, b); case 2: return formatTime(m_time, b); case 3: return formatTime(m_totalSessionTime, b); case 4: return formatTime(m_totalTime, b); case 5: return m_priority > 0 ? QString::number(m_priority) : QStringLiteral("--"); case 6: return QString::number(m_percentComplete); default: return {}; } } case SortRole: { // QSortFilterProxyModel::lessThan() supports comparison of a few data types, // here we use some of those: QString, qlonglong, int. switch (column) { case 0: return m_name; case 1: return QVariant::fromValue(m_sessionTime); case 2: return QVariant::fromValue(m_time); case 3: return QVariant::fromValue(m_totalSessionTime); case 4: return QVariant::fromValue(m_totalTime); case 5: return QVariant::fromValue(m_priority); case 6: return QVariant::fromValue(m_percentComplete); default: return {}; } } default: return {}; } } // Update a row, containing one task void Task::update() { QModelIndex first = m_projectModel->tasksModel()->index(this, 0); QModelIndex last = m_projectModel->tasksModel()->index(this, 6); emit m_projectModel->tasksModel()->dataChanged(first, last, QVector{Qt::DisplayRole}); } -void Task::addComment(const QString& comment, TimeTrackerStorage* storage) -{ - m_comment = m_comment + QString::fromLatin1("\n") + comment; - - // TODO: Use libkcalcore comments - // todo->addComment(comment); - - // TODO check return value - storage->save(); -} - void Task::startNewSession() { changeTimes(-m_sessionTime, 0, nullptr); m_sessionStartTime = QDateTime::currentDateTime(); } //BEGIN Properties QString Task::uid() const { return m_uid; } -QString Task::comment() const -{ - return m_comment; -} - int Task::percentComplete() const { return m_percentComplete; } int Task::priority() const { return m_priority; } QString Task::name() const { return m_name; } QString Task::description() const { return m_description; } QDateTime Task::startTime() const { return m_lastStart; } QDateTime Task::sessionStartTiMe() const { return m_sessionStartTime; } DesktopList Task::desktops() const { return m_desktops; } //END diff --git a/src/model/task.h b/src/model/task.h index baf69d2..6502a98 100644 --- a/src/model/task.h +++ b/src/model/task.h @@ -1,368 +1,350 @@ /* * Copyright (C) 1997 by Stephan Kulow * Copyright (C) 2019 Alexander Potashev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA. * */ #ifndef KTIMETRACKER_TASK_H #define KTIMETRACKER_TASK_H #include #include #include "desktoplist.h" // Required b/c DesktopList is a typedef not a class. #include "model/tasksmodelitem.h" class TimeTrackerStorage; class ProjectModel; class EventsModel; /** \brief A class representing a task * * A "Task" object stores information about a task such as it's name, * total and session times. * * It can log when the task is started, stopped or deleted. * * If a task is associated with some desktop's activity it can remember that * too. * * It can also contain subtasks - these are managed using the * QListViewItem class. */ class Task : public TasksModelItem { public: enum { SortRole = Qt::UserRole, }; Task( const QString& taskname, const QString& taskdescription, int64_t minutes, int64_t sessionTime, const DesktopList& desktops, ProjectModel *projectModel, Task* parentTask); Task(const KCalendarCore::Todo::Ptr &todo, ProjectModel *projectModel); /* destructor */ ~Task() override; Task* parentTask() const { return dynamic_cast(TasksModelItem::parent()); } /** Return unique iCalendar Todo ID for this task. */ QString uid() const; /** * Deliver the depth of a task, i.e. how many tasks are supertasks to it. * A toplevel task has the depth 0. */ int depth(); void delete_recursive(); /** cut Task out of parent Task or the TaskView */ void cut(); /** cut Task out of parent Task or the TaskView and into the * destination Task */ void move(TasksModelItem* destination); /** insert Task into the destination Task */ void paste(TasksModelItem* destination); //@{ timing related functions /** * Change task time. Adds minutes to both total time and session time by adding an event. * * @param minutes minutes to add to - may be negative * @param storage Pointer to TimeTrackerStorage instance. * If zero, don't save changes. */ void changeTime(int64_t minutes, EventsModel *eventsModel); /** * Add minutes to time and session time by adding an event, and write to storage. * * @param minutesSession minutes to add to task session time * @param minutes minutes to add to task time * @param storage Pointer to TimeTrackerStorage instance. * If zero, don't save changes. */ void changeTimes(int64_t minutesSession, int64_t minutes, EventsModel *eventsModel); /** adds minutes to total and session time by adding an event * * @param minutesSession minutes to add to task total session time * @param minutes minutes to add to task total time */ void changeTotalTimes(int64_t minutesSession, int64_t minutes); /** Adds minutes to the time of the task and the total time of its supertasks. This does not add an event. * * @param minutes minutes to add to the time */ void addTime(int64_t minutes); /** Adds minutes to the total time of the task and its supertasks. This does not add an event. * * @param minutes minutes to add to the time */ void addTotalTime(int64_t minutes); /** Adds minutes to the task's session time and its supertasks' total session time. This does not add an event. * * @param minutes minutes to add to the session time */ void addSessionTime(int64_t minutes); /** Adds minutes to the task's and its supertasks' total session time. This does not add an event. * * @param minutes minutes to add to the session time */ void addTotalSessionTime(int64_t minutes); /** Sets the time (not session time). This does not add an event. * * @param minutes minutes to set time to * */ QString setTime(int64_t minutes); /** Sets the total time, does not change the parent's total time. This means the parent's total time can run out of sync. */ void setTotalTime(int64_t minutes) { m_totalTime = minutes; } /** Sets the total session time, does not change the parent's total session time. This means the parent's total session time can run out of sync. */ void setTotalSessionTime(int64_t minutes) { m_totalSessionTime = minutes; } /** A recursive function to calculate the total time/session time of a task. */ void recalculateTotalTimesSubtree(); /** Sets the session time. * Set the session time without changing totalTime nor sessionTime. * Do not change the parent's totalTime. * Do not add an event. * See also: changeTimes(long, long) and resetTimes * * @param minutes minutes to set session time to * */ QString setSessionTime(int64_t minutes); /** * Reset all times to 0 and adjust parent task's totalTiMes. */ void resetTimes(); /** @return time in minutes */ int64_t time() const { return m_time; } /** @return total time in minutes */ int64_t totalTime() const { return m_totalTime; } int64_t sessionTime() const { return m_sessionTime; } int64_t totalSessionTime() const { return m_totalSessionTime; } QDateTime sessionStartTiMe() const; /** * Return time the task was started. */ QDateTime startTime() const; /** sets session time to zero. */ void startNewSession(); //@} //@{ desktop related functions void setDesktopList(const DesktopList& desktopList); DesktopList desktops() const; QString getDesktopStr() const; //@} //@{ name related functions /** sets the name of the task * @param name a pointer to the name. A deep copy will be made. */ void setName(const QString &name); /** sets the description of the task */ void setDescription(const QString &description); /** returns the name of this task. * @return a pointer to the name. */ QString name() const; /** returns the description of this task. * @return a pointer to the description. */ QString description() const; /** * Returns that task name, prefixed by parent tree up to root. * * Task names are separated by a forward slash: / */ QString fullName() const; //@} /** Update the display of the task (all columns) in the UI. */ void update(); //@{ the state of a Task - stopped, running /** starts or stops a task * @param on True or false for starting or stopping a task * @param when Time when the task was started or stopped. Normally * QDateTime::currentDateTime, but if calendar has * been changed by another program and being reloaded * the task is set to running with another start date */ void setRunning(bool on, const QDateTime &when = QDateTime::currentDateTime()); /** * Resume the running state of a task. * This is the same as setrunning, but the storage is not modified. */ void resumeRunning(); /** return the state of a task - if it's running or not * @return true or false depending on whether the task is running */ bool isRunning() const; //@} /** * Parses an incidence. This is needed e.g. when you create a task out of a todo. * You read the todo, extract its custom properties (like session time) * and use these data to initialize the task. */ bool parseIncidence( const KCalendarCore::Incidence::Ptr &, int64_t& minutes, int64_t& sessionMinutes, QString& sessionStartTiMe, QString& name, QString& description, DesktopList& desktops, int& percent_complete, int& priority); /** * Load the todo passed in with this tasks info. */ KCalendarCore::Todo::Ptr asTodo(const KCalendarCore::Todo::Ptr& todo) const; - /** - * Add a comment to this task. - * A comment is called "description" in the context of KCalCore::ToDo - * - * iCal allows multiple comment tags. So we just add a new comment to the - * todo for this task and write the calendar. - * - * @param task The task that gets the comment - * @param comment The comment - */ - void addComment(const QString& comment, TimeTrackerStorage* storage); - - /** Retrieve the entire comment for the task. */ - QString comment() const; - /** tells you whether this task is the root of the task tree */ bool isRoot() const { return !parentTask(); } /** remove Task with all it's children * Removes task as well as all event history for this task. * * @param storage a pointer to a TimeTrackerStorage object. */ bool remove(TimeTrackerStorage* storage); /** * Update percent complete for this task. * * Tasks that are complete (i.e., percent = 100) do not show up in * taskview. If percent NULL, set to zero. If greater than 100, set to * 100. If less than zero, set to zero. */ void setPercentComplete(int percent); int percentComplete() const; /** * Update priority for this task. * * Priority is allowed from 0 to 9. 0 unspecified, 1 highest and 9 lowest. */ void setPriority(int priority); int priority() const; /** Return true if task is complete (percent complete equals 100). */ bool isComplete(); QVariant data(int column, int role) const override; protected: void changeParentTotalTimes(int64_t minutesSession, int64_t minutes); private: /** initialize a task */ void init( const QString& taskname, const QString& taskdescription, int64_t minutes, int64_t sessionTime, const QString& sessionStartTiMe, const DesktopList& desktops, int percent_complete, int priority); bool m_isRunning; /** The iCal unique ID of the Todo for this task. */ QString m_uid; - /** The comment associated with this Task. */ - QString m_comment; - int m_percentComplete; /** task name */ QString m_name; /** task description */ QString m_description; /** Last time this task was started. */ QDateTime m_lastStart; /** totals of the whole subtree including self */ int64_t m_totalTime; int64_t m_totalSessionTime; /** times spend on the task itself */ int64_t m_time; int64_t m_sessionTime; /** time when the session was started */ QDateTime m_sessionStartTime; DesktopList m_desktops; /** Priority of the task. */ int m_priority; ProjectModel *m_projectModel; }; #endif // KTIMETRACKER_TASK_H