File indexing completed on 2024-05-19 05:21:54
0001 /* 0002 * Copyright (C) 2003 by Scott Monachello <smonach@cox.net> 0003 * Copyright (C) 2019 Alexander Potashev <aspotashev@gmail.com> 0004 * 0005 * This program is free software; you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation; either version 2 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License along 0016 * with this program; if not, write to the 0017 * Free Software Foundation, Inc. 0018 * 51 Franklin Street, Fifth Floor 0019 * Boston, MA 02110-1301 USA. 0020 * 0021 */ 0022 0023 #include "taskview.h" 0024 0025 #include <QMouseEvent> 0026 #include <QPointer> 0027 #include <QProgressDialog> 0028 #include <QSortFilterProxyModel> 0029 #include <QTimer> 0030 0031 #include <KMessageBox> 0032 0033 #include "desktoptracker.h" 0034 #include "dialogs/edittimedialog.h" 0035 #include "dialogs/exportdialog.h" 0036 #include "dialogs/historydialog.h" 0037 #include "dialogs/taskpropertiesdialog.h" 0038 #include "export/export.h" 0039 #include "focusdetector.h" 0040 #include "idletimedetector.h" 0041 #include "import/plannerparser.h" 0042 #include "ktimetracker.h" 0043 #include "ktimetrackerutility.h" 0044 #include "ktt_debug.h" 0045 #include "model/eventsmodel.h" 0046 #include "model/projectmodel.h" 0047 #include "model/task.h" 0048 #include "model/tasksmodel.h" 0049 #include "treeviewheadercontextmenu.h" 0050 #include "widgets/taskswidget.h" 0051 0052 void deleteEntry(const QString &key) 0053 { 0054 KConfigGroup config = KSharedConfig::openConfig()->group(QString()); 0055 config.deleteEntry(key); 0056 config.sync(); 0057 } 0058 0059 TaskView::TaskView(QWidget *parent) 0060 : QObject(parent) 0061 , m_filterProxyModel(new QSortFilterProxyModel(this)) 0062 , m_storage(new TimeTrackerStorage()) 0063 , m_focusTrackingActive(false) 0064 , m_lastTaskWithFocus(nullptr) 0065 , m_focusDetector(new FocusDetector()) 0066 , m_tasksWidget(nullptr) 0067 { 0068 m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0069 m_filterProxyModel->setRecursiveFilteringEnabled(true); 0070 m_filterProxyModel->setSortRole(Task::SortRole); 0071 0072 connect(m_focusDetector, &FocusDetector::newFocus, this, &TaskView::newFocusWindowDetected); 0073 0074 // set up the minuteTimer 0075 m_minuteTimer = new QTimer(this); 0076 connect(m_minuteTimer, &QTimer::timeout, this, &TaskView::minuteUpdate); 0077 m_minuteTimer->start(1000 * secsPerMinute); 0078 0079 // Set up the idle detection. 0080 m_idleTimeDetector = new IdleTimeDetector(KTimeTrackerSettings::period()); 0081 connect(m_idleTimeDetector, &IdleTimeDetector::subtractTime, this, &TaskView::subtractTime); 0082 connect(m_idleTimeDetector, &IdleTimeDetector::stopAllTimers, this, &TaskView::stopAllTimers); 0083 if (!IdleTimeDetector::isIdleDetectionPossible()) { 0084 KTimeTrackerSettings::setEnabled(false); 0085 } 0086 0087 // Setup auto save timer 0088 m_autoSaveTimer = new QTimer(this); 0089 connect(m_autoSaveTimer, &QTimer::timeout, this, &TaskView::save); 0090 0091 // Connect desktop tracker events to task starting/stopping 0092 m_desktopTracker = new DesktopTracker(); 0093 connect(m_desktopTracker, &DesktopTracker::reachedActiveDesktop, this, &TaskView::startTimerForNow); 0094 connect(m_desktopTracker, &DesktopTracker::leftActiveDesktop, this, &TaskView::stopTimerFor); 0095 } 0096 0097 void TaskView::newFocusWindowDetected(const QString &taskName) 0098 { 0099 QString newTaskName = taskName; 0100 newTaskName.remove(QChar::fromLatin1('\n')); 0101 0102 if (!m_focusTrackingActive) { 0103 return; 0104 } 0105 0106 bool found = false; // has taskName been found in our tasks 0107 stopTimerFor(m_lastTaskWithFocus); 0108 for (Task *task : storage()->tasksModel()->getAllTasks()) { 0109 if (task->name() == newTaskName) { 0110 found = true; 0111 startTimerForNow(task); 0112 m_lastTaskWithFocus = task; 0113 } 0114 } 0115 if (!found) { 0116 if (!addTask(newTaskName)) { 0117 KMessageBox::error(nullptr, 0118 i18n("Error storing new task. Your changes were not saved. " 0119 "Make sure you can edit your iCalendar file. " 0120 "Also quit all applications using this file and remove " 0121 "any lock file related to its name from " 0122 "~/.kde/share/apps/kabc/lock/ ")); 0123 } 0124 for (Task *task : storage()->tasksModel()->getAllTasks()) { 0125 if (task->name() == newTaskName) { 0126 startTimerForNow(task); 0127 m_lastTaskWithFocus = task; 0128 } 0129 } 0130 } 0131 Q_EMIT updateButtons(); 0132 } 0133 0134 TimeTrackerStorage *TaskView::storage() 0135 { 0136 return m_storage; 0137 } 0138 0139 TaskView::~TaskView() 0140 { 0141 delete m_storage; 0142 KTimeTrackerSettings::self()->save(); 0143 } 0144 0145 void TaskView::load(const QUrl &url) 0146 { 0147 if (m_tasksWidget) { 0148 qFatal("TaskView::load must be called only once"); 0149 } 0150 0151 // if the program is used as an embedded plugin for konqueror, there may be a need 0152 // to load from a file without touching the preferences. 0153 QString err = m_storage->load(this, url); 0154 if (!err.isEmpty()) { 0155 KMessageBox::error(m_tasksWidget, err); 0156 qCDebug(KTT_LOG) << "Leaving TaskView::load"; 0157 return; 0158 } 0159 0160 m_tasksWidget = new TasksWidget(dynamic_cast<QWidget *>(parent()), m_filterProxyModel, nullptr); 0161 connect(m_tasksWidget, &TasksWidget::updateButtons, this, &TaskView::updateButtons); 0162 connect(m_tasksWidget, &TasksWidget::contextMenuRequested, this, &TaskView::contextMenuRequested); 0163 connect(m_tasksWidget, &TasksWidget::taskDoubleClicked, this, &TaskView::onTaskDoubleClicked); 0164 m_tasksWidget->setRootIsDecorated(true); 0165 0166 reconfigureModel(); 0167 0168 // Connect to the new model created by TimeTrackerStorage::load() 0169 auto *tasksModel = m_storage->tasksModel(); 0170 m_filterProxyModel->setSourceModel(tasksModel); 0171 m_tasksWidget->setSourceModel(tasksModel); 0172 m_tasksWidget->reconfigure(); 0173 for (int i = 0; i <= tasksModel->columnCount(QModelIndex()); ++i) { 0174 m_tasksWidget->resizeColumnToContents(i); 0175 } 0176 0177 // Table header context menu 0178 auto *headerContextMenu = new TreeViewHeaderContextMenu(this, m_tasksWidget, QVector<int>{0}); 0179 connect(headerContextMenu, &TreeViewHeaderContextMenu::columnToggled, this, &TaskView::slotColumnToggled); 0180 0181 connect(tasksModel, &TasksModel::taskCompleted, this, &TaskView::stopTimerFor); 0182 connect(tasksModel, &TasksModel::taskDropped, this, &TaskView::reFreshTimes); 0183 connect(tasksModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &TaskView::taskAboutToBeRemoved); 0184 connect(tasksModel, &QAbstractItemModel::rowsRemoved, this, &TaskView::taskRemoved); 0185 connect(storage()->eventsModel(), &EventsModel::timesChanged, this, &TaskView::reFreshTimes); 0186 0187 // Register tasks with desktop tracker 0188 for (Task *task : storage()->tasksModel()->getAllTasks()) { 0189 m_desktopTracker->registerForDesktops(task, task->desktops()); 0190 } 0191 0192 // Start all tasks that have an event without endtime 0193 for (Task *task : storage()->tasksModel()->getAllTasks()) { 0194 if (!m_storage->allEventsHaveEndTime(task)) { 0195 task->resumeRunning(); 0196 } 0197 } 0198 Q_EMIT updateButtons(); 0199 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0200 0201 if (storage()->tasksModel()->getActiveTasks().isEmpty()) { 0202 Q_EMIT timersInactive(); 0203 } else { 0204 Q_EMIT timersActive(); 0205 } 0206 0207 if (tasksModel->topLevelItemCount() > 0) { 0208 m_tasksWidget->restoreItemState(); 0209 m_tasksWidget->setCurrentIndex( 0210 m_filterProxyModel->mapFromSource(tasksModel->index(tasksModel->topLevelItem(0), 0))); 0211 0212 if (!m_desktopTracker->startTracking().isEmpty()) { 0213 KMessageBox::error(nullptr, 0214 i18n("Your virtual desktop number is too high, " 0215 "desktop tracking will not work.")); 0216 } 0217 } 0218 0219 storage()->projectModel()->refresh(); 0220 tasksWidget()->refresh(); 0221 0222 for (int i = 0; i <= tasksModel->columnCount(QModelIndex()); ++i) { 0223 m_tasksWidget->resizeColumnToContents(i); 0224 } 0225 } 0226 0227 void TaskView::closeStorage() 0228 { 0229 m_storage->closeStorage(); 0230 } 0231 0232 QString TaskView::reFreshTimes() 0233 { 0234 storage()->projectModel()->refreshTimes(); 0235 tasksWidget()->refresh(); 0236 return QString(); 0237 } 0238 0239 void TaskView::importPlanner(const QString &fileName) 0240 { 0241 storage()->projectModel()->importPlanner(fileName, m_tasksWidget->currentItem()); 0242 tasksWidget()->refresh(); 0243 } 0244 0245 void TaskView::save() 0246 { 0247 qCDebug(KTT_LOG) << "Entering TaskView::save()"; 0248 QString err = m_storage->save(); 0249 0250 if (!err.isNull()) { 0251 KMessageBox::error(m_tasksWidget, err); 0252 } 0253 } 0254 0255 void TaskView::startCurrentTimer() 0256 { 0257 startTimerForNow(m_tasksWidget->currentItem()); 0258 } 0259 0260 void TaskView::startTimerFor(Task *task, const QDateTime &startTime) 0261 { 0262 qCDebug(KTT_LOG) << "Entering function"; 0263 if (task != nullptr && !task->isRunning()) { 0264 if (!task->isComplete()) { 0265 if (KTimeTrackerSettings::uniTasking()) { 0266 stopAllTimers(); 0267 } 0268 m_idleTimeDetector->startIdleDetection(); 0269 0270 task->setRunning(true, startTime); 0271 } 0272 } 0273 0274 Q_EMIT updateButtons(); 0275 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0276 0277 if (!storage()->tasksModel()->getActiveTasks().isEmpty()) { 0278 Q_EMIT timersActive(); 0279 } 0280 } 0281 0282 void TaskView::startTimerForNow(Task *task) 0283 { 0284 startTimerFor(task, QDateTime::currentDateTime()); 0285 } 0286 0287 void TaskView::stopAllTimers(const QDateTime &when) 0288 { 0289 qCDebug(KTT_LOG) << "Entering function"; 0290 QProgressDialog dialog(i18nc("@info:progress", "Stopping timers..."), 0291 i18n("Cancel"), 0292 0, 0293 storage()->tasksModel()->getActiveTasks().size(), 0294 m_tasksWidget); 0295 if (storage()->tasksModel()->getActiveTasks().size() > 1) { 0296 dialog.show(); 0297 } 0298 0299 for (Task *task : storage()->tasksModel()->getActiveTasks()) { 0300 QApplication::processEvents(); 0301 0302 task->setRunning(false, when); 0303 0304 dialog.setValue(dialog.value() + 1); 0305 } 0306 0307 m_idleTimeDetector->stopIdleDetection(); 0308 Q_EMIT updateButtons(); 0309 Q_EMIT timersInactive(); 0310 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0311 } 0312 0313 void TaskView::toggleFocusTracking() 0314 { 0315 m_focusTrackingActive = !m_focusTrackingActive; 0316 0317 if (m_focusTrackingActive) { 0318 // FIXME: should get the currently active window and start tracking it? 0319 } else { 0320 stopTimerFor(m_lastTaskWithFocus); 0321 } 0322 0323 Q_EMIT updateButtons(); 0324 } 0325 0326 void TaskView::stopTimerFor(Task *task) 0327 { 0328 qCDebug(KTT_LOG) << "Entering function"; 0329 if (task != nullptr && task->isRunning()) { 0330 task->setRunning(false); 0331 0332 if (storage()->tasksModel()->getActiveTasks().isEmpty()) { 0333 m_idleTimeDetector->stopIdleDetection(); 0334 Q_EMIT timersInactive(); 0335 } 0336 Q_EMIT updateButtons(); 0337 } 0338 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0339 } 0340 0341 void TaskView::stopCurrentTimer() 0342 { 0343 stopTimerFor(m_tasksWidget->currentItem()); 0344 if (m_focusTrackingActive && m_lastTaskWithFocus == m_tasksWidget->currentItem()) { 0345 toggleFocusTracking(); 0346 } 0347 } 0348 0349 void TaskView::minuteUpdate() 0350 { 0351 storage()->tasksModel()->addTimeToActiveTasks(1); 0352 Q_EMIT minutesUpdated(storage()->tasksModel()->getActiveTasks()); 0353 } 0354 0355 void TaskView::newTask(const QString &caption, Task *parent) 0356 { 0357 QPointer<TaskPropertiesDialog> dialog = 0358 new TaskPropertiesDialog(m_tasksWidget->parentWidget(), caption, QString(), QString(), DesktopList()); 0359 0360 if (dialog->exec() == QDialog::Accepted) { 0361 QString taskName = i18n("Unnamed Task"); 0362 if (!dialog->name().isEmpty()) { 0363 taskName = dialog->name(); 0364 } 0365 QString taskDescription = dialog->description(); 0366 0367 auto desktopList = dialog->desktops(); 0368 0369 // If all available desktops are checked, disable auto tracking, 0370 // since it makes no sense to track for every desktop. 0371 if (desktopList.size() == m_desktopTracker->desktopCount()) { 0372 desktopList.clear(); 0373 } 0374 0375 int64_t total = 0; 0376 int64_t session = 0; 0377 auto *task = addTask(taskName, taskDescription, total, session, desktopList, parent); 0378 if (!task) { 0379 KMessageBox::error(nullptr, 0380 i18n("Error storing new task. Your changes were not saved. " 0381 "Make sure you can edit your iCalendar file. Also quit " 0382 "all applications using this file and remove any lock " 0383 "file related to its name from " 0384 "~/.kde/share/apps/kabc/lock/")); 0385 } 0386 } 0387 delete dialog; 0388 0389 Q_EMIT updateButtons(); 0390 } 0391 0392 Task *TaskView::addTask(const QString &taskname, 0393 const QString &taskdescription, 0394 int64_t total, 0395 int64_t session, 0396 const DesktopList &desktops, 0397 Task *parent) 0398 { 0399 qCDebug(KTT_LOG) << "Entering function; taskname =" << taskname; 0400 m_tasksWidget->setSortingEnabled(false); 0401 0402 Task *task = new Task(taskname, taskdescription, total, session, desktops, storage()->projectModel(), parent); 0403 if (task->uid().isNull()) { 0404 qFatal("failed to generate UID"); 0405 } 0406 0407 m_desktopTracker->registerForDesktops(task, desktops); 0408 m_tasksWidget->setCurrentIndex(m_filterProxyModel->mapFromSource(storage()->tasksModel()->index(task, 0))); 0409 task->invalidateCompletedState(); 0410 0411 m_tasksWidget->setSortingEnabled(true); 0412 return task; 0413 } 0414 0415 void TaskView::newSubTask() 0416 { 0417 Task *task = m_tasksWidget->currentItem(); 0418 if (!task) { 0419 return; 0420 } 0421 0422 newTask(i18nc("@title:window", "New Sub Task"), task); 0423 0424 m_tasksWidget->setExpanded(m_filterProxyModel->mapFromSource(storage()->tasksModel()->index(task, 0)), true); 0425 0426 storage()->projectModel()->refresh(); 0427 tasksWidget()->refresh(); 0428 } 0429 0430 void TaskView::editTask() 0431 { 0432 qCDebug(KTT_LOG) << "Entering editTask"; 0433 Task *task = m_tasksWidget->currentItem(); 0434 if (!task) { 0435 return; 0436 } 0437 0438 auto oldDeskTopList = task->desktops(); 0439 QPointer<TaskPropertiesDialog> dialog = new TaskPropertiesDialog(m_tasksWidget->parentWidget(), 0440 i18nc("@title:window", "Edit Task"), 0441 task->name(), 0442 task->description(), 0443 oldDeskTopList); 0444 if (dialog->exec() == QDialog::Accepted) { 0445 QString name = i18n("Unnamed Task"); 0446 if (!dialog->name().isEmpty()) { 0447 name = dialog->name(); 0448 } 0449 0450 // setName only does something if the new name is different 0451 task->setName(name); 0452 task->setDescription(dialog->description()); 0453 auto desktopList = dialog->desktops(); 0454 // If all available desktops are checked, disable auto tracking, 0455 // since it makes no sense to track for every desktop. 0456 if (desktopList.size() == m_desktopTracker->desktopCount()) { 0457 desktopList.clear(); 0458 } 0459 // only do something for autotracking if the new setting is different 0460 if (oldDeskTopList != desktopList) { 0461 task->setDesktopList(desktopList); 0462 m_desktopTracker->registerForDesktops(task, desktopList); 0463 } 0464 Q_EMIT updateButtons(); 0465 } 0466 0467 delete dialog; 0468 } 0469 0470 void TaskView::setPerCentComplete(int completion) 0471 { 0472 Task *task = m_tasksWidget->currentItem(); 0473 if (!task) { 0474 KMessageBox::information(nullptr, i18n("No task selected.")); 0475 return; 0476 } 0477 0478 if (completion < 0) { 0479 completion = 0; 0480 } 0481 if (completion < 100) { 0482 task->setPercentComplete(completion); 0483 task->invalidateCompletedState(); 0484 Q_EMIT updateButtons(); 0485 } 0486 } 0487 0488 void TaskView::deleteTaskBatch(Task *task) 0489 { 0490 QString uid = task->uid(); 0491 task->remove(); 0492 deleteEntry(uid); // forget if the item was expanded or collapsed 0493 0494 task->delete_recursive(); 0495 0496 // Stop idle detection if no more counters are running 0497 if (storage()->tasksModel()->getActiveTasks().isEmpty()) { 0498 m_idleTimeDetector->stopIdleDetection(); 0499 Q_EMIT timersInactive(); 0500 } 0501 0502 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0503 } 0504 0505 void TaskView::deleteTask(Task *task) 0506 /* Attention when popping up a window asking for confirmation. 0507 If you have "Track active applications" on, this window will create a new task and 0508 make this task running and selected. */ 0509 { 0510 if (!task) { 0511 task = m_tasksWidget->currentItem(); 0512 } 0513 0514 if (!m_tasksWidget->currentItem()) { 0515 KMessageBox::information(nullptr, i18n("No task selected.")); 0516 } else { 0517 int response = KMessageBox::Continue; 0518 if (KTimeTrackerSettings::promptDelete()) { 0519 response = KMessageBox::warningContinueCancel(nullptr, 0520 i18n("Are you sure you want to delete the selected task and " 0521 "its entire history?\n" 0522 "Note: All subtasks and their history will also be " 0523 "deleted."), 0524 i18nc("@title:window", "Deleting Task"), 0525 KStandardGuiItem::del()); 0526 } 0527 0528 if (response == KMessageBox::Continue) { 0529 deleteTaskBatch(task); 0530 } 0531 } 0532 } 0533 0534 void TaskView::markTaskAsComplete() 0535 { 0536 if (!m_tasksWidget->currentItem()) { 0537 KMessageBox::information(nullptr, i18n("No task selected.")); 0538 return; 0539 } 0540 0541 m_tasksWidget->currentItem()->setPercentComplete(100); 0542 m_tasksWidget->currentItem()->invalidateCompletedState(); 0543 Q_EMIT updateButtons(); 0544 } 0545 0546 void TaskView::subtractTime(int64_t minutes) 0547 { 0548 storage()->tasksModel()->addTimeToActiveTasks(-minutes); 0549 } 0550 0551 void TaskView::markTaskAsIncomplete() 0552 { 0553 setPerCentComplete(50); // if it has been reopened, assume half-done 0554 } 0555 0556 void TaskView::slotColumnToggled(int column) 0557 { 0558 switch (column) { 0559 case 1: 0560 KTimeTrackerSettings::setDisplaySessionTime(!m_tasksWidget->isColumnHidden(1)); 0561 break; 0562 case 2: 0563 KTimeTrackerSettings::setDisplayTime(!m_tasksWidget->isColumnHidden(2)); 0564 break; 0565 case 3: 0566 KTimeTrackerSettings::setDisplayTotalSessionTime(!m_tasksWidget->isColumnHidden(3)); 0567 break; 0568 case 4: 0569 KTimeTrackerSettings::setDisplayTotalTime(!m_tasksWidget->isColumnHidden(4)); 0570 break; 0571 case 5: 0572 KTimeTrackerSettings::setDisplayPriority(!m_tasksWidget->isColumnHidden(5)); 0573 break; 0574 case 6: 0575 KTimeTrackerSettings::setDisplayPercentComplete(!m_tasksWidget->isColumnHidden(6)); 0576 break; 0577 } 0578 KTimeTrackerSettings::self()->save(); 0579 } 0580 0581 bool TaskView::isFocusTrackingActive() const 0582 { 0583 return m_focusTrackingActive; 0584 } 0585 0586 void TaskView::reconfigureModel() 0587 { 0588 /* idleness */ 0589 m_idleTimeDetector->setMaxIdle(KTimeTrackerSettings::period()); 0590 m_idleTimeDetector->toggleOverAllIdleDetection(KTimeTrackerSettings::enabled()); 0591 0592 /* auto save */ 0593 if (KTimeTrackerSettings::autoSave()) { 0594 m_autoSaveTimer->start(KTimeTrackerSettings::autoSavePeriod() * 1000 * secsPerMinute); 0595 } else if (m_autoSaveTimer->isActive()) { 0596 m_autoSaveTimer->stop(); 0597 } 0598 0599 storage()->projectModel()->refresh(); 0600 } 0601 0602 //---------------------------------------------------------------------------- 0603 0604 void TaskView::onTaskDoubleClicked(Task *task) 0605 { 0606 if (task->isRunning()) { 0607 // if task is running, stop it 0608 stopCurrentTimer(); 0609 } else if (!task->isComplete()) { 0610 // if task is not running, start it 0611 stopAllTimers(); 0612 startCurrentTimer(); 0613 } 0614 } 0615 0616 void TaskView::editTaskTime(const QString &taskUid, int64_t minutes) 0617 { 0618 // update session time if the time was changed 0619 auto *task = m_storage->tasksModel()->taskByUID(taskUid); 0620 if (task) { 0621 task->changeTime(minutes, m_storage->eventsModel()); 0622 } 0623 } 0624 0625 void TaskView::taskAboutToBeRemoved(const QModelIndex &parent, int first, int last) 0626 { 0627 if (first != last) { 0628 qFatal( 0629 "taskAboutToBeRemoved: unexpected removal of multiple items at " 0630 "once"); 0631 } 0632 0633 TasksModelItem *item = nullptr; 0634 if (parent.isValid()) { 0635 // Nested task 0636 auto *parentItem = storage()->tasksModel()->item(parent); 0637 if (!parentItem) { 0638 qFatal("taskAboutToBeRemoved: parentItem is nullptr"); 0639 } 0640 0641 item = parentItem->child(first); 0642 } else { 0643 // Top-level task 0644 item = storage()->tasksModel()->topLevelItem(first); 0645 } 0646 0647 if (!item) { 0648 qFatal("taskAboutToBeRemoved: item is nullptr"); 0649 } 0650 0651 // We use static_cast here instead of dynamic_cast because this 0652 // taskAboutToBeRemoved() slot is called from TasksModelItem's destructor 0653 // when the Task object is already destructed, thus dynamic_cast would 0654 // return nullptr. 0655 auto *task = dynamic_cast<Task *>(item); 0656 if (!task) { 0657 qFatal("taskAboutToBeRemoved: task is nullptr"); 0658 } 0659 0660 // Handle task deletion 0661 m_desktopTracker->registerForDesktops(task, {}); 0662 } 0663 0664 void TaskView::taskRemoved(const QModelIndex & /*parent*/, int /*first*/, int /*last*/) 0665 { 0666 Q_EMIT tasksChanged(storage()->tasksModel()->getActiveTasks()); 0667 } 0668 0669 #include "moc_taskview.cpp"