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 "e)
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