From b36b55f575776af35b310f1633477f6e2bda114d Mon Sep 17 00:00:00 2001 From: spiros Date: Wed, 29 May 2024 03:49:25 +0300 Subject: [PATCH] First commit --- .gitignore | 32 +++++ CMakeLists.txt | 73 ++++++++++ custombutton.cpp | 22 +++ custombutton.h | 23 ++++ main.cpp | 11 ++ mainwindow.cpp | 348 +++++++++++++++++++++++++++++++++++++++++++++++ mainwindow.h | 44 ++++++ mainwindow.ui | 326 ++++++++++++++++++++++++++++++++++++++++++++ statusdot.cpp | 17 +++ statusdot.h | 20 +++ 10 files changed, 916 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 custombutton.cpp create mode 100644 custombutton.h create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 statusdot.cpp create mode 100644 statusdot.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..259148f --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c1a2629 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,73 @@ +cmake_minimum_required(VERSION 3.5) + +project(KDiskInfo VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) + +set(PROJECT_SOURCES + main.cpp + mainwindow.cpp + mainwindow.h + mainwindow.ui +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(KDiskInfo + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + mainwindow.ui + statusdot.h statusdot.cpp + custombutton.h custombutton.cpp + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET KDiskInfo APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(KDiskInfo SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(KDiskInfo + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_link_libraries(KDiskInfo PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.KDiskInfo) +endif() +set_target_properties(KDiskInfo PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS KDiskInfo + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(KDiskInfo) +endif() diff --git a/custombutton.cpp b/custombutton.cpp new file mode 100644 index 0000000..920d3b2 --- /dev/null +++ b/custombutton.cpp @@ -0,0 +1,22 @@ +#include "custombutton.h" + +CustomButton::CustomButton(const QString &text1, const QString &text2, const QString &text3, const QColor &lineColor, QWidget *parent) + : QPushButton(parent), text1(text1), text2(text2), text3(text3), lineColor(lineColor) { + setMinimumHeight(60); + setMinimumWidth(100); +} + +void CustomButton::paintEvent(QPaintEvent *event) { + QPushButton::paintEvent(event); + QPainter painter(this); + QPalette pal = palette(); + QColor textColor = pal.color(QPalette::ButtonText); + + QPen pen(lineColor, 5); + painter.setPen(pen); + painter.drawLine(10, 9, 10, 50); + + QRect rect(20, 0, width() - 25, height()); + painter.setPen(textColor); + painter.drawText(rect, Qt::AlignVCenter | Qt::AlignLeft, text1 + "\n" + text2 + "\n" + text3); +} diff --git a/custombutton.h b/custombutton.h new file mode 100644 index 0000000..8dd7e98 --- /dev/null +++ b/custombutton.h @@ -0,0 +1,23 @@ +#ifndef CUSTOMBUTTON_H +#define CUSTOMBUTTON_H + +#include +#include + +class CustomButton : public QPushButton { + Q_OBJECT + +public: + CustomButton(const QString &text1, const QString &text2, const QString &text3, const QColor &lineColor, QWidget *parent = nullptr); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + QString text1; + QString text2; + QString text3; + QColor lineColor; +}; + +#endif diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..fd3e533 --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..3d095f4 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,348 @@ +#include "mainwindow.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + buttonGroup = new QButtonGroup(this); + buttonGroup->setExclusive(true); + + horizontalLayout = ui->horizontalLayout; + + diskName = qobject_cast(ui->centralwidget->findChild("diskName")); + temperatureValue = qobject_cast(ui->centralwidget->findChild("temperatureValueLabel")); + healthStatusValue = qobject_cast(ui->centralwidget->findChild("healthStatusValueLabel")); + + firmwareLineEdit = qobject_cast(ui->centralwidget->findChild("firmwareLineEdit")); + serialNumberLineEdit = qobject_cast(ui->centralwidget->findChild("serialNumberLineEdit")); + typeLineEdit = qobject_cast(ui->centralwidget->findChild("typeLineEdit")); + protocolLineEdit = qobject_cast(ui->centralwidget->findChild("protocolLineEdit")); + deviceNodeLineEdit = qobject_cast(ui->centralwidget->findChild("deviceNodeLineEdit")); + + totalReadsLineEdit = qobject_cast(ui->centralwidget->findChild("totalReadsLineEdit")); + totalWritesLineEdit = qobject_cast(ui->centralwidget->findChild("totalWritesLineEdit")); + rotationRateLineEdit = qobject_cast(ui->centralwidget->findChild("rotationRateLineEdit")); + powerOnCountLineEdit = qobject_cast(ui->centralwidget->findChild("powerOnCountLineEdit")); + powerOnHoursLineEdit = qobject_cast(ui->centralwidget->findChild("powerOnHoursLineEdit")); + + tableWidget = qobject_cast(ui->centralwidget->findChild("dataTable"));; + serialNumberLineEdit->setEchoMode(QLineEdit::Password); + + QAction *toggleEchoModeAction = serialNumberLineEdit->addAction(QIcon::fromTheme(QStringLiteral("visibility")), QLineEdit::TrailingPosition); + connect(toggleEchoModeAction, &QAction::triggered, [=]() { + if (serialNumberLineEdit->echoMode() == QLineEdit::Password) { + serialNumberLineEdit->setEchoMode(QLineEdit::Normal); + toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("hint"))); + } else if (serialNumberLineEdit->echoMode() == QLineEdit::Normal) { + serialNumberLineEdit->setEchoMode(QLineEdit::Password); + toggleEchoModeAction->setIcon(QIcon::fromTheme(QStringLiteral("visibility"))); + } + }); + + scanDevices(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::scanDevices() +{ + QString output = getSmartctlOutput({"--scan", "--json"}, false); + QJsonDocument doc = QJsonDocument::fromJson(output.toUtf8()); + QJsonObject jsonObj = doc.object(); + QJsonArray devices = jsonObj["devices"].toArray(); + QJsonObject globalObj; + QString globalHealth; + bool firstTime = true; + + for (const QJsonValue &value : devices) { + QJsonObject device = value.toObject(); + QString deviceName = device["name"].toString(); + + QString allOutput = getSmartctlOutput({"--all", "--json", deviceName}, true); + + QJsonDocument localDoc = QJsonDocument::fromJson(allOutput.toUtf8()); + QJsonObject localObj = localDoc.object(); + + QJsonArray attributes = localObj["ata_smart_attributes"].toObject()["table"].toArray(); + QString temperature = "N/A"; + bool healthPassed = localObj["smart_status"].toObject()["passed"].toBool(); + bool caution = false; + QString health; + QColor healthColor; + + bool isNvme = false; + QString protocol = localObj["device"].toObject()["protocol"].toString(); + if (protocol == "NVMe") { + isNvme = true; + } + + for (const QJsonValue &attr : attributes) { // Need different logic for NVMe + QJsonObject attrObj = attr.toObject(); + if (attrObj["id"] == 194 && !isNvme) { + QString raw = attrObj["raw"].toObject()["string"].toString(); + int spaceIndex = raw.indexOf(' '); + if (spaceIndex != -1) { + raw = raw.left(spaceIndex); + } + int tempInt = raw.toInt(); + temperature = QString::number(tempInt) + " °C"; + } else if (!isNvme && (attrObj["id"] == 5 || attrObj["id"] == 197 || attrObj["id"] == 198) && (attrObj["raw"].toObject()["value"].toInt() != 0)) { + caution = true; + } + } + + if (healthPassed && !caution) { + health = "Good"; + healthColor = Qt::green; + } else if (healthPassed && caution) { + health = "Caution"; + healthColor = Qt::yellow; + } else { + health = "Bad"; + healthColor = Qt::red; + } + + CustomButton *button = new CustomButton(health, deviceName, temperature, healthColor, this); + + buttonGroup->addButton(button); + horizontalLayout->addWidget(button); + + button->setCheckable(true); + button->setAutoExclusive(true); + + connect(button, &QPushButton::clicked, this, [=]() { + populateWindow(localObj, health); + }); + + if (firstTime) { + globalObj = localObj; + globalHealth = health; + button->setChecked(true); + } + + firstTime = false; + } + horizontalLayout->addStretch(); + populateWindow(globalObj, globalHealth); +} + +void MainWindow::populateWindow(const QJsonObject &localObj, const QString &health) +{ + QJsonArray attributes = localObj["ata_smart_attributes"].toObject()["table"].toArray(); + QString modelName = localObj["model_name"].toString(); + QString firmwareVersion = localObj["firmware_version"].toString(); + float userCapacityGB = localObj.value("user_capacity").toObject().value("bytes").toDouble() / 1e9; + QString userCapacityString = QString::number(static_cast(userCapacityGB)) + "." + QString::number(static_cast((userCapacityGB - static_cast(userCapacityGB)) * 10)) + " GB"; + QString totalReads = "----"; + QString totalWrites = "----"; + QString serialNumber = localObj["serial_number"].toString(); + QJsonObject deviceObj = localObj["device"].toObject(); + QString protocol = deviceObj["protocol"].toString(); + QString type = deviceObj["type"].toString(); + QString name = deviceObj["name"].toString(); + int tempInt = 0; + + bool isNvme = false; + if (protocol == "NVMe") { + isNvme = true; + } + + diskName->setText("

" + modelName + " " + userCapacityString + "

"); + firmwareLineEdit->setText(firmwareVersion); + serialNumberLineEdit->setText(serialNumber); + typeLineEdit->setText(type); + protocolLineEdit->setText(protocol); + deviceNodeLineEdit->setText(name); + + int rotationRateInt = localObj["rotation_rate"].toInt(-1); + QString rotationRate; + if (rotationRateInt > 0) { + rotationRate = QString::number(rotationRateInt); + } else if (rotationRateInt == 0) { + rotationRate = "---- (SSD)"; + } else { + rotationRate = "----"; + } + + rotationRateLineEdit->setText(rotationRate); + rotationRateLineEdit->setAlignment(Qt::AlignRight); + + int powerCycleCountInt = localObj["power_cycle_count"].toInt(-1); + QString powerCycleCount; + if (powerCycleCountInt >= 0) { + powerCycleCount = QString::number(powerCycleCountInt) + " count"; + } else { + powerCycleCount = "Unknown"; + } + + powerOnCountLineEdit->setText(powerCycleCount); + powerOnCountLineEdit->setAlignment(Qt::AlignRight); + + int powerOnTimeInt = localObj["power_on_time"].toObject().value("hours").toInt(-1); + QString powerOnTime; + if (powerOnTimeInt >= 0) { + powerOnTime = QString::number(powerOnTimeInt) + " hours"; + } else { + powerOnTime = "Unknown"; + } + + powerOnHoursLineEdit->setText(powerOnTime); + powerOnHoursLineEdit->setAlignment(Qt::AlignRight); + + for (const QJsonValue &attr : attributes) { //Need different logic for NVMe + QJsonObject attrObj = attr.toObject(); + if (attrObj["id"] == 241 && !isNvme) { + totalWrites = QString::number(attrObj["raw"].toObject()["value"].toInt()) + " GB"; + } else if (attrObj["id"] == 242 && !isNvme) { + totalReads = QString::number(attrObj["raw"].toObject()["value"].toInt()) + " GB"; + } else if (attrObj["id"] == 194 && !isNvme) { + QString raw = attrObj["raw"].toObject()["string"].toString(); + int spaceIndex = raw.indexOf(' '); + if (spaceIndex != -1) { + raw = raw.left(spaceIndex); + } + tempInt = raw.toInt(); + } + } + + totalReadsLineEdit->setText(totalReads); + totalReadsLineEdit->setAlignment(Qt::AlignRight); + + totalWritesLineEdit->setText(totalWrites); + totalWritesLineEdit->setAlignment(Qt::AlignRight); + + if (tempInt > 55) { + temperatureValue->setStyleSheet("background-color: " + QColor(Qt::red).name() + ";"); + } else if ((tempInt < 55) && (tempInt > 50)){ + temperatureValue->setStyleSheet("background-color: " + QColor(Qt::yellow).name() + ";"); + } else if (tempInt == 0) { + temperatureValue->setStyleSheet("background-color: " + QColor(Qt::gray).name() + ";"); + } else { + temperatureValue->setStyleSheet("background-color: " + QColor(Qt::green).name() + ";"); + } + + QString labelStyle = "font-size:12pt; font-weight:700; color:black"; + + if (tempInt > 0) { + temperatureValue->setText("

" + QString::number(tempInt) + " °C

"); + } else { + temperatureValue->setText("

N/A

"); + } + + temperatureValue->setAlignment(Qt::AlignCenter); + + if (health == "Bad") { + healthStatusValue->setStyleSheet("background-color: " + QColor(Qt::red).name() + ";"); + } else if (health == "Caution"){ + healthStatusValue->setStyleSheet("background-color: " + QColor(Qt::yellow).name() + ";"); + } else { + healthStatusValue->setStyleSheet("background-color: " + QColor(Qt::green).name() + ";"); + } + + healthStatusValue->setText("

" + health + "

"); + healthStatusValue->setAlignment(Qt::AlignCenter); + + if (protocol != "NVMe") { + addSmartAttributesTable(attributes); + } +} + +void MainWindow::addSmartAttributesTable(const QJsonArray &attributes) +{ + tableWidget->setColumnCount(7); + tableWidget->setHorizontalHeaderLabels({"", "ID", "Attribute Name", "Current", "Worst", "Threshold", "Raw Values"}); + tableWidget->verticalHeader()->setVisible(false); + tableWidget->setItemDelegateForColumn(0, new StatusDot(tableWidget)); + tableWidget->setRowCount(attributes.size()); + + int row = 0; + for (const QJsonValue &attr : attributes) { + QJsonObject attrObj = attr.toObject(); + QString id = QString("%1").arg(attrObj["id"].toInt(), 2, 16, QChar('0')).toUpper(); + QString name = attrObj["name"].toString().replace("_", " "); + int value = attrObj["value"].toInt(); + int worst = attrObj["worst"].toInt(); + int thresh = attrObj["thresh"].toInt(); + QString raw = attrObj["raw"].toObject()["string"].toString(); + + int spaceIndex = raw.indexOf(' '); + if (spaceIndex != -1) { + raw = raw.left(spaceIndex); + } + raw = QString("%1").arg(raw.toUInt(nullptr), 12, 16, QChar('0')).toUpper(); + + QColor statusColor; + if ((thresh != 0) && (value < thresh)) { + statusColor = Qt::red; + } else if ((id == "05" || id == "C5" || id == "C6") && (raw != "0")) { + statusColor = Qt::yellow; + } else { + statusColor = Qt::green; + } + + QTableWidgetItem *statusItem = new QTableWidgetItem(); + statusItem->setBackground(Qt::transparent); + statusItem->setData(Qt::BackgroundRole, QVariant(statusColor)); + + QTableWidgetItem *idItem = new QTableWidgetItem(id); + idItem->setTextAlignment(Qt::AlignCenter); + + QTableWidgetItem *nameItem = new QTableWidgetItem(name); + QTableWidgetItem *valueItem = new QTableWidgetItem(QString::number(value)); + valueItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + + QTableWidgetItem *worstItem = new QTableWidgetItem(QString::number(worst)); + worstItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + + QTableWidgetItem *threshItem = new QTableWidgetItem(QString::number(thresh)); + threshItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + + QTableWidgetItem *rawItem = new QTableWidgetItem(raw); + rawItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); + + tableWidget->setItem(row, 0, statusItem); + tableWidget->setItem(row, 1, idItem); + tableWidget->setItem(row, 2, nameItem); + tableWidget->setItem(row, 3, valueItem); + tableWidget->setItem(row, 4, worstItem); + tableWidget->setItem(row, 5, threshItem); + tableWidget->setItem(row, 6, rawItem); + + ++row; + } + + tableWidget->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + for (int i = 0; i < tableWidget->columnCount(); ++i) { + if (i != 2) { + tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents); + } + } + + tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); +} + +QString MainWindow::getSmartctlOutput(const QStringList &arguments, bool root) +{ + QProcess process; + QString command; + QStringList commandArgs; + if(root) { + command = "pkexec"; + commandArgs = {"smartctl"}; + } else { + command = "smartctl"; + commandArgs = {}; + } + + commandArgs.append(arguments); + process.start(command, commandArgs); + process.waitForFinished(); + return process.readAllStandardOutput(); +} + diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..a829e33 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,44 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include + +#include "statusdot.h" +#include "custombutton.h" +#include "./ui_mainwindow.h" + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + QButtonGroup *buttonGroup; + QHBoxLayout *horizontalLayout; + QLabel *diskName, *temperatureValue, *healthStatusValue; + QLineEdit *firmwareLineEdit, *serialNumberLineEdit, *typeLineEdit, *protocolLineEdit, *deviceNodeLineEdit; + QLineEdit *totalReadsLineEdit, *totalWritesLineEdit, *rotationRateLineEdit, *powerOnCountLineEdit, *powerOnHoursLineEdit; + QTableWidget *tableWidget; + + void scanDevices(); + void populateWindow(const QJsonObject &tempObj, const QString &health); + void addSmartAttributesTable(const QJsonArray &attributes); + QString getSmartctlOutput(const QStringList &arguments, bool root); +}; +#endif diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..85cee04 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,326 @@ + + + MainWindow + + + Qt::WindowModality::NonModal + + + + 0 + 0 + 800 + 600 + + + + + 0 + 0 + + + + KDiskInfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p align="center"><span style=" font-size:14pt; font-weight:700;">Hard Drive Name</span></p></body></html> + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + + + + <html><head/><body><p align="center">Health Status</p></body></html> + + + + + + + background-color: rgb(0, 255, 0); + + + <html><head/><body><p align="center"><span style=" font-size:12pt; font-weight:700; color:#000000;">Good</span></p></body></html> + + + + + + + <html><head/><body><p align="center">Temperature</p></body></html> + + + + + + + + 16777215 + 50 + + + + background-color: rgb(0, 255, 0); + + + <html><head/><body><p align="center"><span style=" font-size:12pt; font-weight:700; color:#000000;">23° C</span></p></body></html> + + + + + + + + + + + + + Firmware + + + + + + + true + + + + + + + Serial Number + + + + + + + true + + + + + + + Protocol + + + + + + + true + + + + + + + Device Node + + + + + + + true + + + + + + true + + + + + + + Type + + + + + + + true + + + + + + + + + + + + + + + Total Host Reads + + + + + + + true + + + + + + + Total Host Writes + + + + + + + true + + + + + + + Rotation Rate + + + + + + + true + + + + + + + Power On Count + + + + + + + true + + + + + + + Power On Hours + + + + + + + true + + + + + + + + + + + + + false + + + QFrame::Shadow::Raised + + + true + + + true + + + + + + + + + + + diff --git a/statusdot.cpp b/statusdot.cpp new file mode 100644 index 0000000..0921c5e --- /dev/null +++ b/statusdot.cpp @@ -0,0 +1,17 @@ +#include "statusdot.h" + +void StatusDot::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + int dotSize = qMin(opt.rect.width(), opt.rect.height()) * 0.5; + + painter->save(); + painter->setRenderHint(QPainter::Antialiasing, true); + QRect dotRect(opt.rect.center().x() - dotSize / 2, opt.rect.center().y() - dotSize / 2, dotSize, dotSize); + QColor color = QColor(index.data(Qt::BackgroundRole).value()); + painter->setBrush(color); + painter->setPen(Qt::NoPen); + painter->drawEllipse(dotRect); + painter->restore(); +} diff --git a/statusdot.h b/statusdot.h new file mode 100644 index 0000000..572a2d3 --- /dev/null +++ b/statusdot.h @@ -0,0 +1,20 @@ +#ifndef STATUSDOT_H +#define STATUSDOT_H + +#include +#include +#include + +class StatusDot : public QStyledItemDelegate +{ +public: + StatusDot(QObject *parent = nullptr) + : QStyledItemDelegate(parent) + {} + + void paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +#endif