445 lines
14 KiB
C++
445 lines
14 KiB
C++
#include "TearingDataTableWidget.h"
|
||
#include <QVBoxLayout>
|
||
#include <QHeaderView>
|
||
#include <QTableWidgetItem>
|
||
#include <QBrush>
|
||
#include <QColor>
|
||
#include <QApplication>
|
||
#include <QStyle>
|
||
#include <QRegExp>
|
||
#include <QFontMetrics>
|
||
|
||
namespace {
|
||
enum TableColumn {
|
||
IdColumn = 0,
|
||
LengthColumn,
|
||
WidthColumn,
|
||
DepthColumn,
|
||
TimeColumn,
|
||
ColumnCount
|
||
};
|
||
|
||
const int DeviceNameRole = Qt::UserRole;
|
||
|
||
QString formatMeasurement(const QString &value)
|
||
{
|
||
bool ok = false;
|
||
const double number = value.toDouble(&ok);
|
||
return ok ? QString::number(number, 'f', 3) : value;
|
||
}
|
||
}
|
||
|
||
TearingDataTableWidget::TearingDataTableWidget(QWidget *parent)
|
||
: QWidget(parent)
|
||
, m_tableWidget(nullptr)
|
||
{
|
||
// 初始化与服务端颜色协调的颜色数组
|
||
m_tearColors[0] = QColor(255, 105, 97); // 珊瑚红
|
||
m_tearColors[1] = QColor(255, 160, 122); // 浅鲑鱼色
|
||
m_tearColors[2] = QColor(173, 216, 230); // 浅蓝色
|
||
m_tearColors[3] = QColor(144, 238, 144); // 浅绿色
|
||
m_tearColors[4] = QColor(255, 182, 193); // 浅粉色
|
||
m_tearColors[5] = QColor(221, 160, 221); // 梅花色
|
||
m_tearColors[6] = QColor(255, 215, 0); // 金色
|
||
m_tearColors[7] = QColor(240, 128, 128); // 玫瑰色
|
||
m_tearColors[8] = QColor(135, 206, 250); // 天蓝色
|
||
m_tearColors[9] = QColor(127, 255, 212); // 碧绿色
|
||
|
||
setupUI();
|
||
setupTable();
|
||
}
|
||
|
||
TearingDataTableWidget::~TearingDataTableWidget()
|
||
{
|
||
}
|
||
|
||
void TearingDataTableWidget::setupUI()
|
||
{
|
||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||
layout->setContentsMargins(0, 0, 0, 0);
|
||
|
||
m_tableWidget = new QTableWidget(this);
|
||
layout->addWidget(m_tableWidget);
|
||
|
||
setLayout(layout);
|
||
}
|
||
|
||
void TearingDataTableWidget::setupTable()
|
||
{
|
||
const QStringList headers = {
|
||
"编号", "长度(mm)", "宽度(mm)", "深度(mm)", "时间"
|
||
};
|
||
|
||
// 设置表格属性
|
||
m_tableWidget->setColumnCount(ColumnCount);
|
||
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||
m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||
m_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||
m_tableWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||
m_tableWidget->verticalHeader()->setVisible(false);
|
||
m_tableWidget->setHorizontalHeaderLabels(headers);
|
||
|
||
QFont tableFont = m_tableWidget->font();
|
||
int basePointSize = tableFont.pointSize();
|
||
if (basePointSize <= 0) {
|
||
basePointSize = QApplication::font().pointSize();
|
||
}
|
||
tableFont.setPointSize(qBound(9, qRound(basePointSize * 1.15), 11));
|
||
m_tableWidget->setFont(tableFont);
|
||
m_tableWidget->horizontalHeader()->setFont(tableFont);
|
||
|
||
const int sectionHeight = QFontMetrics(tableFont).height() + 10;
|
||
m_tableWidget->verticalHeader()->setDefaultSectionSize(sectionHeight);
|
||
m_tableWidget->horizontalHeader()->setMinimumHeight(sectionHeight);
|
||
|
||
QHeaderView *header = m_tableWidget->horizontalHeader();
|
||
header->setMinimumSectionSize(40);
|
||
header->setSectionResizeMode(QHeaderView::Stretch);
|
||
// 时间字符串较长,按内容计算列宽;其余列自动平分剩余空间。
|
||
header->setSectionResizeMode(TimeColumn, QHeaderView::ResizeToContents);
|
||
m_tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||
// 设置样式表
|
||
m_tableWidget->setStyleSheet(
|
||
"QTableWidget {"
|
||
" background-color: rgb(37,38,42);"
|
||
" gridline-color: rgb(60,60,60);"
|
||
" selection-background-color: rgb(80,80,80);"
|
||
"}"
|
||
"QTableWidget::item {"
|
||
" color: white;"
|
||
"}"
|
||
"QHeaderView::section {"
|
||
" background-color: rgb(45,45,45);"
|
||
" color: white;"
|
||
" padding: 2px;"
|
||
"}"
|
||
);
|
||
|
||
// 启用表格排序功能
|
||
m_tableWidget->setSortingEnabled(true);
|
||
|
||
// 设置默认按编号列倒序排序
|
||
m_tableWidget->sortItems(IdColumn, Qt::DescendingOrder);
|
||
}
|
||
|
||
|
||
void TearingDataTableWidget::_AddDataToTable(const QString devName, const TearingData &data, int row)
|
||
{
|
||
NumericTableWidgetItem *idItem = new NumericTableWidgetItem(data.id);
|
||
idItem->setData(DeviceNameRole, devName);
|
||
m_tableWidget->setItem(row, IdColumn, idItem);
|
||
|
||
m_tableWidget->setItem(row, LengthColumn, new NumericTableWidgetItem(formatMeasurement(data.tearLength)));
|
||
m_tableWidget->setItem(row, WidthColumn, new NumericTableWidgetItem(formatMeasurement(data.tearWidth)));
|
||
m_tableWidget->setItem(row, DepthColumn, new NumericTableWidgetItem(formatMeasurement(data.tearDepth)));
|
||
|
||
QString currentTime = QString::fromStdString(CVrDateUtils::GetStrNowTime(false));
|
||
QTableWidgetItem *timeItem = new QTableWidgetItem(currentTime);
|
||
m_tableWidget->setItem(row, TimeColumn, timeItem);
|
||
|
||
for (int i = 0; i < ColumnCount; ++i) {
|
||
QTableWidgetItem *item = m_tableWidget->item(row, i);
|
||
if (item) {
|
||
item->setTextAlignment(Qt::AlignCenter);
|
||
item->setForeground(QBrush(Qt::white));
|
||
}
|
||
}
|
||
}
|
||
|
||
void TearingDataTableWidget::addData(const QString devName, const TearingData &data)
|
||
{
|
||
// 构造键值
|
||
QString key = devName + ":" + data.id;
|
||
|
||
// 禁用排序以提高性能,批量处理后再启用
|
||
m_tableWidget->setSortingEnabled(false);
|
||
|
||
// 查找是否已存在相同设备名称和ID的行
|
||
int existingRow = -1;
|
||
if (m_devIdSet.contains(key)) {
|
||
existingRow = findExistingRowById(devName, data.id);
|
||
}
|
||
|
||
int row;
|
||
if (existingRow >= 0) {
|
||
// 如果存在相同设备名称和ID,更新该行
|
||
row = existingRow;
|
||
} else {
|
||
// 如果不存在,插入新行
|
||
row = m_tableWidget->rowCount();
|
||
m_tableWidget->insertRow(row);
|
||
// 添加到集合中
|
||
m_devIdSet.insert(key);
|
||
}
|
||
|
||
_AddDataToTable(devName, data, row);
|
||
|
||
// 重新启用排序
|
||
m_tableWidget->setSortingEnabled(true);
|
||
|
||
// 手动触发一次按ID列倒序排序
|
||
m_tableWidget->sortItems(IdColumn, Qt::DescendingOrder);
|
||
|
||
// 检查是否需要限制行数
|
||
limitRowsIfNeeded();
|
||
}
|
||
|
||
void TearingDataTableWidget::addData(const QString devName, const std::vector<TearingData> &dataList)
|
||
{
|
||
if (dataList.empty()) {
|
||
return;
|
||
}
|
||
|
||
// 对于批量数据,禁用排序和更新以提高性能
|
||
m_tableWidget->setSortingEnabled(false);
|
||
m_tableWidget->setUpdatesEnabled(false);
|
||
|
||
// 遍历vector中的所有数据并添加到表格
|
||
for (const auto &data : dataList) {
|
||
addData(devName, data);
|
||
}
|
||
|
||
// 重新启用更新和排序
|
||
m_tableWidget->setUpdatesEnabled(true);
|
||
m_tableWidget->setSortingEnabled(true);
|
||
|
||
// 手动触发一次按ID列倒序排序
|
||
m_tableWidget->sortItems(IdColumn, Qt::DescendingOrder);
|
||
|
||
// 检查是否需要限制行数
|
||
limitRowsIfNeeded();
|
||
}
|
||
|
||
void TearingDataTableWidget::addDataBatch(const QString devName, const std::vector<TearingData> &dataList)
|
||
{
|
||
if (dataList.empty()) {
|
||
return;
|
||
}
|
||
|
||
// 对于大批量数据,采用更激进的优化策略
|
||
m_tableWidget->setSortingEnabled(false);
|
||
m_tableWidget->setUpdatesEnabled(false);
|
||
|
||
// 预先计算新行数
|
||
int currentRowCount = m_tableWidget->rowCount();
|
||
int newDataCount = dataList.size();
|
||
|
||
// 批量插入新行(如果有新数据需要插入)
|
||
// 先找出需要新增的数据量
|
||
int rowsToAdd = 0;
|
||
for (const auto &data : dataList) {
|
||
QString key = devName + ":" + data.id;
|
||
if (!m_devIdSet.contains(key)) {
|
||
rowsToAdd++;
|
||
}
|
||
}
|
||
|
||
// 批量插入新行
|
||
if (rowsToAdd > 0) {
|
||
m_tableWidget->setRowCount(currentRowCount + rowsToAdd);
|
||
}
|
||
|
||
// 填充数据
|
||
for (const auto &data : dataList) {
|
||
// 构造键值
|
||
QString key = devName + ":" + data.id;
|
||
|
||
// 查找是否已存在相同设备名称和ID的行
|
||
int existingRow = -1;
|
||
if (m_devIdSet.contains(key)) {
|
||
existingRow = findExistingRowById(devName, data.id);
|
||
}
|
||
|
||
int row;
|
||
if (existingRow >= 0) {
|
||
// 如果存在相同设备名称和ID,更新该行
|
||
row = existingRow;
|
||
} else {
|
||
// 如果不存在,使用新行
|
||
row = m_tableWidget->rowCount() - rowsToAdd;
|
||
rowsToAdd--; // 减少剩余需要分配的行数
|
||
// 添加到集合中
|
||
m_devIdSet.insert(key);
|
||
}
|
||
|
||
_AddDataToTable(devName, data, row);
|
||
}
|
||
|
||
|
||
// 重新启用更新和排序
|
||
m_tableWidget->setUpdatesEnabled(true);
|
||
m_tableWidget->setSortingEnabled(true);
|
||
|
||
// 手动触发一次按ID列倒序排序
|
||
m_tableWidget->sortItems(IdColumn, Qt::DescendingOrder);
|
||
|
||
// 检查是否需要限制行数
|
||
limitRowsIfNeeded();
|
||
}
|
||
|
||
void TearingDataTableWidget::clearData()
|
||
{
|
||
// 添加空指针检查,防止在m_tableWidget为nullptr时崩溃
|
||
if (m_tableWidget) {
|
||
// 清除所有选择
|
||
m_tableWidget->clearSelection();
|
||
|
||
// 清除所有行数据
|
||
m_tableWidget->setRowCount(0);
|
||
|
||
// 清空集合
|
||
m_devIdSet.clear();
|
||
|
||
// 重置排序状态,默认按ID列倒序排序
|
||
m_tableWidget->setSortingEnabled(false);
|
||
m_tableWidget->setSortingEnabled(true);
|
||
m_tableWidget->sortItems(IdColumn, Qt::DescendingOrder);
|
||
}
|
||
}
|
||
|
||
void TearingDataTableWidget::setMaximumRows(int maxRows)
|
||
{
|
||
// 设置表格最大行数,防止内存占用过大
|
||
m_maxRows = maxRows;
|
||
}
|
||
|
||
void TearingDataTableWidget::removeRowFromSet(int row)
|
||
{
|
||
if (row >= 0 && row < m_tableWidget->rowCount()) {
|
||
QTableWidgetItem *idItem = m_tableWidget->item(row, IdColumn);
|
||
|
||
if (idItem) {
|
||
QString key = idItem->data(DeviceNameRole).toString() + ":" + idItem->text();
|
||
m_devIdSet.remove(key);
|
||
}
|
||
}
|
||
}
|
||
|
||
void TearingDataTableWidget::limitRowsIfNeeded()
|
||
{
|
||
// 添加空指针检查,防止在m_tableWidget为nullptr时崩溃
|
||
if (!m_tableWidget) {
|
||
return;
|
||
}
|
||
|
||
// 如果设置了最大行数限制,检查是否需要删除旧数据
|
||
if (m_maxRows > 0 && m_tableWidget->rowCount() > m_maxRows) {
|
||
int rowsToDelete = m_tableWidget->rowCount() - m_maxRows;
|
||
// 禁用更新以提高性能
|
||
m_tableWidget->setUpdatesEnabled(false);
|
||
|
||
// 从后往前删除多余的行(假设新数据在后面)
|
||
for (int i = 0; i < rowsToDelete; i++) {
|
||
int rowToRemove = m_tableWidget->rowCount() - 1 - i;
|
||
// 从集合中移除
|
||
removeRowFromSet(rowToRemove);
|
||
m_tableWidget->removeRow(rowToRemove);
|
||
}
|
||
|
||
// 重新启用更新
|
||
m_tableWidget->setUpdatesEnabled(true);
|
||
}
|
||
}
|
||
|
||
QString TearingDataTableWidget::getTearStatusText(const QString &status)
|
||
{
|
||
// 将ESG_tearStatus枚举值转换为中文显示
|
||
if (status == "0" || status.contains("Uknown")) {
|
||
return "未知";
|
||
} else if (status == "1" || status.contains("New")) {
|
||
return "新增";
|
||
} else if (status == "2" || status.contains("Growing")) {
|
||
return "进行";
|
||
} else if (status == "3" || status.contains("Ended")) {
|
||
return "结束";
|
||
} else if (status == "4" || status.contains("Invalid")) {
|
||
return "无效";
|
||
}
|
||
return status; // 如果无法识别,返回原值
|
||
}
|
||
|
||
QString TearingDataTableWidget::getTearTypeText(const QString &type)
|
||
{
|
||
// 将ESG_tearType枚举值转换为中文显示
|
||
if (type == "0" || type.contains("Uknown")) {
|
||
return "未知";
|
||
} else if (type == "1" || type.contains("MachineDir")) {
|
||
return "纵撕";
|
||
} else if (type == "2" || type.contains("CrossWise")) {
|
||
return "横撕";
|
||
}
|
||
return type; // 如果无法识别,返回原值
|
||
}
|
||
|
||
int TearingDataTableWidget::findExistingRowById(const QString &devName, const QString &id)
|
||
{
|
||
// 构造键值
|
||
QString key = devName + ":" + id;
|
||
|
||
// 使用哈希表快速判断是否存在
|
||
if (!m_devIdSet.contains(key)) {
|
||
return -1; // 不存在直接返回-1
|
||
}
|
||
|
||
// 如果存在,则在表格中查找具体行号(虽然概率很小,但为了确保数据一致性仍需查找)
|
||
int rowCount = m_tableWidget->rowCount();
|
||
for (int row = 0; row < rowCount; ++row) {
|
||
QTableWidgetItem *idItem = m_tableWidget->item(row, IdColumn);
|
||
if (idItem && idItem->data(DeviceNameRole).toString() == devName && idItem->text() == id) {
|
||
return row;
|
||
}
|
||
}
|
||
return -1; // 未找到
|
||
}
|
||
|
||
// NumericTableWidgetItem类的实现
|
||
NumericTableWidgetItem::NumericTableWidgetItem()
|
||
: QTableWidgetItem()
|
||
{
|
||
}
|
||
|
||
NumericTableWidgetItem::NumericTableWidgetItem(const QString &text)
|
||
: QTableWidgetItem(text)
|
||
{
|
||
}
|
||
|
||
bool NumericTableWidgetItem::operator<(const QTableWidgetItem &other) const
|
||
{
|
||
// 尝试将文本转换为数字进行比较
|
||
double thisValue = toDouble();
|
||
double otherValue = static_cast<const NumericTableWidgetItem*>(&other)->toDouble();
|
||
|
||
// 如果两个值都有效,则按数字比较
|
||
if (thisValue != -1 && otherValue != -1) {
|
||
return thisValue < otherValue;
|
||
}
|
||
|
||
// 否则按默认的字符串比较
|
||
return QTableWidgetItem::operator<(other);
|
||
}
|
||
|
||
QTableWidgetItem* NumericTableWidgetItem::clone() const
|
||
{
|
||
// 创建一个新的NumericTableWidgetItem实例并返回
|
||
return new NumericTableWidgetItem(text());
|
||
}
|
||
|
||
double NumericTableWidgetItem::toDouble() const
|
||
{
|
||
bool ok;
|
||
// 尝试直接转换为double
|
||
double value = text().toDouble(&ok);
|
||
if (ok) {
|
||
return value;
|
||
}
|
||
|
||
// 如果直接转换失败,尝试提取数字部分
|
||
QRegExp rx("(\\d+)");
|
||
if (rx.indexIn(text()) != -1) {
|
||
return rx.cap(1).toDouble(&ok);
|
||
}
|
||
|
||
// 如果所有方法都失败,返回-1表示无效值
|
||
return -1;
|
||
}
|