1 /*
2  * Copyright (C) 2007 by Mathias Soeken <msoeken@tzi.de>
3  * Copyright (C) 2019  Alexander Potashev <aspotashev@gmail.com>
4  *
5  *   This program is free software; you can redistribute it and/or modify
6  *   it under the terms of the GNU General Public License as published by
7  *   the Free Software Foundation; either version 2 of the License, or
8  *   (at your option) any later version.
9  *
10  *   This program is distributed in the hope that it will be useful,
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *   GNU General Public License for more details.
14  *
15  *   You should have received a copy of the GNU General Public License along
16  *   with this program; if not, write to the
17  *      Free Software Foundation, Inc.
18  *      51 Franklin Street, Fifth Floor
19  *      Boston, MA  02110-1301  USA.
20  *
21  */
22 
23 #include "timetrackerwidget.h"
24 
25 #include <QFileDialog>
26 #include <QPointer>
27 
28 #include <KActionCollection>
29 #include <KConfigDialog>
30 #include <KMessageBox>
31 #include <KStandardAction>
32 
33 #include "dialogs/edittimedialog.h"
34 #include "dialogs/exportdialog.h"
35 #include "dialogs/historydialog.h"
36 #include "export/export.h"
37 #include "idletimedetector.h"
38 #include "ktimetracker-version.h"
39 #include "ktimetracker.h"
40 #include "ktimetrackerutility.h"
41 #include "ktt_debug.h"
42 #include "mainwindow.h"
43 #include "model/eventsmodel.h"
44 #include "model/projectmodel.h"
45 #include "model/task.h"
46 #include "model/tasksmodel.h"
47 #include "reportcriteria.h"
48 #include "settings/ktimetrackerconfigdialog.h"
49 #include "taskview.h"
50 #include "widgets/searchline.h"
51 #include "widgets/taskswidget.h"
52 
TimeTrackerWidget(QWidget * parent)53 TimeTrackerWidget::TimeTrackerWidget(QWidget *parent)
54     : QWidget(parent)
55     , m_searchLine(nullptr)
56     , m_taskView(nullptr)
57     , m_actionCollection(nullptr)
58 {
59     registerDBus();
60 
61     QLayout* layout = new QVBoxLayout;
62     layout->setMargin(0);
63     layout->setSpacing(0);
64     setLayout(layout);
65 
66     fillLayout(nullptr);
67 }
68 
fillLayout(TasksWidget * tasksWidget)69 void TimeTrackerWidget::fillLayout(TasksWidget *tasksWidget)
70 {
71     // Remove all items from layout
72     QLayoutItem *child;
73     while ((child = layout()->takeAt(0)) != nullptr) {
74         delete child->widget();
75         delete child;
76     }
77 
78     if (tasksWidget) {
79         m_searchLine = new SearchLine(this);
80         connect(m_searchLine, &SearchLine::textSubmitted, this, &TimeTrackerWidget::slotAddTask);
81         connect(m_searchLine, &SearchLine::searchUpdated, tasksWidget, &TasksWidget::setFilterText);
82         layout()->addWidget(m_searchLine);
83 
84         layout()->addWidget(tasksWidget);
85 
86         showSearchBar(!KTimeTrackerSettings::configPDA() && KTimeTrackerSettings::showSearchBar());
87     } else {
88         auto *placeholderWidget = new QWidget(this);
89         placeholderWidget->setBackgroundRole(QPalette::Dark);
90         placeholderWidget->setAutoFillBackground(true);
91         layout()->addWidget(placeholderWidget);
92     }
93 }
94 
focusSearchBar()95 int TimeTrackerWidget::focusSearchBar()
96 {
97     if (m_searchLine->isEnabled()) {
98         m_searchLine->setFocus();
99     }
100     return 0;
101 }
102 
addTaskView(const QUrl & url)103 void TimeTrackerWidget::addTaskView(const QUrl &url)
104 {
105     qCDebug(KTT_LOG) << "Entering function (url=" << url << ")";
106 
107     closeFile();
108 
109     m_taskView = new TaskView(this);
110     connect(m_taskView, &TaskView::contextMenuRequested, this, &TimeTrackerWidget::contextMenuRequested);
111     connect(m_taskView, &TaskView::timersActive, this, &TimeTrackerWidget::timersActive);
112     connect(m_taskView, &TaskView::timersInactive, this, &TimeTrackerWidget::timersInactive);
113     connect(m_taskView, &TaskView::tasksChanged, this, &TimeTrackerWidget::tasksChanged);
114 
115     emit setCaption(url.toString());
116     m_taskView->load(url);
117 
118     fillLayout(m_taskView->tasksWidget());
119 
120     emit currentTaskViewChanged();
121     slotCurrentChanged();
122 }
123 
currentTaskView() const124 TaskView* TimeTrackerWidget::currentTaskView() const
125 {
126     return m_taskView;
127 }
128 
currentTask()129 Task* TimeTrackerWidget::currentTask()
130 {
131     auto *taskView = currentTaskView();
132     if (taskView == nullptr) {
133         return nullptr;
134     }
135 
136     auto *tasksWidget = taskView->tasksWidget();
137     if (tasksWidget == nullptr) {
138         return nullptr;
139     }
140 
141     return tasksWidget->currentItem();
142 }
143 
setupActions(KActionCollection * actionCollection)144 void TimeTrackerWidget::setupActions(KActionCollection* actionCollection)
145 {
146     Q_INIT_RESOURCE(pics);
147 
148     m_actionCollection = actionCollection;
149 
150     KStandardAction::open(this, &TimeTrackerWidget::openFileDialog, actionCollection);
151     KStandardAction::save(this, &TimeTrackerWidget::saveFile, actionCollection);
152     KStandardAction::preferences(this, &TimeTrackerWidget::showSettingsDialog, actionCollection);
153 
154     QAction* startNewSession = actionCollection->addAction(QStringLiteral("start_new_session"));
155     startNewSession->setText(i18nc("@action:inmenu", "Start &New Session"));
156     startNewSession->setToolTip(i18nc("@info:tooltip", "Starts a new session"));
157     startNewSession->setWhatsThis(i18nc(
158         "@info:whatsthis",
159         "This will reset the "
160         "session time to 0 for all tasks, to start a new session, without "
161         "affecting the totals."));
162     connect(startNewSession, &QAction::triggered, this, &TimeTrackerWidget::startNewSession);
163 
164     QAction* editHistory = actionCollection->addAction(QStringLiteral("edit_history"));
165     editHistory->setText(i18nc("@action:inmenu", "Edit History..."));
166     editHistory->setToolTip(i18nc("@info:tooltip", "Edits history of all tasks of the current document"));
167     editHistory->setWhatsThis(i18nc(
168         "@info:whatsthis",
169         "A window will "
170         "be opened where you can change start and stop times of tasks or add a "
171         "comment to them."));
172     editHistory->setIcon(QIcon::fromTheme("view-history"));
173     connect(editHistory, &QAction::triggered, this, &TimeTrackerWidget::editHistory);
174 
175     QAction* resetAllTimes = actionCollection->addAction(QStringLiteral("reset_all_times"));
176     resetAllTimes->setText(i18nc("@action:inmenu", "&Reset All Times"));
177     resetAllTimes->setToolTip(i18nc("@info:tooltip", "Resets all times"));
178     resetAllTimes->setWhatsThis(i18nc(
179         "@info:whatsthis",
180         "This will reset the session "
181         "and total time to 0 for all tasks, to restart from scratch."));
182     connect(resetAllTimes, &QAction::triggered, this, &TimeTrackerWidget::resetAllTimes);
183 
184     QAction* startCurrentTimer = actionCollection->addAction(QStringLiteral("start"));
185     startCurrentTimer->setText(i18nc("@action:inmenu", "&Start"));
186     startCurrentTimer->setToolTip(i18nc("@info:tooltip", "Starts timing for selected task"));
187     startCurrentTimer->setWhatsThis(i18nc(
188         "@info:whatsthis",
189         "This will start timing for the "
190         "selected task.\nIt is even possible to time several tasks "
191         "simultaneously.\n\nYou may also start timing of tasks by double clicking "
192         "the left mouse button on a given task. This will, however, stop timing "
193         "of other tasks."));
194     startCurrentTimer->setIcon(QIcon::fromTheme("media-playback-start"));
195     actionCollection->setDefaultShortcut(startCurrentTimer, QKeySequence(Qt::Key_G));
196     connect(startCurrentTimer, &QAction::triggered, this, &TimeTrackerWidget::startCurrentTimer);
197 
198     QAction* stopCurrentTimer = actionCollection->addAction(QStringLiteral("stop"));
199     stopCurrentTimer->setText(i18nc("@action:inmenu", "S&top"));
200     stopCurrentTimer->setToolTip(i18nc("@info:tooltip", "Stops timing of the selected task"));
201     stopCurrentTimer->setWhatsThis(i18nc("@info:whatsthis", "Stops timing of the selected task"));
202     stopCurrentTimer->setIcon(QIcon::fromTheme("media-playback-stop"));
203     connect(stopCurrentTimer, &QAction::triggered, this, &TimeTrackerWidget::stopCurrentTimer);
204 
205     QAction* focusSearchBar = actionCollection->addAction(QStringLiteral("focusSearchBar"));
206     focusSearchBar->setText(i18nc("@action:inmenu", "Focus on Searchbar"));
207     focusSearchBar->setToolTip(i18nc("@info:tooltip", "Sets the focus on the searchbar"));
208     focusSearchBar->setWhatsThis(i18nc("@info:whatsthis", "Sets the focus on the searchbar"));
209     actionCollection->setDefaultShortcut(focusSearchBar, QKeySequence(Qt::Key_S));
210     connect(focusSearchBar, &QAction::triggered, this, &TimeTrackerWidget::focusSearchBar);
211 
212     QAction* stopAllTimers = actionCollection->addAction(QStringLiteral("stopAll"));
213     stopAllTimers->setText(i18nc("@action:inmenu", "Stop &All Timers"));
214     stopAllTimers->setToolTip(i18nc("@info:tooltip", "Stops all of the active timers"));
215     stopAllTimers->setWhatsThis(i18nc("@info:whatsthis", "Stops all of the active timers"));
216     actionCollection->setDefaultShortcut(stopAllTimers, QKeySequence(Qt::Key_Escape));
217     connect(stopAllTimers, &QAction::triggered, this, &TimeTrackerWidget::stopAllTimers);
218 
219     QAction* focusTracking = actionCollection->addAction(QStringLiteral("focustracking"));
220     focusTracking->setCheckable(true);
221     focusTracking->setText(i18nc("@action:inmenu", "Track Active Applications"));
222     focusTracking->setToolTip(i18nc(
223         "@info:tooltip",
224         "Auto-creates and updates tasks when the focus of the current window has changed"));
225     focusTracking->setWhatsThis(i18nc(
226         "@info:whatsthis",
227         "If the focus of a window changes for the "
228         "first time when this action is enabled, a new task will be created "
229         "with the title of the window as its name and will be started. If there "
230         "already exists such an task it will be started."));
231     connect(focusTracking, &QAction::triggered, this, &TimeTrackerWidget::focusTracking);
232 
233     QAction* newTask = actionCollection->addAction(QStringLiteral("new_task"));
234     newTask->setText(i18nc("@action:inmenu", "&New Task..."));
235     newTask->setToolTip(i18nc("@info:tooltip", "Creates new top level task"));
236     newTask->setWhatsThis(i18nc("@info:whatsthis", "This will create a new top level task."));
237     newTask->setIcon(QIcon::fromTheme("document-new"));
238     actionCollection->setDefaultShortcut(newTask, QKeySequence(Qt::CTRL + Qt::Key_T));
239     connect(newTask, &QAction::triggered, this, &TimeTrackerWidget::newTask);
240 
241     QAction* newSubTask = actionCollection->addAction(QStringLiteral("new_sub_task"));
242     newSubTask->setText(i18nc("@action:inmenu", "New &Subtask..."));
243     newSubTask->setToolTip(i18nc("@info:tooltip", "Creates a new subtask to the current selected task"));
244     newSubTask->setWhatsThis(i18nc("@info:whatsthis", "This will create a new subtask to the current selected task."));
245     newSubTask->setIcon(QIcon::fromTheme("subtask-new-ktimetracker"));
246     actionCollection->setDefaultShortcut(newSubTask, QKeySequence(Qt::CTRL + Qt::Key_B));
247     connect(newSubTask, &QAction::triggered, this, &TimeTrackerWidget::newSubTask);
248 
249     QAction* deleteTask = actionCollection->addAction(QStringLiteral("delete_task"));
250     deleteTask->setText(i18nc("@action:inmenu", "&Delete"));
251     deleteTask->setToolTip(i18nc("@info:tooltip", "Deletes selected task"));
252     deleteTask->setWhatsThis(i18nc("@info:whatsthis", "This will delete the selected task(s) and all subtasks."));
253     deleteTask->setIcon(QIcon::fromTheme("edit-delete"));
254     actionCollection->setDefaultShortcut(deleteTask, QKeySequence(Qt::Key_Delete));
255     connect(deleteTask, &QAction::triggered, this, QOverload<>::of(&TimeTrackerWidget::deleteTask));
256 
257     QAction* editTask = actionCollection->addAction(QStringLiteral("edit_task"));
258     editTask->setText(i18nc("@action:inmenu", "&Properties"));
259     editTask->setToolTip(i18nc("@info:tooltip", "Edit name or description for selected task"));
260     editTask->setWhatsThis(i18nc(
261         "@info:whatsthis",
262         "This will bring up a dialog "
263         "box where you may edit the parameters for the selected task."));
264     editTask->setIcon(QIcon::fromTheme("document-properties"));
265     actionCollection->setDefaultShortcut(editTask, QKeySequence(Qt::CTRL + Qt::Key_E));
266     connect(editTask, &QAction::triggered, this, &TimeTrackerWidget::editTask);
267 
268     QAction* editTaskTime = actionCollection->addAction(QStringLiteral("edit_task_time"));
269     editTaskTime->setText(i18nc("@action:inmenu", "Edit &Time..."));
270     editTaskTime->setToolTip(i18nc("@info:tooltip", "Edit time for selected task"));
271     editTaskTime->setWhatsThis(i18nc(
272         "@info:whatsthis",
273         "This will bring up a dialog "
274         "box where you may edit the times for the selected task."));
275     editTaskTime->setIcon(QIcon::fromTheme("document-edit"));
276     actionCollection->setDefaultShortcut(editTaskTime, QKeySequence(Qt::Key_E));
277     connect(editTaskTime, &QAction::triggered, this, &TimeTrackerWidget::editTaskTime);
278 
279     QAction* markTaskAsComplete = actionCollection->addAction(QStringLiteral("mark_as_complete"));
280     markTaskAsComplete->setText(i18nc("@action:inmenu", "&Mark as Complete"));
281     markTaskAsComplete->setIcon(QPixmap(":/pics/task-complete.xpm"));
282     actionCollection->setDefaultShortcut(markTaskAsComplete, QKeySequence(Qt::CTRL + Qt::Key_M));
283     connect(markTaskAsComplete, &QAction::triggered, this, &TimeTrackerWidget::markTaskAsComplete);
284 
285     QAction* markTaskAsIncomplete = actionCollection->addAction(QStringLiteral("mark_as_incomplete"));
286     markTaskAsIncomplete->setText(i18nc("@action:inmenu", "&Mark as Incomplete"));
287     markTaskAsIncomplete->setIcon(QPixmap(":/pics/task-incomplete.xpm"));
288     actionCollection->setDefaultShortcut(markTaskAsIncomplete, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M));
289     connect(markTaskAsIncomplete, &QAction::triggered, this, &TimeTrackerWidget::markTaskAsIncomplete);
290 
291     QAction* exportAction = actionCollection->addAction(QStringLiteral("export_dialog"));
292     exportAction->setText(i18nc("@action:inmenu", "&Export..."));
293     exportAction->setIcon(QIcon::fromTheme("document-export"));
294     connect(exportAction, &QAction::triggered, this, &TimeTrackerWidget::exportDialog);
295 
296     QAction* importPlanner = actionCollection->addAction(QStringLiteral("import_planner"));
297     importPlanner->setText(i18nc("@action:inmenu", "Import Tasks From &Planner..."));
298     importPlanner->setIcon(QIcon::fromTheme("document-import"));
299     connect(importPlanner, &QAction::triggered, [=]() {
300         const QString &fileName = QFileDialog::getOpenFileName();
301         importPlannerFile(fileName);
302     });
303 
304     QAction* showSearchBar = actionCollection->addAction(QStringLiteral("searchbar"));
305     showSearchBar->setCheckable(true);
306     showSearchBar->setChecked(KTimeTrackerSettings::showSearchBar());
307     showSearchBar->setText(i18nc("@action:inmenu", "Show Searchbar"));
308     connect(showSearchBar, &QAction::triggered, this, &TimeTrackerWidget::slotSearchBar);
309 
310     connect(this, &TimeTrackerWidget::currentTaskChanged, this, &TimeTrackerWidget::slotUpdateButtons);
311     connect(this, &TimeTrackerWidget::currentTaskViewChanged, this, &TimeTrackerWidget::slotUpdateButtons);
312     connect(this, &TimeTrackerWidget::updateButtons, this, &TimeTrackerWidget::slotUpdateButtons);
313     slotUpdateButtons();
314 }
315 
action(const QString & name) const316 QAction *TimeTrackerWidget::action(const QString &name) const
317 {
318     return m_actionCollection->action(name);
319 }
320 
openFile(const QUrl & url)321 void TimeTrackerWidget::openFile(const QUrl &url)
322 {
323     qCDebug(KTT_LOG) << "Entering function, url is " << url;
324     addTaskView(url);
325 }
326 
openFileDialog()327 void TimeTrackerWidget::openFileDialog()
328 {
329     const QString &path = QFileDialog::getOpenFileName(this);
330     if (!path.isEmpty()) {
331         openFile(QUrl::fromLocalFile(path));
332     }
333 }
334 
closeFile()335 bool TimeTrackerWidget::closeFile()
336 {
337     qCDebug(KTT_LOG) << "Entering TimeTrackerWidget::closeFile";
338     TaskView* taskView = currentTaskView();
339 
340     if (taskView) {
341         taskView->save();
342         taskView->closeStorage();
343     }
344 
345     emit currentTaskViewChanged();
346     emit setCaption(QString());
347     slotCurrentChanged();
348 
349     delete taskView; // removeTab does not delete its widget.
350     m_taskView = nullptr;
351     return true;
352 }
353 
saveFile()354 void TimeTrackerWidget::saveFile()
355 {
356     currentTaskView()->save();
357 }
358 
showSearchBar(bool visible)359 void TimeTrackerWidget::showSearchBar(bool visible)
360 {
361     m_searchLine->setVisible(visible);
362 }
363 
closeAllFiles()364 bool TimeTrackerWidget::closeAllFiles()
365 {
366     qCDebug(KTT_LOG) << "Entering TimeTrackerWidget::closeAllFiles";
367     bool err = true;
368     if (m_taskView) {
369         m_taskView->stopAllTimers();
370         err = closeFile();
371     }
372     return err;
373 }
374 
slotCurrentChanged()375 void TimeTrackerWidget::slotCurrentChanged()
376 {
377     qDebug() << "entering KTimeTrackerWidget::slotCurrentChanged";
378 
379     if (m_taskView) {
380         connect(m_taskView, &TaskView::updateButtons, this, &TimeTrackerWidget::updateButtons, Qt::UniqueConnection);
381         connect(m_taskView, &TaskView::setStatusBarText, this, &TimeTrackerWidget::statusBarTextChangeRequested, Qt::UniqueConnection);
382         connect(m_taskView, &TaskView::timersActive, this, &TimeTrackerWidget::timersActive, Qt::UniqueConnection);
383         connect(m_taskView, &TaskView::timersInactive, this, &TimeTrackerWidget::timersInactive, Qt::UniqueConnection);
384         connect(m_taskView, &TaskView::tasksChanged, this, &TimeTrackerWidget::tasksChanged, Qt::UniqueConnection);
385 
386         emit setCaption(m_taskView->storage()->fileUrl().toString());
387     }
388 }
389 
slotAddTask(const QString & taskName)390 void TimeTrackerWidget::slotAddTask(const QString &taskName)
391 {
392     TaskView *taskView = currentTaskView();
393     taskView->addTask(taskName, QString(), 0, 0, DesktopList(), nullptr);
394 }
395 
slotUpdateButtons()396 void TimeTrackerWidget::slotUpdateButtons()
397 {
398     Task *item = currentTask();
399 
400     action(QStringLiteral("start"))->setEnabled(item && !item->isRunning() && !item->isComplete());
401     action(QStringLiteral("stop"))->setEnabled(item && item->isRunning());
402     action(QStringLiteral("delete_task"))->setEnabled(item);
403     action(QStringLiteral("edit_task_time"))->setEnabled(item);
404     action(QStringLiteral("edit_task"))->setEnabled(item);
405     action(QStringLiteral("mark_as_complete"))->setEnabled(item && !item->isComplete());
406     action(QStringLiteral("mark_as_incomplete"))->setEnabled(item && item->isComplete());
407 
408     action(QStringLiteral("new_task"))->setEnabled(currentTaskView());
409     action(QStringLiteral("new_sub_task"))->setEnabled(currentTaskView() && currentTaskView()->storage()->isLoaded() && currentTaskView()->storage()->tasksModel()->getAllTasks().size());
410     action(QStringLiteral("focustracking"))->setEnabled(currentTaskView());
411     action(QStringLiteral("focustracking"))->setChecked(currentTaskView() && currentTaskView()->isFocusTrackingActive());
412     action(QStringLiteral("start_new_session"))->setEnabled(currentTaskView());
413     action(QStringLiteral("edit_history"))->setEnabled(currentTaskView());
414     action(QStringLiteral("reset_all_times"))->setEnabled(currentTaskView());
415     action(QStringLiteral("export_dialog"))->setEnabled(currentTaskView());
416     action(QStringLiteral("import_planner"))->setEnabled(currentTaskView());
417     action(QStringLiteral("file_save"))->setEnabled(currentTaskView());
418 }
419 
showSettingsDialog()420 void TimeTrackerWidget::showSettingsDialog()
421 {
422     if (KConfigDialog::showDialog("settings")) {
423         return;
424     }
425 
426     auto *dialog = new KConfigDialog(this, "settings", KTimeTrackerSettings::self());
427     dialog->setFaceType(KPageDialog::List);
428     dialog->addPage(new KTimeTrackerBehaviorConfig(dialog), i18nc("@title:tab", "Behavior"), QStringLiteral("preferences-other"));
429     dialog->addPage(new KTimeTrackerDisplayConfig(dialog), i18nc("@title:tab", "Appearance"), QStringLiteral("preferences-desktop-theme"));
430     dialog->addPage(new KTimeTrackerStorageConfig(dialog), i18nc("@title:tab", "Storage"), QStringLiteral("system-file-manager"));
431     connect(dialog, &KConfigDialog::settingsChanged, this, &TimeTrackerWidget::loadSettings);
432     dialog->show();
433 }
434 
loadSettings()435 void TimeTrackerWidget::loadSettings()
436 {
437     KTimeTrackerSettings::self()->load();
438 
439     showSearchBar(!KTimeTrackerSettings::configPDA() && KTimeTrackerSettings::showSearchBar());
440     currentTaskView()->reconfigureModel();
441     currentTaskView()->tasksWidget()->reconfigure();
442 }
443 
444 //BEGIN wrapper slots
startCurrentTimer()445 void TimeTrackerWidget::startCurrentTimer()
446 {
447     currentTaskView()->startCurrentTimer();
448 }
449 
stopCurrentTimer()450 void TimeTrackerWidget::stopCurrentTimer()
451 {
452     currentTaskView()->stopCurrentTimer();
453 }
454 
stopAllTimers()455 void TimeTrackerWidget::stopAllTimers()
456 {
457     currentTaskView()->stopAllTimers(QDateTime::currentDateTime());
458 }
459 
newTask()460 void TimeTrackerWidget::newTask()
461 {
462     currentTaskView()->newTask(i18nc("@title:window", "New Task"), nullptr);
463 }
464 
newSubTask()465 void TimeTrackerWidget::newSubTask()
466 {
467     currentTaskView()->newSubTask();
468 }
469 
editTask()470 void TimeTrackerWidget::editTask()
471 {
472     currentTaskView()->editTask();
473 }
474 
editTaskTime()475 void TimeTrackerWidget::editTaskTime()
476 {
477     qCDebug(KTT_LOG) <<"Entering editTask";
478     Task* task = currentTask();
479     if (!task) {
480         return;
481     }
482 
483     QPointer<EditTimeDialog> editTimeDialog = new EditTimeDialog(
484         this, task->name(), task->description(), static_cast<int>(task->time()));
485 
486     if (editTimeDialog->exec() == QDialog::Accepted) {
487         if (editTimeDialog->editHistoryRequested()) {
488             editHistory();
489         } else {
490             currentTaskView()->editTaskTime(task->uid(), editTimeDialog->changeMinutes());
491         }
492     }
493 
494     delete editTimeDialog;
495 }
496 
deleteTask()497 void TimeTrackerWidget::deleteTask()
498 {
499     currentTaskView()->deleteTask();
500 }
501 
markTaskAsComplete()502 void TimeTrackerWidget::markTaskAsComplete()
503 {
504     currentTaskView()->markTaskAsComplete();
505 }
506 
markTaskAsIncomplete()507 void TimeTrackerWidget::markTaskAsIncomplete()
508 {
509     currentTaskView()->markTaskAsIncomplete();
510 }
511 
exportDialog()512 void TimeTrackerWidget::exportDialog()
513 {
514     qCDebug(KTT_LOG) << "TimeTrackerWidget::exportDialog()";
515 
516     auto *taskView = currentTaskView();
517     ExportDialog dialog(taskView->tasksWidget(), taskView);
518     if (taskView->tasksWidget()->currentItem() && taskView->tasksWidget()->currentItem()->isRoot()) {
519         dialog.enableTasksToExportQuestion();
520     }
521     dialog.exec();
522 }
523 
startNewSession()524 void TimeTrackerWidget::startNewSession()
525 {
526     currentTaskView()->storage()->tasksModel()->startNewSession();
527 }
528 
editHistory()529 void TimeTrackerWidget::editHistory()
530 {
531     // HistoryDialog is the new HistoryDialog, but the EditHiStoryDiaLog exists as well.
532     // HistoryDialog can be edited with qtcreator and qtdesigner, EditHiStoryDiaLog cannot.
533     if (currentTaskView()) {
534         QPointer<HistoryDialog> dialog = new HistoryDialog(
535             currentTaskView()->tasksWidget(), currentTaskView()->storage()->projectModel());
536         if (currentTaskView()->storage()->eventsModel()->events().count() != 0) {
537             dialog->exec();
538         } else {
539             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."));
540         }
541     }
542 }
543 
resetAllTimes()544 void TimeTrackerWidget::resetAllTimes()
545 {
546     if (currentTaskView()) {
547         if (KMessageBox::warningContinueCancel(
548             this,
549             i18n("Do you really want to reset the time to zero for all tasks? This will delete the entire history."),
550             i18nc("@title:window", "Confirmation Required"),
551             KGuiItem(i18nc("@action:button", "Reset All Times"))) == KMessageBox::Continue) {
552             currentTaskView()->storage()->projectModel()->resetTimeForAllTasks();
553         }
554     }
555 }
556 
focusTracking()557 void TimeTrackerWidget::focusTracking()
558 {
559     currentTaskView()->toggleFocusTracking();
560     action(QStringLiteral("focustracking"))->setChecked(currentTaskView()->isFocusTrackingActive());
561 }
562 
slotSearchBar()563 void TimeTrackerWidget::slotSearchBar()
564 {
565     bool currentVisible = KTimeTrackerSettings::showSearchBar();
566     KTimeTrackerSettings::setShowSearchBar(!currentVisible);
567     action(QStringLiteral("searchbar"))->setChecked(!currentVisible);
568     showSearchBar(!currentVisible);
569 }
570 //END
571 
572 /** \defgroup dbus slots ‘‘dbus slots’’ */
573 /* @{ */
574 #include "mainadaptor.h"
575 
registerDBus()576 void TimeTrackerWidget::registerDBus()
577 {
578     new MainAdaptor(this);
579     QDBusConnection::sessionBus().registerObject("/KTimeTracker", this);
580 }
581 
version() const582 QString TimeTrackerWidget::version() const
583 {
584     return KTIMETRACKER_VERSION_STRING;
585 }
586 
taskIdsFromName(const QString & taskName) const587 QStringList TimeTrackerWidget::taskIdsFromName( const QString &taskName ) const
588 {
589     QStringList result;
590 
591     TaskView *taskView = currentTaskView();
592     if (!taskView) {
593         return result;
594     }
595 
596     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
597         if (task->name() == taskName) {
598             result << task->uid();
599         }
600     }
601 
602     return result;
603 }
604 
addTask(const QString & taskName)605 void TimeTrackerWidget::addTask( const QString &taskName )
606 {
607     TaskView *taskView = currentTaskView();
608 
609     if (taskView) {
610         taskView->addTask(taskName, QString(), 0, 0, DesktopList(), nullptr);
611     }
612 }
613 
addSubTask(const QString & taskName,const QString & taskId)614 void TimeTrackerWidget::addSubTask(const QString &taskName, const QString &taskId)
615 {
616     TaskView *taskView = currentTaskView();
617 
618     if (taskView) {
619         taskView->addTask(taskName, QString(), 0, 0, DesktopList(), taskView->storage()->tasksModel()->taskByUID(taskId));
620         taskView->storage()->projectModel()->refresh();
621         taskView->tasksWidget()->refresh();
622     }
623 }
624 
deleteTask(const QString & taskId)625 void TimeTrackerWidget::deleteTask(const QString &taskId)
626 {
627     TaskView *taskView = currentTaskView();
628 
629     if (!taskView) {
630         return;
631     }
632 
633     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
634         if (task->uid() == taskId) {
635             taskView->deleteTaskBatch(task);
636             break;
637         }
638     }
639 }
640 
setPercentComplete(const QString & taskId,int percent)641 void TimeTrackerWidget::setPercentComplete(const QString &taskId, int percent)
642 {
643     TaskView *taskView = currentTaskView();
644 
645     if (!taskView) {
646         return;
647     }
648 
649     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
650         if (task->uid() == taskId) {
651             task->setPercentComplete(percent);
652         }
653     }
654 }
655 
bookTime(const QString & taskId,const QString & dateTime,int64_t minutes)656 int TimeTrackerWidget::bookTime(const QString &taskId, const QString &dateTime, int64_t minutes)
657 {
658     QDate startDate;
659     QTime startTime;
660     QDateTime startDateTime;
661 
662     if (minutes <= 0) {
663         return KTIMETRACKER_ERR_INVALID_DURATION;
664     }
665 
666     Task *task = nullptr;
667     TaskView *taskView = currentTaskView();
668     if (taskView) {
669         for (Task *t : taskView->storage()->tasksModel()->getAllTasks()) {
670             if (t->uid() == taskId) {
671                 task = t;
672                 break;
673             }
674         }
675     }
676 
677     if (!task) {
678         return KTIMETRACKER_ERR_UID_NOT_FOUND;
679     }
680 
681     // Parse datetime
682     startDate = QDate::fromString(dateTime, Qt::ISODate);
683 
684     if (dateTime.length() > 10) { // "YYYY-MM-DD".length() = 10
685         startTime = QTime::fromString(dateTime, Qt::ISODate);
686     } else {
687         startTime = QTime(12, 0);
688     }
689 
690     if (startDate.isValid() && startTime.isValid()) {
691         startDateTime = QDateTime(startDate, startTime);
692     } else {
693         return KTIMETRACKER_ERR_INVALID_DATE;
694     }
695 
696     // Update task totals (session and total) and save to disk
697     task->changeTotalTimes(task->sessionTime() + minutes, task->totalTime() + minutes);
698     if (!taskView->storage()->bookTime(task, startDateTime, minutes * 60)) {
699         return KTIMETRACKER_ERR_GENERIC_SAVE_FAILED;
700     }
701 
702     return 0;
703 }
704 
changeTime(const QString & taskId,int64_t minutes)705 int TimeTrackerWidget::changeTime(const QString &taskId, int64_t minutes)
706 {
707     if (minutes <= 0) {
708         return KTIMETRACKER_ERR_INVALID_DURATION;
709     }
710 
711     // Find task
712     TaskView *taskView = currentTaskView();
713     if (!taskView) {
714         //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?
715         return KTIMETRACKER_ERR_UID_NOT_FOUND;
716     }
717 
718     Task *task = nullptr;
719     for (Task *t : taskView->storage()->tasksModel()->getAllTasks()) {
720         if (t->uid() == taskId) {
721             task = t;
722             break;
723         }
724     }
725 
726     if (!task) {
727         return KTIMETRACKER_ERR_UID_NOT_FOUND;
728     }
729 
730     task->changeTime(minutes, taskView->storage()->eventsModel());
731     return 0;
732 }
733 
error(int errorCode) const734 QString TimeTrackerWidget::error( int errorCode ) const
735 {
736     switch ( errorCode )
737     {
738         case KTIMETRACKER_ERR_GENERIC_SAVE_FAILED:
739             return i18n( "Save failed, most likely because the file could not be locked." );
740         case KTIMETRACKER_ERR_COULD_NOT_MODIFY_RESOURCE:
741             return i18n( "Could not modify calendar resource." );
742         case KTIMETRACKER_ERR_MEMORY_EXHAUSTED:
743             return i18n( "Out of memory--could not create object." );
744         case KTIMETRACKER_ERR_UID_NOT_FOUND:
745             return i18n( "UID not found." );
746         case KTIMETRACKER_ERR_INVALID_DATE:
747             return i18n( "Invalidate date--format is YYYY-MM-DD." );
748         case KTIMETRACKER_ERR_INVALID_TIME:
749             return i18n( "Invalid time--format is YYYY-MM-DDTHH:MM:SS." );
750         case KTIMETRACKER_ERR_INVALID_DURATION:
751             return i18n( "Invalid task duration--must be greater than zero." );
752         default:
753             return i18n( "Invalid error number: %1", errorCode );
754     }
755 }
756 
isIdleDetectionPossible() const757 bool TimeTrackerWidget::isIdleDetectionPossible() const
758 {
759     return IdleTimeDetector::isIdleDetectionPossible();
760 }
761 
totalMinutesForTaskId(const QString & taskId) const762 int TimeTrackerWidget::totalMinutesForTaskId(const QString &taskId) const
763 {
764     TaskView *taskView = currentTaskView();
765     if (!taskView) {
766         return -1;
767     }
768 
769     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
770         if (task->uid() == taskId) {
771             return task->totalTime();
772         }
773     }
774 
775     return -1;
776 }
777 
startTimerFor(const QString & taskId)778 void TimeTrackerWidget::startTimerFor(const QString &taskId)
779 {
780     qDebug();
781 
782     TaskView *taskView = currentTaskView();
783     if (!taskView) {
784         return;
785     }
786 
787     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
788         if (task->uid() == taskId) {
789             taskView->startTimerForNow(task);
790             return;
791         }
792     }
793 }
794 
startTimerForTaskName(const QString & taskName)795 bool TimeTrackerWidget::startTimerForTaskName( const QString &taskName )
796 {
797     TaskView *taskView = currentTaskView();
798     if (!taskView) {
799         return false;
800     }
801 
802     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
803         if (task->name() == taskName ) {
804             taskView->startTimerForNow(task);
805             return true;
806         }
807     }
808 
809     return false;
810 }
811 
stopTimerForTaskName(const QString & taskName)812 bool TimeTrackerWidget::stopTimerForTaskName(const QString &taskName)
813 {
814     TaskView *taskView = currentTaskView();
815     if (!taskView) {
816         return false;
817     }
818 
819     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
820         if (task->name() == taskName) {
821             taskView->stopTimerFor(task);
822             return true;
823         }
824     }
825 
826     return false;
827 }
828 
stopTimerFor(const QString & taskId)829 void TimeTrackerWidget::stopTimerFor(const QString &taskId)
830 {
831     TaskView *taskView = currentTaskView();
832     if (!taskView) {
833         return;
834     }
835 
836     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
837         if (task->uid() == taskId) {
838             taskView->stopTimerFor(task);
839             return;
840         }
841     }
842 }
843 
stopAllTimersDBUS()844 void TimeTrackerWidget::stopAllTimersDBUS()
845 {
846     TaskView *taskView = currentTaskView();
847     if (taskView) {
848         taskView->stopAllTimers();
849     }
850 }
851 
exportCSVFile(const QString & filename,const QString & from,const QString & to,int type,bool decimalMinutes,bool allTasks,const QString & delimiter,const QString & quote)852 QString TimeTrackerWidget::exportCSVFile(
853     const QString &filename, const QString &from, const QString &to, int type,
854     bool decimalMinutes, bool allTasks, const QString &delimiter, const QString &quote)
855 {
856     TaskView *taskView = currentTaskView();
857 
858     if (!taskView) {
859         return "";
860     }
861 
862     ReportCriteria rc;
863 
864     rc.from = QDate::fromString(from);
865     if (rc.from.isNull()) {
866         rc.from = QDate::fromString(from, Qt::ISODate);
867     }
868 
869     rc.to = QDate::fromString(to);
870     if (rc.to.isNull()) {
871         rc.to = QDate::fromString(to, Qt::ISODate);
872     }
873 
874     rc.reportType = static_cast<ReportCriteria::REPORTTYPE>(type);
875     rc.decimalMinutes = decimalMinutes;
876     rc.allTasks = allTasks;
877     rc.delimiter = delimiter;
878     rc.quote = quote;
879 
880     QString output = exportToString(taskView->storage()->projectModel(),
881                                     taskView->tasksWidget()->currentItem(), rc);
882     return writeExport(output, QUrl::fromLocalFile(filename));
883 }
884 
importPlannerFile(const QString & filename)885 void TimeTrackerWidget::importPlannerFile(const QString &filename)
886 {
887     TaskView *taskView = currentTaskView();
888     if (!taskView) {
889         return;
890     }
891 
892     taskView->importPlanner(filename);
893 }
894 
isActive(const QString & taskId) const895 bool TimeTrackerWidget::isActive(const QString &taskId) const
896 {
897     TaskView *taskView = currentTaskView();
898     if (!taskView) {
899         return false;
900     }
901 
902     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
903         if (task->uid() == taskId) {
904             return task->isRunning();
905         }
906     }
907 
908     return false;
909 }
910 
isTaskNameActive(const QString & taskName) const911 bool TimeTrackerWidget::isTaskNameActive(const QString &taskName) const
912 {
913     TaskView *taskView = currentTaskView();
914     if (!taskView) {
915         return false;
916     }
917 
918     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
919         if (task->name() == taskName) {
920             return task->isRunning();
921         }
922     }
923 
924     return false;
925 }
926 
tasks() const927 QStringList TimeTrackerWidget::tasks() const
928 {
929     QStringList result;
930 
931     TaskView *taskView = currentTaskView();
932     if (!taskView) {
933         return result;
934     }
935 
936     for (Task *task : taskView->storage()->tasksModel()->getAllTasks()) {
937         result << task->name();
938     }
939 
940     return result;
941 }
942 
activeTasks() const943 QStringList TimeTrackerWidget::activeTasks() const
944 {
945     QStringList result;
946     TaskView* taskView = currentTaskView();
947     if (!taskView) {
948         return result;
949     }
950 
951     for (Task *task : taskView->storage()->tasksModel()->getActiveTasks()) {
952         result << task->name();
953     }
954 
955     return result;
956 }
957 
saveAll()958 void TimeTrackerWidget::saveAll()
959 {
960     currentTaskView()->save();
961 }
962 
quit()963 void TimeTrackerWidget::quit()
964 {
965     auto* mainWindow = dynamic_cast<MainWindow*>(parent()->parent());
966     if (mainWindow) {
967         mainWindow->quit();
968     } else {
969         qCWarning(KTT_LOG) << "Cast to MainWindow failed";
970     }
971 }
972 
event(QEvent * event)973 bool TimeTrackerWidget::event(QEvent *event) // inherited from QWidget
974 {
975     if (event->type() == QEvent::QueryWhatsThis) {
976         if (m_taskView->storage()->tasksModel()->getAllTasks().empty()) {
977             setWhatsThis(i18nc(
978                 "@info:whatsthis",
979                 "This is ktimetracker, KDE's program to help you track your time. "
980                 "Best, start with creating your first task - enter it into the field "
981                 "where you see \"Search or add task\"."));
982         } else {
983             setWhatsThis(i18nc(
984                 "@info:whatsthis",
985                 "You have already created a task. You can now start and stop timing."));
986         }
987     }
988 
989     return QWidget::event(event);
990 }
991 // END of dbus slots group
992 /* @} */
993