diff --git a/src/model/tasksmodel.cpp b/src/model/tasksmodel.cpp index 1d39bf4..a0dd287 100644 --- a/src/model/tasksmodel.cpp +++ b/src/model/tasksmodel.cpp @@ -1,377 +1,389 @@ /* * 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 "tasksmodel.h" #include #include #include #include #include "task.h" TasksModel::TasksModel() : m_rootItem(new TasksModelItem(this, nullptr)) , m_headerLabels{ i18nc("@title:column", "Task Name"), i18nc("@title:column", "Session Time"), i18nc("@title:column", "Time"), i18nc("@title:column", "Total Session Time"), i18nc("@title:column", "Total Time"), i18nc("@title:column", "Priority"), i18nc("@title:column", "Percent Complete")} , m_clockAnimation(nullptr) , m_dragCutTaskId() { Q_INIT_RESOURCE(pics); m_clockAnimation = new QMovie(":/pics/watch.gif", QByteArray(), this); // Prepare animated icon connect(m_clockAnimation, &QMovie::frameChanged, this, &TasksModel::setActiveIcon); // TODO: stop animation when no tasks are active m_clockAnimation->start(); } void TasksModel::clear() { beginResetModel(); // Empty "m_children", move it to "children". QList children; children.swap(m_rootItem->m_children); for (int i = 0; i < children.count(); ++i) { TasksModelItem *item = children.at(i); item->m_parent = nullptr; delete item; } endResetModel(); } int TasksModel::topLevelItemCount() const { return m_rootItem->childCount(); } int TasksModel::indexOfTopLevelItem(TasksModelItem *item) const { return m_rootItem->m_children.indexOf(item); } TasksModelItem *TasksModel::topLevelItem(int index) const { return m_rootItem->child(index); } TasksModelItem *TasksModel::takeTopLevelItem(int index) { return m_rootItem->takeChild(index); } TasksModelItem *TasksModel::item(const QModelIndex &index) const { if (!index.isValid()) { return nullptr; } return static_cast(index.internalPointer()); } QModelIndex TasksModel::index(TasksModelItem *item, int column) const { if (!item || item == m_rootItem) { return {}; } const TasksModelItem *par = item->parent(); auto *itm = const_cast(item); if (!par) { par = m_rootItem; } int row = par->m_children.lastIndexOf(itm); return createIndex(row, column, itm); } QModelIndex TasksModel::index(int row, int column, const QModelIndex &parent) const { int c = columnCount(parent); if (row < 0 || column < 0 || column >= c) { return {}; } TasksModelItem *parentItem = parent.isValid() ? item(parent) : m_rootItem; if (parentItem && row < parentItem->childCount()) { TasksModelItem *itm = parentItem->child(row); if (itm) { return createIndex(row, column, itm); } return {}; } return {}; } QModelIndex TasksModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return {}; } auto *item = static_cast(child.internalPointer()); if (!item || item == m_rootItem) { return {}; } TasksModelItem *parent = item->parent(); return index(parent, 0); } int TasksModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return m_rootItem->childCount(); } TasksModelItem *parentItem = item(parent); if (parentItem) { return parentItem->childCount(); } return 0; } int TasksModel::columnCount(const QModelIndex& /*parent*/) const { return m_headerLabels.size(); } QVariant TasksModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return {}; } switch (role) { case Qt::TextAlignmentRole: // Align task name: left // Align priority: center // Align HH:MM: right if (index.column() == 5) { return Qt::AlignCenter; } else if (index.column() >= 1) { return {Qt::AlignRight | Qt::AlignVCenter}; } break; case Qt::WhatsThisRole: if (index.column() == 0) { return i18nc("@info:whatsthis", "The task name is what you call the task, it can be chosen freely."); } else if (index.column() == 1) { return i18nc("@info:whatsthis", "The session time is the time since you last chose \"Start New Session\"."); } break; case Qt::DecorationRole: { auto *task = dynamic_cast(item(index)); if (!task) { return {}; } if (index.column() == 0) { return QPixmap(task->isComplete() ? ":/pics/task-complete.xpm" : ":/pics/task-incomplete.xpm"); } else if (index.column() == 1) { return task->isRunning() ? m_clockAnimation->currentPixmap() : QPixmap(":/pics/empty-watch.xpm"); } break; } default: { TasksModelItem *itm = item(index); if (itm) { return itm->data(index.column(), role); } break; } } return {}; } QList TasksModel::getAllItems() { QList res; QStack stack; stack.push(m_rootItem); while (!stack.isEmpty()) { TasksModelItem *item = stack.pop(); if (item != m_rootItem) { res.append(item); } for (int c = 0; c < item->m_children.count(); ++c) { stack.push(item->m_children.at(c)); } } return res; } QList TasksModel::getAllTasks() { QList tasks; for (TasksModelItem *item : getAllItems()) { // If "item" is not a Task, then we are probably in the middle // of class Task or TasksModelItem destructor. Task *task = dynamic_cast(item); if (task) { tasks.append(task); } } return tasks; } +QList TasksModel::getActiveTasks() +{ + QList activeTasks; + for (Task *task : getAllTasks()) { + if (task->isRunning()) { + activeTasks.append(task); + } + } + + return activeTasks; +} + QVariant TasksModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section < 0 || orientation != Qt::Horizontal || section >= m_headerLabels.size()) { return {}; } switch (role) { case Qt::DisplayRole: return m_headerLabels[section]; case Qt::WhatsThisRole: switch (section) { case 0: return i18nc("@info:whatsthis", "The task name is what you call the task, it can be chosen freely."); case 1: return i18nc("@info:whatsthis", "The session time is the time since you last chose \"Start New Session\"."); case 3: return i18nc("@info:whatsthis", "The total session time is the session time of this task and all its subtasks."); case 4: return i18nc("@info:whatsthis", "The total time is the time of this task and all its subtasks."); default: break; } break; default: break; } return QAbstractItemModel::headerData(section, orientation, role); } void TasksModel::setActiveIcon(int /*unused*/) { // TODO: only touch those tasks that are currently running for (auto *task : getAllItems()) { task->invalidateRunningState(); } } void TasksModel::addChild(TasksModelItem *item) { m_rootItem->addChild(item); } Qt::ItemFlags TasksModel::flags(const QModelIndex &index) const { if (!index.isValid()) { // Allow move to top-level return Qt::ItemIsDropEnabled; } return QAbstractItemModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } Qt::DropActions TasksModel::supportedDropActions() const { return Qt::MoveAction; } bool TasksModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (m_dragCutTaskId.isEmpty()) { return false; } return QAbstractItemModel::canDropMimeData(data, action, row, column, parent); } bool TasksModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (!canDropMimeData(data, action, row, column, parent)) { return false; } if (action == Qt::IgnoreAction) { return true; } auto *task = taskByUID(m_dragCutTaskId); if (!task) { return false; } // If have parent, move to subtasks of the parent task. // If don't have parent, move to top-level tasks. TasksModelItem *newParent = parent.isValid() ? item(parent) : m_rootItem; if (!newParent) { return false; } task->move(newParent); emit taskDropped(); return true; } QMimeData *TasksModel::mimeData(const QModelIndexList &indexes) const { auto *task = dynamic_cast(item(indexes[0])); if (!task) { return nullptr; } m_dragCutTaskId = task->uid(); return QAbstractItemModel::mimeData(indexes); } Task *TasksModel::taskByUID(const QString &uid) { for (auto *item : getAllItems()) { auto *task = dynamic_cast(item); if (task->uid() == uid) { return task; } } return nullptr; } void TasksModel::startNewSession() { for (Task *task : getAllTasks()) { task->startNewSession(); } } diff --git a/src/model/tasksmodel.h b/src/model/tasksmodel.h index 10f03ac..860f15d 100644 --- a/src/model/tasksmodel.h +++ b/src/model/tasksmodel.h @@ -1,103 +1,105 @@ /* * 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_TASKSMODEL_H #define KTIMETRACKER_TASKSMODEL_H #include QT_BEGIN_NAMESPACE class QMovie; QT_END_NAMESPACE class TasksModelItem; class Task; class TasksModel : public QAbstractItemModel { Q_OBJECT friend class TasksModelItem; public: TasksModel(); ~TasksModel() override = default; // Clears the tree widget by removing all of its items and selections. void clear(); int topLevelItemCount() const; int indexOfTopLevelItem(TasksModelItem *item) const; TasksModelItem *topLevelItem(int index) const; TasksModelItem *takeTopLevelItem(int index); QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex index(TasksModelItem *item, int column) const; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; TasksModelItem *item(const QModelIndex &index) const; QList getAllItems(); QList getAllTasks(); + QList getActiveTasks(); + void addChild(TasksModelItem *item); Qt::DropActions supportedDropActions() const override; bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; QMimeData *mimeData(const QModelIndexList &indexes) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; /** return the task with the given UID */ Task *taskByUID(const QString &uid); /* * Reset session time to zero for all tasks. * * This procedure starts a new session. We speak of session times, * overalltimes (comprising all sessions) and total times (comprising all subtasks). * That is why there is also a total session time. */ void startNewSession(); public Q_SLOTS: void setActiveIcon(int frameNumber); Q_SIGNALS: void taskCompleted(Task *task); void taskDropped(); private: TasksModelItem *m_rootItem; QStringList m_headerLabels; QMovie *m_clockAnimation; mutable QString m_dragCutTaskId; }; #endif // KTIMETRACKER_TASKSMODEL_H diff --git a/src/timetrackerstorage.cpp b/src/timetrackerstorage.cpp index 2451077..06fb340 100644 --- a/src/timetrackerstorage.cpp +++ b/src/timetrackerstorage.cpp @@ -1,279 +1,277 @@ /* * 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 "ktt_debug.h" #include "model/eventsmodel.h" #include "model/projectmodel.h" #include "model/task.h" #include "model/tasksmodel.h" #include "taskview.h" #include "widgets/taskswidget.h" const QByteArray eventAppName = QByteArray("ktimetracker"); TimeTrackerStorage::TimeTrackerStorage() : m_model(nullptr) , 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 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()); - } + for (Task *task : tasksModel()->getActiveTasks()) { + 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]); } } } 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; } 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, int64_t durationInSeconds) { return eventsModel()->bookTime(task, startDateTime, durationInSeconds); } void TimeTrackerStorage::onFileModified() { if (!m_model) { qCWarning(KTT_LOG) << "TaskView::onFileModified(): model is null"; return; } // TODO resolve conflicts if KTimeTracker has unsaved changes in its data structures qCDebug(KTT_LOG) << "entering function"; FileCalendar m_calendar(m_url); // m_calendar->setTimeSpec( KSystemTimeZones::local() ); m_calendar.reload(); buildTaskView(m_calendar.rawTodos(), m_taskView); m_taskView->storage()->projectModel()->refresh(); m_taskView->tasksWidget()->refresh(); qCDebug(KTT_LOG) << "exiting onFileModified"; } diff --git a/src/timetrackerwidget.cpp b/src/timetrackerwidget.cpp index 9a43787..0307a47 100644 --- a/src/timetrackerwidget.cpp +++ b/src/timetrackerwidget.cpp @@ -1,994 +1,992 @@ /* * Copyright (C) 2007 by Mathias Soeken * 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 "timetrackerwidget.h" #include #include #include #include #include #include #include "dialogs/edittimedialog.h" #include "dialogs/exportdialog.h" #include "dialogs/historydialog.h" #include "export/export.h" #include "idletimedetector.h" #include "ktimetracker-version.h" #include "ktimetracker.h" #include "ktimetrackerutility.h" #include "ktt_debug.h" #include "mainwindow.h" #include "model/eventsmodel.h" #include "model/projectmodel.h" #include "model/task.h" #include "model/tasksmodel.h" #include "reportcriteria.h" #include "settings/ktimetrackerconfigdialog.h" #include "taskview.h" #include "widgets/searchline.h" #include "widgets/taskswidget.h" TimeTrackerWidget::TimeTrackerWidget(QWidget *parent) : QWidget(parent) , m_searchLine(nullptr) , m_taskView(nullptr) , m_actionCollection(nullptr) { registerDBus(); QLayout* layout = new QVBoxLayout; layout->setMargin(0); layout->setSpacing(0); setLayout(layout); fillLayout(nullptr); } void TimeTrackerWidget::fillLayout(TasksWidget *tasksWidget) { // Remove all items from layout QLayoutItem *child; while ((child = layout()->takeAt(0)) != nullptr) { delete child->widget(); delete child; } if (tasksWidget) { m_searchLine = new SearchLine(this); connect(m_searchLine, &SearchLine::textSubmitted, this, &TimeTrackerWidget::slotAddTask); connect(m_searchLine, &SearchLine::searchUpdated, tasksWidget, &TasksWidget::setFilterText); layout()->addWidget(m_searchLine); layout()->addWidget(tasksWidget); showSearchBar(!KTimeTrackerSettings::configPDA() && KTimeTrackerSettings::showSearchBar()); } else { auto *placeholderWidget = new QWidget(this); placeholderWidget->setBackgroundRole(QPalette::Dark); placeholderWidget->setAutoFillBackground(true); layout()->addWidget(placeholderWidget); } } int TimeTrackerWidget::focusSearchBar() { if (m_searchLine->isEnabled()) { m_searchLine->setFocus(); } return 0; } void TimeTrackerWidget::addTaskView(const QUrl &url) { qCDebug(KTT_LOG) << "Entering function (url=" << url << ")"; closeFile(); m_taskView = new TaskView(this); connect(m_taskView, &TaskView::contextMenuRequested, this, &TimeTrackerWidget::contextMenuRequested); connect(m_taskView, &TaskView::timersActive, this, &TimeTrackerWidget::timersActive); connect(m_taskView, &TaskView::timersInactive, this, &TimeTrackerWidget::timersInactive); connect(m_taskView, &TaskView::tasksChanged, this, &TimeTrackerWidget::tasksChanged); emit setCaption(url.toString()); m_taskView->load(url); fillLayout(m_taskView->tasksWidget()); emit currentTaskViewChanged(); slotCurrentChanged(); } TaskView* TimeTrackerWidget::currentTaskView() const { return m_taskView; } Task* TimeTrackerWidget::currentTask() { auto *taskView = currentTaskView(); if (taskView == nullptr) { return nullptr; } auto *tasksWidget = taskView->tasksWidget(); if (tasksWidget == nullptr) { return nullptr; } return tasksWidget->currentItem(); } void TimeTrackerWidget::setupActions(KActionCollection* actionCollection) { Q_INIT_RESOURCE(pics); m_actionCollection = actionCollection; KStandardAction::open(this, &TimeTrackerWidget::openFileDialog, actionCollection); KStandardAction::save(this, &TimeTrackerWidget::saveFile, actionCollection); KStandardAction::preferences(this, &TimeTrackerWidget::showSettingsDialog, actionCollection); QAction* startNewSession = actionCollection->addAction(QStringLiteral("start_new_session")); startNewSession->setText(i18nc("@action:inmenu", "Start &New Session")); startNewSession->setToolTip(i18nc("@info:tooltip", "Starts a new session")); startNewSession->setWhatsThis(i18nc( "@info:whatsthis", "This will reset the " "session time to 0 for all tasks, to start a new session, without " "affecting the totals.")); connect(startNewSession, &QAction::triggered, this, &TimeTrackerWidget::startNewSession); QAction* editHistory = actionCollection->addAction(QStringLiteral("edit_history")); editHistory->setText(i18nc("@action:inmenu", "Edit History...")); editHistory->setToolTip(i18nc("@info:tooltip", "Edits history of all tasks of the current document")); editHistory->setWhatsThis(i18nc( "@info:whatsthis", "A window will " "be opened where you can change start and stop times of tasks or add a " "comment to them.")); editHistory->setIcon(QIcon::fromTheme("view-history")); connect(editHistory, &QAction::triggered, this, &TimeTrackerWidget::editHistory); QAction* resetAllTimes = actionCollection->addAction(QStringLiteral("reset_all_times")); resetAllTimes->setText(i18nc("@action:inmenu", "&Reset All Times")); resetAllTimes->setToolTip(i18nc("@info:tooltip", "Resets all times")); resetAllTimes->setWhatsThis(i18nc( "@info:whatsthis", "This will reset the session " "and total time to 0 for all tasks, to restart from scratch.")); connect(resetAllTimes, &QAction::triggered, this, &TimeTrackerWidget::resetAllTimes); QAction* startCurrentTimer = actionCollection->addAction(QStringLiteral("start")); startCurrentTimer->setText(i18nc("@action:inmenu", "&Start")); startCurrentTimer->setToolTip(i18nc("@info:tooltip", "Starts timing for selected task")); startCurrentTimer->setWhatsThis(i18nc( "@info:whatsthis", "This will start timing for the " "selected task.\nIt is even possible to time several tasks " "simultaneously.\n\nYou may also start timing of tasks by double clicking " "the left mouse button on a given task. This will, however, stop timing " "of other tasks.")); startCurrentTimer->setIcon(QIcon::fromTheme("media-playback-start")); actionCollection->setDefaultShortcut(startCurrentTimer, QKeySequence(Qt::Key_G)); connect(startCurrentTimer, &QAction::triggered, this, &TimeTrackerWidget::startCurrentTimer); QAction* stopCurrentTimer = actionCollection->addAction(QStringLiteral("stop")); stopCurrentTimer->setText(i18nc("@action:inmenu", "S&top")); stopCurrentTimer->setToolTip(i18nc("@info:tooltip", "Stops timing of the selected task")); stopCurrentTimer->setWhatsThis(i18nc("@info:whatsthis", "Stops timing of the selected task")); stopCurrentTimer->setIcon(QIcon::fromTheme("media-playback-stop")); connect(stopCurrentTimer, &QAction::triggered, this, &TimeTrackerWidget::stopCurrentTimer); QAction* focusSearchBar = actionCollection->addAction(QStringLiteral("focusSearchBar")); focusSearchBar->setText(i18nc("@action:inmenu", "Focus on Searchbar")); focusSearchBar->setToolTip(i18nc("@info:tooltip", "Sets the focus on the searchbar")); focusSearchBar->setWhatsThis(i18nc("@info:whatsthis", "Sets the focus on the searchbar")); actionCollection->setDefaultShortcut(focusSearchBar, QKeySequence(Qt::Key_S)); connect(focusSearchBar, &QAction::triggered, this, &TimeTrackerWidget::focusSearchBar); QAction* stopAllTimers = actionCollection->addAction(QStringLiteral("stopAll")); stopAllTimers->setText(i18nc("@action:inmenu", "Stop &All Timers")); stopAllTimers->setToolTip(i18nc("@info:tooltip", "Stops all of the active timers")); stopAllTimers->setWhatsThis(i18nc("@info:whatsthis", "Stops all of the active timers")); actionCollection->setDefaultShortcut(stopAllTimers, QKeySequence(Qt::Key_Escape)); connect(stopAllTimers, &QAction::triggered, this, &TimeTrackerWidget::stopAllTimers); QAction* focusTracking = actionCollection->addAction(QStringLiteral("focustracking")); focusTracking->setCheckable(true); focusTracking->setText(i18nc("@action:inmenu", "Track Active Applications")); focusTracking->setToolTip(i18nc( "@info:tooltip", "Auto-creates and updates tasks when the focus of the current window has changed")); focusTracking->setWhatsThis(i18nc( "@info:whatsthis", "If the focus of a window changes for the " "first time when this action is enabled, a new task will be created " "with the title of the window as its name and will be started. If there " "already exists such an task it will be started.")); connect(focusTracking, &QAction::triggered, this, &TimeTrackerWidget::focusTracking); QAction* newTask = actionCollection->addAction(QStringLiteral("new_task")); newTask->setText(i18nc("@action:inmenu", "&New Task...")); newTask->setToolTip(i18nc("@info:tooltip", "Creates new top level task")); newTask->setWhatsThis(i18nc("@info:whatsthis", "This will create a new top level task.")); newTask->setIcon(QIcon::fromTheme("document-new")); actionCollection->setDefaultShortcut(newTask, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(newTask, &QAction::triggered, this, &TimeTrackerWidget::newTask); QAction* newSubTask = actionCollection->addAction(QStringLiteral("new_sub_task")); newSubTask->setText(i18nc("@action:inmenu", "New &Subtask...")); newSubTask->setToolTip(i18nc("@info:tooltip", "Creates a new subtask to the current selected task")); newSubTask->setWhatsThis(i18nc("@info:whatsthis", "This will create a new subtask to the current selected task.")); newSubTask->setIcon(QIcon::fromTheme("subtask-new-ktimetracker")); actionCollection->setDefaultShortcut(newSubTask, QKeySequence(Qt::CTRL + Qt::Key_B)); connect(newSubTask, &QAction::triggered, this, &TimeTrackerWidget::newSubTask); QAction* deleteTask = actionCollection->addAction(QStringLiteral("delete_task")); deleteTask->setText(i18nc("@action:inmenu", "&Delete")); deleteTask->setToolTip(i18nc("@info:tooltip", "Deletes selected task")); deleteTask->setWhatsThis(i18nc("@info:whatsthis", "This will delete the selected task(s) and all subtasks.")); deleteTask->setIcon(QIcon::fromTheme("edit-delete")); actionCollection->setDefaultShortcut(deleteTask, QKeySequence(Qt::Key_Delete)); connect(deleteTask, &QAction::triggered, this, QOverload<>::of(&TimeTrackerWidget::deleteTask)); QAction* editTask = actionCollection->addAction(QStringLiteral("edit_task")); editTask->setText(i18nc("@action:inmenu", "&Properties")); editTask->setToolTip(i18nc("@info:tooltip", "Edit name or description for selected task")); editTask->setWhatsThis(i18nc( "@info:whatsthis", "This will bring up a dialog " "box where you may edit the parameters for the selected task.")); editTask->setIcon(QIcon::fromTheme("document-properties")); actionCollection->setDefaultShortcut(editTask, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(editTask, &QAction::triggered, this, &TimeTrackerWidget::editTask); QAction* editTaskTime = actionCollection->addAction(QStringLiteral("edit_task_time")); editTaskTime->setText(i18nc("@action:inmenu", "Edit &Time...")); editTaskTime->setToolTip(i18nc("@info:tooltip", "Edit time for selected task")); editTaskTime->setWhatsThis(i18nc( "@info:whatsthis", "This will bring up a dialog " "box where you may edit the times for the selected task.")); editTaskTime->setIcon(QIcon::fromTheme("document-edit")); actionCollection->setDefaultShortcut(editTaskTime, QKeySequence(Qt::Key_E)); connect(editTaskTime, &QAction::triggered, this, &TimeTrackerWidget::editTaskTime); QAction* markTaskAsComplete = actionCollection->addAction(QStringLiteral("mark_as_complete")); markTaskAsComplete->setText(i18nc("@action:inmenu", "&Mark as Complete")); markTaskAsComplete->setIcon(QPixmap(":/pics/task-complete.xpm")); actionCollection->setDefaultShortcut(markTaskAsComplete, QKeySequence(Qt::CTRL + Qt::Key_M)); connect(markTaskAsComplete, &QAction::triggered, this, &TimeTrackerWidget::markTaskAsComplete); QAction* markTaskAsIncomplete = actionCollection->addAction(QStringLiteral("mark_as_incomplete")); markTaskAsIncomplete->setText(i18nc("@action:inmenu", "&Mark as Incomplete")); markTaskAsIncomplete->setIcon(QPixmap(":/pics/task-incomplete.xpm")); actionCollection->setDefaultShortcut(markTaskAsIncomplete, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); connect(markTaskAsIncomplete, &QAction::triggered, this, &TimeTrackerWidget::markTaskAsIncomplete); QAction* exportAction = actionCollection->addAction(QStringLiteral("export_dialog")); exportAction->setText(i18nc("@action:inmenu", "&Export...")); exportAction->setIcon(QIcon::fromTheme("document-export")); connect(exportAction, &QAction::triggered, this, &TimeTrackerWidget::exportDialog); QAction* importPlanner = actionCollection->addAction(QStringLiteral("import_planner")); importPlanner->setText(i18nc("@action:inmenu", "Import Tasks From &Planner...")); importPlanner->setIcon(QIcon::fromTheme("document-import")); connect(importPlanner, &QAction::triggered, [=]() { const QString &fileName = QFileDialog::getOpenFileName(); importPlannerFile(fileName); }); QAction* showSearchBar = actionCollection->addAction(QStringLiteral("searchbar")); showSearchBar->setCheckable(true); showSearchBar->setChecked(KTimeTrackerSettings::showSearchBar()); showSearchBar->setText(i18nc("@action:inmenu", "Show Searchbar")); connect(showSearchBar, &QAction::triggered, this, &TimeTrackerWidget::slotSearchBar); connect(this, &TimeTrackerWidget::currentTaskChanged, this, &TimeTrackerWidget::slotUpdateButtons); connect(this, &TimeTrackerWidget::currentTaskViewChanged, this, &TimeTrackerWidget::slotUpdateButtons); connect(this, &TimeTrackerWidget::updateButtons, this, &TimeTrackerWidget::slotUpdateButtons); slotUpdateButtons(); } QAction *TimeTrackerWidget::action(const QString &name) const { return m_actionCollection->action(name); } void TimeTrackerWidget::openFile(const QUrl &url) { qCDebug(KTT_LOG) << "Entering function, url is " << url; addTaskView(url); } void TimeTrackerWidget::openFileDialog() { const QString &path = QFileDialog::getOpenFileName(this); if (!path.isEmpty()) { openFile(QUrl::fromLocalFile(path)); } } bool TimeTrackerWidget::closeFile() { qCDebug(KTT_LOG) << "Entering TimeTrackerWidget::closeFile"; TaskView* taskView = currentTaskView(); if (taskView) { taskView->save(); taskView->closeStorage(); } emit currentTaskViewChanged(); emit setCaption(QString()); slotCurrentChanged(); delete taskView; // removeTab does not delete its widget. m_taskView = nullptr; return true; } void TimeTrackerWidget::saveFile() { currentTaskView()->save(); } void TimeTrackerWidget::showSearchBar(bool visible) { m_searchLine->setVisible(visible); } bool TimeTrackerWidget::closeAllFiles() { qCDebug(KTT_LOG) << "Entering TimeTrackerWidget::closeAllFiles"; bool err = true; if (m_taskView) { m_taskView->stopAllTimers(); err = closeFile(); } return err; } void TimeTrackerWidget::slotCurrentChanged() { qDebug() << "entering KTimeTrackerWidget::slotCurrentChanged"; if (m_taskView) { connect(m_taskView, &TaskView::updateButtons, this, &TimeTrackerWidget::updateButtons, Qt::UniqueConnection); connect(m_taskView, &TaskView::setStatusBarText, this, &TimeTrackerWidget::statusBarTextChangeRequested, Qt::UniqueConnection); connect(m_taskView, &TaskView::timersActive, this, &TimeTrackerWidget::timersActive, Qt::UniqueConnection); connect(m_taskView, &TaskView::timersInactive, this, &TimeTrackerWidget::timersInactive, Qt::UniqueConnection); connect(m_taskView, &TaskView::tasksChanged, this, &TimeTrackerWidget::tasksChanged, Qt::UniqueConnection); emit setCaption(m_taskView->storage()->fileUrl().toString()); } } void TimeTrackerWidget::slotAddTask(const QString &taskName) { TaskView *taskView = currentTaskView(); taskView->addTask(taskName, QString(), 0, 0, DesktopList(), nullptr); } void TimeTrackerWidget::slotUpdateButtons() { Task *item = currentTask(); action(QStringLiteral("start"))->setEnabled(item && !item->isRunning() && !item->isComplete()); action(QStringLiteral("stop"))->setEnabled(item && item->isRunning()); action(QStringLiteral("delete_task"))->setEnabled(item); action(QStringLiteral("edit_task_time"))->setEnabled(item); action(QStringLiteral("edit_task"))->setEnabled(item); action(QStringLiteral("mark_as_complete"))->setEnabled(item && !item->isComplete()); action(QStringLiteral("mark_as_incomplete"))->setEnabled(item && item->isComplete()); action(QStringLiteral("new_task"))->setEnabled(currentTaskView()); action(QStringLiteral("new_sub_task"))->setEnabled(currentTaskView() && currentTaskView()->storage()->isLoaded() && currentTaskView()->storage()->tasksModel()->getAllTasks().size()); action(QStringLiteral("focustracking"))->setEnabled(currentTaskView()); action(QStringLiteral("focustracking"))->setChecked(currentTaskView() && currentTaskView()->isFocusTrackingActive()); action(QStringLiteral("start_new_session"))->setEnabled(currentTaskView()); action(QStringLiteral("edit_history"))->setEnabled(currentTaskView()); action(QStringLiteral("reset_all_times"))->setEnabled(currentTaskView()); action(QStringLiteral("export_dialog"))->setEnabled(currentTaskView()); action(QStringLiteral("import_planner"))->setEnabled(currentTaskView()); action(QStringLiteral("file_save"))->setEnabled(currentTaskView()); } void TimeTrackerWidget::showSettingsDialog() { if (KConfigDialog::showDialog("settings")) { return; } auto *dialog = new KConfigDialog(this, "settings", KTimeTrackerSettings::self()); dialog->setFaceType(KPageDialog::List); dialog->addPage(new KTimeTrackerBehaviorConfig(dialog), i18nc("@title:tab", "Behavior"), QStringLiteral("preferences-other")); dialog->addPage(new KTimeTrackerDisplayConfig(dialog), i18nc("@title:tab", "Appearance"), QStringLiteral("preferences-desktop-theme")); dialog->addPage(new KTimeTrackerStorageConfig(dialog), i18nc("@title:tab", "Storage"), QStringLiteral("system-file-manager")); connect(dialog, &KConfigDialog::settingsChanged, this, &TimeTrackerWidget::loadSettings); dialog->show(); } void TimeTrackerWidget::loadSettings() { KTimeTrackerSettings::self()->load(); showSearchBar(!KTimeTrackerSettings::configPDA() && KTimeTrackerSettings::showSearchBar()); currentTaskView()->reconfigureModel(); currentTaskView()->tasksWidget()->reconfigure(); } //BEGIN wrapper slots void TimeTrackerWidget::startCurrentTimer() { currentTaskView()->startCurrentTimer(); } void TimeTrackerWidget::stopCurrentTimer() { currentTaskView()->stopCurrentTimer(); } void TimeTrackerWidget::stopAllTimers() { currentTaskView()->stopAllTimers(QDateTime::currentDateTime()); } void TimeTrackerWidget::newTask() { currentTaskView()->newTask(i18nc("@title:window", "New Task"), nullptr); } void TimeTrackerWidget::newSubTask() { currentTaskView()->newSubTask(); } void TimeTrackerWidget::editTask() { currentTaskView()->editTask(); } void TimeTrackerWidget::editTaskTime() { qCDebug(KTT_LOG) <<"Entering editTask"; Task* task = currentTask(); if (!task) { return; } QPointer editTimeDialog = new EditTimeDialog( this, task->name(), task->description(), static_cast(task->time())); if (editTimeDialog->exec() == QDialog::Accepted) { if (editTimeDialog->editHistoryRequested()) { editHistory(); } else { currentTaskView()->editTaskTime(task->uid(), editTimeDialog->changeMinutes()); } } delete editTimeDialog; } void TimeTrackerWidget::deleteTask() { currentTaskView()->deleteTask(); } void TimeTrackerWidget::markTaskAsComplete() { currentTaskView()->markTaskAsComplete(); } void TimeTrackerWidget::markTaskAsIncomplete() { currentTaskView()->markTaskAsIncomplete(); } void TimeTrackerWidget::exportDialog() { qCDebug(KTT_LOG) << "TimeTrackerWidget::exportDialog()"; auto *taskView = currentTaskView(); ExportDialog dialog(taskView->tasksWidget(), taskView); if (taskView->tasksWidget()->currentItem() && taskView->tasksWidget()->currentItem()->isRoot()) { dialog.enableTasksToExportQuestion(); } dialog.exec(); } void TimeTrackerWidget::startNewSession() { currentTaskView()->storage()->tasksModel()->startNewSession(); } void TimeTrackerWidget::editHistory() { // HistoryDialog is the new HistoryDialog, but the EditHiStoryDiaLog exists as well. // HistoryDialog can be edited with qtcreator and qtdesigner, EditHiStoryDiaLog cannot. if (currentTaskView()) { QPointer dialog = new HistoryDialog( currentTaskView()->tasksWidget(), currentTaskView()->storage()->projectModel()); if (currentTaskView()->storage()->eventsModel()->events().count() != 0) { dialog->exec(); } else { KMessageBox::information(nullptr, i18nc("@info in message box", "There is no history yet. Start and stop a task and you will have an entry in your history.")); } } } void TimeTrackerWidget::resetAllTimes() { if (currentTaskView()) { if (KMessageBox::warningContinueCancel( this, i18n("Do you really want to reset the time to zero for all tasks? This will delete the entire history."), i18nc("@title:window", "Confirmation Required"), KGuiItem(i18nc("@action:button", "Reset All Times"))) == KMessageBox::Continue) { currentTaskView()->storage()->projectModel()->resetTimeForAllTasks(); } } } void TimeTrackerWidget::focusTracking() { currentTaskView()->toggleFocusTracking(); action(QStringLiteral("focustracking"))->setChecked(currentTaskView()->isFocusTrackingActive()); } void TimeTrackerWidget::slotSearchBar() { bool currentVisible = KTimeTrackerSettings::showSearchBar(); KTimeTrackerSettings::setShowSearchBar(!currentVisible); action(QStringLiteral("searchbar"))->setChecked(!currentVisible); showSearchBar(!currentVisible); } //END /** \defgroup dbus slots ‘‘dbus slots’’ */ /* @{ */ #include "mainadaptor.h" void TimeTrackerWidget::registerDBus() { new MainAdaptor(this); QDBusConnection::sessionBus().registerObject("/KTimeTracker", this); } QString TimeTrackerWidget::version() const { return KTIMETRACKER_VERSION_STRING; } QStringList TimeTrackerWidget::taskIdsFromName( const QString &taskName ) const { QStringList result; TaskView *taskView = currentTaskView(); if (!taskView) { return result; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->name() == taskName) { result << task->uid(); } } return result; } void TimeTrackerWidget::addTask( const QString &taskName ) { TaskView *taskView = currentTaskView(); if (taskView) { taskView->addTask(taskName, QString(), 0, 0, DesktopList(), nullptr); } } void TimeTrackerWidget::addSubTask(const QString &taskName, const QString &taskId) { TaskView *taskView = currentTaskView(); if (taskView) { taskView->addTask(taskName, QString(), 0, 0, DesktopList(), taskView->storage()->tasksModel()->taskByUID(taskId)); taskView->storage()->projectModel()->refresh(); taskView->tasksWidget()->refresh(); } } void TimeTrackerWidget::deleteTask(const QString &taskId) { TaskView *taskView = currentTaskView(); if (!taskView) { return; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { taskView->deleteTaskBatch(task); break; } } } void TimeTrackerWidget::setPercentComplete(const QString &taskId, int percent) { TaskView *taskView = currentTaskView(); if (!taskView) { return; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { task->setPercentComplete(percent); } } } int TimeTrackerWidget::bookTime(const QString &taskId, const QString &dateTime, int64_t minutes) { QDate startDate; QTime startTime; QDateTime startDateTime; if (minutes <= 0) { return KTIMETRACKER_ERR_INVALID_DURATION; } Task *task = nullptr; TaskView *taskView = currentTaskView(); if (taskView) { for (Task *t : taskView->storage()->tasksModel()->getAllTasks()) { if (t->uid() == taskId) { task = t; break; } } } if (!task) { return KTIMETRACKER_ERR_UID_NOT_FOUND; } // Parse datetime startDate = QDate::fromString(dateTime, Qt::ISODate); if (dateTime.length() > 10) { // "YYYY-MM-DD".length() = 10 startTime = QTime::fromString(dateTime, Qt::ISODate); } else { startTime = QTime(12, 0); } if (startDate.isValid() && startTime.isValid()) { startDateTime = QDateTime(startDate, startTime); } else { return KTIMETRACKER_ERR_INVALID_DATE; } // Update task totals (session and total) and save to disk task->changeTotalTimes(task->sessionTime() + minutes, task->totalTime() + minutes); if (!taskView->storage()->bookTime(task, startDateTime, minutes * 60)) { return KTIMETRACKER_ERR_GENERIC_SAVE_FAILED; } return 0; } int TimeTrackerWidget::changeTime(const QString &taskId, int64_t minutes) { if (minutes <= 0) { return KTIMETRACKER_ERR_INVALID_DURATION; } // Find task TaskView *taskView = currentTaskView(); if (!taskView) { //FIXME: it mimics the behaviour with the for loop, but I am not sure semantics were right. Maybe a new error code must be defined? return KTIMETRACKER_ERR_UID_NOT_FOUND; } Task *task = nullptr; for (Task *t : taskView->storage()->tasksModel()->getAllTasks()) { if (t->uid() == taskId) { task = t; break; } } if (!task) { return KTIMETRACKER_ERR_UID_NOT_FOUND; } task->changeTime(minutes, taskView->storage()->eventsModel()); return 0; } QString TimeTrackerWidget::error( int errorCode ) const { switch ( errorCode ) { case KTIMETRACKER_ERR_GENERIC_SAVE_FAILED: return i18n( "Save failed, most likely because the file could not be locked." ); case KTIMETRACKER_ERR_COULD_NOT_MODIFY_RESOURCE: return i18n( "Could not modify calendar resource." ); case KTIMETRACKER_ERR_MEMORY_EXHAUSTED: return i18n( "Out of memory--could not create object." ); case KTIMETRACKER_ERR_UID_NOT_FOUND: return i18n( "UID not found." ); case KTIMETRACKER_ERR_INVALID_DATE: return i18n( "Invalidate date--format is YYYY-MM-DD." ); case KTIMETRACKER_ERR_INVALID_TIME: return i18n( "Invalid time--format is YYYY-MM-DDTHH:MM:SS." ); case KTIMETRACKER_ERR_INVALID_DURATION: return i18n( "Invalid task duration--must be greater than zero." ); default: return i18n( "Invalid error number: %1", errorCode ); } } bool TimeTrackerWidget::isIdleDetectionPossible() const { return IdleTimeDetector::isIdleDetectionPossible(); } int TimeTrackerWidget::totalMinutesForTaskId(const QString &taskId) const { TaskView *taskView = currentTaskView(); if (!taskView) { return -1; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { return task->totalTime(); } } return -1; } void TimeTrackerWidget::startTimerFor(const QString &taskId) { qDebug(); TaskView *taskView = currentTaskView(); if (!taskView) { return; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { taskView->startTimerForNow(task); return; } } } bool TimeTrackerWidget::startTimerForTaskName( const QString &taskName ) { TaskView *taskView = currentTaskView(); if (!taskView) { return false; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->name() == taskName ) { taskView->startTimerForNow(task); return true; } } return false; } bool TimeTrackerWidget::stopTimerForTaskName(const QString &taskName) { TaskView *taskView = currentTaskView(); if (!taskView) { return false; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->name() == taskName) { taskView->stopTimerFor(task); return true; } } return false; } void TimeTrackerWidget::stopTimerFor(const QString &taskId) { TaskView *taskView = currentTaskView(); if (!taskView) { return; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { taskView->stopTimerFor(task); return; } } } void TimeTrackerWidget::stopAllTimersDBUS() { TaskView *taskView = currentTaskView(); if (taskView) { taskView->stopAllTimers(); } } QString TimeTrackerWidget::exportCSVFile( const QString &filename, const QString &from, const QString &to, int type, bool decimalMinutes, bool allTasks, const QString &delimiter, const QString "e) { TaskView *taskView = currentTaskView(); if (!taskView) { return ""; } ReportCriteria rc; rc.from = QDate::fromString(from); if (rc.from.isNull()) { rc.from = QDate::fromString(from, Qt::ISODate); } rc.to = QDate::fromString(to); if (rc.to.isNull()) { rc.to = QDate::fromString(to, Qt::ISODate); } rc.reportType = static_cast(type); rc.decimalMinutes = decimalMinutes; rc.allTasks = allTasks; rc.delimiter = delimiter; rc.quote = quote; QString output = exportToString(taskView->storage()->projectModel(), taskView->tasksWidget()->currentItem(), rc); return writeExport(output, QUrl::fromLocalFile(filename)); } void TimeTrackerWidget::importPlannerFile(const QString &filename) { TaskView *taskView = currentTaskView(); if (!taskView) { return; } taskView->importPlanner(filename); } bool TimeTrackerWidget::isActive(const QString &taskId) const { TaskView *taskView = currentTaskView(); if (!taskView) { return false; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->uid() == taskId) { return task->isRunning(); } } return false; } bool TimeTrackerWidget::isTaskNameActive(const QString &taskName) const { TaskView *taskView = currentTaskView(); if (!taskView) { return false; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { if (task->name() == taskName) { return task->isRunning(); } } return false; } QStringList TimeTrackerWidget::tasks() const { QStringList result; TaskView *taskView = currentTaskView(); if (!taskView) { return result; } for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { result << task->name(); } return result; } QStringList TimeTrackerWidget::activeTasks() const { QStringList result; TaskView* taskView = currentTaskView(); if (!taskView) { return result; } - for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) { - if (task->isRunning()) { - result << task->name(); - } + for (Task *task : taskView->storage()->tasksModel()->getActiveTasks()) { + result << task->name(); } return result; } void TimeTrackerWidget::saveAll() { currentTaskView()->save(); } void TimeTrackerWidget::quit() { auto* mainWindow = dynamic_cast(parent()->parent()); if (mainWindow) { mainWindow->quit(); } else { qCWarning(KTT_LOG) << "Cast to MainWindow failed"; } } bool TimeTrackerWidget::event(QEvent *event) // inherited from QWidget { if (event->type() == QEvent::QueryWhatsThis) { if (m_taskView->storage()->tasksModel()->getAllTasks().empty()) { setWhatsThis(i18nc( "@info:whatsthis", "This is ktimetracker, KDE's program to help you track your time. " "Best, start with creating your first task - enter it into the field " "where you see \"Search or add task\".")); } else { setWhatsThis(i18nc( "@info:whatsthis", "You have already created a task. You can now start and stop timing.")); } } return QWidget::event(event); } // END of dbus slots group /* @} */