diff --git a/dist/status/Disk_Bad.svg b/dist/status/Disk_Bad.svg new file mode 100644 index 0000000..5e1956e --- /dev/null +++ b/dist/status/Disk_Bad.svg @@ -0,0 +1,255 @@ + + + + diff --git a/dist/status/Disk_Caution.svg b/dist/status/Disk_Caution.svg new file mode 100644 index 0000000..7fe1d31 --- /dev/null +++ b/dist/status/Disk_Caution.svg @@ -0,0 +1,252 @@ + + diff --git a/dist/status/Disk_Good.svg b/dist/status/Disk_Good.svg new file mode 100644 index 0000000..3130366 --- /dev/null +++ b/dist/status/Disk_Good.svg @@ -0,0 +1,251 @@ + + diff --git a/dist/status/Disk_Unknown.svg b/dist/status/Disk_Unknown.svg new file mode 100644 index 0000000..16c29bd --- /dev/null +++ b/dist/status/Disk_Unknown.svg @@ -0,0 +1,252 @@ + + diff --git a/include/diskitem.h b/include/diskitem.h new file mode 100644 index 0000000..0f49c7e --- /dev/null +++ b/include/diskitem.h @@ -0,0 +1,7 @@ +#include + +struct DiskItem { + QString name; + QString temperature; + QString health; +}; diff --git a/include/gridview.h b/include/gridview.h new file mode 100644 index 0000000..3d43c11 --- /dev/null +++ b/include/gridview.h @@ -0,0 +1,39 @@ +#pragma once + +#include "diskitem.h" + +#include +#include +#include +#include + +class GridView : public QWidget { + Q_OBJECT + +public: + explicit GridView(QWidget *parent = nullptr); + void setDisks(const QVector &newDisks); + void highlightDisk(qsizetype index); + void setActiveIndex(qsizetype index); + +protected: + void resizeEvent(QResizeEvent *) override; + +signals: + void diskSelected(int index); + +private: + QString searchQuery; + QScrollArea *scrollArea; + QWidget *gridContainer; + QGridLayout *gridLayout; + QPushButton *selectedButton; + QString bgColor, borderColor, hoverColor, selectedColor; + + QList disks; + + void populateGrid(); + void extractDisksFromVector(const QVector &filteredDisks, int &cols, int &row, int &col); + + qsizetype activeIndex = -1; +}; diff --git a/include/mainwindow.h b/include/mainwindow.h index b25ba71..ebc7e82 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -1,5 +1,8 @@ #pragma once +#include "utils.h" +#include "gridview.h" + #include #include #include @@ -13,8 +16,6 @@ #include #include -#include "utils.h" - QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; @@ -46,6 +47,8 @@ private slots: void on_actionASCII_View_triggered(); + void on_actionGrid_View_triggered(); + private: Ui::MainWindow *ui; QSettings settings; @@ -78,11 +81,12 @@ private: QString globalHealth; bool globalIsNvme; QVector> globalNvmeSmartOrdered; + GridView *gridView; void onNextButtonClicked(); void onPrevButtonClicked(); void updateNavigationButtons(qsizetype currentIndex); - void updateUI(); + void updateUI(const QString ¤tDeviceName = QString()); void populateWindow(const QJsonObject &tempObj, const QString &health, const QVector>& nvmeLogOrdered = QVector>()); void addSCSIErrorCounterLogTable(const QJsonObject &scsiErrorCounterLog); void addNvmeLogTable(const QVector>& nvmeLogOrdered); diff --git a/include/utils.h b/include/utils.h index cc53e11..06fa8ec 100644 --- a/include/utils.h +++ b/include/utils.h @@ -11,7 +11,7 @@ class utils public: utils() = default; - void clearButtonGroup(QButtonGroup* buttonGroup, QHBoxLayout* horizontalLayout, QSpacerItem* buttonStretch, QMenu* menuDisk); + QString clearButtonGroup(QButtonGroup* buttonGroup, QHBoxLayout* horizontalLayout, QSpacerItem* buttonStretch, QMenu* menuDisk); QString getSmartctlPath(); QString getSmartctlOutput(const QStringList &arguments, bool root, bool initializing); QPair scanDevices(bool initializing); diff --git a/src/gridview.cpp b/src/gridview.cpp new file mode 100644 index 0000000..f2c929f --- /dev/null +++ b/src/gridview.cpp @@ -0,0 +1,177 @@ +#include "gridview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int gridLayoutSpacing = 10; +int iconButtonSize = 64; + +GridView::GridView(QWidget *parent) : QWidget(parent) { + setWindowTitle(tr("Grid View")); + resize(600, 300); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + + QComboBox *searchField = new QComboBox(); + searchField->setEditable(true); + QLineEdit *lineEdit = searchField->lineEdit(); + lineEdit->setPlaceholderText(tr("Search for a disk...")); + connect(lineEdit, &QLineEdit::textChanged, this, [this](const QString &text) { + this->searchQuery = text; + populateGrid(); + }); + mainLayout->addWidget(searchField); + + QPalette palette = this->palette(); + bgColor = palette.color(QPalette::Base).name(); + borderColor = palette.color(QPalette::Mid).name(); + hoverColor = palette.color(QPalette::Highlight).name(); + selectedColor = palette.color(QPalette::Highlight).name(); + + QFrame *gridFrame = new QFrame(); + + scrollArea = new QScrollArea(); + scrollArea->setWidgetResizable(true); + scrollArea->setFrameStyle(QFrame::NoFrame); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setWidget(gridFrame); + mainLayout->addWidget(scrollArea); + + gridContainer = new QWidget(); + gridLayout = new QGridLayout(gridContainer); + gridLayout->setAlignment(Qt::AlignTop | Qt::AlignHCenter); + gridLayout->setSpacing(gridLayoutSpacing); + gridFrame->setLayout(gridLayout); + + setLayout(mainLayout); +} + +void GridView::resizeEvent(QResizeEvent *) { + populateGrid(); +} + +void GridView::setDisks(const QVector &newDisks) { + disks = newDisks; + populateGrid(); +} + +void GridView::highlightDisk(qsizetype index) { + if (index < 0 || index >= gridLayout->count()) + return; + + QWidget *diskWidget = gridLayout->itemAt(static_cast(index))->widget(); + if (!diskWidget) + return; + + QPushButton *iconButton = diskWidget->findChild(); + if (!iconButton) + return; + + if (selectedButton) { + selectedButton->setStyleSheet(iconButton->styleSheet()); + } + + selectedButton = iconButton; + selectedButton->setStyleSheet( + QString("QPushButton { border: 2px solid %1; background-color: %2; }") + .arg(selectedColor, hoverColor)); +} + +void GridView::setActiveIndex(qsizetype index) { + activeIndex = index; + if (gridLayout && gridLayout->count() > 0) { + highlightDisk(activeIndex); + } +} + +void GridView::populateGrid() { + selectedButton = nullptr; + QLayoutItem *child; + while ((child = gridLayout->takeAt(0)) != nullptr) { + delete child->widget(); + delete child; + } + + int width = scrollArea->viewport()->width(); + int columnsFormula = (width + gridLayoutSpacing) / (iconButtonSize + gridLayoutSpacing); + int cols = qMax(1, columnsFormula - 1); + int row = 0, col = 0; + + + QVector filteredDisks; + filteredDisks.reserve(disks.size()); + + for (auto it = disks.cbegin(); it != disks.cend(); ++it) { + const DiskItem &disk = *it; + if (searchQuery.isEmpty() || + disk.name.contains(searchQuery, Qt::CaseInsensitive) || + disk.temperature.contains(searchQuery, Qt::CaseInsensitive)) { + filteredDisks.append(disk); + } + } + + extractDisksFromVector(filteredDisks, cols, row, col); + + if (activeIndex >= 0 && activeIndex < filteredDisks.size()) { + highlightDisk(activeIndex); + } +} + +void GridView::extractDisksFromVector(const QVector &filteredDisks, int &cols, int &row, int &col) { + for (int i = 0; i < filteredDisks.size(); ++i) { + const auto &disk = filteredDisks[i]; + QWidget *diskWidget = new QWidget(); + QVBoxLayout *diskLayout = new QVBoxLayout(diskWidget); + diskLayout->setAlignment(Qt::AlignCenter); + diskLayout->setContentsMargins(0, 0, 0, 0); + + QPushButton *iconButton = new QPushButton(); + QString iconPath = QString(":/icons/Disk_%1.svg").arg(disk.health); + iconButton->setIcon(QIcon(iconPath)); + iconButton->setIconSize(QSize(48, 48)); + iconButton->setFixedSize(iconButtonSize, iconButtonSize); + iconButton->setStyleSheet( + QString("QPushButton { border: 2px solid transparent; " + "border-radius: 10px; }" + "QPushButton:hover { background-color: %1; }" + "QPushButton:pressed { border: 2px solid %2; }") + .arg(hoverColor, selectedColor)); + + QLabel *nameLabel = new QLabel(disk.name); + nameLabel->setAlignment(Qt::AlignCenter); + QLabel *temperatureLabel = new QLabel(disk.temperature); + temperatureLabel->setAlignment(Qt::AlignCenter); + temperatureLabel->setStyleSheet("font-size: 10px; color: gray;"); + + connect(iconButton, &QPushButton::clicked, this, [this, iconButton, disk, i]() { + if (selectedButton) { + selectedButton->setStyleSheet(iconButton->styleSheet()); + } + selectedButton = iconButton; + selectedButton->setStyleSheet( + QString( + "QPushButton { border: 2px solid %1; background-color: %2; }") + .arg(selectedColor, hoverColor)); + emit diskSelected(i); + }); + + diskLayout->addWidget(iconButton, 0, Qt::AlignCenter); + diskLayout->addWidget(nameLabel, 0, Qt::AlignCenter); + diskLayout->addWidget(temperatureLabel, 0, Qt::AlignCenter); + diskWidget->setLayout(diskLayout); + gridLayout->addWidget(diskWidget, row, col); + + col++; + if (col >= cols) { + col = 0; + row++; + } + } +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e935cc9..ec3a182 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -93,6 +93,9 @@ MainWindow::MainWindow(QWidget *parent) statusLabel = new QLabel; + gridView = new GridView(this); + gridView->setWindowFlag(Qt::Window); + ui->actionIgnore_C4_Reallocation_Event_Count->setChecked(settings.value("IgnoreC4", true).toBool()); ui->actionHEX->setChecked(settings.value("HEX", true).toBool()); ui->actionUse_Fahrenheit->setChecked(settings.value("Fahrenheit", false).toBool()); @@ -167,15 +170,33 @@ void MainWindow::onPrevButtonClicked() void MainWindow::updateNavigationButtons(qsizetype currentIndex) { - prevButton->setEnabled(currentIndex > 0 || (ui->actionCyclic_Navigation->isChecked() && buttonGroup->buttons().size() > 1)); // We can use setVisible if we want to mimic CrystalDiskInfo - nextButton->setEnabled(currentIndex < buttonGroup->buttons().size() - 1 || ui->actionCyclic_Navigation->isChecked()); + const qsizetype totalButtons = buttonGroup->buttons().size(); + const bool isCyclic = ui->actionCyclic_Navigation->isChecked(); + + prevButton->setEnabled( // We can use setVisible if we want to mimic CrystalDiskInfo + (currentIndex > 0) || (isCyclic && totalButtons > 1) + ); + + nextButton->setEnabled( + (currentIndex < totalButtons - 1) || (isCyclic && totalButtons > 1) + ); } -void MainWindow::updateUI() +void MainWindow::updateUI(const QString ¤tDeviceName) { + QVector diskItems; + bool firstTime = true; + bool isFahrenheit = ui->actionUse_Fahrenheit->isChecked(); globalIsNvme = false; + QString degreeSymbol; + if (isFahrenheit) { // We don't do Kelvin + degreeSymbol = "°F"; + } else { + degreeSymbol = "°C"; + } + QList oldActions = disksGroup->actions(); for (QAction *action : std::as_const(oldActions)) { disksGroup->removeAction(action); @@ -183,6 +204,8 @@ void MainWindow::updateUI() delete action; } + int deviceToSelect = -1; + for (int i = 0; i < devices.size(); ++i) { QJsonObject device = devices[i].toObject(); QString deviceName = device["name"].toString(); @@ -207,7 +230,7 @@ void MainWindow::updateUI() } QJsonArray attributes = localObj["ata_smart_attributes"].toObject()["table"].toArray(); - QString temperature = "-- °C"; + QString temperature = "-- " + degreeSymbol; QJsonValue smartStatusValue = localObj.value("smart_status"); bool healthPassed = localObj["smart_status"].toObject()["passed"].toBool(); bool caution = false; @@ -230,12 +253,13 @@ void MainWindow::updateUI() QJsonObject temperatureObj = localObj["temperature"].toObject(); int temperatureInt = temperatureObj["current"].toInt(); if (temperatureInt > 0) { - if (ui->actionUse_Fahrenheit->isChecked()) { + if (isFahrenheit) { int fahrenheit = static_cast((temperatureInt * 9.0 / 5.0) + 32.0); - temperature = QString::number(fahrenheit) + " °F"; + temperature = QString::number(fahrenheit); } else { - temperature = QString::number(temperatureInt) + " °C"; + temperature = QString::number(temperatureInt); } + temperature = temperature + " " + degreeSymbol; } QVector> nvmeSmartOrdered; @@ -311,6 +335,7 @@ void MainWindow::updateUI() CustomButton *button = new CustomButton(health, temperature, deviceName, healthColor, this); button->setToolTip(tr("Disk") + " " + QString::number(i) + " : " + modelName + " : " + diskCapacityString); + button->setProperty("deviceName", deviceName); buttonGroup->addButton(button); horizontalLayout->addWidget(button); @@ -325,6 +350,8 @@ void MainWindow::updateUI() qsizetype buttonIndex = buttonGroup->buttons().indexOf(button); + diskItems.append({ deviceName, temperature, health }); + auto updateWindow = [=]() { if (isNvme) { populateWindow(localObj, health, nvmeSmartOrdered); @@ -337,11 +364,15 @@ void MainWindow::updateUI() connect(button, &QPushButton::clicked, this, [=]() { updateWindow(); disksGroup->actions().at(buttonIndex)->setChecked(true); + gridView->highlightDisk(buttonIndex); + gridView->setActiveIndex(buttonIndex); }); connect(diskAction, &QAction::triggered, this, [=]() { updateWindow(); button->setChecked(true); + gridView->highlightDisk(buttonIndex); + gridView->setActiveIndex(buttonIndex); }); if (firstTime) { @@ -349,12 +380,35 @@ void MainWindow::updateUI() globalHealth = health; button->setChecked(true); diskAction->setChecked(true); + gridView->setActiveIndex(0); firstTime = false; globalIsNvme = isNvme; if (isNvme) { globalNvmeSmartOrdered = nvmeSmartOrdered; } } + + if (!currentDeviceName.isEmpty() && deviceName == currentDeviceName) { + deviceToSelect = i; + globalObj = localObj; + globalHealth = health; + globalIsNvme = isNvme; + if (isNvme) { + globalNvmeSmartOrdered = nvmeSmartOrdered; + } + button->setChecked(true); + diskAction->setChecked(true); + firstTime = false; + } + + connect(gridView, &GridView::diskSelected, this, [=](int selectedIndex) { + if (selectedIndex >= 0 && selectedIndex < buttonGroup->buttons().size()) { + QAbstractButton *gridButton = buttonGroup->buttons().at(selectedIndex); + if (gridButton) { + gridButton->click(); + } + } + }); } buttonStretch = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); @@ -366,7 +420,10 @@ void MainWindow::updateUI() populateWindow(globalObj, globalHealth); } - updateNavigationButtons(buttonGroup->buttons().indexOf(buttonGroup->checkedButton())); + gridView->setDisks(diskItems); + int activeIndex = deviceToSelect >= 0 ? deviceToSelect : 0; + gridView->setActiveIndex(activeIndex); + updateNavigationButtons(activeIndex); } void MainWindow::populateWindow(const QJsonObject &localObj, const QString &health, const QVector>& nvmeLogOrdered) @@ -1214,6 +1271,16 @@ void MainWindow::transformWindow() { ui->centralwidget->setAutoFillBackground(true); } +void MainWindow::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::ForwardButton && nextButton->isEnabled()) { + onNextButtonClicked(); + } else if (event->button() == Qt::BackButton && prevButton->isEnabled()) { + onPrevButtonClicked(); + } +} + +// Slots void MainWindow::on_actionQuit_triggered() { qApp->quit(); @@ -1275,8 +1342,7 @@ void MainWindow::on_actionRescan_Refresh_triggered() deviceOutputs = values.first; devices = values.second; if (!deviceOutputs.isEmpty()) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } @@ -1284,8 +1350,7 @@ void MainWindow::on_actionIgnore_C4_Reallocation_Event_Count_toggled(bool enable { settings.setValue("IgnoreC4", enabled); if (!initializing) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } @@ -1293,8 +1358,7 @@ void MainWindow::on_actionHEX_toggled(bool enabled) { settings.setValue("HEX", enabled); if (!initializing) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } @@ -1302,8 +1366,7 @@ void MainWindow::on_actionUse_Fahrenheit_toggled(bool enabled) { settings.setValue("Fahrenheit", enabled); if (!initializing) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } @@ -1318,17 +1381,7 @@ void MainWindow::on_actionUse_GB_instead_of_TB_toggled(bool gigabytes) { settings.setValue("UseGB", gigabytes); if (!initializing) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); - } -} - -void MainWindow::mousePressEvent(QMouseEvent *event) -{ - if (event->button() == Qt::ForwardButton && nextButton->isEnabled()) { - onNextButtonClicked(); - } else if (event->button() == Qt::BackButton && prevButton->isEnabled()) { - onPrevButtonClicked(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } @@ -1359,8 +1412,7 @@ void MainWindow::on_actionClear_Settings_triggered() ui->actionUse_GB_instead_of_TB->setChecked(false); if (!initializing) { - Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk); - updateUI(); + updateUI(Utils.clearButtonGroup(buttonGroup, horizontalLayout, buttonStretch, menuDisk)); } } } @@ -1419,3 +1471,8 @@ void MainWindow::on_actionASCII_View_triggered() asciiViewDialog->exec(); } +void MainWindow::on_actionGrid_View_triggered() +{ + gridView->show(); +} + diff --git a/src/mainwindow.ui b/src/mainwindow.ui index d7c18f3..2b7d47b 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -534,6 +534,8 @@ Disk + + @@ -651,6 +653,14 @@ ASCII View + + + + + + Grid View + + diff --git a/src/resources.qrc b/src/resources.qrc index 3fd7313..b51a6d2 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,5 +1,9 @@ ../dist/QDiskInfo.svg + ../dist/status/Disk_Good.svg + ../dist/status/Disk_Bad.svg + ../dist/status/Disk_Caution.svg + ../dist/status/Disk_Unknown.svg diff --git a/src/utils.cpp b/src/utils.cpp index 2295803..1b52780 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -9,9 +9,12 @@ #include #include #include +#include -void utils::clearButtonGroup(QButtonGroup* buttonGroup, QHBoxLayout* horizontalLayout, QSpacerItem* buttonStretch, QMenu* menuDisk) +QString utils::clearButtonGroup(QButtonGroup* buttonGroup, QHBoxLayout* horizontalLayout, QSpacerItem* buttonStretch, QMenu* menuDisk) { + QString currentDeviceName = buttonGroup->checkedButton()->property("deviceName").toString(); + QList buttons = buttonGroup->buttons(); for (QAbstractButton* button : std::as_const(buttons)) { buttonGroup->removeButton(button); @@ -19,7 +22,21 @@ void utils::clearButtonGroup(QButtonGroup* buttonGroup, QHBoxLayout* horizontalL } horizontalLayout->removeItem(buttonStretch); delete buttonStretch; - menuDisk->clear(); + + // Dirty hack to remove only the radio buttons + QList actions = menuDisk->actions(); + bool foundSeparator = false; + + for (int i = 0; i < actions.size(); ++i) { + QAction* action = actions[i]; + if (foundSeparator) { + menuDisk->removeAction(action); + } else if (action->isSeparator()) { + foundSeparator = true; + } + } + + return currentDeviceName; } QString utils::getSmartctlPath() {