From 05b0cb43202342a44c520e601fa319a91bf86352 Mon Sep 17 00:00:00 2001 From: HwangKC Date: Fri, 7 Mar 2025 16:16:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=9B=B4=E6=96=B0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=88=B0Panel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- applications/EmsShower/EmsShower.pro | 8 +- applications/EmsShower/customdisplaypanel.cpp | 417 +++++ applications/EmsShower/customdisplaypanel.h | 106 ++ applications/EmsShower/customwidget.cpp | 100 -- applications/EmsShower/customwidget.h | 21 - applications/EmsShower/icons/logo-en.png | Bin 0 -> 15852 bytes applications/EmsShower/icons/main_UPS.png | Bin 0 -> 1261 bytes applications/EmsShower/icons/main_ac.png | Bin 0 -> 2262 bytes applications/EmsShower/icons/main_alarm.png | Bin 0 -> 900 bytes applications/EmsShower/icons/main_battery.png | Bin 0 -> 848 bytes applications/EmsShower/icons/main_cab.png | Bin 0 -> 1343 bytes applications/EmsShower/icons/main_door.png | Bin 0 -> 1730 bytes applications/EmsShower/icons/main_fan.png | Bin 0 -> 3277 bytes applications/EmsShower/icons/main_fire.png | Bin 0 -> 2492 bytes .../EmsShower/icons/main_invertor.png | Bin 0 -> 1289 bytes .../EmsShower/icons/main_multi_cab.png | Bin 0 -> 1796 bytes applications/EmsShower/icons/main_peidian.png | Bin 0 -> 929 bytes applications/EmsShower/icons/main_power.png | Bin 0 -> 1643 bytes applications/EmsShower/icons/main_pv.png | Bin 0 -> 2872 bytes applications/EmsShower/icons/main_temp.png | Bin 0 -> 2621 bytes applications/EmsShower/icons/main_thunder.png | Bin 0 -> 1476 bytes applications/EmsShower/icons/main_video.png | Bin 0 -> 2606 bytes applications/EmsShower/icons/main_water.png | Bin 0 -> 2338 bytes applications/EmsShower/mainwindow.cpp | 300 +++- applications/EmsShower/mainwindow.h | 26 +- applications/EmsShower/openjson.cpp | 1431 +++++++++++++++++ applications/EmsShower/openjson.h | 276 ++++ applications/EmsShower/qss.qrc | 18 + .../examples/quc_demo/frmgaugeweather.cpp | 41 + .../examples/quc_demo/frmgaugeweather.h | 30 + .../examples/quc_demo/frmgaugeweather.ui | 135 ++ .../examples/quc_demo/gaugeweather.cpp | 951 +++++++++++ applications/examples/quc_demo/gaugeweather.h | 252 +++ applications/examples/quc_demo/image/bg2.png | Bin 0 -> 47566 bytes applications/examples/quc_demo/res.qrc | 5 + 35 files changed, 3940 insertions(+), 177 deletions(-) create mode 100644 applications/EmsShower/customdisplaypanel.cpp create mode 100644 applications/EmsShower/customdisplaypanel.h delete mode 100644 applications/EmsShower/customwidget.cpp delete mode 100644 applications/EmsShower/customwidget.h create mode 100644 applications/EmsShower/icons/logo-en.png create mode 100644 applications/EmsShower/icons/main_UPS.png create mode 100644 applications/EmsShower/icons/main_ac.png create mode 100644 applications/EmsShower/icons/main_alarm.png create mode 100644 applications/EmsShower/icons/main_battery.png create mode 100644 applications/EmsShower/icons/main_cab.png create mode 100644 applications/EmsShower/icons/main_door.png create mode 100644 applications/EmsShower/icons/main_fan.png create mode 100644 applications/EmsShower/icons/main_fire.png create mode 100644 applications/EmsShower/icons/main_invertor.png create mode 100644 applications/EmsShower/icons/main_multi_cab.png create mode 100644 applications/EmsShower/icons/main_peidian.png create mode 100644 applications/EmsShower/icons/main_power.png create mode 100644 applications/EmsShower/icons/main_pv.png create mode 100644 applications/EmsShower/icons/main_temp.png create mode 100644 applications/EmsShower/icons/main_thunder.png create mode 100644 applications/EmsShower/icons/main_video.png create mode 100644 applications/EmsShower/icons/main_water.png create mode 100644 applications/EmsShower/openjson.cpp create mode 100644 applications/EmsShower/openjson.h create mode 100644 applications/examples/quc_demo/frmgaugeweather.cpp create mode 100644 applications/examples/quc_demo/frmgaugeweather.h create mode 100644 applications/examples/quc_demo/frmgaugeweather.ui create mode 100644 applications/examples/quc_demo/gaugeweather.cpp create mode 100644 applications/examples/quc_demo/gaugeweather.h create mode 100644 applications/examples/quc_demo/image/bg2.png create mode 100644 applications/examples/quc_demo/res.qrc diff --git a/applications/EmsShower/EmsShower.pro b/applications/EmsShower/EmsShower.pro index 97c7e88..9713d93 100644 --- a/applications/EmsShower/EmsShower.pro +++ b/applications/EmsShower/EmsShower.pro @@ -10,18 +10,19 @@ CONFIG += c++17 SOURCES += \ appinit.cpp \ - customwidget.cpp \ + customdisplaypanel.cpp \ formserialportsettingdialog.cpp \ libmodbus/modbus-data.c \ libmodbus/modbus-rtu.c \ libmodbus/modbus-tcp.c \ libmodbus/modbus.c \ main.cpp \ - mainwindow.cpp + mainwindow.cpp \ + openjson.cpp HEADERS += \ appinit.h \ - customwidget.h \ + customdisplaypanel.h \ formserialportsettingdialog.h \ libmodbus/config.h \ libmodbus/modbus-private.h \ @@ -32,6 +33,7 @@ HEADERS += \ libmodbus/modbus-version.h \ libmodbus/modbus.h \ mainwindow.h \ + openjson.h \ slave_define.h FORMS += \ diff --git a/applications/EmsShower/customdisplaypanel.cpp b/applications/EmsShower/customdisplaypanel.cpp new file mode 100644 index 0000000..9ec8c9b --- /dev/null +++ b/applications/EmsShower/customdisplaypanel.cpp @@ -0,0 +1,417 @@ +#include "customdisplaypanel.h" +#include +#include +#include +#include +#include + +CustomDisplayPanel::CustomDisplayPanel(QWidget *parent) + : QWidget{parent} + ,m_label_count{5},m_table_rows_count{10},m_table_rols_count{2},m_imagePath{":/icons/home.png"},m_pTableWidget(nullptr) +{ +} + +void CustomDisplayPanel::Build() +{ + // **让 CustomWidget 背景透明,避免遮挡阴影** + this->setAttribute(Qt::WA_TranslucentBackground); + this->setStyleSheet("background: transparent;"); + + // **创建一个子容器 `containerWidget`,所有内容都放在这里** + QWidget *containerWidget = new QWidget(this); + containerWidget->setStyleSheet("background-color: white; border-radius: 10px;"); + + // **为 `containerWidget` 添加阴影** + QGraphicsDropShadowEffect *shadowMain = new QGraphicsDropShadowEffect(this); + shadowMain->setBlurRadius(10); + shadowMain->setOffset(5, 5); + shadowMain->setColor(QColor(0, 0, 0, 120)); // 半透明黑色 + containerWidget->setGraphicsEffect(shadowMain); + + // **主垂直布局** + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(containerWidget); + mainLayout->setContentsMargins(10, 10, 10, 10); // 预留空间显示阴影 + this->setLayout(mainLayout); + + // **容器内的布局** + QVBoxLayout *containerLayout = new QVBoxLayout(containerWidget); + + // **上半部分(固定高度 200)** + QWidget *topWidget = new QWidget(containerWidget); + topWidget->setFixedHeight(200); + topWidget->setStyleSheet("background-color: white; border-radius: 10px;"); + + // **应用阴影效果** + QGraphicsDropShadowEffect *shadowTop = new QGraphicsDropShadowEffect(this); + shadowTop->setBlurRadius(10); + shadowTop->setOffset(5, 5); + shadowTop->setColor(QColor(0, 0, 0, 100)); // 半透明黑色 + topWidget->setGraphicsEffect(shadowTop); + + QHBoxLayout *topLayout = new QHBoxLayout(topWidget); + + // **左侧布局(上下排列:64x64 图片 + QLabel)** + QVBoxLayout *leftLayout = new QVBoxLayout(); + QLabel *imageLabel = new QLabel(topWidget); + QPixmap pixmap(m_imagePath); // 替换为实际图片路径 + imageLabel->setPixmap(pixmap.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + int width = 120; + imageLabel->setFixedSize(width, 64); + imageLabel->setAlignment(Qt::AlignCenter); + + QLabel *textLabel = new QLabel(m_mainLabel, topWidget); + textLabel->setAlignment(Qt::AlignCenter); + textLabel->setStyleSheet( + "QLabel {" + //" font-family: 'Alimama DongFangDaKai';" // 需确保字体已加载 + " font-family: 'Arial';" + " font-size: 20px;" + " color: #13396E;" + " font-weight: bold;" + " font-style: black;" + "}" + ); + textLabel->setFixedSize(width, 40); + + leftLayout->addWidget(imageLabel); + leftLayout->addWidget(textLabel); + leftLayout->setAlignment(Qt::AlignCenter); + + // **右侧布局:包含 5 个 QLabel** + // 加载字体 + int fontId = QFontDatabase::addApplicationFont(":/fonts/Alimama_DongFangDaKai_Regular.ttf"); + if (fontId == -1) + { + qDebug() << "字体加载失败"; + } + + QVBoxLayout *rightLayout = new QVBoxLayout(); + for (int i = 0; i < m_label_count; ++i) + { + QLabel *label = new QLabel(m_lables[i], topWidget); + label->setStyleSheet( + "QLabel {" + //" font-family: 'Alimama DongFangDaKai';" // 需确保字体已加载 + " font-family: 'Arial';" + " font-size: 20px;" + " color: #2E86C1;" + " font-weight: bold;" + "}" + ); + label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + rightLayout->addWidget(label); + m_txtLabels.push_back(label); + } + + // **组装上半部分布局** + QWidget *leftWidget = new QWidget(topWidget); + leftWidget->setLayout(leftLayout); + + QWidget *rightWidget = new QWidget(topWidget); + rightWidget->setLayout(rightLayout); + + topLayout->addWidget(leftWidget, 1); // 1/3 宽度 + topLayout->addWidget(rightWidget, 4); // 2/3 宽度 + containerLayout->addWidget(topWidget,1); + + // **下半部分:2 列 5 行表格** + QTableWidget *tableWidget = new QTableWidget(m_table_rows_count, m_table_rols_count, containerWidget); + m_pTableWidget = tableWidget; + + // tableWidget->setHorizontalHeaderLabels({"Signal", "Value"}); + + // 设置列宽策略 + tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + tableWidget->setColumnWidth(0, 150); + tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + //tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + //tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + // 隐藏所有表头 + tableWidget->horizontalHeader()->setVisible(false); // 隐藏水平表头[1](@ref) + tableWidget->verticalHeader()->setVisible(false); // 隐藏垂直表头[1](@ref) + + // 禁止表格编辑 + tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // 设置网格线样式 + tableWidget->setShowGrid(true); // 默认显示网格线[3](@ref) + tableWidget->setStyleSheet("QTableWidget { gridline-color: #e0e0e0; background-color: white; border-radius: 10px;}"); + + // **应用阴影效果** + QGraphicsDropShadowEffect *shadowTable = new QGraphicsDropShadowEffect(this); + shadowTable->setBlurRadius(10); + shadowTable->setOffset(5, 5); + shadowTable->setColor(QColor(0, 0, 0, 100)); + tableWidget->setGraphicsEffect(shadowTable); + + containerLayout->addWidget(tableWidget,1); + m_pTableWidget->setRowCount(0); +} + + +void CustomDisplayPanel::UpdateData(OpenJson& json) +{ + int panel_type = json["panel_type"].i32(); + qDebug() << panel_type; + + if (panel_type == CustomDisplayPanel::PANEL_TEMPERATURE) + { + UpdateTemperature(json); + } + else if (panel_type == CustomDisplayPanel::PANEL_ALARM) + { + UpdateAlarm(json); + } +} + +void CustomDisplayPanel::UpdateTemperature(OpenJson &json) +{ + auto& nodeLabel = json["text_panel"]; + qDebug() << nodeLabel.size(); + for(size_t i=0; isetText(disp); + } + + auto& nodeTable = json["table"]; + qDebug() << nodeTable.size(); + + m_pTableWidget->clear(); + m_pTableWidget->setRowCount(nodeTable.size()); + + // 加载字体 + int fontId = QFontDatabase::addApplicationFont(":/fonts/Alimama_DongFangDaKai_Regular.ttf"); + if (fontId == -1) + { + qDebug() << "字体加载失败"; + } + + for(size_t i=0; isetTextAlignment(Qt::AlignRight | Qt::AlignVCenter); // 右对齐+垂直居中 + // 仅第一列设置特殊字体 + + QFont font; + //font.setFamily("Alimama DongFangDaKai"); + font.setFamily("Arial"); + font.setPixelSize(20); + font.setBold(true); + item->setFont(font); + //item->setForeground(QBrush(QColor("#2E86C1"))); + item->setForeground(QColor(Qt::red)); + + m_pTableWidget->setItem(i, col++, item); + + // **填充表格数据** + QTableWidgetItem *item2 = new QTableWidgetItem(QString::fromStdString(value)); + item2->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); // 右对齐+垂直居中 + + QFont font2; + font2.setFamily("Arial"); + font2.setPixelSize(20); + //font2.setBold(true); + item2->setFont(font2); + //item2->setForeground(QBrush(QColor("#2E86C1"))); + item2->setForeground(QColor(Qt::red)); + + m_pTableWidget->setItem(i, col, item2); + } +} + +void CustomDisplayPanel::UpdateAlarm(OpenJson &json) +{ + qDebug() << "更新告警信息"; + QFont font; + font.setFamily("Arial"); + font.setPixelSize(16); + font.setBold(false); + + auto& nodeTable = json["alarm"]; + qDebug() << nodeTable.size(); + for(size_t i=0; iinsertRow(0); + auto& node = nodeTable[i]; + std::string s = node["time"].s(); + std::string title = node["signal"].s(); + std::string value = node["value"].s(); + + int col = 0; + QTableWidgetItem *item0 = new QTableWidgetItem(QString::fromStdString(s)); + item0->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); // 右对齐+垂直居中 + QTableWidgetItem *item1 = new QTableWidgetItem(QString::fromStdString(title)); + QTableWidgetItem *item2 = new QTableWidgetItem(QString::fromStdString(value)); + + item0->setFont(font); + item1->setFont(font); + item2->setFont(font); + item0->setForeground(QBrush(QColor("#2E86C1"))); + item1->setForeground(QBrush(QColor("#2E86C1"))); + item2->setForeground(QBrush(QColor("#2E86C1"))); + + m_pTableWidget->setItem(0, 0, item0); + m_pTableWidget->setItem(0, 1, item1); + m_pTableWidget->setItem(0, 2, item2); + } +} + +//////// +/// \brief CustomWarningPanel::CustomWarningPanel +/// +/// +CustomWarningPanel::CustomWarningPanel(QWidget *parent) + : CustomDisplayPanel{parent} +{ + +} + +void CustomWarningPanel::Build() +{ + // **让 CustomWidget 背景透明,避免遮挡阴影** + this->setAttribute(Qt::WA_TranslucentBackground); + this->setStyleSheet("background: transparent;"); + + // **创建一个子容器 `containerWidget`,所有内容都放在这里** + QWidget *containerWidget = new QWidget(this); + containerWidget->setStyleSheet("background-color: white; border-radius: 10px;"); + + // **为 `containerWidget` 添加阴影** + QGraphicsDropShadowEffect *shadowMain = new QGraphicsDropShadowEffect(this); + shadowMain->setBlurRadius(10); + shadowMain->setOffset(5, 5); + shadowMain->setColor(QColor(0, 0, 0, 120)); // 半透明黑色 + containerWidget->setGraphicsEffect(shadowMain); + + // **主垂直布局** + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->addWidget(containerWidget); + mainLayout->setContentsMargins(10, 10, 10, 10); // 预留空间显示阴影 + this->setLayout(mainLayout); + + // **容器内的布局** + QVBoxLayout *containerLayout = new QVBoxLayout(containerWidget); + + // **上半部分(固定高度 200)** + QWidget *topWidget = new QWidget(containerWidget); + topWidget->setFixedHeight(100); + topWidget->setStyleSheet("background-color: white; border-radius: 10px;"); + + // **应用阴影效果** + QGraphicsDropShadowEffect *shadowTop = new QGraphicsDropShadowEffect(this); + shadowTop->setBlurRadius(10); + shadowTop->setOffset(5, 5); + shadowTop->setColor(QColor(0, 0, 0, 100)); // 半透明黑色 + topWidget->setGraphicsEffect(shadowTop); + + QHBoxLayout *topLayout = new QHBoxLayout(topWidget); + + // **左侧布局(上下排列:64x64 图片 + QLabel)** + QVBoxLayout *leftLayout = new QVBoxLayout(); + QLabel *imageLabel = new QLabel(topWidget); + QPixmap pixmap(m_imagePath); // 替换为实际图片路径 + imageLabel->setPixmap(pixmap.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + int width = 80; + imageLabel->setFixedSize(width, 80); + imageLabel->setAlignment(Qt::AlignCenter); + + leftLayout->addWidget(imageLabel); + leftLayout->setAlignment(Qt::AlignCenter); + + // **右侧布局:包含 5 个 QLabel** + // 加载字体 + int fontId = QFontDatabase::addApplicationFont(":/fonts/Alimama_DongFangDaKai_Regular.ttf"); + if (fontId == -1) + { + qDebug() << "字体加载失败"; + } + + QVBoxLayout *rightLayout = new QVBoxLayout(); + for (int i = 0; i < m_label_count; ++i) + { + QLabel *label = new QLabel(m_lables[i], topWidget); + label->setStyleSheet( + "QLabel {" + //" font-family: 'Alimama DongFangDaKai';" // 需确保字体已加载 + " font-family: 'Arial';" + " font-size: 20px;" + " color: #2E86C1;" + " font-weight: bold;" + "}" + ); + label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + rightLayout->addWidget(label); + m_txtLabels.push_back(label); + } + + // **组装上半部分布局** + QWidget *leftWidget = new QWidget(topWidget); + leftWidget->setLayout(leftLayout); + + QWidget *rightWidget = new QWidget(topWidget); + rightWidget->setLayout(rightLayout); + + topLayout->addWidget(leftWidget, 1); // 1/3 宽度 + topLayout->addWidget(rightWidget, 4); // 2/3 宽度 + containerLayout->addWidget(topWidget,1); + + // **下半部分:2 列 5 行表格** + QTableWidget *tableWidget = new QTableWidget(m_table_rows_count, m_table_rols_count, containerWidget); + m_pTableWidget = tableWidget; + + // tableWidget->setHorizontalHeaderLabels({"Signal", "Value"}); + + // 设置列宽策略 + tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + tableWidget->setColumnWidth(0, 150); + tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + //tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + //tableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + // 隐藏所有表头 + tableWidget->horizontalHeader()->setVisible(false); // 隐藏水平表头[1](@ref) + tableWidget->verticalHeader()->setVisible(false); // 隐藏垂直表头[1](@ref) + + // 禁止表格编辑 + tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + // 设置网格线样式 + tableWidget->setShowGrid(true); // 默认显示网格线[3](@ref) + tableWidget->setStyleSheet("QTableWidget { gridline-color: #e0e0e0; background-color: white; border-radius: 10px;}"); + + // **应用阴影效果** + QGraphicsDropShadowEffect *shadowTable = new QGraphicsDropShadowEffect(this); + shadowTable->setBlurRadius(10); + shadowTable->setOffset(5, 5); + shadowTable->setColor(QColor(0, 0, 0, 100)); + tableWidget->setGraphicsEffect(shadowTable); + + containerLayout->addWidget(tableWidget,1); + m_pTableWidget->setRowCount(0); +} diff --git a/applications/EmsShower/customdisplaypanel.h b/applications/EmsShower/customdisplaypanel.h new file mode 100644 index 0000000..3e1ac55 --- /dev/null +++ b/applications/EmsShower/customdisplaypanel.h @@ -0,0 +1,106 @@ +#ifndef CUSTOMDISPLAYPANEL_H +#define CUSTOMDISPLAYPANEL_H + +#include +#include +#include +#include +#include +#include + +#include "openjson.h" + +/** + * 创建一个自定义的显示面板 + * 上半部分的左边为图示,右边为显示的主要KPI指标标签 + * 下半部为表格,显示主要的参数 +*/ + +class CustomDisplayPanel : public QWidget +{ + Q_OBJECT +public: + explicit CustomDisplayPanel(QWidget *parent = nullptr); + +protected: + int m_label_count; + int m_table_rows_count; + int m_table_rols_count; + QStringList m_lables; + QStringList m_rowItems; + QString m_imagePath; + QString m_mainLabel; + + QVector m_txtLabels; + QTableWidget* m_pTableWidget; + +public: + typedef enum _tagPanelType: int + { + PANEL_TEMPERATURE = 1, + PANEL_BATTERY, + PANEL_POWER, + PANEL_AC, + PANEL_PV, + PANEL_ALARM, + PANEL_INVERTER, + PANEL_HOME, + } PanelType; + +public: + void setImage(const QString& img) + { + m_imagePath = img; + } + + //设置显示指标和表格数据,其中显示的标签数量需要跟QStringlist的数量一致 + void setLableCount(int number = 5) + { + m_label_count = number; + }; + void setTableRolCount(int number = 2) + { + m_table_rols_count = number; + } + void setTableRowsCount(int number = 10) + { + m_table_rows_count = number; + }; + + void setLables(const QStringList& sl) + { + m_lables = sl; + } + void setRowItems(const QStringList& sl) + { + m_rowItems = sl; + } + //设置Panel的标题 + void setMainLabel(QString label) + { + m_mainLabel = label; + } + + virtual void Build(); + +public: + //更新数据 + void UpdateData(OpenJson& json); + + void UpdateTemperature(OpenJson& json); + void UpdateAlarm(OpenJson& json); +public: + + +signals: +}; + +class CustomWarningPanel : public CustomDisplayPanel +{ + Q_OBJECT +public: + explicit CustomWarningPanel(QWidget *parent = nullptr); + virtual void Build(); +}; + +#endif // CUSTOMDISPLAYPANEL_H diff --git a/applications/EmsShower/customwidget.cpp b/applications/EmsShower/customwidget.cpp deleted file mode 100644 index 1a47df8..0000000 --- a/applications/EmsShower/customwidget.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "customwidget.h" -#include -#include -#include -#include -#include -#include - -CustomWidget::CustomWidget(QWidget *parent) - : QWidget{parent} -{ - QWidget *centralWidget = new QWidget(); - QGridLayout *mainLayout = new QGridLayout(this); - mainLayout->addWidget(centralWidget); - - //实例阴影shadow - QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); - //设置阴影距离 - shadow->setOffset(0, 0); - //设置阴影颜色 - shadow->setColor(QColor(44,44,44)); - //设置阴影圆角 - shadow->setBlurRadius(16); - //给嵌套QDialog设置阴影 - centralWidget->setGraphicsEffect(shadow); - - QVBoxLayout *mainLayout1 = new QVBoxLayout(centralWidget); - mainLayout1->setSpacing(5); // 控件间间隔 - - // 上1/3:QLabel - QWidget *topWidget = new QWidget(); - mainLayout1->addWidget(topWidget, 1); // 拉伸因子为1 - setupTopSection(topWidget); - - // 中1/3:两个垂直排列的QTextEdit - QWidget *midWidget = new QWidget(); - mainLayout1->addWidget(midWidget, 1); - setupMidSection(midWidget); - - // 下1/3:表格 - QWidget *bottomWidget = new QWidget(); - mainLayout1->addWidget(bottomWidget, 1); - setupBottomSection(bottomWidget); -} - -void CustomWidget::setupTopSection(QWidget *parent) -{ - //实例阴影shadow - QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); - //设置阴影距离 - shadow->setOffset(0, 0); - //设置阴影颜色 - shadow->setColor(QColor(244,44,44)); - //设置阴影圆角 - shadow->setBlurRadius(16); - - QLabel *label = new QLabel("顶部标签", parent); - label->setAlignment(Qt::AlignCenter); - label->setGraphicsEffect(shadow); - - QVBoxLayout *layout = new QVBoxLayout(parent); - layout->addWidget(label); -} - -void CustomWidget::setupMidSection(QWidget *parent) -{ - //实例阴影shadow - QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); - //设置阴影距离 - shadow->setOffset(0, 0); - //设置阴影颜色 - shadow->setColor(QColor(244,44,44)); - //设置阴影圆角 - shadow->setBlurRadius(16); - - QVBoxLayout *layout = new QVBoxLayout(parent); - QTextEdit *textEdit1 = new QTextEdit(); - QTextEdit *textEdit2 = new QTextEdit(); - layout->addWidget(textEdit1, 1); // 各占中间区域的一半高度 - layout->addWidget(textEdit2, 1); -} - -void CustomWidget::setupBottomSection(QWidget *parent) -{ - //实例阴影shadow - QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); - //设置阴影距离 - shadow->setOffset(0, 0); - //设置阴影颜色 - shadow->setColor(QColor(244,44,44)); - //设置阴影圆角 - shadow->setBlurRadius(16); - - QTableWidget *table = new QTableWidget(5, 2, parent); // 5行2列 - table->setHorizontalHeaderLabels({"列1", "列2"}); - table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // 列宽自适应 - table->setGraphicsEffect(shadow); - QVBoxLayout *layout = new QVBoxLayout(parent); - layout->addWidget(table); -} diff --git a/applications/EmsShower/customwidget.h b/applications/EmsShower/customwidget.h deleted file mode 100644 index bc0a364..0000000 --- a/applications/EmsShower/customwidget.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CUSTOMWIDGET_H -#define CUSTOMWIDGET_H - -#include - - -class CustomWidget : public QWidget -{ - Q_OBJECT -public: - explicit CustomWidget(QWidget *parent = nullptr); - -private: - void setupTopSection(QWidget *parent); - void setupMidSection(QWidget *parent); - void setupBottomSection(QWidget *parent); - -signals: -}; - -#endif // CUSTOMWIDGET_H diff --git a/applications/EmsShower/icons/logo-en.png b/applications/EmsShower/icons/logo-en.png new file mode 100644 index 0000000000000000000000000000000000000000..9e761e7b420381e801213a88402e20cb4566929f GIT binary patch literal 15852 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 literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_UPS.png b/applications/EmsShower/icons/main_UPS.png new file mode 100644 index 0000000000000000000000000000000000000000..b98ce80c49cd033f0116d275562e28fe1ce8ffdf GIT binary patch literal 1261 zcmVPx(r%6OXRCr$Pn@xxuMHI*Xuf_x~#vC*l%wclSfSZ|3+yy^E<|E=EdR{LcOhCLP zpa^k#5O)dFg9$`1c!(eb^kh6mjR!UJ5&Q_a=+5kl2Eh?Q4~w99@gq}zsojk9c4oF~ z!fbE%(0!Y!SJm}?_1>#`Rn>wao~hZ|UH@)XCkUAqurNf`4HqS(F5p*NC}-x&xnFHx zD7J$8+~$EUcu9b5wskg1t|LMz10*u38wK-O*0rol+ah%B{AxxF@hu>z2F8>a!h zXwzVXWS(s~`L}}lV+-JC0K0-3i&Q!FT&rB14eC=MfJyht{e=ELsG-P}j|#xvleL0M zsaoU|4C+trpZ2^KdDZd2`%qYqQd=$Xeb=wNj0xs&Ver@sPVto|K*|f3WZQk7# zJp;-c1Lea4K=4G{DV(zD_^4&FmHKxO+0(b(umH52eBewnYQ=l)Wh=E=h?Na_t z^Lwpga<%2;9*!RXlRNnQFyl6BCX1x#$A#)+lffCq1o}Qe?xpZ45W5BPDnx!Eam|#s zom_FO06_2V`D+^GfikiCScYz*|tb6$(P!9JZJb@?MPX3Y~LgX#LDCqUytlRhzKw60| zYOdxNMn(a@lidY>_O=27sV@OsYdQIw=KH7v_}uujP>wH@b7wQv=JP_H0WjZk^3%Ny z7Qe4&Dzzg5q_-e-?r+`QV-hifXWLHUc&seI6WG@DI|Z3AQ{x~2*=lnUJ=ecKnKFp(A+U_cV?)b2MANo5$MS3PEdNFbXWjLeY9OJo;MxIR2xqV z@y;mq^|mD<=NC%3qvpST)mmCpncSlP6BYpfT7DxBQFT`ry(`Dw2Fs>Sg9!lNu5e-j z{q7M^&zbbr_U6V)O57*N#nuIE_V;X3-M9i+ZaMiK!_EwuYJFy9Z_x9^6#&fiCFst5 zQz}{|OPT7GDS}^xN=^tqa^xxPx-kV!;ARCr$PTYZomRT2O7+v5_U3N#i5MKBeTGLY=W!on8JXR8d3y5AcjbGXHSwV%*pNS9eRpZN)$qsXplrn zF8f+%b2B$Px3fF9x3Lm#=dZi>`t|Gnz3zV9J+p*K{?H`XA58$77BI;XXu7~8H-OO~ z*rsP@6eZ#W%y=(@!h!}#@Lsbs@mEXFk_$xBxm%g|f%mfFUfl@vR9=lOr-+`r2h6um zCgmMniEgBbX6ys7U{V4Am=*v$NtG|D-2|X%0ZkWZXbd>S-~$ADgFpu)r1dw0@L4b} zJQ-t6BVxe6LD)t`dN{u>_L85Hk?!3eN2p~Hm=}Yv2*3wU20$hPK+peT=+j3+V)Ibv z!gt1_Ojz%kDOlNQ2NM-2qUUZWu+EH79lZX+pg+6U5%~Q8 z+Q!3cTmUd(dkAuMe=@PZ(k2({duA2ov5tHy_Mkl<)^pzg%f5md-|Oohn(+cqa*IHZ z04?lSV=wy3z{fNOe<=vF@c{7Rkx;0klDUU<<0b;G2BU*P*@7BN*qghq+1H4%Lkh8} zkZkYs3K!9`n*h48y3qXgw4;1Zwkz>&E#)HVOczMpS&J?p0Q4t>#4@vU;gFl*d@XlT z5&XD2m97GSvy_Qe7m~4WR{-H@wNG6 ze7l!XRLfq^APay|<@_zcMI`SsQ;DuI0F-Wu(C;e=ESZ-Kq~g2Wa}nK05pcde#w>T4 zYGRxH|3w>qeSQneyy_VmPlkbiwYJI&`qm`= zJ_?#)0l2Xd09bN~kjT^l039t(ZTaxv>dXEyYVO7@M0m)>Y@ewrpY&tyH$FB()1#&u zyUC`Xqy6fGt#sO(u=9foZWB%$4!w7uLQ2`>KA5@yV8*kBlyapf=)B_1=?Q#8UX4Ey z6ofV{_Zdmp>J3cZyFIKKKOs9)B*qYQ&pnn!O7SNq7E?61<*%Qem}o1@x5|4%C2V6x7=d{x+ky3I{i+rTJlp3m~h(I zOoWwn0D!NVs^UcBa5~#TL}kH>fo1Lh=C>IW-C9V-+^&O2+IS0uX`_D5LuM*IXEa9S zE(WnXVftC|sJw2lD{&~OB?L)^d~X3E=*s@Jv1iMImiPEwcX?DdmM~+dk8rOQr=Hg4 zjwdDIypKSynN0lB8gA=0X3G%%P)0)N1&bfa;xV%t2%!+22Z*bJq{F@d$f8VCi|Sqh zYPHsx@RdSRc{1qf%}e)A7j)!6Q2Ki58U{c_&)y8Chy4U$$ZHB};+dd=F6i2K`f%&v z!$Ilmr3(x|C6jfhvu)|zGD-XkKNk>3Wu(6=anPO*>&9j<&2-a;1gHAfDtoN4Pv6;l z=6|LfdAnZ4g0AxgfLVUkUGqou%;jLQ!_C<1CQ&{27&EW%Zte|&3pVUJYgo2AV2sfB zo2oM3UK>s4mNIcE9-PRhS}J_6sVdGJqp+5lLl90TX9?x4{H(FCmc4^O*8zyYdhq~p ze_m~OY6XiXq8oREk#Z}>p!*7H>^6HWNCa;LF#Z+*JYP_irS>K>x-wJGY!!b5@KJk= z7;Ew=rR+WrB2>lnlthP_Y+sn zH!lN$@N+Y%e9p})qUBx%_=0-*2VGaI8r*0mV|O?JY|Nj5;e$O4`es3mIT775S7P~S ze%<1~`E6WEfw_2|nM(ZFrm;aFh^z8yyAwd8y76UZICL(a?=Yy3xM+|FeTYiGBx!Zex+TUfJygE6rHNKq=K_#&(bxDyZ#A zm(~0ffOEP8d_Au!PHyh4BIkB(Z4I?fxB7ifN$UXMDT>^AJ`*o5B;!thAguQ+AePpP z->Oe706?H;@@lN2FGh9aHfG%G31)shL5GY8Yx2p;u_K~oRe;i!q^^{l*?j?1RmObo z6V`GWI&KNRPfY-huPiT&tP#H`9i%6CoaNXiht)F>Ay}th@udkdHsw>wD#!Xlw1FT~`T(fB*oMs?{h=Ik>hx?-pp*1J6x4 z{qPx2g7JkKR=Xc8w=jhi@9A25Tk!!xECu6|TIO%f2L`|*mOSO0fz%~$x*5;ca%UI8 zDu8yYJM06%Fn}Jg?5WfYwRju@|B!$sL45+aheN?629HFm!} zAJ%g#0Lm4T?PYIqM(2BG3dkpzaaki33(9NU0QmWD&<)*mVa>RL0DUBDtcS&f*VG5V zh_8d>cidM`P-(&&j2I+taavwM7oGsRx&d%xAsU1%ZX+S~__;>u*67;#A<_E2*+t@& z#QrU+6QE;NmJ`h9?_M6M*Jd zV$H9_Ci|zdll^09D=OFO>7Q(oyAkp@pUd0KCiM>{8iDAk%A@(CsZ;gj8YOqr0vd(n ksmiBm0jKK8HA?RP0;5bA$Mb+iS^xk507*qoM6N<$f*IgD)Bpeg literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_alarm.png b/applications/EmsShower/icons/main_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..9043bdaba4ac52d911b38d8d786878bded1abe12 GIT binary patch literal 900 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 BatHtb literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_door.png b/applications/EmsShower/icons/main_door.png new file mode 100644 index 0000000000000000000000000000000000000000..9720c3890deef2e44714ccd2715fd87beedba484 GIT binary patch literal 1730 zcmV;z20i(SP)Px*d`Uz>RCr$PTWf3-RTMsFwk?9nLy4#%QISA|?rgC}z`#s5LgWz>AVH(}*Vhjc z@X6kVP=Y%P1cmTNG{zW>#FQBLp#%`!ovj#D8oGBuF=|3EMhJ??Lu%WdV`kfVwq+mO zmR;EWcW3WC_ndFObMCq4-a)v*e|Ur24^;pLg8?C}brK;og3&!$gnX1Ro$b!@qiY`h zRfIgZ4B-rrkA`y!l`_6q z0P17Oxf0_3oJ|C0YXhAV+E&f|CkH?SEZb1PfQ z8!X1y{`wBY06DI&1;aqUp22u>DH66gvQXnpiK481H60+}_WAEPQ>S~3M`!_}sXg>@ zsB85p7XaLP_^U?p#>9BYO263v5TtliRnbokK#cQ>oJ9*;iVHna^*+47nL5&AT%S_6v68d!*75|v8|eYy zAZO|<8-Q5q8AAHV6$n?F3~<--f9FiKd<25=)(J?4=QM`ESe#+u-Q@^CP)jUAkaoEO z-H$w~PO;kc>l01xqzi#I`2jf2nfibe026bNq%YjYN@PrHkA~aJ5rBqRqQOAvh)cKA zoT+!(0BkyVztr9Ri_1km^aJ2o0{tt1IZ)~}#Rs97gL45a4si7-O9Yfa%YpdLH6g&Xn7&4m02SX85I!u7BMC094L0XjYuuS!+;B zK8t|wTJ|8~izvsE#{tp0$k5H1TI&gLnw|jw*Fj_jB|^S^i+|68NK4RPk(H1hZjUs5 z@1o6i1bE&?t%6cv|F&M%bd&xI90tIH7}WID2(aF@G_1)m^$j}^##1j4(c8}d6|)&S z#hH4Sce&Qj0K!=F83CNnFm)6LH|Po)pgV!VyA;;PQnG}`epkA@gEMuRS8#b8jd>~V zb=6?}zyzdPcUHTPGj$%`+nl&*WS#UE066FEwH>$cS0B6NxItnO5;UMBolHm6_q{=} zw33!zMzQSy3nIOBKdB40x)ZigTz`@XK5?%AX3?alCK%WE0Kt6gPk$W9r(C{*2m*YZ z%^L6JlVkg4jWo$!gXnKuu{FUy0g!NBI;!mJTNFh+1~r$byBUyA@jL){a*KSjY}WND zVR=Vh6#$6GhIiCG^6k1A=k1drt1KSCK+6%P{7|T(IR;(fet>^1E>y06=jI<*EC9Bi z0N}Q9(2vH-34s2W)&!yT=IwAh+Edf{&&|e>o-;022iNB?J*~$&!IYThh^{ zmxRg`Da?`;)RHp+r5R{sdk6&DvA=w+yDRo07*qoM6N<$f>cr%S^xk5 literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_fan.png b/applications/EmsShower/icons/main_fan.png new file mode 100644 index 0000000000000000000000000000000000000000..de3e9e5e52d9c4297db16f3a5ed3f631716f51c0 GIT binary patch literal 3277 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 LNkvXXu0mjfU@a`< literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_fire.png b/applications/EmsShower/icons/main_fire.png new file mode 100644 index 0000000000000000000000000000000000000000..01fd599b60e2aa6a40c88b543a18132449d0e590 GIT binary patch literal 2492 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 literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_multi_cab.png b/applications/EmsShower/icons/main_multi_cab.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b123d3f120335f769620b1f44b071c18c2b6c4 GIT binary patch literal 1796 zcmV+f2mAPmP)Px*zDYzuRCr$Pn|p}d)fLA-=gvNYtFvHDEEZc!E3s8~@d4V3PC~2JAhe+>MNmsx zqy?dh8q6-m1#yC{P-*c+{HIc=^-V>sRWy@IDfDGGP5r|*Et*1n%umP7Z1b48-}cU9 zGdoT)Gr!EvOm;f(V}8tU?!D)Jf9Ia_J?EZl#QdBx^U8|jMs3UXO>3?RT}-4i^FUO+ zBqB2+Op(bVkFsViGVh~8A`w}^M{aa)G<)l%`IsVfq?yI(W#W!kSCc3_0e1TARI1d+ zL^`85eO21LEyx;Vf%nZ6dAVpr+%N!m_9YYSud+Ib7Bi3(@|9%kG$2 zyQf_l+6G`Uz4aPl4_6235CDzkmYw5Obk4cow=uEZpe!5&Oz`gZsnn)MeY6ZfQ2Fsu zyI-R?NZhb}I(2X8e=@!8QK2iteIo!2-R@<7cczWPCR<$^ZEHZ;%Da0_ zY&hH?9ROh0>{ROH>KJF-x%Faa_UncKhzDk;Q{S!rcjC^>-!MMc3J7fjaArEQ!{GDH zI`04gb06;atNt~!c5fK#%*~nWO*~i{x6TL(9^5{a`c74*$#mv6!RBT%hUaYq(5hSO z000=ghdAT-*$RAoy$3YhP<7>S^hhJ~mPuP8Nq zOW;>p$vE1HV8KYP1g!$X_3>t7#R8zF(_bQXVSEEPz8L^Q8?eMID&*x|zTghb@7IKKw~v=a_D#6Ok|km+Y8D+1S5{PLv|j|0aY!4ha$L3$buR(L>IpJV^Cyk%P; z-orSdTN=<000bTo4AS!$R|B^I4|NNGp7Q|26J|;X3%olI`XksCg9AWirok2<8!<{A zp(zh&d675yV;?Mm1s+hc1zx(O13aK*eEMMt+$Uqbfvb>~Uh#n5xWNkXImiRxjS4ed zc#AQZ;L)Q2VeLC8R8!#wn*`HGNQI9c04hBFAP<1&>QhilsR4ap1gYf#R|_Yq#F_1p*w9(8DBAs=A; z6|rV;yg>Oi&&%{=B_9y>@fY5HZMJop?hWb@uzf(t0*zt*GUUQ;(UI&6996+khy_N0 z07BRo_;kJW%iIXo?F$Sd52(op%p2S>p!onjVz@+gmw1qf&eZ^rs= z@_>F=0_A)F=8?v+P%~7j7#YZYfx+eh!kihO9HaPCLA&<L{5OQ3eL zpZ)Q3qsQdOo8g`Fke6<_<0(}QSbPx&TS-JgRCr$PTRmt~Q562peE}!Y#lcM%3u+TfT?ErKgW#Yz_=DY?D!6nJYEz^U zX<9{xItX@htDp{|gW!-qacZ$iUFxEngG)gWEPeOb_nPC z6|C=t#i6i9Tla;Fu7=z5xTqy}t2WaT5nXHIoYMi$9V`O?bFraY7rdbNqiIpf6WCZnJ%@?eGb( z?ZjN$_N^KOaojlQAXXcvh%s9kPu<)IaI6uQjW0QE&i!|6^J4<KSh-tO#3d^>*oD0aoh@1?`(U_bK%HgP-4o6I=Ajjji$}p)9kotg4 z2Bbhj3M3jBkO~p05ZU7uB8go27(*{dO*+;K6K?=~9-!;du=oW)@7D`ApGU)S6)ruM zO!Ditd4O{PlDQQ{^GBo&j*k5a%aIja0{yb3v(&305mvPD{l|-*h>#rnzOU zFx$4@Hx{dRX>lxF@c!wao9epYJ3q z&93`5k(ew#W5TcryWQ4@Ko2XKfl z=5fceIa5(pSRG$u6q(xHsfttx*y+4w`Xpoo7)oNj2V+Wn$3ws{I}9ymZXA(@cAM`1 zZJP+S3k6^9&U~U>TmO0lZLvQBJehq6yFp!GJ750-*x*HOvfB%+00000NkvXXu0mjf Df!C;A literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_power.png b/applications/EmsShower/icons/main_power.png new file mode 100644 index 0000000000000000000000000000000000000000..c23811e2210a1d6ca1d2d4ec00084d6a6a2e6d3f GIT binary patch literal 1643 zcmV-x29)`UP)Px*B}qg zN3wK;u0HPVJf+@ut`c8Bk1rtj6o40ivD31wqbJve6SbH?wJ_VfvSw@tVOc>AgA!>q z7_MlD7Jr&ZdS@UFv-M0m8h*Dj0LeIkLq!_K4l}AOuf>Mi0nm*<08AKUOh4rG%SyVO zVzOp@1j^6;1?0NSsIssePv8|JK;vMRd_QZd%C#d60GBFYWvp-|*9_<-GYO!Y=VU|8 zC&f$0GF7FfNvw%E8v?q~1fb~wB?vS_U@YVSSvPiqXcm}nt0l{hG_RdpE7ci}Nl=a* zUIMmujkNM!j$f|Ma0Wn&w}YhhUMeu+vKWqa#zg}_xjLf>fRk&=V=x4o9#CeJ)(nAW z2sj{cQRaX^?C^B%@1J7hAxQ|0wK~8~f}F{0D9m?ZyH2teUkpeKhcqbaSh?8B5UevG z`aL@g^CX5eaZtCXj^o>+$!_0q<^z)TjVHtO*N#8Dq+(FI+F7t@moZ+&5Q(NrM$A9 zQ0tKs>Hq+r#Q2_Bq6yD7RMw1k5Y_|qbEY@r?`{wOJp_PUVm#*c>fkv5%vh0*D%(Y7 zf5t}RM$5;q12ivBzuHulE#-Ivnz5V+Z;HqkrSTj9vifgGwcYz$H3j^dv4{w75HK-c z41IV0it{Z$v_8;Nj<==b^Xpj}_iHuTZoYd8K(*7IVp7wM7y)YsDoV_)sc2|hHRYX7 zm8$Vn`tB(J0C$>d=<#v`%39nH@@+1yJQ*jLI*aF7NQ3t?4h5cGmmXLYa zamPu#*^GoWL9^ssaSz?Fsn7&t@3E6qr#v(oI!dZKYdKr{);y0H~N zYteYZ0kE|yt%grkbL|FCfXAv`u9=-{ zg1Y>9TCA0f?QtnQ)Ux5W#By0bdB>=3iCuu>B5sNf0HFcf;y~R)z$x4t;b5eo7s;A& z4uq>+=B2ShewJEvV;ci2#)?I*>|pF5e}^$y^6`VPxOnN9$*26E=X9==_J;U$;kv!! z1qrWrT;AwhqZGC_n9=Z%3bYA;6Ru4UaJbK3qXUjSdou)@IiQ&Yo2!86GAPFq%fKXb zz&Y~SO-utOypWD4Z2@R)~+$1&KP%NU%$YK2PKt0RKkg7RiT$049qJ#Ehlss4}eQ zH!U&aF=o6kHcs`Yw@z|g17HiuKBy9#neR6@E?7rS6f0ZW#|9^002ovPDHLkV1gc*25JBR literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_pv.png b/applications/EmsShower/icons/main_pv.png new file mode 100644 index 0000000000000000000000000000000000000000..13e3dbba679a7ee8f7e813243811af7cee96aeac GIT binary patch literal 2872 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 Qy8r+H07*qoM6N<$g7vo0a{vGU literal 0 HcmV?d00001 diff --git a/applications/EmsShower/icons/main_water.png b/applications/EmsShower/icons/main_water.png new file mode 100644 index 0000000000000000000000000000000000000000..f0ab7d5d3598cbb92de549c3296313962179e689 GIT binary patch literal 2338 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 literal 0 HcmV?d00001 diff --git a/applications/EmsShower/mainwindow.cpp b/applications/EmsShower/mainwindow.cpp index 4b2f918..45aeca2 100644 --- a/applications/EmsShower/mainwindow.cpp +++ b/applications/EmsShower/mainwindow.cpp @@ -12,10 +12,11 @@ #include #include #include +#include #include "libmodbus/modbus.h" -#include "customwidget.h" +#include "customdisplaypanel.h" #include "formserialportsettingdialog.h" @@ -91,7 +92,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) ,m_pModbus(nullptr) + ,m_pTimer(nullptr) ,m_pSettings(nullptr) + ,m_bInitializeModbus(false) + { ui->setupUi(this); @@ -203,9 +207,11 @@ void MainWindow::getConfiguration(QString iniFilePath) bool MainWindow::InitializeUI() { #ifndef NDEBUG - this->showMaximized(); + //this->showMaximized(); #endif + ui->statusbar->showMessage(tr("Ready"), 0); + //初始化窗口边框 QWidget *centralWidget = this->centralWidget(); // new QWidget(this); QGridLayout *mainLayout = new QGridLayout(centralWidget); @@ -231,27 +237,102 @@ bool MainWindow::InitializeUI() //给垂直布局器设置边距(此步很重要, 设置宽度为阴影的宽度) //->setMargin(12); - // 创建2行4列布局(共8个控件示例) - for(int row=0; row<2; ++row) - { - for(int col=0; col<4; ++col) - { -#if 0 - QLabel *label = new QLabel(QString("Cell %1-%2").arg(row).arg(col),this); - label->setStyleSheet("background-color: rgb(192,192,192);" - "color: #333333;" - "border-radius: 8px;" - "font: bold 14px;"); - label->setAutoFillBackground(true); - label->setAlignment(Qt::AlignCenter); - label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); - mainLayout->addWidget(label, row, col); - m_panel_labels.push_back(label); -#endif - CustomWidget* pWidget = new CustomWidget(this); - mainLayout->addWidget(pWidget, row, col); - } - } + //温度 + m_pTemperaturePanel = new CustomDisplayPanel(this); + m_pTemperaturePanel->setImage(":/icons/main_temp.png"); + QStringList l1{tr("Online"),tr("Temperature"),tr("Humidity")}; + m_pTemperaturePanel->setLableCount(l1.count()); + m_pTemperaturePanel->setTableRowsCount(10); + m_pTemperaturePanel->setLables(l1); + m_pTemperaturePanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pTemperaturePanel->setMainLabel(tr("Temp&RH")); + m_pTemperaturePanel->Build(); + mainLayout->addWidget(m_pTemperaturePanel, 0, 0); + + //电源 + m_pPowerPanel = new CustomDisplayPanel(this); + m_pPowerPanel->setImage(":/icons/main_power.png"); + QStringList l2{QStringList{tr("Online"),tr("​Input Voltage"),tr("​Output Voltage"),tr("​Output Current"),tr("Module Temp.")}}; + m_pPowerPanel->setLableCount(l2.count()); + m_pPowerPanel->setTableRowsCount(10); + m_pPowerPanel->setLables(l2); + m_pPowerPanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pPowerPanel->setMainLabel(tr("Power")); + m_pPowerPanel->Build(); + mainLayout->addWidget(m_pPowerPanel, 0, 1); + + //电池 + m_pBatteryPanel = new CustomDisplayPanel(this); + m_pBatteryPanel->setImage(":/icons/main_battery.png"); + QStringList l3{QStringList{tr("Online"),tr("SOC"),tr("SOH"),tr("Group Voltage"),tr("Cell V.Avg"),tr("Cell V.Max"),tr("Cell V.Min")}}; + m_pBatteryPanel->setLableCount(l3.count()); + m_pBatteryPanel->setTableRowsCount(10); + m_pBatteryPanel->setLables(l3); + m_pBatteryPanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pBatteryPanel->setMainLabel(tr("​Battery Pack")); + m_pBatteryPanel->Build(); + mainLayout->addWidget(m_pBatteryPanel, 0, 2); + + //空调 + m_pACPanel = new CustomDisplayPanel(this); + m_pACPanel->setImage(":/icons/main_ac.png"); + QStringList l4{QStringList{tr("Online"),tr("Machine"),tr("IFM"),tr("Compressor"),tr("OFM"),tr("Discharge Temp."),tr("Room Temp.")}}; + m_pACPanel->setLableCount(l4.count()); + m_pACPanel->setTableRowsCount(10); + m_pACPanel->setLables(l4); + m_pACPanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pACPanel->setMainLabel(tr("HVACR")); + m_pACPanel->Build(); + mainLayout->addWidget(m_pACPanel, 0, 3); + + //交流配电 + m_pInverterPanel = new CustomDisplayPanel(this); + m_pInverterPanel->setImage(":/icons/main_invertor.png"); + QStringList l5{QStringList{tr("Online"),tr("​Input Voltage"),tr("​Input Current"),tr("Module Temp.")}}; + m_pInverterPanel->setLableCount(l5.count()); + m_pInverterPanel->setTableRowsCount(10); + m_pInverterPanel->setLables(l5); + m_pInverterPanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pInverterPanel->setMainLabel(tr("AC Power")); + m_pInverterPanel->Build(); + mainLayout->addWidget(m_pInverterPanel, 1, 0); + + //PV太阳能 + m_pPVPanel = new CustomDisplayPanel(this); + m_pPVPanel->setImage(":/icons/main_pv.png"); + QStringList l6{QStringList{tr("Online"),tr("​Output Voltage"),tr("​Output Current"),tr("Module Temp.")}}; + m_pPVPanel->setLableCount(l6.count()); + m_pPVPanel->setTableRowsCount(10); + m_pPVPanel->setLables(l6); + m_pPVPanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pPVPanel->setMainLabel(tr("​PV Module")); + m_pPVPanel->Build(); + mainLayout->addWidget(m_pPVPanel, 1, 1); + + //门禁 + m_pHomePanel = new CustomDisplayPanel(this); + m_pHomePanel->setImage(":/icons/main_cab.png"); + QStringList l7{QStringList{tr("Online"),tr("​Output Voltage"),tr("​Output Current"),tr("Module Temp.")}}; + m_pHomePanel->setLableCount(l7.count()); + m_pHomePanel->setTableRowsCount(10); + m_pHomePanel->setLables(l7); + m_pHomePanel->setRowItems(QStringList{tr("露点"),tr("DO"),tr("DI1"),tr("DI2"),tr("高温"),tr("低温"),tr("高湿")}); + m_pHomePanel->setMainLabel(tr("Sensors")); + m_pHomePanel->Build(); + mainLayout->addWidget(m_pHomePanel, 1, 2); + + //告警 + m_pAlarmPanel = new CustomWarningPanel(this); + m_pAlarmPanel->setImage(":/icons/main_alarm.png"); + QStringList l8{QStringList{tr("Warning Board")}}; + m_pAlarmPanel->setLableCount(l8.count()); + m_pAlarmPanel->setTableRowsCount(10); + m_pAlarmPanel->setTableRolCount(3); + m_pAlarmPanel->setLables(l8); + m_pAlarmPanel->setRowItems(QStringList{tr("")}); + m_pAlarmPanel->setMainLabel(tr("Warning")); + m_pAlarmPanel->Build(); + mainLayout->addWidget(m_pAlarmPanel, 1, 3); // 设置布局的间距和边距 mainLayout->setSpacing(5); @@ -278,6 +359,13 @@ bool MainWindow::InitializeUI() QAction *actionClose = new QAction(QIcon(":/icons/close.png"), tr("Close"), this); actionClose->setToolTip(tr("Close Application")); + //添加logo + QLabel *logoLabel = new QLabel(this); + QPixmap pixmap(":/icons/logo-en.png"); // 替换为实际图片路径 + logoLabel->setPixmap(pixmap.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + toolBar->addWidget(logoLabel); + toolBar->addSeparator(); + //添加按钮 toolBar->addAction(actionRead); toolBar->addSeparator(); @@ -288,25 +376,26 @@ bool MainWindow::InitializeUI() toolBar->addWidget(spacer); // 添加中央标签 - QLabel *label = new QLabel(tr("综合电源柜监测"), this); + QLabel *label = new QLabel(tr("​Integrated Power Cabinet Monitoring"), this); - // 加载字体 - int fontId = QFontDatabase::addApplicationFont(":/fonts/Alimama_DongFangDaKai_Regular.ttf"); - if (fontId == -1) - { - qDebug() << "字体加载失败"; - return -1; - } + // // 加载字体 + // int fontId = QFontDatabase::addApplicationFont(":/fonts/Alimama_DongFangDaKai_Regular.ttf"); + // if (fontId == -1) + // { + // ui->statusbar->showMessage(tr("Failed to load font")); + // } //QFont customFont("Alimama DongFangDaKai", 48); //customFont.setBold(true); //label->setFont(customFont); //label->setStyleSheet("color: #2E86C1;"); label->setStyleSheet( "QLabel {" - " font-family: 'Alimama DongFangDaKai';" // 需确保字体已加载 + //" font-family: 'Alimama DongFangDaKai';" // 需确保字体已加载 + " font-family: 'Arial';" // 需确保字体已加载 " font-size: 52px;" " color: #2E86C1;" - //" font-weight: bold;" + " font-weight: bold;" + " font-style: black;" "}" ); toolBar->addWidget(label); @@ -327,17 +416,20 @@ bool MainWindow::InitializeUI() connect(actionSetting, &QAction::triggered, this, &MainWindow::SettingSerialPort); connect(actionRead, &QAction::triggered, this, &MainWindow::ReadSerialPortData); - ui->statusbar->showMessage(tr("Ready"), 0); return true; } bool MainWindow::InitializeModbus() { + if (m_bInitializeModbus) + return true; + QString appDir = QCoreApplication::applicationDirPath(); QString iniFilePath = appDir + QString::fromStdString("/emsshower.ini"); getConfiguration(iniFilePath); + m_bInitializeModbus = true; switch (m_modbus_type) { case 0: @@ -413,7 +505,11 @@ bool MainWindow::InitializeTcp() bool MainWindow::readRegister(int addr,int nb,uint16_t* dest) { - InitializeModbus(); + if(!InitializeModbus()) //这里有问题,如果是虚拟串口,连接的通常会返回成功 + { + ui->statusbar->showMessage(tr("Failed to open Modbus device,Check modbus connection please!")); //打开MODBUS设备失败,请检查设备连接情况!")); + return false; + } for (auto it = m_SlaveData.begin(); it != m_SlaveData.end(); ++it) { @@ -443,16 +539,17 @@ bool MainWindow::readRegister(int addr,int nb,uint16_t* dest) { TemperatureData* pData = (TemperatureData*)pDevice; assert(pData); - QString value; - if (pData->bDecodeTemp) + OpenJson json; + if(CreateJson(pData,json)) { - //ui->txt_content_1->append(QString(tr("温度:%1\n湿度: %2\n露点: %3\n")).arg(pData->TempValue).arg(pData->HumidityValue).arg(pData->DewPointValue)); - //m_panel_labels[0]->setText(QString(tr("温度: %1\n湿度: %2\n露点: %3\n")).arg(pData->TempValue).arg(pData->HumidityValue).arg(pData->DewPointValue)); + if(pData->bDecodeTemp) + m_pTemperaturePanel->UpdateData(json); + if(pData->bDecodeAlarm) + m_pAlarmPanel->UpdateAlarm(json); } - if (pData->bDecodeAlarm) + else { - //ui->txt_content_1->append(QString(tr("高温告警: %1\n低温告警: %2\n湿度告警: %3\n")).arg(pData->TempHighAlarm).arg(pData->TempLowAlarm).arg(pData->HumidityHighAlarm)); - //m_panel_labels[0]->setText(QString(tr("高温告警: %1\n低温告警: %2\n湿度告警: %3\n")).arg(pData->TempHighAlarm).arg(pData->TempLowAlarm).arg(pData->HumidityHighAlarm)); + ui->statusbar->showMessage(tr("Failed to decode temperaure data")); //解析温度数据失败!")); } } @@ -500,6 +597,26 @@ void MainWindow::startAsyncProcess(const QVector& array,int slave_id,i void MainWindow::ReadSerialPortData() +{ + if (!m_pTimer) + m_pTimer = new QTimer(this); + + if (m_pTimer->isActive()) + { + m_pTimer->stop(); + ui->statusbar->showMessage(tr("Stop Reading")); + } + else + { + m_pTimer->setInterval(10000); // 10秒 + connect(m_pTimer, &QTimer::timeout, this, &MainWindow::onTimeout); + ui->statusbar->showMessage(tr("Begin Readding")); + m_pTimer->start(); + onTimeout(); + } +} + +void MainWindow::onTimeout() { readRegister(0,0,0); } @@ -508,11 +625,98 @@ void MainWindow::SettingSerialPort() { FormSerialPortSettingDialog* dlg = new FormSerialPortSettingDialog(this); dlg->setWindowFlags(dlg->windowFlags()&~(Qt::WindowMinMaxButtonsHint|Qt::WindowContextHelpButtonHint)); - //dlg.setModal(true); - //dlg->show(); - if(dlg->exec() == QDialog::Accepted) - QMessageBox::information(this, "OK Clicked", "Button 1 was clicked!"); - else - QMessageBox::information(this, "Cancel Clicked", "Cancel was clicked!"); + dlg->exec(); + + // if(dlg->exec() == QDialog::Accepted) + // QMessageBox::information(this, "OK Clicked", "Button 1 was clicked!"); + // else + // QMessageBox::information(this, "Cancel Clicked", "Cancel was clicked!"); } +bool MainWindow::CreateJson(DeviceData* pData,OpenJson& json) +{ + int data_type = pData->m_device_type; + if (data_type == 81) //根据协议定义的设备id进行分类,参考DeviceData定义 + { + TemperatureData* pTempData = (TemperatureData*)pData; + if (pTempData->bDecodeTemp) + { + json["panel_type"] = CustomDisplayPanel::PANEL_TEMPERATURE; + + auto& nodeLabel = json["text_panel"]; + nodeLabel[0]["value"] = pTempData->m_device_online_state; + nodeLabel[0]["title"] = "Online"; + + nodeLabel[1]["value"] = pTempData->TempValue; + nodeLabel[1]["title"] = "Temp(℃)"; + + nodeLabel[2]["value"] = pTempData->HumidityValue; + nodeLabel[2]["title"] = "RH (%) "; + + auto& nodeTable = json["table"]; + int i = 0; + + nodeTable[i]["value"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss").toStdString(); + nodeTable[i]["signal"] = "Time"; + + i++; + nodeTable[i]["value"] = pTempData->DewPointValue; + nodeTable[i]["signal"] = "Dew Point"; + + i++; + nodeTable[i]["value"] = pTempData->DO; + nodeTable[i]["signal"] = "DO"; + + i++; + nodeTable[i]["value"] = pTempData->DI1; + nodeTable[i]["signal"] = "DI1"; + + i++; + nodeTable[i]["value"] = pTempData->DI2; + nodeTable[i]["signal"] = "DI2"; + + std::string a = json.encode(); + + qDebug() << QString::fromStdString(a); + } + + if (pTempData->bDecodeAlarm) + { + json["panel_type"] = CustomDisplayPanel::PANEL_ALARM; + auto& nodeTable = json["alarm"]; + + int i = -1; + if(pTempData->TempHighAlarm != 0) + { + i++; + nodeTable[i]["time"] = QDateTime::currentDateTime().toString("MM-dd HH:mm:ss").toStdString(); + nodeTable[i]["signal"] = "High Temperature"; + nodeTable[i]["value"] = pTempData->TempHighAlarm; + } + + if(pTempData->TempLowAlarm != 0) + { + i++; + nodeTable[i]["time"] = QDateTime::currentDateTime().toString("MM-dd HH:mm:ss").toStdString(); + nodeTable[i]["signal"] = "High Temperature"; + nodeTable[i]["value"] = pTempData->TempLowAlarm; + } + + if(pTempData->HumidityHighAlarm != 0) + { + i++; + nodeTable[i]["time"] = QDateTime::currentDateTime().toString("MM-dd HH:mm:ss").toStdString(); + nodeTable[i]["signal"] = "High Humidity"; + nodeTable[i]["value"] = pTempData->HumidityHighAlarm; + } + + std::string a = json.encode(); + + qDebug() << QString::fromStdString(a); + } + + return true; + } + + return false; +} diff --git a/applications/EmsShower/mainwindow.h b/applications/EmsShower/mainwindow.h index da21cf9..4232257 100644 --- a/applications/EmsShower/mainwindow.h +++ b/applications/EmsShower/mainwindow.h @@ -5,10 +5,15 @@ #include #include #include +#include #include "libmodbus/modbus.h" #include "slave_define.h" +#include "openjson.h" + +class CustomDisplayPanel; + QT_BEGIN_NAMESPACE namespace Ui { @@ -65,7 +70,6 @@ public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); - protected: void getConfiguration(QString iniFilePath); bool InitializeUI(); @@ -80,7 +84,7 @@ private: protected: modbus_t *m_pModbus; SlaveData m_SlaveData; - + QTimer *m_pTimer; protected: QString m_version; int m_modbus_type; @@ -93,10 +97,8 @@ protected: int m_modbus_stop; int m_modbus_slave_id; QSettings* m_pSettings; + bool m_bInitializeModbus; - -protected: - QVector m_panel_labels; protected: //异步处理数据 void startAsyncProcess(const QVector& array,int slave_id,int start_addr,int quantity,DeviceData* pData); @@ -104,6 +106,20 @@ protected: private slots: void ReadSerialPortData(); void SettingSerialPort(); + void onTimeout(); + +private: + CustomDisplayPanel* m_pTemperaturePanel; //温湿度显示 + CustomDisplayPanel* m_pBatteryPanel; //电池显示 + CustomDisplayPanel* m_pPowerPanel; //电源显示 + CustomDisplayPanel* m_pACPanel; //空调显示 + CustomDisplayPanel* m_pHomePanel; //主柜子信息 + CustomDisplayPanel* m_pAlarmPanel; //告警 + CustomDisplayPanel* m_pInverterPanel; //逆变器 + CustomDisplayPanel* m_pPVPanel; //太阳能 + +private: + bool CreateJson(DeviceData* pData,OpenJson& json); signals: // 处理进度信号 diff --git a/applications/EmsShower/openjson.cpp b/applications/EmsShower/openjson.cpp new file mode 100644 index 0000000..1e3241b --- /dev/null +++ b/applications/EmsShower/openjson.cpp @@ -0,0 +1,1431 @@ +/*************************************************************************** + * Copyright (C) 2023-, openlinyou, + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openjson.h" + + +#define PRINTF printf +#if (defined(_MSC_VER) && (_MSC_VER >= 1400 )) +inline int SNPRINTF(char* buffer, size_t size, const char* format, ...) +{ + va_list va; + va_start(va, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, va); + va_end(va); + return result; +} +#define SSCANF sscanf_s +#else +#define SNPRINTF snprintf +#define SSCANF sscanf +#endif + +static inline void doubleToStr(double v, char* buffer, int size) +{ + double tmp = floor(v); + if (tmp == v) + SNPRINTF(buffer, size, "%ld", (long)v); + else + SNPRINTF(buffer, size, "%g", v); +} + +static inline bool strToDouble(const char* str, double* value) +{ + return SSCANF(str, "%lf", value) == 1 ? true : false; +} + +static void int32ToStr(int32_t n, char* str, size_t size) +{ + if (str == 0 || size < 1) return; + str[size - 1] = 0; + if (size < 2) return; + if (n == 0) + { + str[0] = '0'; + return; + } + size_t i = 0; + char buf[128] = { 0 }; + int32_t tmp = n < 0 ? -n : n; + while (tmp && i < 128) + { + buf[i++] = (tmp % 10) + '0'; + tmp = tmp / 10; + } + size_t len = n < 0 ? ++i : i; + if (len > size) + { + len = size; + i = len - 1; + } + str[i] = 0; + while (1) + { + --i; + if (i < 0 || buf[len - i - 1] == 0) break; + str[i] = buf[len - i - 1]; + } + if (i == 0) str[i] = '-'; +} + +static void int64ToStr(int64_t n, char* str, size_t size) +{ + if (str == 0 || size < 1) return; + str[size - 1] = 0; + if (size < 2) return; + if (n == 0) + { + str[0] = '0'; + return; + } + size_t i = 0; + char buf[128] = { 0 }; + int64_t tmp = n < 0 ? -n : n; + while (tmp && i < 128) + { + buf[i++] = (tmp % 10) + '0'; + tmp = tmp / 10; + } + size_t len = n < 0 ? ++i : i; + if (len > size) + { + len = size; + i = len - 1; + } + str[i] = 0; + while (1) + { + --i; + if (i < 0 || buf[len - i - 1] == 0) break; + str[i] = buf[len - i - 1]; + } + if (i == 0) str[i] = '-'; +} + +static int32_t strToInt32(const char* str) +{ + const char* ptr = str; + if (*ptr == '-' || *ptr == '+') ptr++; + int32_t tmp = 0; + while (*ptr != 0) + { + if ((*ptr < '0') || (*ptr > '9')) break; + tmp = tmp * 10 + (*ptr - '0'); + ptr++; + } + if (*str == '-') tmp = -tmp; + return tmp; +} + +static int64_t strToInt64(const char* str) +{ + const char* ptr = str; + if (*ptr == '-' || *ptr == '+') ptr++; + int64_t temp = 0; + while (*ptr != 0) + { + if ((*ptr < '0') || (*ptr > '9')) break; + temp = temp * 10 + (*ptr - '0'); + ptr++; + } + if (*str == '-') temp = -temp; + return temp; +} + + +//JsonBox +OpenJson::Box::Box() +{ +} + +OpenJson::Box::~Box() +{ + for (size_t i = 0; i < childs_.size(); i++) + { + if (childs_[i]) + { + delete childs_[i]; + } + } + childs_.clear(); +} + +bool OpenJson::Box::remove(OpenJson* node) +{ + if (!node) return false; + std::vector::iterator iter; + for (iter = childs_.begin(); iter != childs_.end(); iter++) + { + if (*iter == node) + { + childs_.erase(iter); + delete node; + return true; + } + } + return false; +} + +//JsonContext +OpenJson::Context::Context() + : + offset_(0), + data_(0), + root_(0), + size_(0) +{ +} + +OpenJson::Context::~Context() +{ +} + +void OpenJson::Context::startRead() +{ + size_ = rbuffer_.size(); + data_ = (char*)rbuffer_.data(); + offset_ = 0; +} + +void OpenJson::Context::startWrite() +{ + wbuffer_.clear(); +} + +//Segment +OpenJson::Segment::Segment(SegmentType type) +{ + setType(type); +} +OpenJson::Segment::~Segment() +{ +} +void OpenJson::Segment::setType(SegmentType type) +{ + type_ = type; + value_.int64_ = 0; +} +void OpenJson::Segment::clear() +{ + value_.int64_ = 0; +} + +void OpenJson::Segment::toString() +{ + switch (type_) + { + case NIL: + content_ = "null"; + break; + case BOOL: + content_ = value_.bool_ ? "true" : "false"; + break; + case INT32: + { + char buffer[64] = { 0 }; + int32ToStr(value_.int32_, buffer, sizeof(buffer)); + content_ = buffer; + } + break; + case INT64: + { + char buffer[64] = { 0 }; + int64ToStr(value_.int64_, buffer, sizeof(buffer)); + content_ = buffer; + } + break; + case DOUBLE: + { + char buffer[64] = { 0 }; + doubleToStr(value_.double_, buffer, sizeof(buffer)); + content_ = buffer; + } + break; + case STRING: + break; + default: + content_.clear(); + break; + } +} + +//OpenJson +bool OpenJson::EnableLog_ = true; + +OpenJson OpenJson::NodeNull; +std::string OpenJson::StringNull; + +OpenJson::OpenJson(JsonType type) + :type_(type), + context_(0), + wcontext_(0), + box_(0), + idx_(0), + key_(0), + len_(0), + segment_(0) +{ +} + +OpenJson::~OpenJson() +{ + clear(); +} + +OpenJson* OpenJson::createNode(unsigned char code) +{ + JsonType ctype = UNKNOWN; + switch (code) + { + case '"': + case '\'': + ctype = STRING; + break; + case '{': + ctype = OBJECT; + break; + case '[': + ctype = ARRAY; + break; + default: + ctype = NUMBER; + break; + } + OpenJson* node = new OpenJson(ctype); + return node; +} + +OpenJson::JsonType OpenJson::codeToType(unsigned char code) +{ + JsonType ctype = UNKNOWN; + switch (code) + { + case '"': + case '\'': + ctype = STRING; + break; + case '{': + ctype = OBJECT; + break; + case '[': + ctype = ARRAY; + break; + default: + ctype = NUMBER; + break; + } + return ctype; +} + +const std::string& OpenJson::emptyString() +{ + if (context_) + { + context_->stringNull_.clear(); + return context_->stringNull_; + } + if (wcontext_) + { + wcontext_->stringNull_.clear(); + return wcontext_->stringNull_; + } + return OpenJson::StringNull; +} + +const std::string& OpenJson::key() +{ + if (key_) return key_->s(); + return emptyString(); +} + +const char* OpenJson::data() +{ + if (context_ && context_->data_) + { + if (idx_ < context_->size_) + { + return context_->data_ + idx_; + } + } + Log("JsonNode is Empty"); + return emptyString().c_str(); +} + +double OpenJson::stringToDouble() +{ + const char* str = data(); + double dval = 0; + if (!str || strlen(str) == 0) + dval = (float)(1e+300 * 1e+300) * 0.0F; + else if (strcmp(str, "true") == 0) + dval = 1.0; + else if (strcmp(str, "false") == 0) + dval = 0.0; + else + dval = atof(str); + return dval; +} + +int32_t OpenJson::stringToInt32() +{ + int32_t ret = atoi(data()); + return ret; +} + +int64_t OpenJson::stringToInt64() +{ + int64_t ret = atoll(data()); + return ret; +} + +const std::string& OpenJson::s() +{ + if (type_ == STRING) + { + if (!segment_) + { + segment_ = new Segment(Segment::STRING); + segment_->content_ = data(); + } + if (segment_->type_ == Segment::STRING) + { + return segment_->content_; + } + segment_->toString(); + return segment_->content_; + } + else if (type_ == NUMBER) + { + Log("JsonNode is no STRING"); + if (!segment_) + { + if (!context_ || !context_->data_ || len_ < 1) + { + return emptyString(); + } + segment_ = new Segment(Segment::NIL); + segment_->content_ = data(); + return segment_->content_; + } + if (segment_) + { + if (segment_->type_ != Segment::NIL) + { + segment_->toString(); + } + return segment_->content_; + } + } + else + { + Log("JsonNode is no STRING"); + } + return emptyString(); +} + +double OpenJson::d(double def) +{ + if (type_ != NUMBER) + { + Log("JsonNode is no NUMBER"); + return def; + } + if (segment_ == 0) + { + if (!context_ || !context_->data_ || len_ < 1) + { + return def; + } + segment_ = new Segment(Segment::DOUBLE); + segment_->value_.double_ = stringToDouble(); + } + if (segment_->type_ != Segment::DOUBLE) + { + if (!context_ || !context_->data_ || len_ < 1) + { + Log("JsonNode is no DOUBLE NUMBER"); + } + else + { + segment_->setType(Segment::DOUBLE); + segment_->value_.double_ = stringToDouble(); + } + } + switch (segment_->type_) + { + case OpenJson::Segment::BOOL: + return segment_->value_.bool_; + case OpenJson::Segment::INT32: + return (double)segment_->value_.int32_; + case OpenJson::Segment::INT64: + return (double)segment_->value_.int64_; + case OpenJson::Segment::DOUBLE: + return segment_->value_.double_; + case OpenJson::Segment::STRING: + return atof(segment_->content_.c_str()); + default: + break; + } + return def; +} + +bool OpenJson::b(bool def) +{ + if (type_ != NUMBER) + { + Log("JsonNode is no NUMBER"); + return def; + } + if (segment_ == 0) + { + if (!context_ || !context_->data_ || len_ < 1) + { + return def; + } + segment_ = new Segment(Segment::BOOL); + segment_->value_.bool_ = stringToDouble() != 0 ? true : false; + } + if (segment_->type_ != Segment::BOOL) + { + if (!context_ || !context_->data_ || len_ < 1) + { + Log("JsonNode is no BOOL NUMBER"); + } + else + { + segment_->setType(Segment::BOOL); + segment_->value_.bool_ = stringToDouble() != 0 ? true : false; + } + } + switch (segment_->type_) + { + case OpenJson::Segment::BOOL: + return segment_->value_.bool_; + case OpenJson::Segment::INT32: + return (bool)segment_->value_.int32_; + case OpenJson::Segment::INT64: + return (bool)segment_->value_.int64_; + case OpenJson::Segment::DOUBLE: + return (bool)segment_->value_.double_; + case OpenJson::Segment::STRING: + return segment_->content_.size() > 0; + default: + break; + } + return def; +} + +int32_t OpenJson::i32(int32_t def) +{ + if (type_ != NUMBER) + { + Log("JsonNode is no NUMBER"); + return def; + } + if (segment_ == 0) + { + if (!context_ || !context_->data_ || len_ < 1) + { + return def; + } + segment_ = new Segment(Segment::INT32); + segment_->value_.int32_ = stringToInt32(); + } + if (segment_->type_ != Segment::INT32) + { + if (!context_ || !context_->data_ || len_ < 1) + { + Log("JsonNode is no INT32 NUMBER"); + } + else + { + segment_->setType(Segment::INT32); + segment_->value_.int32_ = stringToInt32(); + } + } + switch (segment_->type_) + { + case OpenJson::Segment::BOOL: + return segment_->value_.bool_; + case OpenJson::Segment::INT32: + return segment_->value_.int32_; + case OpenJson::Segment::INT64: + return (int32_t)segment_->value_.int64_; + case OpenJson::Segment::DOUBLE: + return (int32_t)segment_->value_.double_; + case OpenJson::Segment::STRING: + return atoi(segment_->content_.c_str()); + default: + break; + } + return def; +} + +int64_t OpenJson::i64(int64_t def) +{ + if (type_ != NUMBER) + { + Log("JsonNode is no NUMBER"); + return def; + } + if (segment_ && segment_->type_ == Segment::NIL) + { + delete segment_; + segment_ = 0; + } + if (segment_ == 0) + { + if (!context_ || !context_->data_ || len_ < 1) + { + return def; + } + segment_ = new Segment(Segment::INT64); + segment_->value_.int64_ = stringToInt64(); + } + if (segment_->type_ != Segment::INT64) + { + Log("JsonNode is no INT64 NUMBER"); + } + switch (segment_->type_) + { + case OpenJson::Segment::BOOL: + return segment_->value_.bool_; + case OpenJson::Segment::INT32: + return segment_->value_.int32_; + case OpenJson::Segment::INT64: + return segment_->value_.int64_; + case OpenJson::Segment::DOUBLE: + return (int64_t)segment_->value_.double_; + case OpenJson::Segment::STRING: + return atoll(segment_->content_.c_str()); + default: + break; + } + return def; +} + +void OpenJson::operator=(const std::string& val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != STRING) type_ = STRING; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::STRING); + //const char* ptr = 0; + for (size_t i = 0; i < val.size(); ++i) + { + if (val[i] == '"' || val[i] == '\'') + { + segment_->content_.push_back('\\'); + } + segment_->content_.push_back(val[i]); + } +} + +void OpenJson::operator=(const char* val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != STRING) type_ = STRING; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::STRING); + segment_->content_.clear(); + const char* ptr = 0; + for (size_t i = 0; i < strlen(val); ++i) + { + ptr = val + i; + if (*ptr == '"' || *ptr == '\'') + { + segment_->content_.push_back('\\'); + } + segment_->content_.push_back(*ptr); + } +} + +void OpenJson::operator=(bool val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::BOOL); + segment_->value_.bool_ = val; +} + +void OpenJson::operator=(int32_t val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::INT32); + segment_->value_.int32_ = val; +} + +void OpenJson::operator=(uint32_t val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::INT32); + segment_->value_.int32_ = val; +} + +void OpenJson::operator=(int64_t val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::INT64); + segment_->value_.int64_ = val; +} + +void OpenJson::operator=(uint64_t val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container, not element"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::INT64); + segment_->value_.int64_ = val; +} + +void OpenJson::operator=(double val) +{ + if (type_ == OBJECT || type_ == ARRAY) + { + Log("JsonNode is a container"); + return; + } + if (type_ != NUMBER) type_ = NUMBER; + if (segment_ == 0) segment_ = new Segment; + segment_->setType(Segment::DOUBLE); + segment_->value_.double_ = val; +} + +OpenJson& OpenJson::array(size_t idx) +{ + if (type_ != ARRAY) + { + if (type_ == OBJECT) + { + Log("JsonNode must be ARRAY, not OBJECT"); + } + type_ = ARRAY; + } + else + { + assert(box_); + } + if (!box_) box_ = new Box; + if (idx >= box_->childs_.size()) + { + box_->childs_.resize(idx + 1, 0); + } + OpenJson* child = box_->childs_[idx]; + if (!child) + { + child = new OpenJson(); + box_->childs_[idx] = child; + } + return *child; +} + +OpenJson& OpenJson::object(const char* key) +{ + if (!key) + { + return NodeNull; + } + if (type_ != OBJECT) + { + if (type_ == ARRAY) + { + Log("JsonNode must be OBJECT, not ARRAY"); + } + type_ = OBJECT; + } + else + { + assert(box_); + } + if (!box_) box_ = new Box; + + OpenJson* child = 0; + for (size_t i = 0; i < box_->childs_.size(); ++i) + { + child = box_->childs_[i]; + if (child == 0) continue; + if (strcmp(child->key().c_str(), key) == 0) + { + return *child; + } + } + OpenJson* keyNode = new OpenJson(STRING); + *keyNode = key; + child = new OpenJson(); + child->key_ = keyNode; + size_t i = 0; + for (; i < box_->childs_.size(); ++i) + { + if (!box_->childs_[i]) + { + box_->childs_[i] = child; + break; + } + } + if (i >= box_->childs_.size()) + { + box_->childs_.push_back(child); + } + return *child; +} + +void OpenJson::addNode(OpenJson* node) +{ + if (!node) return; + if (type_ != OBJECT && type_ != ARRAY) + { + Log("JsonNode must be OBJECT or ARRAY"); + type_ = node->key_ ? OBJECT : ARRAY; + } + if (box_ == 0) box_ = new Box; + box_->add(node); +} + +void OpenJson::removeNode(size_t idx) +{ + if (box_ == 0) return; + if (idx >= box_->childs_.size()) return; + box_->remove(box_->childs_[idx]); +} + +void OpenJson::removeNode(const char* key) +{ + if (box_ == 0) return; + OpenJson* child = 0; + for (size_t i = 0; i < box_->childs_.size(); ++i) + { + child = box_->childs_[i]; + if (child == 0) continue; + if (strcmp(child->key().c_str(), key) == 0) + { + box_->remove(child); + break; + } + } +} + +void OpenJson::clear() +{ + if (segment_) + { + delete segment_; + segment_ = 0; + } + if (key_) + { + delete key_; + key_ = 0; + } + if (box_) + { + assert(type_ == OBJECT || type_ == ARRAY); + delete box_; + box_ = 0; + } + if (context_ != 0 && context_->root_ == this) + { + context_->root_ = 0; + delete context_; + } + context_ = 0; + if (wcontext_ != 0 && wcontext_->root_ == this) + { + wcontext_->root_ = 0; + delete wcontext_; + } + wcontext_ = 0; + type_ = EMPTY; + idx_ = 0; + len_ = 0; +} + +void OpenJson::trimSpace() +{ + if (!context_) return; + char code = 0; + for (size_t i = idx_; i < context_->size_; ++i) + { + code = context_->data_[i]; + if (code > ' ') + { + idx_ = i; + break; + } + } +} + +unsigned char OpenJson::getCharCode() +{ + if (!context_) return 0; + if (idx_ < context_->size_) + { + unsigned char tmp = (unsigned char)context_->data_[idx_]; + return tmp; + } + return 0; +} + +unsigned char OpenJson::getChar() +{ + unsigned char code = getCharCode(); + if (code <= ' ') + { + trimSpace(); + code = getCharCode(); + } + return code; +} + +unsigned char OpenJson::checkCode(unsigned char charCode) +{ + unsigned char code = getCharCode(); + if (code != charCode) + { + trimSpace(); + code = getCharCode(); + if (code != charCode) return 0; + } + ++idx_; + return code; +} + +size_t OpenJson::searchCode(unsigned char code) +{ + char* data = context_->data_; + for (size_t i = idx_; i < context_->size_; i++) + { + if (data[i] == code) + { + if (i > 0 && data[i - 1] != '\\') return i; + } + } + return -1; +} + +bool OpenJson::makeRContext() +{ + if (type_ != EMPTY) + { + if (context_ && context_->root_ != this) + { + PRINTF("OpenJson warn:JsonNode is no root or empty!"); + return false; + } + } + else + { + if (context_ && context_->root_ != this) + { + PRINTF("OpenJson warn:JsonNode is no root or empty!"); + return false; + }; + } + clear(); + context_ = new Context(); + context_->root_ = this; + context_->offset_ = 0; + context_->rbuffer_.clear(); + return true; +} + +bool OpenJson::decode(const std::string& buffer) +{ + if (!makeRContext()) return false; + context_->rbuffer_ = buffer; + context_->startRead(); + type_ = codeToType(getChar()); + try + { + read(context_, true); + } + catch (const char* error) + { + PRINTF("OpenJson warn:decode catch exception %s", error); + } + return true; +} + +bool OpenJson::decodeFile(const std::string& filePath) +{ + if (!makeRContext()) return false; + FILE* fp = 0; +#ifdef _MSC_VER + fopen_s(&fp, filePath.c_str(), "rb"); +#else + fp = fopen(filePath.c_str(), "rb"); +#endif + if (fp == 0) + { +#ifdef _MSC_VER + char buffer[1024] = { 0 }; + strerror_s(buffer, sizeof(buffer), errno); + PRINTF("OpenJson warn:decodeFile error:%s,%s\n", buffer, filePath.c_str()); +#else + PRINTF("OpenJson warn:decodeFile error:%s,%s\n", strerror(errno), filePath.c_str()); +#endif + return false; + } + fseek(fp, 0, SEEK_END); + /*size_t size = */ + ftell(fp); + fseek(fp, 0, SEEK_SET); + + size_t ret = 0; + char buff[1024 * 8] = { 0 }; + while (true) + { + ret = fread(buff, 1, sizeof(buff), fp); + if (ret < 0) + { +#ifdef _MSC_VER + char buffer[1024] = { 0 }; + strerror_s(buffer, sizeof(buffer), errno); + PRINTF("OpenJson warn:decodeFile error:%s,%s\n", buffer, filePath.c_str()); +#else + PRINTF("OpenJson warn:decodeFile error:%s,%s\n", strerror(errno), filePath.c_str()); +#endif + fclose(fp); + return false; + } + else if(ret == 0) break; + context_->rbuffer_.append(buff, ret); + } + fclose(fp); + + context_->startRead(); + type_ = codeToType(getChar()); + try + { + read(context_, true); + } + catch (const char* error) + { + PRINTF("OpenJson warn:decodeFile catch exception %s", error); + } + return true; +} + +const std::string& OpenJson::encode() +{ + if (wcontext_ == 0) + { + wcontext_ = new Context(); + wcontext_->root_ = this; + } + wcontext_->startWrite(); + write(wcontext_, true); + return wcontext_->wbuffer_; +} + +void OpenJson::encodeFile(const std::string& filePath) +{ + FILE* fp = 0; +#ifdef _MSC_VER + fopen_s(&fp, filePath.c_str(), "wb"); +#else + fp = fopen(filePath.c_str(), "wb"); +#endif + if (fp == 0) + { +#ifdef _MSC_VER + char buffer[1024] = { 0 }; + strerror_s(buffer, sizeof(buffer), errno); + PRINTF("OpenJson warn:encodeFile error:%s,%s\n", buffer, filePath.c_str()); +#else + PRINTF("OpenJson warn:encodeFile error:%s,%s\n", strerror(errno), filePath.c_str()); +#endif + return; + } + fseek(fp, 0, SEEK_SET); + const std::string& buffer = encode(); + fwrite(buffer.data(), buffer.size(), 1, fp); + fclose(fp); +} + +void OpenJson::read(Context* context, bool isRoot) +{ + if (context_) + { + if (isRoot) + { + assert(context_ == context); + assert(context_->root_ == this); + } + else + { + assert(context_->root_ != this); + if (context_->root_ == this) return; + } + } + len_ = 0; + context_ = context; + idx_ = context->offset_; + switch (type_) + { + case EMPTY: + break; + case STRING: + readString(); + break; + case NUMBER: + readNumber(); + break; + case OBJECT: + readObject(); + break; + case ARRAY: + readArray(); + break; + case UNKNOWN: + break; + default: + break; + } +} +void OpenJson::readNumber() +{ + assert(type_ == NUMBER); + unsigned char code = 0; + size_t sidx = idx_; + size_t len = context_->size_; + char* data = context_->data_; + for (; idx_ < len; idx_++) + { + code = data[idx_]; + if (code == ',' || code == '}' || code == ']') + { + idx_--; + break; + } + } + if (idx_ < sidx) + { + throwError("lost number value"); + return; + } + len_ = idx_ - sidx + 1; + idx_ = sidx; +} +void OpenJson::readString() +{ + assert(type_ == STRING); + unsigned char code = '"'; + if (!checkCode(code)) + { + code = '\''; + if (!checkCode(code)) + { + throwError("lost '\"' or \"'\""); + return; + } + } + size_t sidx = idx_; + size_t eidx = searchCode(code); + if (eidx < 0) + { + throwError("lost '\"' or \"'\""); + return; + } + idx_ = sidx; + len_ = eidx - sidx + 1; + context_->data_[eidx] = 0; +} +void OpenJson::readObject() +{ + assert(type_ == OBJECT); + if (!checkCode('{')) + { + throwError("lost '{'"); + return; + } + unsigned char code = 0; + OpenJson* keyNode = 0; + OpenJson* valNode = 0; + size_t oidx = idx_; + while (idx_ < context_->size_) + { + code = getChar(); + if (code == 0) + { + throwError("lost '}'"); + return; + } + if (checkCode('}')) break; + keyNode = createNode(code); + if (keyNode->type_ != STRING) + { + throwError("lost key"); + return; + } + context_->offset_ = idx_; + keyNode->read(context_); + idx_ = keyNode->idx_ + keyNode->len_; + if (!checkCode(':')) + { + throwError("lost ':'"); + return; + } + code = getChar(); + valNode = createNode(code); + valNode->key_ = keyNode; + context_->offset_ = idx_; + valNode->read(context_); + idx_ = valNode->idx_ + valNode->len_; + addNode(valNode); + + if (checkCode('}')) + { + context_->data_[idx_ - 1] = 0; + break; + } + if (!checkCode(',')) + { + throwError("lost ','"); + return; + } + context_->data_[idx_ - 1] = 0; + } + len_ = idx_ - oidx; + idx_ = oidx; +} +void OpenJson::readArray() +{ + assert(type_ == ARRAY); + if (!checkCode('[')) + { + throwError("lost '['"); + return; + } + unsigned char code = 0; + OpenJson* valNode = 0; + size_t oidx = idx_; + while (idx_ < context_->size_) + { + code = getChar(); + if (code == 0) + { + throwError("lost ']'"); + return; + } + if (checkCode(']')) break; + valNode = createNode(code); + context_->offset_ = idx_; + valNode->read(context_); + idx_ = valNode->idx_ + valNode->len_; + addNode(valNode); + + if (checkCode(']')) + { + context_->data_[idx_ - 1] = 0; + break; + } + if (!checkCode(',')) + { + throwError("lost ','"); + return; + } + context_->data_[idx_ - 1] = 0; + } + len_ = idx_ - oidx; + idx_ = oidx; +} + +void OpenJson::write(Context* context, bool isRoot) +{ + if (wcontext_) + { + if (isRoot) + { + assert(wcontext_ == context); + assert(wcontext_->root_ == this); + } + else + { + assert(wcontext_->root_ != this); + if (wcontext_->root_ == this) return; + } + } + wcontext_ = context; + switch (type_) + { + case EMPTY: + break; + case STRING: + writeString(); + break; + case NUMBER: + writeNumber(); + break; + case OBJECT: + writeObject(); + break; + case ARRAY: + writeArray(); + break; + case UNKNOWN: + break; + default: + break; + } +} +void OpenJson::writeNumber() +{ + assert(type_ == NUMBER); + if (key_) + { + wcontext_->wbuffer_.append("\"" + key() + "\":"); + } + if (segment_) + { + segment_->toString(); + wcontext_->wbuffer_.append(segment_->content_); + } + else + { + wcontext_->wbuffer_.append(data()); + } +} +void OpenJson::writeString() +{ + assert(type_ == STRING); + if (key_) + { + wcontext_->wbuffer_.append("\"" + key() + "\":"); + } + wcontext_->wbuffer_.append("\"" + s() + "\""); +} +void OpenJson::writeObject() +{ + assert(type_ == OBJECT); + if (key_) + wcontext_->wbuffer_.append("\"" + key() + "\":{"); + else + wcontext_->wbuffer_.append("{"); + + if (box_ != 0) + { + size_t idx = 0; + size_t size = box_->size(); + for (size_t i = 0; i < size; ++i) + { + if (!(*box_)[i]) continue; + if (idx > 0) + { + wcontext_->wbuffer_.append(","); + } + (*box_)[i]->write(wcontext_); + ++idx; + } + } + wcontext_->wbuffer_.append("}"); +} +void OpenJson::writeArray() +{ + assert(type_ == ARRAY); + if (key_) + wcontext_->wbuffer_.append("\"" + key() + "\":["); + else + wcontext_->wbuffer_.append("["); + + if (box_ != 0) + { + size_t idx = 0; + size_t size = box_->size(); + for (size_t i = 0; i < size; ++i) + { + if (!(*box_)[i]) continue; + if (idx > 0) + { + wcontext_->wbuffer_.append(","); + } + (*box_)[i]->write(wcontext_); + ++idx; + } + } + wcontext_->wbuffer_.append("]"); +} + +void OpenJson::EnableLog(bool enable) +{ + EnableLog_ = enable; +} + +void OpenJson::Log(const char* format, ...) +{ + if (!EnableLog_) return; + va_list ap; + va_start(ap, format); + char tmp[1024] = { 0 }; + vsnprintf(tmp, sizeof(tmp), format, ap); + va_end(ap); + PRINTF("OpenJson WARN:%s\n", tmp); +} +void OpenJson::throwError(const char* errMsg) +{ + static const char* InfoTags[6] = { "EMPTY", "STRING", "NUMBER", "OBJECT", "ARRAY", "UNKNOWN" }; + size_t len = sizeof(InfoTags) / sizeof(InfoTags[0]); + const char* tab = type_ < len ? InfoTags[type_] : InfoTags[5]; + PRINTF("OpenJson:throwError [%s] Error: %s\n", tab, errMsg); + + char tmp[126] = { 0 }; + len = context_->size_ - context_->offset_; + len = len > 64 ? 64 : len; + memcpy(tmp, context_->data_ + idx_, len); + PRINTF("OpenJson:throwError content:%s\n", tmp); + throw errMsg; +} + diff --git a/applications/EmsShower/openjson.h b/applications/EmsShower/openjson.h new file mode 100644 index 0000000..ff27449 --- /dev/null +++ b/applications/EmsShower/openjson.h @@ -0,0 +1,276 @@ +/*************************************************************************** + * Copyright (C) 2023-, openlinyou, + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + ***************************************************************************/ + +#ifndef HEADER_OPEN_JSON_H +#define HEADER_OPEN_JSON_H + +#include +#include +#include +#include + + +#ifdef _MSC_VER +#pragma warning( disable: 4251 ) +# if _MSC_VER >= 1600 || defined(__MINGW32__) +# else +# if (_MSC_VER < 1300) +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +# else +typedef signed __int8 int8_t; +typedef signed __int16 int16_t; +typedef signed __int32 int32_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +# endif +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +# endif +#endif // _MSC_VER + +//#pragma warning(disable:4100) + +class OpenJson +{ + enum JsonType + { + EMPTY = 0, + STRING, + NUMBER, + OBJECT, + ARRAY, + UNKNOWN + }; + class Box + { + std::vector childs_; + public: + Box(); + ~Box(); + inline void clear() + { + childs_.clear(); + } + inline bool empty() + { + return childs_.empty(); + } + inline size_t size() + { + return childs_.size(); + } + inline void add(OpenJson* node) + { + childs_.push_back(node); + } + inline OpenJson* operator[](size_t idx) + { + return childs_[idx]; + } + bool remove(OpenJson* node); + friend class OpenJson; + }; + class Context + { + char* data_; + size_t size_; + size_t offset_; + + OpenJson* root_; + std::string rbuffer_; + std::string wbuffer_; + + std::string stringNull_; + public: + Context(); + ~Context(); + void startRead(); + void startWrite(); + friend class OpenJson; + }; + class Segment + { + public: + enum SegmentType + { + NIL = 0, + BOOL, + INT32, + INT64, + DOUBLE, + STRING + }; + SegmentType type_; + std::string content_; + union + { + bool bool_; + int32_t int32_; + int64_t int64_; + double double_; + } value_; + + Segment(SegmentType type = NIL); + ~Segment(); + + void clear(); + void toString(); + void setType(SegmentType type); + }; + + JsonType type_; + Context* context_; + Context* wcontext_; + size_t idx_; + size_t len_; + Box* box_; + OpenJson* key_; + Segment* segment_; + + void trimSpace(); + bool makeRContext(); + OpenJson* createNode(unsigned char code); + JsonType codeToType(unsigned char code); + unsigned char getCharCode(); + unsigned char getChar(); + unsigned char checkCode(unsigned char charCode); + size_t searchCode(unsigned char charCode); + void throwError(const char* errMsg); + + const char* data(); + int32_t stringToInt32(); + int64_t stringToInt64(); + double stringToDouble(); + + OpenJson& array(size_t idx); + OpenJson& object(const char* key); + void addNode(OpenJson* node); + void removeNode(size_t idx); + void removeNode(const char* key); + OpenJson(OpenJson& /*json*/) {} + OpenJson(const OpenJson& /*json*/) {} + void operator=(OpenJson& /*json*/) {} + void operator=(const OpenJson& /*json*/) {} + + const std::string& emptyString(); + static OpenJson NodeNull; + static std::string StringNull; +public: + OpenJson(JsonType type = EMPTY); + ~OpenJson(); + + inline size_t size() + { + return box_ ? box_->size() : 0; + } + inline bool empty() + { + return box_ ? box_->empty() : true; + } + inline OpenJson& operator[] (int idx) + { + return array(idx); + } + inline OpenJson& operator[] (size_t idx) + { + return array(idx); + } + inline OpenJson& operator[] (const char* key) + { + return object(key); + } + inline OpenJson& operator[] (const std::string& key) + { + return object(key.c_str()); + } + + inline void remove(int idx) + { + removeNode(idx); + } + inline void remove(size_t idx) + { + removeNode(idx); + } + inline void remove(const char* key) + { + removeNode(key); + } + inline void remove(const std::string& key) + { + removeNode(key.c_str()); + } + void clear(); + + inline bool isNull() + { + return type_ == EMPTY; + } + inline bool isNumber() + { + return type_ == NUMBER; + } + inline bool isString() + { + return type_ == STRING; + } + inline bool isObject() + { + return type_ == OBJECT; + } + inline bool isArray() + { + return type_ == ARRAY; + } + + bool b(bool def = false); + int32_t i32(int32_t def = 0); + int64_t i64(int64_t def = 0); + double d(double def = 0); + const std::string& s(); + const std::string& key(); + + void operator=(bool val); + void operator=(int32_t val); + void operator=(uint32_t val); + void operator=(int64_t val); + void operator=(uint64_t val); + void operator=(double val); + void operator=(const char* val); + void operator=(const std::string& val); + + bool decode(const std::string& buffer); + bool decodeFile(const std::string& filePath); + const std::string& encode(); + void encodeFile(const std::string& filePath); + + static void EnableLog(bool enable); +private: + static bool EnableLog_; + static void Log(const char* format, ...); + void read(Context* context, bool isRoot = false); + void readNumber(); + void readString(); + void readObject(); + void readArray(); + + void write(Context* context, bool isRoot = false); + void writeNumber(); + void writeString(); + void writeObject(); + void writeArray(); +}; + + +#endif /* HEADER_OPEN_JSON_H */ diff --git a/applications/EmsShower/qss.qrc b/applications/EmsShower/qss.qrc index c7a43a6..1261ddb 100644 --- a/applications/EmsShower/qss.qrc +++ b/applications/EmsShower/qss.qrc @@ -25,5 +25,23 @@ icons/up.png icons/warn.png fonts/Alimama_DongFangDaKai_Regular.ttf + icons/main_power.png + icons/main_ac.png + icons/main_battery.png + icons/main_cab.png + icons/main_door.png + icons/main_fan.png + icons/main_fire.png + icons/main_invertor.png + icons/main_multi_cab.png + icons/main_peidian.png + icons/main_pv.png + icons/main_temp.png + icons/main_thunder.png + icons/main_video.png + icons/main_water.png + icons/main_UPS.png + icons/main_alarm.png + icons/logo-en.png diff --git a/applications/examples/quc_demo/frmgaugeweather.cpp b/applications/examples/quc_demo/frmgaugeweather.cpp new file mode 100644 index 0000000..b16a2ab --- /dev/null +++ b/applications/examples/quc_demo/frmgaugeweather.cpp @@ -0,0 +1,41 @@ +#include "frmgaugeweather.h" +#include "ui_frmgaugeweather.h" + +frmGaugeWeather::frmGaugeWeather(QWidget *parent) : QWidget(parent), ui(new Ui::frmGaugeWeather) +{ + ui->setupUi(this); + this->initForm(); +} + +frmGaugeWeather::~frmGaugeWeather() +{ + delete ui; +} + +void frmGaugeWeather::initForm() +{ + ui->widget->setStyleSheet("QWidget#widget{border-image: url(:/image/bg2.jpg);}"); + ui->horizontalSlider1->setValue(65); + ui->horizontalSlider2->setValue(28); +} + +void frmGaugeWeather::on_horizontalSlider1_valueChanged(int value) +{ + ui->gaugeWeather->setOuterValue(value); +} + +void frmGaugeWeather::on_horizontalSlider2_valueChanged(int value) +{ + ui->gaugeWeather->setInnerValue(value); +} + +void frmGaugeWeather::on_comboBox_currentIndexChanged(int index) +{ + GaugeWeather::WeatherType type = (GaugeWeather::WeatherType)index; + ui->gaugeWeather->setWeatherType(type); +} + +void frmGaugeWeather::on_ckAnimation_stateChanged(int arg1) +{ + ui->gaugeWeather->setAnimation(arg1 != 0); +} diff --git a/applications/examples/quc_demo/frmgaugeweather.h b/applications/examples/quc_demo/frmgaugeweather.h new file mode 100644 index 0000000..1d97a35 --- /dev/null +++ b/applications/examples/quc_demo/frmgaugeweather.h @@ -0,0 +1,30 @@ +#ifndef FRMGAUGEWEATHER_H +#define FRMGAUGEWEATHER_H + +#include +#include + +namespace Ui { +class frmGaugeWeather; +} + +class frmGaugeWeather : public QWidget +{ + Q_OBJECT + +public: + explicit frmGaugeWeather(QWidget *parent = 0); + ~frmGaugeWeather(); + +private: + Ui::frmGaugeWeather *ui; + +private slots: + void initForm(); + void on_horizontalSlider1_valueChanged(int value); + void on_horizontalSlider2_valueChanged(int value); + void on_comboBox_currentIndexChanged(int index); + void on_ckAnimation_stateChanged(int arg1); +}; + +#endif // FRMGAUGEWEATHER_H diff --git a/applications/examples/quc_demo/frmgaugeweather.ui b/applications/examples/quc_demo/frmgaugeweather.ui new file mode 100644 index 0000000..5961dfb --- /dev/null +++ b/applications/examples/quc_demo/frmgaugeweather.ui @@ -0,0 +1,135 @@ + + + frmGaugeWeather + + + + 0 + 0 + 455 + 278 + + + + Form + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + 100 + + + 1 + + + Qt::Horizontal + + + + + + + -50 + + + 50 + + + Qt::Horizontal + + + + + + + 动画 + + + + + + + + + + + + + + + + + + + + + + + 多云 + + + + + + + + + + 雪雨 + + + + + 冰雹 + + + + + 闪电 + + + + + + + + + + 局部多云 + + + + + + + + + + + GaugeWeather + QWidget +
gaugeweather.h
+
+
+ + +
diff --git a/applications/examples/quc_demo/gaugeweather.cpp b/applications/examples/quc_demo/gaugeweather.cpp new file mode 100644 index 0000000..354cf41 --- /dev/null +++ b/applications/examples/quc_demo/gaugeweather.cpp @@ -0,0 +1,951 @@ +#pragma execution_character_set("utf-8") + +#include "gaugeweather.h" +//#include "qsvgrenderer.h" +#include "qpainter.h" +#include +#include "qmath.h" +#include "qtimer.h" +#include "qfile.h" +#include "qdebug.h" + +GaugeWeather::GaugeWeather(QWidget *parent) : QWidget(parent) +{ + outerMinValue = 0; + outerMaxValue = 100; + outerStartAngle = 0; + outerEndAngle = 0; + outerValue = 0; + outerCurrValue = 0; + outerRingBgColor = QColor(250, 250, 250); + outerRingColor = QColor(34, 163, 169); + + innerValue = 0; + innerMinValue = -50; + innerMaxValue = 50; + innerStartAngle = 0; + innerEndAngle = 135; + innerCurrValue = 0; + innerRingBgColor = QColor(250, 250, 250); + innerRingNegativeColor = QColor(214, 5, 5); + innerRingPositiveColor = QColor(0, 254, 254); + + innerScaleMajor = 2; + innerScaleMinor = 0; + innerScaleColor = QColor(255, 255, 255); + innerScaleNumColor = QColor(255, 255, 255); + + centerPixMapNegativeColor = QColor(214, 5, 5); + centerPixMapPositiveColor = QColor(0, 254, 254); + + outerValueTextColor = QColor(34, 163, 169); + innerNegativeValueTextColor = QColor(214, 5, 5); + innerPositiveValueTextColor = QColor(0, 254, 254); + + precision = 0; + outerReverse = false; + innerReverse = false; + clockWise = true; + + animation = false; + animationStep = 2; + weatherType = WeatherType_SUNNY; + centerSvgPath = ":/svg/weather-sunny.svg"; + + outerTimer = new QTimer(this); + outerTimer->setInterval(10); + connect(outerTimer, SIGNAL(timeout()), this, SLOT(updateOuterValue())); + + innerTimer = new QTimer(this); + innerTimer->setInterval(10); + connect(innerTimer, SIGNAL(timeout()), this, SLOT(updateInnerValue())); + + setFont(QFont("Arial", 9)); +} + +GaugeWeather::~GaugeWeather() +{ + if (outerTimer->isActive()) + { + outerTimer->stop(); + } + + if(innerTimer->isActive()) + { + innerTimer->stop(); + } +} + +void GaugeWeather::paintEvent(QPaintEvent *) +{ + int width = this->width(); + int height = this->height(); + int side = qMin(width, height); + + //绘制准备工作,启用反锯齿,平移坐标轴中心,等比例缩放 + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); + painter.translate(width / 2, height / 2); + painter.scale(side / 200.0, side / 200.0); + + //绘制外环背景 + drawOuterRingBg(&painter); + //绘制外环进度 + drawOuterRing(&painter); + //绘制内环背景 + drawInnerRingBg(&painter); + //绘制内环进度 + drawInnerRing(&painter); + //绘制内环刻度值 + drawInnerRingScaleNum(&painter); + //绘制中心图片 + drawCenterPixMap(&painter); + //绘制数值 + drawValue(&painter); +} + +void GaugeWeather::drawOuterRingBg(QPainter *painter) +{ + int radius = 99; + painter->save(); + painter->setBrush(Qt::NoBrush); + + //绘制圆弧方法绘制圆环 + int penWidth = 13; + QRectF rect(-radius + penWidth / 2, -radius + penWidth / 2, radius * 2 - penWidth, radius * 2 - penWidth); + //可以自行修改画笔的后三个参数,形成各种各样的效果,例如Qt::FlatCap改为Qt::RoundCap可以产生圆角效果 + QPen pen(outerRingBgColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MPenJoinStyle); + + //计算总范围角度 + double angleAll = 360.0 - outerStartAngle - outerEndAngle; + + //绘制总范围角度圆弧 + pen.setColor(outerRingBgColor); + painter->setPen(pen); + painter->drawArc(rect, (270 - outerStartAngle - angleAll) * 16, angleAll * 16); + painter->restore(); +} + +void GaugeWeather::drawOuterRing(QPainter *painter) +{ + int radius = 99; + painter->save(); + painter->setBrush(Qt::NoBrush); + + //绘制圆弧方法绘制圆环 + int penWidth = 13; + QRectF rect(-radius + penWidth / 2, -radius + penWidth / 2, radius * 2 - penWidth, radius * 2 - penWidth); + //可以自行修改画笔的后三个参数,形成各种各样的效果,例如Qt::FlatCap改为Qt::RoundCap可以产生圆角效果 + QPen pen(outerRingColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MPenJoinStyle); + + //计算总范围角度,当前值范围角度,剩余值范围角度 + double angleAll = 360.0 - outerStartAngle - outerEndAngle; + double angleCurrent = angleAll * ((outerCurrValue - outerMinValue) / (outerMaxValue - outerMinValue)); + + //绘制当前值饼圆 + painter->setPen(pen); + painter->drawArc(rect, (270 - outerStartAngle - angleCurrent) * 16, angleCurrent * 16); + painter->restore(); +} + +void GaugeWeather::drawInnerRingBg(QPainter *painter) +{ + int radius = 77; + painter->save(); + painter->setBrush(Qt::NoBrush); + + double penWidth = 13; + QPen pen(innerRingBgColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MPenJoinStyle); + painter->setPen(pen); + + double angleAll = 360.0 - innerStartAngle - innerEndAngle; + QRectF rect = QRectF(-radius, -radius, radius * 2, radius * 2); + + //零值以上背景 + painter->drawArc(rect, (270 - innerStartAngle - angleAll) * 16, angleAll * 16); + //零值以下背景 + //painter->drawArc(rect,(270 -innerStartAngle - null2MinAngle) * 16 ,null2MinAngle * 16); + + painter->restore(); +} + +void GaugeWeather::drawInnerRing(QPainter *painter) +{ + int radius = 77; + painter->save(); + painter->setBrush(Qt::NoBrush); + + int penWidth = 13.5; + QPen pen(innerRingPositiveColor, penWidth, Qt::SolidLine, Qt::FlatCap, Qt::MPenJoinStyle); + + double angleAll = 360.0 - innerStartAngle - innerEndAngle; + double null2MinAngle = angleAll * (( 0 - innerMinValue) / (innerMaxValue - innerMinValue)); //零点所占的角度 + double nullUpAllAngle = angleAll - null2MinAngle; //正基本角度 + double currAngle; + if(innerCurrValue >= 0) + { + //正角度 + pen.setColor(innerRingNegativeColor); + currAngle = nullUpAllAngle * (innerCurrValue / innerMaxValue) * -1; + } + else + { + //负角度 + pen.setColor(innerRingPositiveColor); + currAngle = null2MinAngle * (innerCurrValue / innerMinValue); + } + + painter->setPen(pen); + + QRectF rect = QRectF(-radius, -radius, radius * 2, radius * 2); + painter->drawArc(rect, (270 - innerStartAngle - null2MinAngle) * 16, currAngle * 16); + + painter->restore(); + +} + +void GaugeWeather::drawInnerRingScale(QPainter *painter) +{ + int radius = 76; + painter->save(); + painter->setPen(innerScaleColor); + + painter->rotate(innerStartAngle); + int steps = (innerScaleMajor * innerScaleMinor); + double angleStep = (360.0 - innerStartAngle - innerEndAngle) / steps; + QPen pen = painter->pen(); + pen.setCapStyle(Qt::RoundCap); + + for (int i = 0; i <= steps; i++) + { + if (i % innerScaleMinor == 0) + { + pen.setWidthF(1.5); + painter->setPen(pen); + painter->drawLine(0, radius - 12, 0, radius); + } + else + { + pen.setWidthF(1.0); + painter->setPen(pen); + painter->drawLine(0, radius - 5, 0, radius); + } + + painter->rotate(angleStep); + } + + painter->restore(); +} + +void GaugeWeather::drawInnerRingScaleNum(QPainter *painter) +{ + int radius = 60; + painter->save(); + painter->setPen(innerScaleNumColor); + + double startRad = (360 - innerStartAngle - 90) * (M_PI / 180); + double deltaRad = (360 - innerStartAngle - innerEndAngle) * (M_PI / 180) / innerScaleMajor; + + for (int i = 0; i <= innerScaleMajor; i++) + { + double sina = sin(startRad - i * deltaRad); + double cosa = cos(startRad - i * deltaRad); + double value = 1.0 * i * ((innerMaxValue - innerMinValue) / innerScaleMajor) + innerMinValue; + + QString strValue = QString("%1").arg((double)value, 0, 'f', precision); + double textWidth = fontMetrics().size(Qt::TextSingleLine,strValue).width();// .width(strValue); + double textHeight = fontMetrics().height(); + int x = radius * cosa - textWidth / 2; + int y = -radius * sina + textHeight / 4; + painter->drawText(x, y, strValue); + } + + painter->restore(); +} + +void GaugeWeather::drawCenterPixMap(QPainter *painter) +{ + painter->save(); + // int width_svg = 50; + // int height_svg = 50; + + // QFile file(centerSvgPath); + // file.open(QIODevice::ReadOnly); + // QByteArray baData = file.readAll(); + // //QDomDocument doc; + // //doc.setContent(baData); + // file.close(); + + // //正负角度切换不同颜色 + // if(innerCurrValue >= 0) + // { + // setColor(doc.documentElement(), "path", "fill", rgb2HexStr(centerPixMapNegativeColor)); + // } + // else + // { + // setColor(doc.documentElement(), "path", "fill", rgb2HexStr(centerPixMapPositiveColor)); + // } + + // QSvgRenderer m_svgRender(doc.toByteArray()); + // m_svgRender.render(painter, QRectF(-width_svg, -height_svg / 2, width_svg, height_svg)); + painter->restore(); +} + +void GaugeWeather::drawValue(QPainter *painter) +{ + painter->save(); + + QFont font; + font.setPixelSize(12); + painter->setFont(font); + painter->setPen(outerValueTextColor); + + QFontMetrics fm = painter->fontMetrics(); + QString outerStrValue = QString(tr("湿度: %1%")).arg(QString::number(outerCurrValue, 'f', 0)); + QRectF titleRect(5, 0, fm.size(Qt::TextSingleLine,outerStrValue).width(),fm.height());//width(outerStrValue), fm.height()); + painter->drawText(titleRect, Qt::AlignHCenter | Qt::AlignTop, outerStrValue); + if(innerCurrValue >= 0 ) + { + painter->setPen(innerNegativeValueTextColor); + } + else + { + painter->setPen(innerPositiveValueTextColor); + } + + QString innerDir = innerCurrValue >= 0 ? "+" : "-"; + QString innerStrValue = QString(tr("温度: %1%2 ℃")).arg(innerDir).arg(QString::number(abs(innerCurrValue), 'f', 0)); + QRectF innerTitleRect(5, 0, fm.size(Qt::TextSingleLine,innerStrValue).width(),-fm.height()); //fm.width(innerStrValue), -fm.height()); + painter->drawText(innerTitleRect, Qt::AlignHCenter | Qt::AlignVCenter, innerStrValue); + + painter->restore(); +} + +QString GaugeWeather::rgb2HexStr(const QColor &color) +{ + QString redStr = QString("%1").arg(color.red(), 2, 16, QChar('0')); + QString greenStr = QString("%1").arg(color.green(), 2, 16, QChar('0')); + QString blueStr = QString("%1").arg(color.blue(), 2, 16, QChar('0')); + QString key = "#" + redStr + greenStr + blueStr; + return key; +} + +// void GaugeWeather::setColor(QDomElement elem, QString strtagname, QString strattr, QString strattrval) +// { +// if (elem.tagName().compare(strtagname) == 0) { +// elem.setAttribute(strattr, strattrval); +// } + +// for (int i = 0; i < elem.childNodes().count(); i++) { +// if (!elem.childNodes().at(i).isElement()) { +// continue; +// } + +// setColor(elem.childNodes().at(i).toElement(), strtagname, strattr, strattrval); +// } +// } + +double GaugeWeather::getOuterValue() const +{ + return this->outerValue; +} + +double GaugeWeather::getOuterMinValue() const +{ + return this->outerMinValue; +} + +double GaugeWeather::getOuterMaxValue() const +{ + return this->outerMaxValue; +} + +int GaugeWeather::getOuterStartAngle() const +{ + return this->outerStartAngle; +} + +int GaugeWeather::getOuterEndAngle() const +{ + return this->outerEndAngle; +} + +QColor GaugeWeather::getOuterRingBgColor() const +{ + return this->outerRingBgColor; +} + +QColor GaugeWeather::getOuterRingColor() const +{ + return this->outerRingColor; +} + +QColor GaugeWeather::getOuterValueTextColor() const +{ + return this->outerValueTextColor; +} + +double GaugeWeather::getInnerMinValue() const +{ + return this->innerMinValue; +} + +double GaugeWeather::getInnerMaxValue() const +{ + return this->innerMaxValue; +} + +double GaugeWeather::getInnerValue() const +{ + return this->innerValue; +} + +int GaugeWeather::getInnerStartAngle() const +{ + return this->innerStartAngle; +} + +int GaugeWeather::getInnerEndAngle() const +{ + return this->innerEndAngle; +} + +QColor GaugeWeather::getInnerRingBgColor() const +{ + return this->innerRingBgColor; +} + +QColor GaugeWeather::getInnerNegativeColor() const +{ + return this->innerRingNegativeColor; +} + +QColor GaugeWeather::getInnerPositiveColor() const +{ + return this->innerRingPositiveColor; +} + +int GaugeWeather::getInnerScaleMajor() const +{ + return this->innerScaleMajor; +} + +int GaugeWeather::getInnerScaleMinor() const +{ + return this->innerScaleMinor; +} + +QColor GaugeWeather::getInnerScaleColor() const +{ + return this->innerScaleColor; +} + +QColor GaugeWeather::getInnerScaleNumColor() const +{ + return this->innerScaleNumColor; +} + +QColor GaugeWeather::getInnerNegativeValueTextColor() const +{ + return this->innerNegativeValueTextColor; +} + +QColor GaugeWeather::getInnerPositiveValueTextColor() const +{ + return this->innerPositiveValueTextColor; +} + +QColor GaugeWeather::getCenterPixMapNegativeColor() const +{ + return this->centerPixMapNegativeColor; +} + +QColor GaugeWeather::getCenterPixMapPositiveColor() const +{ + return this->centerPixMapPositiveColor; +} + +bool GaugeWeather::getAnimation() const +{ + return this->animation; +} + +double GaugeWeather::getAnimationStep() const +{ + return this->animationStep; +} + +GaugeWeather::WeatherType GaugeWeather::getWeatherType() const +{ + return this->weatherType; +} + +QSize GaugeWeather::sizeHint() const +{ + return QSize(200, 200); +} + +QSize GaugeWeather::minimumSizeHint() const +{ + return QSize(50, 50); +} + +void GaugeWeather::setOuterRange(double minValue, double maxValue) +{ + //如果最小值大于或者等于最大值则不设置 + if (minValue >= maxValue) + { + return; + } + + this->outerMinValue = minValue; + this->outerMaxValue = maxValue; + + //如果目标值不在范围值内,则重新设置目标值 + if (outerValue < minValue || outerValue > maxValue) + { + setOuterValue(outerValue); + } + + this->update(); +} + +void GaugeWeather::setOuterMinValue(double value) +{ + setOuterRange(value, outerMaxValue); +} + +void GaugeWeather::setOuterMaxValue(double value) +{ + setOuterRange(outerMinValue, value); +} + +void GaugeWeather::setOuterValue(double value) +{ + //值小于最小值或者值大于最大值或者值和当前值一致则无需处理 + if (value < outerMinValue || value > outerMaxValue || value == this->outerValue) + { + return; + } + + if (value > this->outerValue) + { + outerReverse = false; + } + else if (value < this->outerValue) + { + outerReverse = true; + } + + this->outerValue = value; + emit valueChanged(value); + + if (!animation) + { + outerCurrValue = this->outerValue; + this->update(); + } + else + { + outerTimer->start(); + } +} + +void GaugeWeather::setOuterStartAngle(int startAngle) +{ + if (this->outerStartAngle != startAngle) + { + this->outerStartAngle = startAngle; + this->update(); + } +} + +void GaugeWeather::setOuterEndAngle(int endAngle) +{ + if (this->outerEndAngle != endAngle) + { + this->outerEndAngle = endAngle; + this->update(); + } +} + +void GaugeWeather::setOuterRingBgColor(const QColor &color) +{ + if (this->outerRingBgColor != color) + { + this->outerRingBgColor = color; + this->update(); + } +} + +void GaugeWeather::setOuterRingColor(const QColor &color) +{ + if (this->outerRingColor != color) + { + this->outerRingColor = color; + this->update(); + } +} + +void GaugeWeather::setOuterValueTextColor(const QColor &color) +{ + if (this->outerValueTextColor != color) + { + this->outerValueTextColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerRange(double minValue, double maxValue) +{ + //如果最小值大于或者等于最大值则不设置 + if (minValue >= maxValue) + { + return; + } + + this->innerMinValue = minValue; + this->innerMaxValue = maxValue; + + //如果目标值不在范围值内,则重新设置目标值 + if (innerValue < minValue || innerValue > maxValue) + { + setInnerValue(innerValue); + } + + this->update(); +} + +void GaugeWeather::setInnerMinValue(double value) +{ + setInnerRange(value, innerMaxValue); +} + +void GaugeWeather::setInnerMaxValue(double value) +{ + setInnerRange(innerMinValue, value); +} + +void GaugeWeather::setInnerValue(double value) +{ + //值小于最小值或者值大于最大值或者值和当前值一致则无需处理 + if (value < innerMinValue || value > innerMaxValue || value == this->innerValue) + { + return; + } + + if(this->innerValue >= 0) + { + clockWise = true; + if(value < this->innerValue) + { + //如果要设置的值小于已有的值,需要回滚 + innerReverse = true; + } + else + { + innerReverse = false; + } + } + else + { + clockWise = false; + if(value > this->innerValue) + { + //如果要设置的值大于已有的值,需要回滚 + innerReverse = true; + } + else + { + innerReverse = false; + } + } + + this->innerValue = value; + emit valueChanged(value); + + if (!animation) + { + innerCurrValue = this->innerValue; + this->update(); + } + else + { + innerTimer->start(); + } +} + +void GaugeWeather::setInnerStartAngle(int startAngle) +{ + if (this->innerStartAngle != startAngle) + { + this->innerStartAngle = startAngle; + this->update(); + } +} + +void GaugeWeather::setInnerEndAngle(int endAngle) +{ + if (this->innerEndAngle != endAngle) + { + this->innerEndAngle = endAngle; + this->update(); + } +} + +void GaugeWeather::setInnerRingBgColor(const QColor &color) +{ + if (this->innerRingBgColor != color) + { + this->innerRingBgColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerNegativeColor(const QColor &color) +{ + if (this->innerRingNegativeColor != color) + { + this->innerRingNegativeColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerPositiveColor(const QColor &color) +{ + if (this->innerRingPositiveColor != color) + { + this->innerRingPositiveColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerScaleMajor(int value) +{ + if (this->innerScaleMajor != value) + { + this->innerScaleMajor = value; + this->update(); + } +} + +void GaugeWeather::setInnerScaleMinor(int value) +{ + if (this->innerScaleMinor != value) + { + this->innerScaleMinor = value; + this->update(); + } +} + +void GaugeWeather::setInnerScaleColor(const QColor &color) +{ + if (this->innerScaleColor != color) + { + this->innerScaleColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerScaleNumColor(const QColor &color) +{ + if (this->innerScaleNumColor != color) + { + this->innerScaleNumColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerNegativeValueTextColor(const QColor &color) +{ + if (this->innerNegativeValueTextColor != color) + { + this->innerNegativeValueTextColor = color; + this->update(); + } +} + +void GaugeWeather::setInnerPositiveValueTextColor(const QColor &color) +{ + if (this->innerPositiveValueTextColor != color) + { + this->innerPositiveValueTextColor = color; + this->update(); + } +} + +void GaugeWeather::setCenterPixMapNegativeColor(const QColor &color) +{ + if (this->centerPixMapNegativeColor != color) + { + this->centerPixMapNegativeColor = color; + this->update(); + } +} + +void GaugeWeather::setCenterPixMapPositiveColor(const QColor &color) +{ + if (this->centerPixMapPositiveColor != color) + { + this->centerPixMapPositiveColor = color; + this->update(); + } +} + +void GaugeWeather::setAnimation(bool animation) +{ + if (this->animation != animation) + { + this->animation = animation; + this->update(); + } +} + +void GaugeWeather::setAnimationStep(double animationStep) +{ + if (this->animationStep != animationStep) + { + this->animationStep = animationStep; + this->update(); + } +} + +void GaugeWeather::setWeatherType(GaugeWeather::WeatherType &type) +{ + if(this->weatherType != type) + { + this->weatherType = type; + switch (type) + { + case WeatherType_SUNNY: + centerSvgPath = ":/svg/weather-sunny.svg"; + break; + case WeatherType_RAINY: + centerSvgPath = ":/svg/weather-rainy.svg"; + break; + case WeatherType_SNOWY: + centerSvgPath = ":/svg/weather-snowy.svg"; + break; + case WeatherType_CLOUDY: + centerSvgPath = ":/svg/weather-cloudy.svg"; + break; + case WeatherType_WINDY: + centerSvgPath = ":/svg/weather-windy.svg"; + break; + case WeatherType_SNOWY_RAINY: + centerSvgPath = ":/svg/weather-snowy-rainy.svg"; + break; + case WeatherType_HAIL: + centerSvgPath = ":/svg/weather-hail.svg"; + break; + case WeatherType_LIGHTNING: + centerSvgPath = ":/svg/weather-lightning.svg"; + break; + case WeatherType_FOG: + centerSvgPath = ":/svg/weather-fog.svg"; + break; + case WeatherType_PARTLYCLOUDY: + centerSvgPath = ":/svg/weather-partlycloudy.svg"; + break; + default: + centerSvgPath = ":/svg/weather-sunny.svg"; + break; + } + + this->update(); + } +} + +void GaugeWeather::updateOuterValue() +{ + if (!outerReverse) + { + if (outerCurrValue >= outerValue) + { + outerCurrValue = outerValue; + outerTimer->stop(); + } + else + { + outerCurrValue += animationStep; + } + } + else + { + if (outerCurrValue <= outerValue) + { + outerCurrValue = outerValue; + outerTimer->stop(); + } + else + { + outerCurrValue -= animationStep; + } + } + + this->update(); +} + +void GaugeWeather::updateInnerValue() +{ + //currValue变成Value的过程 + if(clockWise) + { + //针对value大于零时,当currValue 小于 Value,需要回滚,否则前滚, + if (!innerReverse) + { + if (innerCurrValue >= innerValue) + { + innerCurrValue = innerValue; + innerTimer->stop(); + } + else + { + innerCurrValue += animationStep; + } + } + else + { + if (innerCurrValue <= innerValue) + { + innerCurrValue = innerValue; + innerTimer->stop(); + } + else + { + innerCurrValue -= animationStep; + } + } + + } + else + { + //针对value大于零时,当currValue 大于 Value,需要回滚,否则前滚, + if (!innerReverse) + { + if (innerCurrValue <= innerValue) + { + innerCurrValue = innerValue; + innerTimer->stop(); + } + else + { + innerCurrValue -= animationStep; + } + } + else + { + if (innerCurrValue >= innerValue) + { + innerCurrValue = innerValue; + innerTimer->stop(); + } + else + { + innerCurrValue += animationStep; + } + } + } + + this->update(); +} diff --git a/applications/examples/quc_demo/gaugeweather.h b/applications/examples/quc_demo/gaugeweather.h new file mode 100644 index 0000000..a6f9c7d --- /dev/null +++ b/applications/examples/quc_demo/gaugeweather.h @@ -0,0 +1,252 @@ +#ifndef GAUGEWEATHER_H +#define GAUGEWEATHER_H + +/** + * 天气仪表盘控件 作者:东门吹雪(QQ:709102202) 整理:飞扬青云(QQ:517216493) 2019-4-23 + * 1:可设置两种值,比如温度+湿度 + * 2:可设置两种值的背景颜色+文字颜色 + * 3:可设置零度值左侧右侧两种颜色 + * 4:可设置圆的起始角度和结束角度 + * 5:可设置10种天气,晴天+雨天+阴天+大风等 + * 6:可设置各种其他颜色 + * 7:科设置是否启用动画显示进度以及动画步长 + */ + +#include +#include +//#include + +#ifdef quc +#if (QT_VERSION < QT_VERSION_CHECK(5,7,0)) +#include +#else +#include +#endif + +class QDESIGNER_WIDGET_EXPORT GaugeWeather : public QWidget +#else +class GaugeWeather : public QWidget +#endif + +{ + Q_OBJECT + Q_ENUMS(WeatherType) + + Q_PROPERTY(double outerValue READ getOuterValue WRITE setOuterValue) + Q_PROPERTY(double outerMinValue READ getOuterMinValue WRITE setOuterMinValue) + Q_PROPERTY(double outerMaxValue READ getOuterMaxValue WRITE setOuterMaxValue) + + Q_PROPERTY(int outerStartAngle READ getOuterStartAngle WRITE setOuterStartAngle) + Q_PROPERTY(int outerEndAngle READ getOuterEndAngle WRITE setOuterEndAngle) + + Q_PROPERTY(QColor outerRingBgColor READ getOuterRingBgColor WRITE setOuterRingBgColor) + Q_PROPERTY(QColor outerRingColor READ getOuterRingColor WRITE setOuterRingColor) + + Q_PROPERTY(double innerValue READ getInnerValue WRITE setInnerValue) + Q_PROPERTY(double innerMinValue READ getInnerMinValue WRITE setInnerMinValue) + Q_PROPERTY(double innerMaxValue READ getInnerMaxValue WRITE setInnerMaxValue) + + Q_PROPERTY(int innerStartAngle READ getInnerStartAngle WRITE setInnerStartAngle) + Q_PROPERTY(int innerEndAngle READ getInnerEndAngle WRITE setInnerEndAngle) + + Q_PROPERTY(QColor innerRingBgColor READ getInnerRingBgColor WRITE setInnerRingBgColor) + Q_PROPERTY(QColor innerRingNegativeColor READ getInnerNegativeColor WRITE setInnerNegativeColor) + Q_PROPERTY(QColor innerRingPositiveColor READ getInnerPositiveColor WRITE setInnerPositiveColor) + + Q_PROPERTY(int innerScaleMajor READ getInnerScaleMajor WRITE setInnerScaleMajor) + Q_PROPERTY(int innerScaleMinor READ getInnerScaleMinor WRITE setInnerScaleMinor) + Q_PROPERTY(QColor innerScaleColor READ getInnerScaleColor WRITE setInnerScaleColor) + Q_PROPERTY(QColor innerScaleNumColor READ getInnerScaleNumColor WRITE setInnerScaleNumColor) + + Q_PROPERTY(QColor centerPixMapNegativeColor READ getCenterPixMapNegativeColor WRITE setCenterPixMapNegativeColor) + Q_PROPERTY(QColor centerPixMapPositiveColor READ getCenterPixMapPositiveColor WRITE setCenterPixMapPositiveColor) + + Q_PROPERTY(QColor outerValueTextColor READ getOuterValueTextColor WRITE setOuterValueTextColor) + Q_PROPERTY(QColor innerNegativeValueTextColor READ getInnerNegativeValueTextColor WRITE setInnerNegativeValueTextColor) + Q_PROPERTY(QColor innerPositiveValueTextColor READ getInnerPositiveValueTextColor WRITE setInnerPositiveValueTextColor) + + Q_PROPERTY(bool animation READ getAnimation WRITE setAnimation) + Q_PROPERTY(double animationStep READ getAnimationStep WRITE setAnimationStep) + Q_PROPERTY(WeatherType weatherType READ getWeatherType WRITE setWeatherType) + +public: + enum WeatherType + { + WeatherType_SUNNY = 0, //晴天 + WeatherType_RAINY = 1, //雨天 + WeatherType_SNOWY = 2, //雪天 + WeatherType_CLOUDY = 3, //多云 + WeatherType_WINDY = 4, //风 + WeatherType_SNOWY_RAINY = 5, //雪雨 + WeatherType_HAIL = 6, //冰雹 + WeatherType_LIGHTNING = 7, //闪电 + WeatherType_FOG = 8, //雾 + WeatherType_PARTLYCLOUDY = 9 //局部多云 + }; + + explicit GaugeWeather(QWidget *parent = 0); + ~GaugeWeather(); + +protected: + void paintEvent(QPaintEvent *); + void drawOuterRingBg(QPainter *painter); + void drawOuterRing(QPainter *painter); + void drawInnerRingBg(QPainter *painter); + void drawInnerRing(QPainter *painter); + void drawInnerRingScale(QPainter *painter); + void drawInnerRingScaleNum(QPainter *painter); + void drawCenterPixMap(QPainter *painter); + void drawValue(QPainter *painter); + +private slots: + void updateOuterValue(); //更新外圈数值 + void updateInnerValue(); //更新内圈数值 + +private: + double outerMaxValue; //外圈最大值 + double outerMinValue; //外圈最小值 + double outerValue; //外圈值 + double outerCurrValue; //外圈当前值 + + int outerStartAngle; //外环开始旋转角度 + int outerEndAngle; //外环结束旋转角度 + + QColor outerRingBgColor; //外圈背景色 + QColor outerRingColor; //外圈当前色 + + double innerMaxValue; //内圈最大值 + double innerMinValue; //内圈最小值 + double innerValue; //内圈值 + double innerCurrValue; //内圈当前值 + + int innerStartAngle; //内环开始旋转角度 + int innerEndAngle; //内环结束旋转角度 + + QColor innerRingBgColor; //内圈背景色 + QColor innerRingNegativeColor; //内圈负值当前色 + QColor innerRingPositiveColor; //内圈正值当前色 + + int innerScaleMajor; //内环大刻度数量 + int innerScaleMinor; //内环小刻度数量 + QColor innerScaleColor; //内环刻度颜色 + QColor innerScaleNumColor; //内环刻度值颜色 + int precision; //精确度,小数点后几位 + + QColor centerPixMapNegativeColor; //中心图片颜色 + QColor centerPixMapPositiveColor; //中心图片颜色 + QString centerSvgPath; //当前svg图片路径 + WeatherType weatherType; //天气类型 + + QColor outerValueTextColor; //外环值文本颜色 + QColor innerNegativeValueTextColor; //内环正值文本颜色 + QColor innerPositiveValueTextColor; //内环负值文本颜色 + + bool animation; //是否启用动画显示 + double animationStep; //动画显示时步长 + + bool outerReverse; //是否往回走 + bool innerReverse; //是否往回走 + bool clockWise; //顺时针 + + QTimer *outerTimer; //外环定时器绘制动画 + QTimer *innerTimer; //内环定时器绘制动画 + + //将svg文件中的xml数据颜色替换 + //void setColor(QDomElement elem, QString strtagname, QString strattr, QString strattrval); + QString rgb2HexStr(const QColor &color); + +public: + double getOuterMinValue() const; + double getOuterMaxValue() const; + double getOuterValue() const; + int getOuterStartAngle() const; + int getOuterEndAngle() const; + + QColor getOuterRingBgColor() const; + QColor getOuterRingColor() const; + + double getInnerMaxValue() const; + double getInnerMinValue() const; + double getInnerValue() const; + int getInnerStartAngle() const; + int getInnerEndAngle() const; + + QColor getInnerRingBgColor() const; + QColor getInnerNegativeColor() const; + QColor getInnerPositiveColor() const; + + int getInnerScaleMajor() const; + int getInnerScaleMinor() const; + QColor getInnerScaleColor() const; + QColor getInnerScaleNumColor() const; + + bool getAnimation() const; + double getAnimationStep() const; + + WeatherType getWeatherType() const; + + QColor getCenterPixMapNegativeColor() const; + QColor getCenterPixMapPositiveColor() const; + + QColor getOuterValueTextColor() const; + QColor getInnerNegativeValueTextColor() const; + QColor getInnerPositiveValueTextColor() const; + + QSize sizeHint() const; + QSize minimumSizeHint() const; + +public Q_SLOTS: + void setWeatherType(WeatherType &type); + + //设置范围值 + void setOuterRange(double minValue, double maxValue); + //设置外环最大最小值 + void setOuterMinValue(double value); + void setOuterMaxValue(double value); + + //设置外环值 + void setOuterValue(double value); + //设置外环开始旋转角度 + void setOuterStartAngle(int startAngle); + //设置外环结束旋转角度 + void setOuterEndAngle(int endAngle); + //设置外环背景色 + void setOuterRingBgColor(const QColor &color); + //设置外环进度色 + void setOuterRingColor(const QColor &color); + + //设置范围值 + void setInnerRange(double minValue, double maxValue); + void setInnerMinValue(double value); + void setInnerMaxValue(double value); + void setInnerValue(double value); + void setInnerStartAngle(int startAngle); + void setInnerEndAngle(int endAngle); + + void setInnerRingBgColor(const QColor &color); + void setInnerNegativeColor(const QColor &color); + void setInnerPositiveColor(const QColor &color); + + void setInnerScaleMajor(int value); + void setInnerScaleMinor(int value); + void setInnerScaleColor(const QColor &color); + void setInnerScaleNumColor(const QColor &color); + + //设置中心图标颜色 + void setCenterPixMapNegativeColor(const QColor &color); + void setCenterPixMapPositiveColor(const QColor &color); + + void setOuterValueTextColor(const QColor &color); + void setInnerNegativeValueTextColor(const QColor &color); + void setInnerPositiveValueTextColor(const QColor &color); + + //设置是否启用动画显示 + void setAnimation(bool animation); + //设置动画显示的步长 + void setAnimationStep(double animationStep); + +Q_SIGNALS: + void valueChanged(double value); +}; + +#endif // GAUGEWEATHER_H diff --git a/applications/examples/quc_demo/image/bg2.png b/applications/examples/quc_demo/image/bg2.png new file mode 100644 index 0000000000000000000000000000000000000000..7a06659c2b006ec42fe71b22f28f94a2596d1787 GIT binary patch literal 47566 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 fps{kD2hF + + image/bg2.png + +