diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index d27e6ca2..04d459c5 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -39,10 +39,14 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/CaptureMemorySnapshot.cpp ${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/RetrieveMemorySnapshots.cpp - # Task indicator + # Task indicators ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TaskProgressIndicator/TaskProgressIndicator.cpp + # Task window + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.cpp + # Error dialogue window ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/ErrorDialogue/ErrorDialogue.cpp @@ -155,6 +159,10 @@ qt_add_resources( "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/ascii-view-disabled.svg" "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg" + # Task window + "./UserInterfaces/InsightWindow/Widgets/TaskWindow/UiFiles/TaskWindow.ui" + "./UserInterfaces/InsightWindow/Widgets/TaskWindow/Stylesheets/TaskWindow.qss" + # Target package widgets "./UserInterfaces/InsightWindow/Widgets/TargetWidgets/DIP/Stylesheets/DualInlinePackage.qss" "./UserInterfaces/InsightWindow/Widgets/TargetWidgets/QFP/Stylesheets/QuadFlatPackage.qss" diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.cpp index cd97a4b6..e29ba2e6 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.cpp @@ -13,6 +13,8 @@ namespace Bloom::Widgets this->setObjectName("task-indicator"); this->setFixedSize(50, 26); + this->taskWindow = new TaskWindow(this); + auto* insightSignals = InsightSignals::instance(); QObject::connect( @@ -42,6 +44,11 @@ namespace Bloom::Widgets this->update(); } + void TaskIndicator::mousePressEvent(QMouseEvent* event) { + this->taskWindow->show(); + this->taskWindow->activateWindow(); + } + void TaskIndicator::paintEvent(QPaintEvent* event) { auto painter = QPainter(this); painter.setPen(Qt::PenStyle::NoPen); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.hpp index 72fd9879..a0af3eea 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskIndicator/TaskIndicator.hpp @@ -4,8 +4,10 @@ #include #include #include +#include #include "src/Insight/InsightWorker/Tasks/InsightWorkerTask.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.hpp" namespace Bloom::Widgets { @@ -19,12 +21,15 @@ namespace Bloom::Widgets protected: void enterEvent(QEnterEvent* event) override; void leaveEvent(QEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent* event) override; private: std::unordered_map> activeTasksById; bool hovered = false; + TaskWindow* taskWindow = nullptr; + void onTaskQueued(QSharedPointer task); void onTaskFinished(QSharedPointer task); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Stylesheets/TaskWindow.qss b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Stylesheets/TaskWindow.qss new file mode 100644 index 00000000..e5a215e3 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Stylesheets/TaskWindow.qss @@ -0,0 +1,10 @@ +#task-window, +#task-window #container { + background-color: #373835; + border: none; +} + +#loading-placeholder-label { + color: #737375; + font-size: 14px; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.cpp new file mode 100644 index 00000000..6074d30f --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.cpp @@ -0,0 +1,148 @@ +#include "Task.hpp" + +#include +#include +#include + +namespace Bloom::Widgets +{ + Task::Task( + const QSharedPointer& task, + QWidget* parent + ) + : QWidget(parent) + , task(task) + { + this->setObjectName("task"); + this->setFixedHeight(80); + this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + + this->setContentsMargins(15, 0, 15, 0); + + QObject::connect( + this->task.get(), + &InsightWorkerTask::started, + this, + &Task::onTaskStateChanged + ); + + QObject::connect( + this->task.get(), + &InsightWorkerTask::finished, + this, + &Task::onTaskStateChanged + ); + + QObject::connect( + this->task.get(), + &InsightWorkerTask::failed, + this, + &Task::onTaskStateChanged + ); + + QObject::connect( + this->task.get(), + &InsightWorkerTask::finished, + this, + &Task::onTaskFinished + ); + + QObject::connect( + this->task.get(), + &InsightWorkerTask::progressUpdate, + this, + &Task::onTaskProgressUpdate + ); + } + + void Task::paintEvent(QPaintEvent* event) { + auto painter = QPainter(this); + + const auto margins = this->contentsMargins(); + const auto size = this->size(); + + static constexpr auto backgroundBarColor = QColor(0x8E, 0x8B, 0x83, 40); + static constexpr auto barColor = QColor(0x8E, 0x8B, 0x83, 90); + static constexpr auto fontColor = QColor(0x99, 0x9a, 0x9d); + static constexpr auto statusFontColor = QColor(0x99, 0x9a, 0x9d, 200); + + static auto font = QFont("'Ubuntu', sans-serif"); + font.setPixelSize(14); + + static auto statusFont = QFont("'Ubuntu', sans-serif"); + statusFont.setPixelSize(12); + + painter.setFont(font); + painter.setPen(fontColor); + + static constexpr auto barHeight = 5; + const auto barYPosition = 35; + + painter.drawText( + margins.left(), + barYPosition - 12, + this->task->brief() + ); + + const auto status = QString( + this->task->state == InsightWorkerTaskState::FAILED + ? "Failed" + : this->task->state == InsightWorkerTaskState::COMPLETED + ? "Completed" + : this->task->state == InsightWorkerTaskState::STARTED + ? "Running" + : "Queued" + ); + + painter.setFont(statusFont); + painter.setPen(statusFontColor); + painter.drawText( + margins.left(), + barYPosition + barHeight + 20, + status + ); + + painter.setPen(Qt::PenStyle::NoPen); + painter.setBrush(backgroundBarColor); + + painter.drawRect( + margins.left(), + barYPosition, + size.width() - margins.left() - margins.right(), + barHeight + ); + + painter.setBrush(barColor); + + painter.drawRect( + margins.left(), + barYPosition, + static_cast( + static_cast(size.width() - margins.left() - margins.right()) + * (static_cast(this->task->progressPercentage) / 100) + ), + barHeight + ); + + painter.setPen(backgroundBarColor); + + painter.drawLine( + 0, + size.height() - 1, + size.width(), + size.height() - 1 + ); + } + + void Task::onTaskProgressUpdate() { + this->update(); + } + + void Task::onTaskStateChanged() { + this->update(); + } + + void Task::onTaskFinished() { + emit this->taskComplete(this->task->id); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.hpp new file mode 100644 index 00000000..700a2e67 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Task.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include "src/Insight/InsightWorker/Tasks/InsightWorkerTask.hpp" + +namespace Bloom::Widgets +{ + class Task: public QWidget + { + Q_OBJECT + + public: + Task(const QSharedPointer& task, QWidget* parent); + + signals: + void taskComplete(InsightWorkerTask::IdType taskId); + + protected: + void paintEvent(QPaintEvent* event) override; + + private: + QSharedPointer task; + + void onTaskProgressUpdate(); + void onTaskStateChanged(); + void onTaskFinished(); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.cpp new file mode 100644 index 00000000..54bc8a0c --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.cpp @@ -0,0 +1,100 @@ +#include "TaskWindow.hpp" + +#include +#include + +#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp" + +#include "src/Insight/InsightSignals.hpp" + +#include "src/Services/PathService.hpp" +#include "src/Exceptions/Exception.hpp" + +namespace Bloom::Widgets +{ + TaskWindow::TaskWindow(QWidget* parent) + : QWidget(parent) + { + this->setObjectName("task-window"); + this->setMinimumSize(900, 500); + + this->setWindowFlag(Qt::Window); + this->setWindowTitle("Background Tasks"); + + auto windowUiFile = QFile( + QString::fromStdString(Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/UiFiles/TaskWindow.ui" + ) + ); + + auto stylesheetFile = QFile( + QString::fromStdString(Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/Stylesheets/TaskWindow.qss" + ) + ); + + if (!windowUiFile.open(QFile::ReadOnly)) { + throw Exceptions::Exception("Failed to open TaskWindow UI file"); + } + + if (!stylesheetFile.open(QFile::ReadOnly)) { + throw Exceptions::Exception("Failed to open TaskWindow stylesheet file"); + } + + auto uiLoader = UiLoader(this); + this->container = uiLoader.load(&windowUiFile, this); + this->container->setStyleSheet(stylesheetFile.readAll()); + + this->container->setContentsMargins(0, 0, 0, 0); + + this->taskWidgetLayout = this->container->findChild(); + this->taskPlaceholderLabel = this->container->findChild("loading-placeholder-label"); + + auto* insightSignals = InsightSignals::instance(); + + QObject::connect( + insightSignals, + &InsightSignals::taskQueued, + this, + &TaskWindow::onTaskQueued + ); + } + + void TaskWindow::resizeEvent(QResizeEvent* event) { + this->container->setFixedSize(this->size()); + } + + void TaskWindow::onTaskQueued(const QSharedPointer& task) { + auto* taskWidget = new Task(task, this); + this->taskWidgetLayout->insertWidget(0, taskWidget); + + QObject::connect(taskWidget, &Task::taskComplete, this, [this] (InsightWorkerTask::IdType taskId) { + auto* finishedSignalTimer = new QTimer(); + finishedSignalTimer->setSingleShot(true); + finishedSignalTimer->setInterval(10000); + + QObject::connect(finishedSignalTimer, &QTimer::timeout, this, [this, taskId] { + const auto& taskWidgetIt = this->taskWidgetsByTaskId.find(taskId); + + if (taskWidgetIt == this->taskWidgetsByTaskId.end()) { + return; + } + + auto* taskWidget = taskWidgetIt->second; + this->taskWidgetLayout->removeWidget(taskWidget); + taskWidget->deleteLater(); + this->taskWidgetsByTaskId.erase(taskWidgetIt); + + if (this->taskWidgetsByTaskId.empty()) { + this->taskPlaceholderLabel->show(); + } + }); + + finishedSignalTimer->start(); + }); + + this->taskWidgetsByTaskId.emplace(task->id, taskWidget); + + this->taskPlaceholderLabel->hide(); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.hpp new file mode 100644 index 00000000..f79baff7 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/TaskWindow.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Task.hpp" + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp" +#include "src/Insight/InsightWorker/Tasks/InsightWorkerTask.hpp" + +namespace Bloom::Widgets +{ + class TaskWindow: public QWidget + { + Q_OBJECT + + public: + TaskWindow(QWidget* parent); + + protected: + void resizeEvent(QResizeEvent* event) override; + + private: + QWidget* container = nullptr; + QVBoxLayout* taskWidgetLayout = nullptr; + + Label* taskPlaceholderLabel = nullptr; + + std::unordered_map taskWidgetsByTaskId; + + void onTaskQueued(const QSharedPointer& task); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/UiFiles/TaskWindow.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/UiFiles/TaskWindow.ui new file mode 100644 index 00000000..79fba1b2 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TaskWindow/UiFiles/TaskWindow.ui @@ -0,0 +1,45 @@ + + + + true + Qt::ScrollBarAsNeeded + QAbstractScrollArea::AdjustToContents + Qt::ScrollBarAlwaysOff + + + + + + + + + + 0 + + + 0 + + + QLayout::SetMinAndMaxSize + + + + + + + + true + + + Qt::AlignCenter + + + No background tasks + + + + + + + +