UI: Add group support to the sensor list widget

This commit is contained in:
Carl Philipp Klemm 2026-04-26 14:47:24 +02:00
parent 7af4ec4957
commit afb2d23173
2 changed files with 113 additions and 55 deletions

View file

@ -3,36 +3,39 @@
#include <QDebug> #include <QDebug>
#include <QHeaderView> #include <QHeaderView>
#include <QScroller> #include <QScroller>
#include <QMap>
#include "sensorsettingsdialog.h" #include "sensorsettingsdialog.h"
SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTableWidget(parent), SensorListWidget::SensorListWidget(const bool showHidden, QWidget *parent): QTreeWidget(parent),
showHidden_(showHidden) showHidden_(showHidden)
{ {
setColumnCount(3); setColumnCount(3);
setHeaderLabels({"Sensor", "Value", "Time"});
setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionBehavior(QAbstractItemView::SelectRows);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); header()->setSectionResizeMode(0, QHeaderView::Interactive);
header()->setSectionResizeMode(1, QHeaderView::Interactive);
header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture); QScroller::grabGesture(this, QScroller::LeftMouseButtonGesture);
setAutoScroll(true); setAutoScroll(true);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
sensorsChanged(std::vector<Sensor>()); sensorsChanged(std::vector<Sensor>());
verticalHeader()->hide();
connect(this, &QTableWidget::doubleClicked, this, &SensorListWidget::onDoubleClick); connect(this, &QTreeWidget::itemDoubleClicked, this, &SensorListWidget::onDoubleClick);
} }
SensorListWidget::SensorListWidget(SensorStore& sensorStore, const bool showHidden, SensorListWidget::SensorListWidget(SensorStore& sensorStore, const bool showHidden,
QWidget* parent): QTableWidget (parent), showHidden_(showHidden) QWidget* parent): QTreeWidget (parent), showHidden_(showHidden)
{ {
sensorsChanged(*(sensorStore.getSensors())); sensorsChanged(*(sensorStore.getSensors()));
connect(this, &QTableWidget::doubleClicked, this, &SensorListWidget::onDoubleClick); connect(this, &QTreeWidget::itemDoubleClicked, this, &SensorListWidget::onDoubleClick);
} }
void SensorListWidget::onDoubleClick(const QModelIndex &index) void SensorListWidget::onDoubleClick(QTreeWidgetItem *item, int column)
{ {
if(index.isValid()) if(item && item->type() == 1001)
{ {
const Sensor& sensor = getSensorForIndex(index); const Sensor& sensor = getSensorForIndex(currentIndex());
SensorSettingsDialog diag(sensor, this); SensorSettingsDialog diag(sensor, this);
if(diag.exec()) if(diag.exec())
{ {
@ -47,56 +50,110 @@ void SensorListWidget::onDoubleClick(const QModelIndex &index)
void SensorListWidget::sensorsChanged(std::vector<Sensor> sensors) void SensorListWidget::sensorsChanged(std::vector<Sensor> sensors)
{ {
clear(); QMap<QString, bool> expandedStates;
setHorizontalHeaderItem(0, new QTableWidgetItem("Sensor")); QList<int> columnWidths;
setHorizontalHeaderItem(1, new QTableWidgetItem("Value"));
setHorizontalHeaderItem(2, new QTableWidgetItem("Time")); for(int i = 0; i < columnCount(); ++i)
size_t listLen = 0; columnWidths.append(columnWidth(i));
for(size_t i = 0; i < sensors.size(); ++i)
if(showHidden_ || !sensors[i].hidden) for(int i = 0; i < topLevelItemCount(); ++i)
++listLen;
setRowCount(static_cast<int>(listLen));
size_t row = 0;
for(size_t i = 0; i < sensors.size(); ++i)
{ {
if(showHidden_ || !sensors[i].hidden) QTreeWidgetItem* item = topLevelItem(i);
if(item->type() != 1001)
{ {
QString itemString; expandedStates[item->text(0)] = item->isExpanded();
itemString.append(QString::number(sensors[i].field));
itemString.append(' ');
if(sensors[i].type == Sensor::TYPE_DOOR)
{
if(static_cast<bool>(sensors[i].field))
itemString.append("\"Open\"");
else itemString.append("\"Closed\"");
}
else if(sensors[i].type == Sensor::TYPE_AUDIO_OUTPUT)
{
if(static_cast<bool>(sensors[i].field))
itemString.append("\"Playing\"");
else itemString.append("\"Silent\"");
}
else if(!sensors[i].getUnit().isEmpty())
{
itemString.append(" ");
itemString.append(sensors[i].getUnit());
}
setItem(static_cast<int>(row), 0, new SensorListItem(sensors[i].name + (sensors[i].hidden ? " (H)" : ""), sensors[i]));
setItem(static_cast<int>(row), 1, new QTableWidgetItem(itemString));
if(sensors[i].type <= 128)
setItem(static_cast<int>(row), 2, new QTableWidgetItem(sensors[i].lastSeen.time().toString("hh:mm")));
++row;
} }
} }
clear();
QMap<QString, QTreeWidgetItem*> groupItems;
QStringList headerLabels = {"Sensor", "Value", "Time"};
setHeaderLabels(headerLabels);
QList<SensorListItem*> ungroupedItems;
for(const Sensor& sensor : sensors)
{
if(!showHidden_ && sensor.hidden)
continue;
QString itemString = QString::number(sensor.field);
if(sensor.type == Sensor::TYPE_DOOR)
{
if(static_cast<bool>(sensor.field))
itemString = "\"Open\"";
else
itemString = "\"Closed\"";
}
else if(sensor.type == Sensor::TYPE_AUDIO_OUTPUT)
{
if(static_cast<bool>(sensor.field))
itemString = "\"Playing\"";
else
itemString = "\"Silent\"";
}
else if(!sensor.getUnit().isEmpty())
{
itemString.append(" ");
itemString.append(sensor.getUnit());
}
SensorListItem* sensorItem = new SensorListItem(
sensor.name + (sensor.hidden ? " (H)" : ""), sensor);
sensorItem->setText(0, sensor.name + (sensor.hidden ? " (H)" : ""));
sensorItem->setText(1, itemString);
if(sensor.type <= 128)
sensorItem->setText(2, sensor.lastSeen.time().toString("hh:mm"));
if(sensor.groupName.isEmpty())
{
ungroupedItems.append(sensorItem);
}
else
{
QTreeWidgetItem* groupItem;
auto it = groupItems.find(sensor.groupName);
if(it == groupItems.end())
{
groupItem = new QTreeWidgetItem(this);
groupItem->setText(0, sensor.groupName);
bool wasExpanded = expandedStates.value(sensor.groupName, false);
groupItem->setExpanded(wasExpanded);
groupItems[sensor.groupName] = groupItem;
}
else
{
groupItem = it.value();
}
groupItem->addChild(sensorItem);
}
}
for(SensorListItem* item : ungroupedItems)
{
addTopLevelItem(item);
}
sortItems(0, Qt::AscendingOrder); sortItems(0, Qt::AscendingOrder);
resizeColumnsToContents();
for(auto it = groupItems.begin(); it != groupItems.end(); ++it)
{
it.value()->sortChildren(0, Qt::AscendingOrder);
}
for(int i = 0; i < columnCount() && i < columnWidths.size(); ++i)
setColumnWidth(i, columnWidths.at(i));
} }
const Sensor& SensorListWidget::getSensorForIndex(const QModelIndex &index) const Sensor& SensorListWidget::getSensorForIndex(const QModelIndex &index)
{ {
return static_cast<SensorListItem*>(item(index.row(), 0))->getSensor(); QTreeWidgetItem* item = itemFromIndex(index);
if(item && item->type() == 1001)
return static_cast<SensorListItem*>(item)->getSensor();
static Sensor dummy;
return dummy;
} }
void SensorListWidget::setShowHidden(const bool showHidden) void SensorListWidget::setShowHidden(const bool showHidden)
@ -111,7 +168,8 @@ const Sensor& SensorListItem::getSensor()
} }
SensorListItem::SensorListItem(const QString& text, const Sensor& sensor): SensorListItem::SensorListItem(const QString& text, const Sensor& sensor):
QTableWidgetItem(text, 1001), sensor(sensor) QTreeWidgetItem(1001), sensor(sensor)
{ {
setText(0, text);
} }

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#include <QTableWidget> #include <QTreeWidget>
#include <vector> #include <vector>
#include "sensors/sensor.h" #include "sensors/sensor.h"
class SensorListItem : public QTableWidgetItem class SensorListItem : public QTreeWidgetItem
{ {
Sensor sensor; Sensor sensor;
@ -12,7 +12,7 @@ public:
SensorListItem(const QString& text, const Sensor& sensor); SensorListItem(const QString& text, const Sensor& sensor);
}; };
class SensorListWidget : public QTableWidget class SensorListWidget : public QTreeWidget
{ {
Q_OBJECT Q_OBJECT
@ -32,5 +32,5 @@ public slots:
void sensorsChanged(std::vector<Sensor> sensors); void sensorsChanged(std::vector<Sensor> sensors);
private slots: private slots:
void onDoubleClick(const QModelIndex &index); void onDoubleClick(QTreeWidgetItem *item, int column);
}; };