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() {