diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f740f2..152d654 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,51 +1,51 @@ cmake_minimum_required(VERSION 3.5) set(KDEPIM_VERSION_NUMBER "5.10.80") project(ktimetracker VERSION ${KDEPIM_VERSION_NUMBER}) set(KF5_MIN_VERSION "5.54.0") find_package(ECM ${KF5_MIN_VERSION} CONFIG REQUIRED) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(ECMAddAppIcon) include(ECMInstallIcons) include(ECMQtDeclareLoggingCategory) include(ECMSetupVersion) include(FeatureSummary) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) ecm_setup_version(${KDEPIM_VERSION_NUMBER} VARIABLE_PREFIX KTIMETRACKER VERSION_HEADER src/ktimetracker-version.h ) set(QT_REQUIRED_VERSION "5.10.0") find_package(Qt5 ${QT_REQUIRED_VERSION} CONFIG REQUIRED DBus Gui Widgets Xml Quick) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets DBusAddons DocTools I18n IdleTime JobWidgets KIO Notifications WindowSystem XmlGui ) - -# Find KdepimLibs Package -find_package(KF5CalendarCore ${KCALENDARCORE_LIB_VERSION} CONFIG REQUIRED) +find_package(KF5 5.63.0 REQUIRED COMPONENTS + CalendarCore +) add_subdirectory(pics) add_subdirectory(icons) add_subdirectory(doc) add_subdirectory(src) install(FILES org.kde.ktimetracker.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/export/csvhistory.cpp b/src/export/csvhistory.cpp index f9a18b4..87c55fe 100644 --- a/src/export/csvhistory.cpp +++ b/src/export/csvhistory.cpp @@ -1,174 +1,174 @@ /* * Copyright (C) 2003, 2004 by Mark Bucciarelli * 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 "csvhistory.h" #include "model/projectmodel.h" #include "model/tasksmodel.h" #include "model/task.h" #include "ktimetrackerutility.h" #include "ktt_debug.h" -static int todaySeconds(const QDate &date, const KCalCore::Event::Ptr &event) +static int todaySeconds(const QDate &date, const KCalendarCore::Event::Ptr &event) { if (!event) { return 0; } qCDebug(KTT_LOG) << "found an event for task, event=" << event->uid(); QDateTime startTime = event->dtStart(); QDateTime endTime = event->dtEnd(); QDateTime NextMidNight = startTime; NextMidNight.setTime(QTime(0, 0)); NextMidNight = NextMidNight.addDays(1); // LastMidNight := mdate.setTime(0:00) as it would read in a decent programming language QDateTime LastMidNight = QDateTime::currentDateTime(); LastMidNight.setDate(date); LastMidNight.setTime(QTime(0, 0)); int secsstartTillMidNight = startTime.secsTo(NextMidNight); int secondsToAdd = 0; // seconds that need to be added to the actual cell if (startTime.date() == date && event->dtEnd().date() == date) { // all the event occurred today secondsToAdd = startTime.secsTo(endTime); } if (startTime.date() == date && endTime.date()>date) { // the event started today, but ended later secondsToAdd = secsstartTillMidNight; } if (startTime.date() < date && endTime.date() == date) { // the event started before today and ended today secondsToAdd = LastMidNight.secsTo(event->dtEnd()); } if (startTime.date() < date && endTime.date() > date) { // the event started before today and ended after secondsToAdd = 86400; } return secondsToAdd; } QString exportCSVHistoryToString(ProjectModel *projectModel, const ReportCriteria &rc) { const QDate &from = rc.from; const QDate &to = rc.to; QString delim = rc.delimiter; const QString cr = QStringLiteral("\n"); const int intervalLength = from.daysTo(to) + 1; QMap> secsForUid; QMap uidForName; QString retval; // Step 1: Prepare two hashmaps: // * "uid -> seconds each day": used while traversing events, as uid is their id // "seconds each day" are stored in a vector // * "name -> uid", ordered by name: used when creating the csv file at the end auto tasks = projectModel->tasksModel()->getAllTasks(); qCDebug(KTT_LOG) << "Tasks count: " << tasks.size(); for (Task *task : tasks) { qCDebug(KTT_LOG) << ", Task Name: " << task->name() << ", UID: " << task->uid(); // uid -> seconds each day // * Init each element to zero QVector vector(intervalLength, 0); secsForUid[task->uid()] = vector; // name -> uid // * Create task fullname concatenating each parent's name QString fullName; Task* parentTask; parentTask = task; fullName += parentTask->name(); parentTask = parentTask->parentTask(); while (parentTask) { fullName = parentTask->name() + "->" + fullName; qCDebug(KTT_LOG) << "Fullname(inside): " << fullName; parentTask = parentTask->parentTask(); qCDebug(KTT_LOG) << "Parent task: " << parentTask; } uidForName[fullName] = task->uid(); qCDebug(KTT_LOG) << "Fullname(end): " << fullName; } qCDebug(KTT_LOG) << "secsForUid" << secsForUid; qCDebug(KTT_LOG) << "uidForName" << uidForName; std::unique_ptr calendar = projectModel->asCalendar(QUrl()); // Step 2: For each date, get the events and calculate the seconds // Store the seconds using secsForUid hashmap, so we don't need to translate uids // We rely on rawEventsForDate to get the events qCDebug(KTT_LOG) << "Let's iterate for each date: "; int dayCount = 0; for (QDate mdate = from; mdate.daysTo(to) >= 0; mdate = mdate.addDays(1)) { if (dayCount++ > 365 * 100) { return QStringLiteral("too many days to process"); } qCDebug(KTT_LOG) << mdate.toString(); for (const auto &event : calendar->rawEventsForDate(mdate)) { qCDebug(KTT_LOG) << "Summary: " << event->summary() << ", Related to uid: " << event->relatedTo(); qCDebug(KTT_LOG) << "Today's seconds: " << todaySeconds(mdate, event); secsForUid[event->relatedTo()][from.daysTo(mdate)] += todaySeconds(mdate, event); } } // Step 3: For each task, generate the matching row for the CSV file // We use the two hashmaps to have direct access using the task name // First CSV file line // FIXME: localize strings and date formats retval.append("\"Task name\""); for (int i = 0; i < intervalLength; ++i) { retval.append(delim); retval.append(from.addDays(i).toString()); } retval.append(cr); // Rest of the CSV file QMapIterator nameUid(uidForName); double time; while (nameUid.hasNext()) { nameUid.next(); retval.append(rc.quote + nameUid.key() + rc.quote); qCDebug(KTT_LOG) << nameUid.key() << ": " << nameUid.value() << endl; for (int day = 0; day < intervalLength; day++) { qCDebug(KTT_LOG) << "Secs for day " << day << ":" << secsForUid[nameUid.value()][day]; retval.append(delim); time = secsForUid[nameUid.value()][day] / 60.0; retval.append(formatTime(time, rc.decimalMinutes)); } retval.append(cr); } qDebug() << "Retval is \n" << retval; return retval; } diff --git a/src/file/filecalendar.cpp b/src/file/filecalendar.cpp index 7ddd6d4..be15003 100644 --- a/src/file/filecalendar.cpp +++ b/src/file/filecalendar.cpp @@ -1,81 +1,81 @@ /* * Copyright (C) 2012 by Sérgio Martins * 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 "filecalendar.h" #include -#include +#include #include "icalformatkio.h" #include "ktt_debug.h" FileCalendar::FileCalendar(QUrl url) : m_url(std::move(url)) - , m_calendar(new KCalCore::MemoryCalendar(QTimeZone::systemTimeZone())) + , m_calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())) { } bool FileCalendar::reload() { m_calendar->close(); // delete all TODOs - KCalCore::FileStorage fileStorage(m_calendar, m_url.url(), new ICalFormatKIO()); + KCalendarCore::FileStorage fileStorage(m_calendar, m_url.url(), new ICalFormatKIO()); const bool result = fileStorage.load(); if (!result) { qCritical() << "FileCalendar::reload: problem loading calendar"; } return result; } bool FileCalendar::save() { - KCalCore::FileStorage fileStorage(m_calendar, m_url.url(), new ICalFormatKIO()); + KCalendarCore::FileStorage fileStorage(m_calendar, m_url.url(), new ICalFormatKIO()); const bool result = fileStorage.save(); if (!result) { qCritical() << "FileCalendar::save: problem saving calendar"; } return result; } -KCalCore::Event::List FileCalendar::rawEvents() const +KCalendarCore::Event::List FileCalendar::rawEvents() const { return m_calendar->rawEvents(); } -KCalCore::Event::List FileCalendar::rawEventsForDate(const QDate &date) const +KCalendarCore::Event::List FileCalendar::rawEventsForDate(const QDate &date) const { return m_calendar->rawEventsForDate(date); } -void FileCalendar::addTodo(const KCalCore::Todo::Ptr &todo) +void FileCalendar::addTodo(const KCalendarCore::Todo::Ptr &todo) { m_calendar->addTodo(todo); } -void FileCalendar::addEvent(const KCalCore::Event::Ptr &event) +void FileCalendar::addEvent(const KCalendarCore::Event::Ptr &event) { m_calendar->addEvent(event); } -KCalCore::Todo::List FileCalendar::rawTodos() const { +KCalendarCore::Todo::List FileCalendar::rawTodos() const { return m_calendar->rawTodos(); } diff --git a/src/file/filecalendar.h b/src/file/filecalendar.h index 2d6af7d..9327dfc 100644 --- a/src/file/filecalendar.h +++ b/src/file/filecalendar.h @@ -1,50 +1,50 @@ /* * Copyright (C) 2012 by Sérgio Martins * 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_CALENDAR_H_ #define _KTIMETRACKER_CALENDAR_H_ -#include +#include class FileCalendar { public: explicit FileCalendar(QUrl url); FileCalendar() = delete; ~FileCalendar() = default; bool reload(); bool save(); - void addTodo(const KCalCore::Todo::Ptr &todo); - KCalCore::Todo::List rawTodos() const; + void addTodo(const KCalendarCore::Todo::Ptr &todo); + KCalendarCore::Todo::List rawTodos() const; - void addEvent(const KCalCore::Event::Ptr &event); - KCalCore::Event::List rawEvents() const; - KCalCore::Event::List rawEventsForDate(const QDate &date) const; + void addEvent(const KCalendarCore::Event::Ptr &event); + KCalendarCore::Event::List rawEvents() const; + KCalendarCore::Event::List rawEventsForDate(const QDate &date) const; private: QUrl m_url; - KCalCore::MemoryCalendar::Ptr m_calendar; + KCalendarCore::MemoryCalendar::Ptr m_calendar; }; #endif diff --git a/src/file/icalformatkio.cpp b/src/file/icalformatkio.cpp index 60b9cda..dfd2cf7 100644 --- a/src/file/icalformatkio.cpp +++ b/src/file/icalformatkio.cpp @@ -1,132 +1,132 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "icalformatkio.h" #include #include -#include +#include #include #include "ktt_debug.h" ICalFormatKIO::ICalFormatKIO() - : KCalCore::ICalFormat() + : KCalendarCore::ICalFormat() { } -bool ICalFormatKIO::load(const KCalCore::Calendar::Ptr &calendar, const QString &urlString) +bool ICalFormatKIO::load(const KCalendarCore::Calendar::Ptr &calendar, const QString &urlString) { qCDebug(KTT_LOG) << "ICalFormatKIO::load:" << urlString; clearException(); QUrl url(urlString); if (url.isLocalFile()) { QFile file(url.toLocalFile()); if (!file.exists()) { // Local file does not exist return true; } // Local file exists if (!file.open(QIODevice::ReadOnly)) { qCWarning(KTT_LOG) << "load file open error: " << file.errorString() << ";filename=" << urlString; - setException(new KCalCore::Exception(KCalCore::Exception::LoadError)); + setException(new KCalendarCore::Exception(KCalendarCore::Exception::LoadError)); return false; } const QByteArray text = file.readAll().trimmed(); file.close(); if (text.isEmpty()) { // empty files are valid return true; } else { return fromRawString(calendar, text, false, urlString); } } else { // use remote file auto* const job = KIO::storedGet(url, KIO::Reload); KJobWidgets::setWindow(job, nullptr); // hide progress notification if (!job->exec()) { - setException(new KCalCore::Exception(KCalCore::Exception::SaveErrorSaveFile, QStringList(url.url()))); + setException(new KCalendarCore::Exception(KCalendarCore::Exception::SaveErrorSaveFile, QStringList(url.url()))); return false; } const QByteArray text = job->data(); if (text.isEmpty()) { // empty files are valid return true; } else { return fromRawString(calendar, text, false, urlString); } } } -bool ICalFormatKIO::save(const KCalCore::Calendar::Ptr &calendar, const QString &urlString) +bool ICalFormatKIO::save(const KCalendarCore::Calendar::Ptr &calendar, const QString &urlString) { qCDebug(KTT_LOG) << "ICalFormatKIO::save:" << urlString; clearException(); QString text = toString(calendar); if (text.isEmpty()) { // TODO: save empty file if we have 0 tasks? return false; } QByteArray textUtf8 = text.toUtf8(); // TODO: Write backup file (i.e. backup the existing file somewhere, e.g. to ~/.local/share/apps/ktimetracker/backups/) // const QString backupFile = urlString + QLatin1Char('~'); // QFile::remove(backupFile); // QFile::copy(urlString, backupFile); // save, either locally or remote QUrl url(urlString); if (url.isLocalFile()) { QSaveFile file(url.toLocalFile()); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KTT_LOG) << "save file open error: " << file.errorString() << ";local path" << file.fileName(); - setException(new KCalCore::Exception(KCalCore::Exception::SaveErrorOpenFile, QStringList(urlString))); + setException(new KCalendarCore::Exception(KCalendarCore::Exception::SaveErrorOpenFile, QStringList(urlString))); return false; } file.write(textUtf8.data(), textUtf8.size()); if (!file.commit()) { qCWarning(KTT_LOG) << "file finalize error:" << file.errorString() << ";local path" << file.fileName(); - setException(new KCalCore::Exception(KCalCore::Exception::SaveErrorSaveFile, QStringList(urlString))); + setException(new KCalendarCore::Exception(KCalendarCore::Exception::SaveErrorSaveFile, QStringList(urlString))); return false; } } else { // use remote file auto* const job = KIO::storedPut(textUtf8, url, -1, KIO::Overwrite); KJobWidgets::setWindow(job, nullptr); // hide progress notification if (!job->exec()) { - setException(new KCalCore::Exception(KCalCore::Exception::SaveErrorSaveFile, QStringList(url.url()))); + setException(new KCalendarCore::Exception(KCalendarCore::Exception::SaveErrorSaveFile, QStringList(url.url()))); qCWarning(KTT_LOG) << "save remote error: " << job->errorString(); return false; } } return true; } diff --git a/src/file/icalformatkio.h b/src/file/icalformatkio.h index 4724b3c..dd22715 100644 --- a/src/file/icalformatkio.h +++ b/src/file/icalformatkio.h @@ -1,51 +1,51 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #ifndef KTIMETRACKER_ICALFORMATKIO_H #define KTIMETRACKER_ICALFORMATKIO_H -#include +#include -class ICalFormatKIO : public KCalCore::ICalFormat +class ICalFormatKIO : public KCalendarCore::ICalFormat { public: ICalFormatKIO(); ~ICalFormatKIO() override = default; /** * Read calendar from local or remote file. * * @param calendar * @param urlString Must start with a schema, for example "file:///" or "https://" * @return */ - bool load(const KCalCore::Calendar::Ptr &calendar, const QString &urlString) override; + bool load(const KCalendarCore::Calendar::Ptr &calendar, const QString &urlString) override; /** * Write calendar to local or remote file. * * @param calendar * @param urlString Must start with a schema, for example "file:///" or "https://" * @return */ - bool save(const KCalCore::Calendar::Ptr &calendar, const QString &urlString) override; + bool save(const KCalendarCore::Calendar::Ptr &calendar, const QString &urlString) override; }; #endif // KTIMETRACKER_ICALFORMATKIO_H diff --git a/src/ktimetrackerutility.cpp b/src/ktimetrackerutility.cpp index f94a862..4ca59cd 100644 --- a/src/ktimetrackerutility.cpp +++ b/src/ktimetrackerutility.cpp @@ -1,62 +1,62 @@ /* * Copyright (C) 2007 by Thorsten Staerk * 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 "ktimetrackerutility.h" #include #include QString formatTime(double minutes, bool decimal) { QString time; if (decimal) { time.sprintf("%.2f", minutes / 60.0); time.replace('.', QLocale().decimalPoint()); } else { const long absMinutes = static_cast(std::round(std::fabs(minutes))); time.sprintf( "%s%ld:%02ld", minutes < 0 ? QString(QLocale().negativeSign()).toUtf8().data() : "", absMinutes / 60, absMinutes % 60); } return time; } -QString getCustomProperty(const KCalCore::Incidence::Ptr &incident, const QString &name) +QString getCustomProperty(const KCalendarCore::Incidence::Ptr &incident, const QString &name) { static const QByteArray eventAppName = QByteArray("ktimetracker"); static const QByteArray eventAppNameOld = QByteArray("karm"); const QByteArray nameArray = name.toLatin1(); // If a KDE-karm-* exists and not KDE-ktimetracker-*, change this. if ( incident->customProperty(eventAppName, nameArray).isNull() && !incident->customProperty(eventAppNameOld, nameArray).isNull()) { incident->setCustomProperty( eventAppName, nameArray, incident->customProperty(eventAppNameOld, nameArray)); } return incident->customProperty(eventAppName, nameArray); } diff --git a/src/ktimetrackerutility.h b/src/ktimetrackerutility.h index 5118f9f..03e88df 100644 --- a/src/ktimetrackerutility.h +++ b/src/ktimetrackerutility.h @@ -1,59 +1,59 @@ /* * Copyright (C) 2007 by Thorsten Staerk * 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 KTIMETRACKERUTILITY_H #define KTIMETRACKERUTILITY_H #include -#include +#include /** Format time for output. All times output on screen or report output go through this function. If the second argument is true, the time is output as a two-place decimal. Otherwise the format is hh:mi. Examples: 30 seconds are 0.5 minutes. The output of formatTiMe(0.5,true) is 0.008333, because 0.5 minutes are 0.008333 hours. The output of formatTiMe(0.5,false) is 0:01, because 0.5 minutes are 0:01 hours rounded. */ QString formatTime(double minutes, bool decimal = false); const int secsPerMinute = 60; enum KTIMETRACKER_Errors { KTIMETRACKER_ERR_GENERIC_SAVE_FAILED = 1, KTIMETRACKER_ERR_COULD_NOT_MODIFY_RESOURCE, KTIMETRACKER_ERR_MEMORY_EXHAUSTED, KTIMETRACKER_ERR_UID_NOT_FOUND, KTIMETRACKER_ERR_INVALID_DATE, KTIMETRACKER_ERR_INVALID_TIME, KTIMETRACKER_ERR_INVALID_DURATION, KTIMETRACKER_MAX_ERROR_NO = KTIMETRACKER_ERR_INVALID_DURATION }; -QString getCustomProperty(const KCalCore::Incidence::Ptr &incident, const QString &name); +QString getCustomProperty(const KCalendarCore::Incidence::Ptr &incident, const QString &name); #endif diff --git a/src/model/event.cpp b/src/model/event.cpp index bee6d96..e331bb1 100644 --- a/src/model/event.cpp +++ b/src/model/event.cpp @@ -1,158 +1,158 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "event.h" #include #include "ktt_debug.h" #include "ktimetrackerutility.h" static const QByteArray eventAppName = QByteArray("ktimetracker"); -Event::Event(const KCalCore::Event::Ptr &event) +Event::Event(const KCalendarCore::Event::Ptr &event) : m_summary(event->summary()) , m_dtStart(event->dtStart()) , m_dtEnd(event->hasEndDate() ? event->dtEnd() : QDateTime()) , m_uid(event->uid()) , m_relatedTo(event->relatedTo()) , m_comments(event->comments()) , m_duration(0) { if (!m_dtStart.isValid()) { qFatal("1"); } bool ok = false; m_duration = getCustomProperty(event, QStringLiteral("duration")).toInt(&ok); if (!ok) { m_duration = 0; } } QString Event::summary() const { return m_summary; } void Event::updateDuration(QDateTime &changedDt, const QDateTime &otherDt) { if (m_duration < 0) { // Was a negative duration task if (m_dtEnd > m_dtStart) { // If range is not trivial or inversed, we assume that // event duration now becomes positive, thus we should update it here. m_duration = m_dtStart.secsTo(m_dtEnd); } } else { // Was a regular task, and will stay regular. Calculate duration if possible. if (hasEndDate()) { m_duration = m_dtStart.secsTo(m_dtEnd); if (m_duration < 0) { // We cannot make duration negative. // May be the user tried to make duration zero, let us help him. m_duration = 0; changedDt = otherDt; } } } } void Event::setDtStart(const QDateTime &dtStart) { m_dtStart = dtStart; updateDuration(m_dtStart, m_dtEnd); } QDateTime Event::dtStart() const { return m_dtStart; } void Event::setDtEnd(const QDateTime &dtEnd) { m_dtEnd = dtEnd; updateDuration(m_dtEnd, m_dtStart); } bool Event::hasEndDate() const { return m_dtEnd.isValid(); } QDateTime Event::dtEnd() const { return m_dtEnd; } QString Event::uid() const { return m_uid; } QString Event::relatedTo() const { return m_relatedTo; } void Event::addComment(const QString &comment) { m_comments.append(comment); } QStringList Event::comments() const { return m_comments; } void Event::setDuration(long seconds) { m_duration = seconds; } long Event::duration() const { return m_duration; } -KCalCore::Event::Ptr Event::asCalendarEvent(const KCalCore::Event::Ptr &event) const +KCalendarCore::Event::Ptr Event::asCalendarEvent(const KCalendarCore::Event::Ptr &event) const { Q_ASSERT(event != nullptr); qCDebug(KTT_LOG) << "Task::asTodo: uid" << m_uid << ", summary" << m_summary; event->setSummary(summary()); event->setDtStart(m_dtStart); if (hasEndDate()) { event->setDtEnd(m_dtEnd); } event->setUid(uid()); event->setRelatedTo(relatedTo()); for (const auto &comment : comments()) { event->addComment(comment); } // Use a custom property to keep a record of negative durations event->setCustomProperty(eventAppName, QByteArray("duration"), QString::number(m_duration)); event->setCategories({i18n("KTimeTracker")}); return event; } diff --git a/src/model/event.h b/src/model/event.h index fa99062..d289aef 100644 --- a/src/model/event.h +++ b/src/model/event.h @@ -1,81 +1,81 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #ifndef KTIMETRACKER_EVENT_H #define KTIMETRACKER_EVENT_H -#include +#include /** * We have three types of events: * 1. Finished events of positive duration: * - dtStart and dtEnd are defined * - dtEnd >= dtStart * - duration >= 0 * 2. Unfinished events: * - dtStart is defined * - dtEnd is not defined, thus hasEndDate() returns false * 3. Events of negative duration: * - dtStart and dtEnd are defined * - dtEnd <= dtStart (TODO make dtEnd undefined in the case of negative duration?) * - duration < 0 */ class Event { public: - explicit Event(const KCalCore::Event::Ptr &event); + explicit Event(const KCalendarCore::Event::Ptr &event); QString summary() const; void setDtStart(const QDateTime &dtStart); QDateTime dtStart() const; void setDtEnd(const QDateTime &dtEnd); bool hasEndDate() const; QDateTime dtEnd() const; QString uid() const; QString relatedTo() const; void addComment(const QString &comment); QStringList comments() const; void setDuration(long seconds); long duration() const; /** * Load the event passed in with this event's info. */ - KCalCore::Event::Ptr asCalendarEvent(const KCalCore::Event::Ptr &event) const; + KCalendarCore::Event::Ptr asCalendarEvent(const KCalendarCore::Event::Ptr &event) const; private: void updateDuration(QDateTime &changedDt, const QDateTime &otherDt); QString m_summary; QDateTime m_dtStart; QDateTime m_dtEnd; QString m_uid; QString m_relatedTo; QStringList m_comments; long m_duration; }; #endif // KTIMETRACKER_EVENT_H diff --git a/src/model/eventsmodel.cpp b/src/model/eventsmodel.cpp index 6c43d7c..3605c8a 100644 --- a/src/model/eventsmodel.cpp +++ b/src/model/eventsmodel.cpp @@ -1,172 +1,172 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "eventsmodel.h" #include #include "task.h" #include "ktt_debug.h" EventsModel::EventsModel() : m_events() { } -void EventsModel::load(const KCalCore::Event::List &events) +void EventsModel::load(const KCalendarCore::Event::List &events) { clear(); for (const auto& event : events) { m_events.append(new Event(event)); } } EventsModel::~EventsModel() { clear(); } QList EventsModel::events() const { return m_events; } QList EventsModel::eventsForTask(Task *task) const { QList res; for (auto *event : events()) { if (event->relatedTo() == task->uid()) { res.append(event); } } return res; } Event *EventsModel::eventByUID(const QString &uid) const { for (auto *event : events()) { if (event->uid() == uid) { return event; } } return nullptr; } void EventsModel::clear() { for (auto *event : m_events) { delete event; } m_events.clear(); } void EventsModel::removeAllForTask(const Task *task) { for (auto it = m_events.begin(); it != m_events.end();) { Event *event = *it; if (event->relatedTo() == task->uid()) { delete event; it = m_events.erase(it); } else { ++it; } } } void EventsModel::removeByUID(const QString &uid) { for (auto it = m_events.begin(); it != m_events.end(); ++it) { Event *event = *it; if (event->uid() == uid) { delete event; m_events.erase(it); return; } } } void EventsModel::addEvent(Event *event) { m_events.append(event); } -static KCalCore::Event::Ptr baseEvent(const Task *task) +static KCalendarCore::Event::Ptr baseEvent(const Task *task) { qCDebug(KTT_LOG) << "Entering function"; - KCalCore::Event::Ptr e( new KCalCore::Event() ); + KCalendarCore::Event::Ptr e(new KCalendarCore::Event()); QStringList categories; e->setSummary(task->name()); // Can't use setRelatedToUid()--no error, but no RelatedTo written to disk e->setRelatedTo( task->uid() ); // Debugging: some events where not getting a related-to field written. Q_ASSERT(e->relatedTo() == task->uid()); // Have to turn this off to get datetimes in date fields. e->setAllDay(false); // e->setDtStart(KDateTime(task->startTime(), KDateTime::Spec::LocalZone())); e->setDtStart(task->startTime()); // So someone can filter this mess out of their calendar display categories.append(i18n("KTimeTracker")); e->setCategories(categories); return e; } void EventsModel::changeTime(const Task* task, long deltaSeconds) { qCDebug(KTT_LOG) << "Entering function; deltaSeconds=" << deltaSeconds; QDateTime end; auto kcalEvent = baseEvent(task); auto *e = new Event(kcalEvent); // Don't use duration, as ICalFormatImpl::writeIncidence never writes a // duration, even though it looks like it's used in event.cpp. end = task->startTime(); if ( deltaSeconds > 0 ) end = task->startTime().addSecs(deltaSeconds); e->setDtEnd(end); // Use a custom property to keep a record of negative durations e->setDuration(deltaSeconds); addEvent(e); } bool EventsModel::bookTime(const Task* task, const QDateTime& startDateTime, long durationInSeconds) { qCDebug(KTT_LOG) << "Entering function"; auto kcalEvent = baseEvent(task); auto *e = new Event(kcalEvent); e->setDtStart(startDateTime); e->setDtEnd(startDateTime.addSecs( durationInSeconds)); // Use a custom property to keep a record of negative durations e->setDuration(durationInSeconds); addEvent(e); return true; } diff --git a/src/model/eventsmodel.h b/src/model/eventsmodel.h index 1fde776..bbd1739 100644 --- a/src/model/eventsmodel.h +++ b/src/model/eventsmodel.h @@ -1,103 +1,103 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #ifndef KTIMETRACKER_EVENTSMODEL_H #define KTIMETRACKER_EVENTSMODEL_H -#include +#include #include "event.h" class Task; class EventsModel : public QObject { Q_OBJECT public: EventsModel(); ~EventsModel() override; - void load(const KCalCore::Event::List &events); + void load(const KCalendarCore::Event::List &events); QList events() const; QList eventsForTask(Task *task) const; Event *eventByUID(const QString &uid) const; // Delete all events void clear(); void removeAllForTask(const Task *task); void removeByUID(const QString &uid); void addEvent(Event *event); /** * Log the change in a task's time. * * This is also called when a timer is stopped. * We create an iCalendar event to store each change. The event start * date is set to the current datetime. If time is added to the task, the * task end date is set to start time + delta. If the time is negative, * the end date is set to the start time. * * In both cases (postive or negative delta), we create a custom iCalendar * property that stores the delta (in seconds). This property is called * X-KDE-ktimetracker-duration. * * Note that the ktimetracker UI allows the user to change both the session and * the total task time, and this routine does not account for all posibile * cases. For example, it is possible for the user to do something crazy * like add 10 minutes to the session time and subtract 50 minutes from * the total time. Although this change violates a basic law of physics, * it is allowed. * * For now, you should pass in the change to the total task time. * * @param task The task the change is for. * @param delta Change in task time, in seconds. Can be negative. */ void changeTime(const Task* task, long deltaSeconds); /** * Book time to a task. * * Creates an iCalendar event and adds it to the calendar. Does not write * calendar to disk, just adds event to calendar in memory. However, the * resource framework does try to get a lock on the file. After a * successful lock, the calendar marks this incidence as modified and then * releases the lock. * * @param task Task * @param startDateTime Date and time the booking starts. * @param durationInSeconds Duration of time to book, in seconds. * * @return true if event was added, false if not (if, for example, the * attempted file lock failed). */ bool bookTime(const Task *task, const QDateTime &startDateTime, long durationInSeconds); Q_SIGNALS: void timesChanged(); private: QList m_events; }; #endif // KTIMETRACKER_EVENTSMODEL_H diff --git a/src/model/projectmodel.cpp b/src/model/projectmodel.cpp index 92dd9db..3adf7e8 100644 --- a/src/model/projectmodel.cpp +++ b/src/model/projectmodel.cpp @@ -1,67 +1,67 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include "projectmodel.h" #include "tasksmodel.h" #include "task.h" #include "eventsmodel.h" #include "event.h" ProjectModel::ProjectModel() : m_tasksModel(new TasksModel()) , m_eventsModel(new EventsModel()) { } TasksModel *ProjectModel::tasksModel() { return m_tasksModel; } EventsModel *ProjectModel::eventsModel() { return m_eventsModel; } std::unique_ptr ProjectModel::asCalendar(const QUrl &url) const { std::unique_ptr calendar(new FileCalendar(url)); for (auto *item : m_tasksModel->getAllItems()) { Task *task = dynamic_cast(item); - KCalCore::Todo::Ptr todo(new KCalCore::Todo()); + KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo()); calendar->addTodo(task->asTodo(todo)); } // TODO: Use libkcalcore comments // todo->addComment(comment); // temporary // todo->setDescription(task->comment()); for (Event *event : m_eventsModel->events()) { - KCalCore::Event::Ptr calEvent(new KCalCore::Event()); + KCalendarCore::Event::Ptr calEvent(new KCalendarCore::Event()); calendar->addEvent(event->asCalendarEvent(calEvent)); } return calendar; } diff --git a/src/model/task.cpp b/src/model/task.cpp index 7e748a9..9fd5f5d 100644 --- a/src/model/task.cpp +++ b/src/model/task.cpp @@ -1,592 +1,592 @@ /* * 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 #include "model/projectmodel.h" #include "model/eventsmodel.h" #include "model/tasksmodel.h" #include "ktimetrackerutility.h" #include "ktimetracker.h" #include "timetrackerstorage.h" #include "ktt_debug.h" static const QByteArray eventAppName = QByteArray("ktimetracker"); Task::Task(const QString& taskName, const QString& taskDescription, long minutes, long sessionTime, 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 = KCalCore::CalFormat::createUniqueId(); + m_uid = KCalendarCore::CalFormat::createUniqueId(); } -Task::Task(const KCalCore::Todo::Ptr &todo, ProjectModel *projectModel) +Task::Task(const KCalendarCore::Todo::Ptr &todo, ProjectModel *projectModel) : TasksModelItem(projectModel->tasksModel(), nullptr) , m_projectModel(projectModel) { projectModel->tasksModel()->addChild(this); long minutes = 0; QString name; QString description; long 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); } 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, long minutes, long sessionTime, 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; } } } // 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(long minutes) { m_time += minutes; this->addTotalTime(minutes); } void Task::addTotalTime(long minutes) { m_totalTime += minutes; if (parentTask()) { parentTask()->addTotalTime(minutes); } } void Task::addSessionTime(long minutes) { m_sessionTime += minutes; this->addTotalSessionTime(minutes); } void Task::addTotalSessionTime(long minutes) { m_totalSessionTime += minutes; if (parentTask()) { parentTask()->addTotalSessionTime(minutes); } } QString Task::setTime(long minutes) { m_time = minutes; m_totalTime += minutes; return QString(); } void Task::recalculateTotalTimesSubtree() { long totalMinutes = time(); long 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(long minutes) { m_sessionTime = minutes; m_totalSessionTime += minutes; return QString(); } void Task::changeTimes(long minutesSession, long 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(long minutes, EventsModel *eventsModel) { changeTimes(minutes, minutes, eventsModel); } void Task::changeTotalTimes(long minutesSession, long 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(long minutesSession, long 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 = static_cast(child(i)); task->remove(storage); } if (isRunning()) { 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(); } } -KCalCore::Todo::Ptr Task::asTodo(const KCalCore::Todo::Ptr& todo) const +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 KCalCore::Incidence::Ptr &incident, long& minutes, +bool Task::parseIncidence(const KCalendarCore::Incidence::Ptr &incident, long& minutes, long& sessionMinutes, QString& sessionStartTiMe, QString& name, QString& description, DesktopList& desktops, - int& percent_complete, int& priority ) + 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(); + 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 ec88073..0afa582 100644 --- a/src/model/task.h +++ b/src/model/task.h @@ -1,365 +1,365 @@ /* * 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 #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, stoped 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, long minutes, long sessionTime, DesktopList desktops, ProjectModel *projectModel, Task* parentTask); - Task(const KCalCore::Todo::Ptr &incident, ProjectModel *projectModel); + Task(const KCalendarCore::Todo::Ptr &incident, ProjectModel *projectModel); /* destructor */ ~Task() override = default; 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(long 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(long minutesSession, long 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(long minutesSession, long 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(long 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(long 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(long 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(long minutes); /** Sets the time (not session time). This does not add an event. * * @param minutes minutes to set time to * */ QString setTime(long 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(long 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(long 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(long minutes); /** * Reset all times to 0 and adjust parent task's totalTiMes. */ void resetTimes(); /** @return time in minutes */ long time() const { return m_time; } /** @return total time in minutes */ long totalTime() const { return m_totalTime; } long sessionTime() const { return m_sessionTime; } long 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 KCalCore::Incidence::Ptr &, long& minutes, + bool parseIncidence(const KCalendarCore::Incidence::Ptr &, long& minutes, long& sessionMinutes, QString& sessionStartTiMe, QString& name, QString& description, DesktopList& desktops, - int& percent_complete, int& priority ); + int& percent_complete, int& priority); /** * Load the todo passed in with this tasks info. */ - KCalCore::Todo::Ptr asTodo(const KCalCore::Todo::Ptr &calendar) const; + KCalendarCore::Todo::Ptr asTodo(const KCalendarCore::Todo::Ptr &calendar) 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(long minutesSession, long minutes); private: /** initialize a task */ void init( const QString& taskname, const QString& taskdescription, long minutes, long sessionTime, 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 */ long m_totalTime; long m_totalSessionTime; /** times spend on the task itself */ long m_time; long 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 diff --git a/src/tests/storagetest.cpp b/src/tests/storagetest.cpp index 27eb447..5f0e34e 100644 --- a/src/tests/storagetest.cpp +++ b/src/tests/storagetest.cpp @@ -1,91 +1,91 @@ /* * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . */ #include -#include -#include +#include +#include #include "taskview.h" #include "model/task.h" #include "helpers.h" class StorageTest : public QObject { Q_OBJECT private Q_SLOTS: void testSaveEmpty(); void testSaveSimpleTree(); }; void StorageTest::testSaveEmpty() { auto *taskView = createTaskView(this, false); QVERIFY(taskView); QCOMPARE(taskView->storage()->save(), ""); QCOMPARE(readTextFile(taskView->storage()->fileUrl().toLocalFile()), readTextFile(QFINDTESTDATA("data/empty.ics"))); } void StorageTest::testSaveSimpleTree() { auto *taskView = createTaskView(this, true); QVERIFY(taskView); Task* negativeTask = taskView->addTask("negative duration"); negativeTask->changeTime(-5, taskView->storage()->eventsModel()); // substract 5 minutes QCOMPARE(taskView->storage()->save(), ""); - KCalCore::MemoryCalendar::Ptr calendar(new KCalCore::MemoryCalendar(QTimeZone::systemTimeZone())); - KCalCore::FileStorage fileStorage(calendar, taskView->storage()->fileUrl().toLocalFile(), new KCalCore::ICalFormat()); + KCalendarCore::MemoryCalendar::Ptr calendar(new KCalendarCore::MemoryCalendar(QTimeZone::systemTimeZone())); + KCalendarCore::FileStorage fileStorage(calendar, taskView->storage()->fileUrl().toLocalFile(), new KCalendarCore::ICalFormat()); QVERIFY(fileStorage.load()); - auto todos = calendar->rawTodos(KCalCore::TodoSortSummary); + auto todos = calendar->rawTodos(KCalendarCore::TodoSortSummary); QCOMPARE(todos.size(), 4); QCOMPARE(todos[0]->summary(), "1"); QCOMPARE(todos[0]->customProperty("ktimetracker", "totalSessionTime"), "5"); QCOMPARE(todos[0]->customProperty("ktimetracker", "totalTaskTime"), "5"); QCOMPARE(todos[1]->summary(), "2"); QCOMPARE(todos[1]->relatedTo(), todos[0]->uid()); QCOMPARE(todos[3]->summary(), "negative duration"); QCOMPARE(todos[3]->customProperty("ktimetracker", "totalSessionTime"), "-5"); QCOMPARE(todos[3]->customProperty("ktimetracker", "totalTaskTime"), "-5"); - auto events = calendar->rawEvents(KCalCore::EventSortSummary); + auto events = calendar->rawEvents(KCalendarCore::EventSortSummary); QCOMPARE(events.size(), 4); QCOMPARE(events[0]->summary(), "1"); QCOMPARE(events[0]->customProperty("ktimetracker", "duration"), "300"); QCOMPARE(events[0]->relatedTo(), todos[0]->uid()); QCOMPARE(events[0]->categoriesStr(), "KTimeTracker"); QCOMPARE(events[3]->summary(), "negative duration"); QCOMPARE(events[3]->customProperty("ktimetracker", "duration"), "-300"); QCOMPARE(events[3]->relatedTo(), todos[3]->uid()); } QTEST_MAIN(StorageTest) #include "storagetest.moc" diff --git a/src/timetrackerstorage.cpp b/src/timetrackerstorage.cpp index 148a268..689d1a1 100644 --- a/src/timetrackerstorage.cpp +++ b/src/timetrackerstorage.cpp @@ -1,293 +1,293 @@ /* * Copyright (C) 2003, 2004 by Mark Bucciarelli * 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. * */ /** TimeTrackerStorage * This class cares for the storage of ktimetracker's data. * ktimetracker's data is * - tasks like "programming for customer foo" * - events like "from 2009-09-07, 8pm till 10pm programming for customer foo" * tasks are like the items on your todo list, events are dates when you worked on them. * ktimetracker's data is stored in a ResourceCalendar object hold by mCalendar. */ #include "timetrackerstorage.h" #include #include #include #include #include #include "model/task.h" #include "model/eventsmodel.h" #include "model/projectmodel.h" #include "model/tasksmodel.h" #include "taskview.h" #include "ktt_debug.h" const QByteArray eventAppName = QByteArray("ktimetracker"); TimeTrackerStorage::TimeTrackerStorage() : m_model(nullptr) , m_url() , m_taskView(nullptr) { } // Loads data from filename into view. QString TimeTrackerStorage::load(TaskView *view, const QUrl &url) { if (url.isEmpty()) { return QStringLiteral("TimeTrackerStorage::load() callled with an empty URL"); } // loading might create the file bool removedFromDirWatch = false; if (KDirWatch::self()->contains(m_url.toLocalFile())) { KDirWatch::self()->removeFile(m_url.toLocalFile()); removedFromDirWatch = true; } // If same file, don't reload if (url == m_url) { if (removedFromDirWatch) { KDirWatch::self()->addFile(m_url.toLocalFile()); } return QString(); } if (m_model) { closeStorage(); } m_model = new ProjectModel(); if (url.isLocalFile()) { connect(KDirWatch::self(), &KDirWatch::dirty, this, &TimeTrackerStorage::onFileModified); if (!KDirWatch::self()->contains(url.toLocalFile())) { KDirWatch::self()->addFile(url.toLocalFile()); } } // Create local file resource and add to resources m_url = url; FileCalendar m_calendar(m_url); m_taskView = view; // m_calendar->setTimeSpec( KSystemTimeZones::local() ); m_calendar.reload(); // Build task view from iCal data QString err; eventsModel()->load(m_calendar.rawEvents()); err = buildTaskView(m_calendar.rawTodos(), view); if (removedFromDirWatch) { KDirWatch::self()->addFile(m_url.toLocalFile()); } return err; } QUrl TimeTrackerStorage::fileUrl() { return m_url; } -QString TimeTrackerStorage::buildTaskView(const KCalCore::Todo::List &todos, TaskView *view) +QString TimeTrackerStorage::buildTaskView(const KCalendarCore::Todo::List &todos, TaskView *view) // makes *view contain the tasks out of *rc. { // remember tasks that are running and their start times QVector runningTasks; QVector startTimes; for (Task *task : tasksModel()->getAllTasks()) { if (task->isRunning()) { runningTasks.append(task->uid()); startTimes.append(task->startTime()); } } tasksModel()->clear(); QMultiHash map; for (const auto &todo : todos) { Task* task = new Task(todo, m_model); map.insert(todo->uid(), task); task->invalidateCompletedState(); } // 1.1. Load each task under its parent task. QString err; for (const auto &todo : todos) { Task* task = map.value(todo->uid()); // No relatedTo incident just means this is a top-level task. if (!todo->relatedTo().isEmpty()) { Task* newParent = map.value(todo->relatedTo()); // Complete the loading but return a message if (!newParent) { err = i18n("Error loading \"%1\": could not find parent (uid=%2)", task->name(), todo->relatedTo()); } else { task->move(newParent); } } } view->clearActiveTasks(); // restart tasks that have been running with their start times for (Task *task : tasksModel()->getAllTasks()) { for (int n = 0; n < runningTasks.count(); ++n) { if (runningTasks[n] == task->uid()) { view->startTimerFor(task, startTimes[n]); } } } view->refresh(); return err; } void TimeTrackerStorage::closeStorage() { if (m_model) { delete m_model; m_model = nullptr; } } EventsModel *TimeTrackerStorage::eventsModel() { if (!m_model) { qFatal("TimeTrackerStorage::eventsModel is nullptr"); } return m_model->eventsModel(); } TasksModel *TimeTrackerStorage::tasksModel() { if (!m_model) { qFatal("TimeTrackerStorage::tasksModel is nullptr"); } return m_model->tasksModel(); } ProjectModel *TimeTrackerStorage::projectModel() { if (!m_model) { qFatal("TimeTrackerStorage::projectModel is nullptr"); } return m_model; } bool TimeTrackerStorage::allEventsHaveEndTime(Task *task) { for (const auto *event : m_model->eventsModel()->eventsForTask(task)) { if (!event->hasEndDate()) { return false; } } return true; } bool TimeTrackerStorage::allEventsHaveEndTime() { for (const auto *event : m_model->eventsModel()->events()) { if (!event->hasEndDate()) { return false; } } return true; } QString TimeTrackerStorage::deleteAllEvents() { m_model->eventsModel()->clear(); return QString(); } QString TimeTrackerStorage::save() { bool removedFromDirWatch = false; if (KDirWatch::self()->contains(m_url.toLocalFile())) { KDirWatch::self()->removeFile(m_url.toLocalFile()); removedFromDirWatch = true; } if (!m_model) { qCWarning(KTT_LOG) << "TimeTrackerStorage::save: m_model is nullptr"; // No i18n() here because it's too technical and unlikely to happen return QStringLiteral("m_model is nullptr"); } const QString fileLockPath("ktimetrackerics.lock"); QLockFile fileLock(fileLockPath); if (!fileLock.lock()) { qCWarning(KTT_LOG) << "TimeTrackerStorage::save: m_fileLock->lock() failed"; return i18nc("%1=lock file path", "Could not write lock file \"%1\". Disk full?", fileLockPath); } QString errorMessage; std::unique_ptr calendar = m_model->asCalendar(m_url); if (!calendar->save()) { qCWarning(KTT_LOG) << "TimeTrackerStorage::save: calendar->save() failed"; errorMessage = i18nc("%1=destination file path/URL", "Failed to save iCalendar file as \"%1\".", m_url.toString()); } else { qCDebug(KTT_LOG) << "TimeTrackerStorage::save: wrote tasks to" << m_url; } fileLock.unlock(); if (removedFromDirWatch) { KDirWatch::self()->addFile(m_url.toLocalFile()); } return errorMessage; } //---------------------------------------------------------------------------- bool TimeTrackerStorage::bookTime(const Task* task, const QDateTime& startDateTime, long durationInSeconds) { return eventsModel()->bookTime(task, startDateTime, durationInSeconds); } void TimeTrackerStorage::onFileModified() { if (!m_model) { qCWarning(KTT_LOG) << "TaskView::onFileModified(): model is null"; return; } qCDebug(KTT_LOG) << "entering function"; FileCalendar m_calendar(m_url); // m_calendar->setTimeSpec( KSystemTimeZones::local() ); m_calendar.reload(); buildTaskView(m_calendar.rawTodos(), m_taskView); qCDebug(KTT_LOG) << "exiting onFileModified"; } diff --git a/src/timetrackerstorage.h b/src/timetrackerstorage.h index 4fbff7d..a989f97 100644 --- a/src/timetrackerstorage.h +++ b/src/timetrackerstorage.h @@ -1,160 +1,160 @@ /* * Copyright (C) 2003 by Mark Bucciarelli * 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_STORAGE_H #define KTIMETRACKER_STORAGE_H -#include -#include +#include +#include #include "reportcriteria.h" #include "desktoplist.h" #include "file/filecalendar.h" QT_BEGIN_NAMESPACE class QDateTime; class QLockFile; QT_END_NAMESPACE class ProjectModel; class Task; class TaskView; class TasksModel; class EventsModel; /** * Class to store/retrieve KTimeTracker data to/from persistent storage. * * The storage is an iCalendar file. Its name is contained in this class * in the variable _icalfile and can be read using the function icalfile(). * The name gets set by the load() operation. * * All logic that deals with getting and saving data should go here. * * This program uses iCalendar to store its data. There are tasks and * events. Events have a start and a end date and an associated task. * * @short Logic that gets and stores KTimeTracker data to disk. * @author Mark Bucciarelli */ class TimeTrackerStorage : public QObject { Q_OBJECT public: TimeTrackerStorage(); ~TimeTrackerStorage() override = default; /** Load the list view with tasks read from iCalendar file. Parses iCalendar file, builds list view items in the proper hierarchy, and loads them into the list view widget. If the file name passed in is the same as the last file name that was loaded, this method does nothing. This method considers any of the following conditions errors: @li the iCalendar file does not exist @li the iCalendar file is not readable @li the list group currently has list items @li an iCalendar todo has no related to attribute @li a todo is related to another todo which does not exist @param taskview The list group used in the TaskView. Must not be nullptr. @param url Override preferences' filename @return empty string if success, error message if error. */ QString load(TaskView *taskview, const QUrl &url); /** * Return the name of the iCal file */ QUrl fileUrl(); /** * Build up the taskview. * * This is needed if the iCal file has been modified. */ - QString buildTaskView(const KCalCore::Todo::List &todos, TaskView *view); + QString buildTaskView(const KCalendarCore::Todo::List &todos, TaskView *view); /** Close calendar and clear view. Release lock if holding one. */ void closeStorage(); bool isLoaded() const { return m_model; } /** list of all events */ EventsModel *eventsModel(); TasksModel *tasksModel(); ProjectModel *projectModel(); /** * Deliver if all events of a task have an endtime * * If ktimetracker has been quitted with one task running, it needs to resumeRunning(). * This function delivers if an enddate of an event has not yet been stored. * * @param task The task to be examined */ bool allEventsHaveEndTime(Task *task); /** * Deliver if all events of the actual calendar have an endtime * * If ktimetracker has been quitted with one task running, it needs to resumeRunning(). * This function delivers if an enddate of an event has not yet been stored. * */ bool allEventsHaveEndTime(); QString deleteAllEvents(); /** * Save all tasks and their totals to an iCalendar file. * * All tasks must have an associated VTODO object already created in the * calendar file; that is, the task->uid() must refer to a valid VTODO in * the calendar. * Delivers empty string if successful, else error msg. * * @return Null string on success. On failure, returns human-readable error message to display in a KMessageBox. */ QString save(); bool bookTime(const Task *task, const QDateTime &startDateTime, long durationInSeconds); private Q_SLOTS: void onFileModified(); private: ProjectModel *m_model; QUrl m_url; TaskView* m_taskView; }; #endif // KTIMETRACKER_STORAGE_H