diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 444ddf2..cf16fc0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,92 +1,92 @@ set(ktimetracker_SRCS dialogs/edittimedialog.cpp dialogs/taskpropertiesdialog.cpp export/totalsastext.cpp export/csvhistory.cpp export/csvtotals.cpp export/export.cpp file/filecalendar.cpp file/icalformatkio.cpp model/event.cpp model/eventsmodel.cpp model/projectmodel.cpp model/task.cpp model/tasksmodel.cpp model/tasksmodelitem.cpp settings/ktimetrackerconfigdialog.cpp widgets/searchline.cpp widgets/taskswidget.cpp - csvexportdialog.cpp + dialogs/csvexportdialog.cpp desktoptracker.cpp focusdetector.cpp - historydialog.cpp + dialogs/historydialog.cpp idletimedetector.cpp ktimetrackerutility.cpp mainwindow.cpp import/plannerparser.cpp taskview.cpp timetrackerstorage.cpp timetrackerwidget.cpp tray.cpp treeviewheadercontextmenu.cpp $ $ ) ecm_qt_declare_logging_category(ktimetracker_SRCS HEADER ktt_debug.h IDENTIFIER KTT_LOG CATEGORY_NAME log_ktt ) qt5_add_dbus_adaptor(ktimetracker_SRCS org.kde.ktimetracker.ktimetracker.xml timetrackerwidget.h TimeTrackerWidget mainadaptor MainAdaptor ) ki18n_wrap_ui(ktimetracker_SRCS - csvexportdialog.ui - historydialog.ui + dialogs/csvexportdialog.ui + dialogs/historydialog.ui settings/cfgbehavior.ui settings/cfgdisplay.ui settings/cfgstorage.ui ) kconfig_add_kcfg_files(ktimetracker_SRCS settings/ktimetracker.kcfgc) qt5_add_resources(ktimetracker_SRCS ktimetracker.qrc) add_library(libktimetracker STATIC ${ktimetracker_SRCS}) target_link_libraries(libktimetracker KF5::ConfigWidgets KF5::WindowSystem KF5::Notifications KF5::I18n KF5::XmlGui KF5::JobWidgets KF5::KIOCore KF5::IdleTime KF5::DBusAddons KF5::CalendarCore KF5::TextWidgets ) add_executable(ktimetracker main.cpp) target_link_libraries(ktimetracker libktimetracker) install(TARGETS ktimetracker ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES org.kde.ktimetracker.ktimetracker.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) install(PROGRAMS org.kde.ktimetracker.desktop DESTINATION ${KDE_INSTALL_APPDIR}) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/src/csvexportdialog.cpp b/src/dialogs/csvexportdialog.cpp similarity index 100% rename from src/csvexportdialog.cpp rename to src/dialogs/csvexportdialog.cpp diff --git a/src/csvexportdialog.h b/src/dialogs/csvexportdialog.h similarity index 100% rename from src/csvexportdialog.h rename to src/dialogs/csvexportdialog.h diff --git a/src/csvexportdialog.ui b/src/dialogs/csvexportdialog.ui similarity index 100% rename from src/csvexportdialog.ui rename to src/dialogs/csvexportdialog.ui diff --git a/src/historydialog.cpp b/src/dialogs/historydialog.cpp similarity index 100% rename from src/historydialog.cpp rename to src/dialogs/historydialog.cpp diff --git a/src/historydialog.h b/src/dialogs/historydialog.h similarity index 100% rename from src/historydialog.h rename to src/dialogs/historydialog.h diff --git a/src/historydialog.ui b/src/dialogs/historydialog.ui similarity index 100% rename from src/historydialog.ui rename to src/dialogs/historydialog.ui diff --git a/src/taskview.cpp b/src/taskview.cpp index 10d4979..6ceefea 100644 --- a/src/taskview.cpp +++ b/src/taskview.cpp @@ -1,814 +1,814 @@ /* * Copyright (C) 2003 by Scott Monachello * 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 "taskview.h" #include #include #include #include #include #include -#include "csvexportdialog.h" +#include "dialogs/csvexportdialog.h" #include "desktoptracker.h" #include "dialogs/edittimedialog.h" #include "dialogs/taskpropertiesdialog.h" #include "export/export.h" #include "focusdetector.h" -#include "historydialog.h" +#include "dialogs/historydialog.h" #include "idletimedetector.h" #include "import/plannerparser.h" #include "ktimetracker.h" #include "ktimetrackerutility.h" #include "ktt_debug.h" #include "model/eventsmodel.h" #include "model/task.h" #include "model/tasksmodel.h" #include "treeviewheadercontextmenu.h" #include "widgets/taskswidget.h" void deleteEntry(const QString& key) { KConfigGroup config = KSharedConfig::openConfig()->group(QString()); config.deleteEntry(key); config.sync(); } TaskView::TaskView(QWidget *parent) : QObject(parent) , m_filterProxyModel(new QSortFilterProxyModel(this)) , m_storage(new TimeTrackerStorage()) , m_focusTrackingActive(false) , m_lastTaskWithFocus(nullptr) , m_focusDetector(new FocusDetector()) , m_tasksWidget(nullptr) { m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterProxyModel->setRecursiveFilteringEnabled(true); m_filterProxyModel->setSortRole(Task::SortRole); connect(m_focusDetector, &FocusDetector::newFocus, this, &TaskView::newFocusWindowDetected); // set up the minuteTimer m_minuteTimer = new QTimer(this); connect(m_minuteTimer, &QTimer::timeout, this, &TaskView::minuteUpdate); m_minuteTimer->start(1000 * secsPerMinute); // Set up the idle detection. m_idleTimeDetector = new IdleTimeDetector(KTimeTrackerSettings::period()); connect(m_idleTimeDetector, &IdleTimeDetector::subtractTime, this, &TaskView::subtractTime); connect(m_idleTimeDetector, &IdleTimeDetector::stopAllTimers, this, &TaskView::stopAllTimers); if (!IdleTimeDetector::isIdleDetectionPossible()) { KTimeTrackerSettings::setEnabled(false); } // Setup auto save timer m_autoSaveTimer = new QTimer(this); connect(m_autoSaveTimer, &QTimer::timeout, this, &TaskView::save); // Setup manual save timer (to save changes a little while after they happen) m_manualSaveTimer = new QTimer(this); m_manualSaveTimer->setSingleShot( true ); connect(m_manualSaveTimer, &QTimer::timeout, this, &TaskView::save); // Connect desktop tracker events to task starting/stopping m_desktopTracker = new DesktopTracker(); connect(m_desktopTracker, &DesktopTracker::reachedActiveDesktop, this, &TaskView::startTimerForNow); connect(m_desktopTracker, &DesktopTracker::leftActiveDesktop, this, &TaskView::stopTimerFor); } void TaskView::newFocusWindowDetected(const QString &taskName) { QString newTaskName = taskName; newTaskName.remove('\n'); if (!m_focusTrackingActive) { return; } bool found = false; // has taskName been found in our tasks stopTimerFor(m_lastTaskWithFocus); for (Task *task : storage()->tasksModel()->getAllTasks()) { if (task->name() == newTaskName) { found = true; startTimerForNow(task); m_lastTaskWithFocus = task; } } if (!found) { if (!addTask(newTaskName)) { KMessageBox::error( nullptr, i18n("Error storing new task. Your changes were not saved. " "Make sure you can edit your iCalendar file. " "Also quit all applications using this file and remove " "any lock file related to its name from ~/.kde/share/apps/kabc/lock/ ")); } save(); for (Task *task : storage()->tasksModel()->getAllTasks()) { if (task->name() == newTaskName) { startTimerForNow(task); m_lastTaskWithFocus = task; } } } emit updateButtons(); } TimeTrackerStorage *TaskView::storage() { return m_storage; } TaskView::~TaskView() { delete m_storage; KTimeTrackerSettings::self()->save(); } void TaskView::load(const QUrl &url) { if (m_tasksWidget) { delete m_tasksWidget; m_tasksWidget = nullptr; } // if the program is used as an embedded plugin for konqueror, there may be a need // to load from a file without touching the preferences. QString err = m_storage->load(this, url); if (!err.isEmpty()) { KMessageBox::error(m_tasksWidget, err); qCDebug(KTT_LOG) << "Leaving TaskView::load"; return; } m_tasksWidget = new TasksWidget(dynamic_cast(parent()), m_filterProxyModel, nullptr); connect(m_tasksWidget, &TasksWidget::updateButtons, this, &TaskView::updateButtons); connect(m_tasksWidget, &TasksWidget::contextMenuRequested, this, &TaskView::contextMenuRequested); connect(m_tasksWidget, &TasksWidget::taskDoubleClicked, this, &TaskView::onTaskDoubleClicked); m_tasksWidget->setRootIsDecorated(true); reconfigure(); // Connect to the new model created by TimeTrackerStorage::load() auto *tasksModel = m_storage->tasksModel(); m_filterProxyModel->setSourceModel(tasksModel); m_tasksWidget->setSourceModel(tasksModel); for (int i = 0; i <= tasksModel->columnCount(QModelIndex()); ++i) { m_tasksWidget->resizeColumnToContents(i); } // Table header context menu auto *headerContextMenu = new TreeViewHeaderContextMenu(this, m_tasksWidget, QVector{0}); connect(headerContextMenu, &TreeViewHeaderContextMenu::columnToggled, this, &TaskView::slotColumnToggled); connect(tasksModel, &TasksModel::taskCompleted, this, &TaskView::stopTimerFor); connect(tasksModel, &TasksModel::taskDropped, this, &TaskView::reFreshTimes); connect(tasksModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TaskView::taskAboutToBeRemoved); connect(storage()->eventsModel(), &EventsModel::timesChanged, this, &TaskView::reFreshTimes); // Register tasks with desktop tracker for (Task *task : storage()->tasksModel()->getAllTasks()) { m_desktopTracker->registerForDesktops(task, task->desktops()); } // Start all tasks that have an event without endtime for (Task *task : storage()->tasksModel()->getAllTasks()) { if (!m_storage->allEventsHaveEndTime(task)) { task->resumeRunning(); m_activeTasks.append(task); emit updateButtons(); if (m_activeTasks.count() == 1) { emit timersActive(); } emit tasksChanged(m_activeTasks); } } if (tasksModel->topLevelItemCount() > 0) { m_tasksWidget->restoreItemState(); m_tasksWidget->setCurrentIndex(m_filterProxyModel->mapFromSource( tasksModel->index(tasksModel->topLevelItem(0), 0))); if (!m_desktopTracker->startTracking().isEmpty()) { KMessageBox::error(nullptr, i18n("Your virtual desktop number is too high, desktop tracking will not work.")); } refresh(); } for (int i = 0; i <= tasksModel->columnCount(QModelIndex()); ++i) { m_tasksWidget->resizeColumnToContents(i); } } void TaskView::closeStorage() { m_storage->closeStorage(); } bool TaskView::allEventsHaveEndTiMe() { return m_storage->allEventsHaveEndTime(); } void TaskView::refresh() { if (!m_tasksWidget) { return; } qCDebug(KTT_LOG) << "entering function"; for (Task *task : storage()->tasksModel()->getAllTasks()) { task->invalidateCompletedState(); task->update(); // maybe there was a change in the times's format } // remove root decoration if there is no more child. // int i = 0; // while (itemAt(++i) && itemAt(i)->depth() == 0){}; //setRootIsDecorated( itemAt( i ) && ( itemAt( i )->depth() != 0 ) ); // FIXME workaround? seems that the QItemDelegate for the percent column only // works properly if rootIsDecorated == true. m_tasksWidget->setRootIsDecorated(true); emit updateButtons(); qCDebug(KTT_LOG) << "exiting TaskView::refresh()"; } /** * Refresh the times of the tasks, e.g. when the history has been changed by the user. * Re-calculate the time for every task based on events in the history. */ QString TaskView::reFreshTimes() { QString err; // This procedure resets all times (session and overall) for all tasks and subtasks. // Reset session and total time for all tasks - do not touch the storage. for (Task *task : storage()->tasksModel()->getAllTasks()) { task->resetTimes(); } for (Task *task : storage()->tasksModel()->getAllTasks()) { // get all events for task for (const auto *event : storage()->eventsModel()->eventsForTask(task)) { QDateTime eventStart = event->dtStart(); QDateTime eventEnd = event->dtEnd(); const int64_t duration = event->duration() / 60; task->addTime(duration); qCDebug(KTT_LOG) << "duration is" << duration; if (task->sessionStartTiMe().isValid()) { // if there is a session if (task->sessionStartTiMe().secsTo(eventStart) > 0 && task->sessionStartTiMe().secsTo(eventEnd) > 0) { // if the event is after the session start task->addSessionTime(duration); } } else { // so there is no session at all task->addSessionTime(duration); } } } // Recalculate total times after changing hierarchy by drag&drop for (Task *task : storage()->tasksModel()->getAllTasks()) { // Start recursive method recalculateTotalTimesSubtree() for each top-level task. if (task->isRoot()) { task->recalculateTotalTimesSubtree(); } } refresh(); qCDebug(KTT_LOG) << "Leaving TaskView::reFreshTimes()"; return err; } void TaskView::importPlanner(const QString& fileName) { qCDebug(KTT_LOG) << "entering importPlanner"; auto *handler = new PlannerParser(storage()->projectModel(), m_tasksWidget->currentItem()); QFile xmlFile(fileName); QXmlInputSource source(&xmlFile); QXmlSimpleReader reader; reader.setContentHandler(handler); reader.parse(source); refresh(); } void TaskView::scheduleSave() { m_manualSaveTimer->start(10); } void TaskView::save() { qCDebug(KTT_LOG) << "Entering TaskView::save()"; QString err = m_storage->save(); if (!err.isNull()) { KMessageBox::error(m_tasksWidget, err); } } void TaskView::startCurrentTimer() { startTimerForNow(m_tasksWidget->currentItem()); } void TaskView::startTimerFor(Task *task, const QDateTime &startTime) { qCDebug(KTT_LOG) << "Entering function"; if (task != nullptr && m_activeTasks.indexOf(task) == -1) { if (!task->isComplete()) { if (KTimeTrackerSettings::uniTasking()) { stopAllTimers(); } m_idleTimeDetector->startIdleDetection(); task->setRunning(true, startTime); save(); m_activeTasks.append(task); emit updateButtons(); if (m_activeTasks.count() == 1) { emit timersActive(); } emit tasksChanged(m_activeTasks); } } } void TaskView::startTimerForNow(Task *task) { startTimerFor(task, QDateTime::currentDateTime()); } void TaskView::clearActiveTasks() { m_activeTasks.clear(); } void TaskView::stopAllTimers(const QDateTime& when) { qCDebug(KTT_LOG) << "Entering function"; QProgressDialog dialog( i18nc("@info:progress", "Stopping timers..."), i18n("Cancel"), 0, m_activeTasks.count(), m_tasksWidget); if (m_activeTasks.count() > 1) { dialog.show(); } for (Task *task : m_activeTasks) { QApplication::processEvents(); task->setRunning(false, when); save(); dialog.setValue(dialog.value() + 1); } m_idleTimeDetector->stopIdleDetection(); m_activeTasks.clear(); emit updateButtons(); emit timersInactive(); emit tasksChanged(m_activeTasks); } void TaskView::toggleFocusTracking() { m_focusTrackingActive = !m_focusTrackingActive; if (m_focusTrackingActive) { // FIXME: should get the currently active window and start tracking it? } else { stopTimerFor(m_lastTaskWithFocus); } emit updateButtons(); } void TaskView::startNewSession() /* 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. */ { qCDebug(KTT_LOG) <<"Entering TaskView::startNewSession"; for (Task *task : storage()->tasksModel()->getAllTasks()) { task->startNewSession(); } qCDebug(KTT_LOG) << "Leaving TaskView::startNewSession"; } void TaskView::resetTimeForAllTasks() /* This procedure resets all times (session and overall) for all tasks and subtasks. */ { qCDebug(KTT_LOG) << "Entering function"; for (Task *task : storage()->tasksModel()->getAllTasks()) { task->resetTimes(); } storage()->deleteAllEvents(); qCDebug(KTT_LOG) << "Leaving function"; } void TaskView::stopTimerFor(Task* task) { qCDebug(KTT_LOG) << "Entering function"; if (task != nullptr && m_activeTasks.indexOf(task) != -1) { m_activeTasks.removeAll(task); task->setRunning(false); save(); if (m_activeTasks.count() == 0) { m_idleTimeDetector->stopIdleDetection(); emit timersInactive(); } emit updateButtons(); } emit tasksChanged(m_activeTasks); } void TaskView::stopCurrentTimer() { stopTimerFor(m_tasksWidget->currentItem()); if (m_focusTrackingActive && m_lastTaskWithFocus == m_tasksWidget->currentItem()) { toggleFocusTracking(); } } void TaskView::minuteUpdate() { addTimeToActiveTasks(1, false); } void TaskView::addTimeToActiveTasks(int minutes, bool save_data) { for (Task *task : m_activeTasks) { task->changeTime(minutes, save_data ? m_storage->eventsModel() : nullptr); } scheduleSave(); } void TaskView::newTask() { newTask(i18nc("@title:window", "New Task"), nullptr); } void TaskView::newTask(const QString &caption, Task *parent) { QPointer dialog = new TaskPropertiesDialog( m_tasksWidget->parentWidget(), caption, QString(), QString(), DesktopList()); if (dialog->exec() == QDialog::Accepted) { QString taskName = i18n("Unnamed Task"); if (!dialog->name().isEmpty()) { taskName = dialog->name(); } QString taskDescription = dialog->description(); auto desktopList = dialog->desktops(); // If all available desktops are checked, disable auto tracking, // since it makes no sense to track for every desktop. if (desktopList.size() == m_desktopTracker->desktopCount()) { desktopList.clear(); } int64_t total = 0; int64_t session = 0; auto *task = addTask(taskName, taskDescription, total, session, desktopList, parent); save(); if (!task) { KMessageBox::error(nullptr, i18n( "Error storing new task. Your changes were not saved. " "Make sure you can edit your iCalendar file. Also quit " "all applications using this file and remove any lock " "file related to its name from ~/.kde/share/apps/kabc/lock/")); } } delete dialog; emit updateButtons(); } Task *TaskView::addTask( const QString& taskname, const QString& taskdescription, int64_t total, int64_t session, const DesktopList& desktops, Task* parent) { qCDebug(KTT_LOG) << "Entering function; taskname =" << taskname; m_tasksWidget->setSortingEnabled(false); Task *task = new Task( taskname, taskdescription, total, session, desktops, storage()->projectModel(), parent); if (task->uid().isNull()) { qFatal("failed to generate UID"); } m_desktopTracker->registerForDesktops(task, desktops); m_tasksWidget->setCurrentIndex(m_filterProxyModel->mapFromSource(storage()->tasksModel()->index(task, 0))); task->invalidateCompletedState(); m_tasksWidget->setSortingEnabled(true); return task; } void TaskView::newSubTask() { Task* task = m_tasksWidget->currentItem(); if (!task) { return; } newTask(i18nc("@title:window", "New Sub Task"), task); m_tasksWidget->setExpanded(m_filterProxyModel->mapFromSource(storage()->tasksModel()->index(task, 0)), true); refresh(); } void TaskView::editTask() { qCDebug(KTT_LOG) <<"Entering editTask"; Task* task = m_tasksWidget->currentItem(); if (!task) { return; } auto oldDeskTopList = task->desktops(); QPointer dialog = new TaskPropertiesDialog( m_tasksWidget->parentWidget(), i18nc("@title:window", "Edit Task"), task->name(), task->description(), oldDeskTopList); if (dialog->exec() == QDialog::Accepted) { QString name = i18n("Unnamed Task"); if (!dialog->name().isEmpty()) { name = dialog->name(); } // setName only does something if the new name is different task->setName(name); task->setDescription(dialog->description()); auto desktopList = dialog->desktops(); // If all available desktops are checked, disable auto tracking, // since it makes no sense to track for every desktop. if (desktopList.size() == m_desktopTracker->desktopCount()) { desktopList.clear(); } // only do something for autotracking if the new setting is different if (oldDeskTopList != desktopList) { task->setDesktopList(desktopList); m_desktopTracker->registerForDesktops(task, desktopList); } emit updateButtons(); } delete dialog; } void TaskView::editTaskTime() { qCDebug(KTT_LOG) <<"Entering editTask"; Task* task = m_tasksWidget->currentItem(); if (!task) { return; } QPointer editTimeDialog = new EditTimeDialog( m_tasksWidget->parentWidget(), task->name(), task->description(), static_cast(task->time())); if (editTimeDialog->exec() == QDialog::Accepted) { if (editTimeDialog->editHistoryRequested()) { editHistory(); } else { editTaskTime(task->uid(), editTimeDialog->changeMinutes()); } } delete editTimeDialog; } void TaskView::editHistory() { QPointer dialog = new HistoryDialog(m_tasksWidget->parentWidget(), storage()->projectModel()); dialog->exec(); } void TaskView::setPerCentComplete(int completion) { Task* task = m_tasksWidget->currentItem(); if (!task) { KMessageBox::information(nullptr, i18n("No task selected.")); return; } if (completion < 0) { completion = 0; } if (completion < 100) { task->setPercentComplete(completion); task->invalidateCompletedState(); emit updateButtons(); } } void TaskView::deleteTaskBatch(Task* task) { QString uid = task->uid(); task->remove(m_storage); deleteEntry(uid); // forget if the item was expanded or collapsed // Stop idle detection if no more counters are running if (m_activeTasks.count() == 0) { m_idleTimeDetector->stopIdleDetection(); emit timersInactive(); } task->delete_recursive(); emit tasksChanged(m_activeTasks); } void TaskView::deleteTask(Task* task) /* Attention when popping up a window asking for confirmation. If you have "Track active applications" on, this window will create a new task and make this task running and selected. */ { if (!task) { task = m_tasksWidget->currentItem(); } if (!m_tasksWidget->currentItem()) { KMessageBox::information(nullptr, i18n("No task selected.")); } else { int response = KMessageBox::Continue; if (KTimeTrackerSettings::promptDelete()) { response = KMessageBox::warningContinueCancel(nullptr, i18n("Are you sure you want to delete the selected task and its entire history?\n" "Note: All subtasks and their history will also be deleted."), i18nc("@title:window", "Deleting Task"), KStandardGuiItem::del()); } if (response == KMessageBox::Continue) { deleteTaskBatch(task); } } } void TaskView::markTaskAsComplete() { if (!m_tasksWidget->currentItem()) { KMessageBox::information(nullptr, i18n("No task selected.")); return; } m_tasksWidget->currentItem()->setPercentComplete(100); m_tasksWidget->currentItem()->invalidateCompletedState(); emit updateButtons(); } void TaskView::subtractTime(int64_t minutes) { addTimeToActiveTasks(-minutes, false); // subtract time in memory, but do not store it } void TaskView::markTaskAsIncomplete() { setPerCentComplete(50); // if it has been reopened, assume half-done } void TaskView::slotColumnToggled(int column) { switch (column) { case 1: KTimeTrackerSettings::setDisplaySessionTime(!m_tasksWidget->isColumnHidden(1)); break; case 2: KTimeTrackerSettings::setDisplayTime(!m_tasksWidget->isColumnHidden(2)); break; case 3: KTimeTrackerSettings::setDisplayTotalSessionTime(!m_tasksWidget->isColumnHidden(3)); break; case 4: KTimeTrackerSettings::setDisplayTotalTime(!m_tasksWidget->isColumnHidden(4)); break; case 5: KTimeTrackerSettings::setDisplayPriority(!m_tasksWidget->isColumnHidden(5)); break; case 6: KTimeTrackerSettings::setDisplayPercentComplete(!m_tasksWidget->isColumnHidden(6)); break; } KTimeTrackerSettings::self()->save(); } bool TaskView::isFocusTrackingActive() const { return m_focusTrackingActive; } void TaskView::reconfigure() { /* Adapt columns */ m_tasksWidget->setColumnHidden(1, !KTimeTrackerSettings::displaySessionTime()); m_tasksWidget->setColumnHidden(2, !KTimeTrackerSettings::displayTime()); m_tasksWidget->setColumnHidden(3, !KTimeTrackerSettings::displayTotalSessionTime()); m_tasksWidget->setColumnHidden(4, !KTimeTrackerSettings::displayTotalTime()); m_tasksWidget->setColumnHidden(5, !KTimeTrackerSettings::displayPriority()); m_tasksWidget->setColumnHidden(6, !KTimeTrackerSettings::displayPercentComplete()); /* idleness */ m_idleTimeDetector->setMaxIdle(KTimeTrackerSettings::period()); m_idleTimeDetector->toggleOverAllIdleDetection(KTimeTrackerSettings::enabled()); /* auto save */ if (KTimeTrackerSettings::autoSave()) { m_autoSaveTimer->start(KTimeTrackerSettings::autoSavePeriod() * 1000 * secsPerMinute); } else if (m_autoSaveTimer->isActive()) { m_autoSaveTimer->stop(); } refresh(); } //---------------------------------------------------------------------------- void TaskView::onTaskDoubleClicked(Task *task) { if (task->isRunning()) { // if task is running, stop it stopCurrentTimer(); } else if (!task->isComplete()) { // if task is not running, start it stopAllTimers(); startCurrentTimer(); } } void TaskView::editTaskTime(const QString& taskUid, int minutes) { // update session time if the time was changed auto* task = m_storage->tasksModel()->taskByUID(taskUid); if (task) { task->changeTime(minutes, m_storage->eventsModel()); scheduleSave(); } } void TaskView::taskAboutToBeRemoved(const QModelIndex &parent, int first, int last) { if (first != last) { qFatal("taskAboutToBeRemoved: unexpected removal of multiple items at once"); } TasksModelItem *item = nullptr; if (parent.isValid()) { // Nested task auto *parentItem = storage()->tasksModel()->item(parent); if (!parentItem) { qFatal("taskAboutToBeRemoved: parentItem is nullptr"); } item = parentItem->child(first); } else { // Top-level task item = storage()->tasksModel()->topLevelItem(first); } if (!item) { qFatal("taskAboutToBeRemoved: item is nullptr"); } // We use static_cast here instead of dynamic_cast because this // taskAboutToBeRemoved() slot is called from TasksModelItem's destructor // when the Task object is already destructed, thus dynamic_cast would // return nullptr. auto *deletedTask = static_cast(item); // Handle task deletion DesktopList desktopList; m_desktopTracker->registerForDesktops(deletedTask, desktopList); m_activeTasks.removeAll(deletedTask); emit tasksChanged(m_activeTasks); } diff --git a/src/timetrackerwidget.cpp b/src/timetrackerwidget.cpp index 9ddd112..0217417 100644 --- a/src/timetrackerwidget.cpp +++ b/src/timetrackerwidget.cpp @@ -1,979 +1,979 @@ /* * 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 "csvexportdialog.h" +#include "dialogs/csvexportdialog.h" #include "export/export.h" -#include "historydialog.h" +#include "dialogs/historydialog.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(new TaskView(this)) , 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); } } bool TimeTrackerWidget::allEventsHaveEndTiMe() { return currentTaskView()->allEventsHaveEndTiMe(); } 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 << ")"; TaskView *taskView = m_taskView; connect(taskView, &TaskView::contextMenuRequested, this, &TimeTrackerWidget::contextMenuRequested); connect(taskView, &TaskView::timersActive, this, &TimeTrackerWidget::timersActive); connect(taskView, &TaskView::timersInactive, this, &TimeTrackerWidget::timersInactive); connect(taskView, &TaskView::tasksChanged, this, &TimeTrackerWidget::tasksChanged); emit setCaption(url.toString()); taskView->load(url); fillLayout(m_taskView->tasksWidget()); // When adding the first tab currentChanged is not emitted, so... if (!m_taskView) { emit currentTaskViewChanged(); slotCurrentChanged(); } } TaskView* TimeTrackerWidget::currentTaskView() const { return m_taskView; } Task* TimeTrackerWidget::currentTask() { TaskView* taskView = currentTaskView(); return taskView ? taskView->tasksWidget()->currentItem() : nullptr; } 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); } 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()); } m_searchLine->setEnabled(m_taskView); } void TimeTrackerWidget::slotAddTask(const QString &taskName) { TaskView *taskView = currentTaskView(); taskView->addTask(taskName, QString(), 0, 0, DesktopList(), nullptr); taskView->save(); } 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"))->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()->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(); } void TimeTrackerWidget::newSubTask() { currentTaskView()->newSubTask(); } void TimeTrackerWidget::editTask() { currentTaskView()->editTask(); } void TimeTrackerWidget::editTaskTime() { currentTaskView()->editTaskTime(); } void TimeTrackerWidget::deleteTask() { currentTaskView()->deleteTask(); currentTaskView()->save(); } void TimeTrackerWidget::markTaskAsComplete() { currentTaskView()->markTaskAsComplete(); currentTaskView()->save(); } void TimeTrackerWidget::markTaskAsIncomplete() { currentTaskView()->markTaskAsIncomplete(); currentTaskView()->save(); } void TimeTrackerWidget::exportDialog() { qCDebug(KTT_LOG) << "TimeTrackerWidget::exportDialog()"; auto *taskView = currentTaskView(); CSVExportDialog dialog(taskView->tasksWidget(), taskView); if (taskView->tasksWidget()->currentItem() && taskView->tasksWidget()->currentItem()->isRoot()) { dialog.enableTasksToExportQuestion(); } dialog.exec(); } void TimeTrackerWidget::startNewSession() { currentTaskView()->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()->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); taskView->save(); } } 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->refresh(); taskView->save(); } } 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); taskView->save(); 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, int 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, int 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()); taskView->scheduleSave(); 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(); } } 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 /* @} */