增加通讯协议

This commit is contained in:
xu
2026-10-14 09:59:42 +08:00
parent 32878a9a80
commit 950e9e6c9e
102 changed files with 5538 additions and 2679 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ AlarmBanner::AlarmBanner(QWidget* parent)
m_label = new QLabel(this);
m_label->setAlignment(Qt::AlignCenter);
m_label->setProperty("alert", true);
m_label->setStyleSheet("font-size:30px;font-weight:700;");
m_label->setStyleSheet("font-size:45px;font-weight:700;");
lay->addWidget(m_label);
hide(); // 默认隐藏
+50 -20
View File
@@ -15,47 +15,56 @@ ConfirmDialog::ConfirmDialog(QWidget* parent)
if (parent) setGeometry(parent->rect());
setStyleSheet("background-color: rgba(0,0,0,102);");
QWidget* panel = new QWidget(this);
panel->setObjectName("ConfirmPanel");
panel->setFixedSize(560, 260);
panel->setStyleSheet(QString(
m_panel = new QWidget(this);
m_panel->setObjectName("ConfirmPanel");
m_panel->setFixedSize(700, 420);
m_panel->setStyleSheet(QString(
"QWidget#ConfirmPanel{background-color:%1;border:2px solid %2;border-radius:16px;}")
.arg(C::PANEL, C::BORDER));
QVBoxLayout* lay = new QVBoxLayout(panel);
QVBoxLayout* lay = new QVBoxLayout(m_panel);
lay->setContentsMargins(28, 24, 28, 24);
lay->setSpacing(16);
lay->setSpacing(0); // 标题与正文不留间隙(与“操作说明”弹窗一致)
m_title = new QLabel("", panel);
m_title->setStyleSheet(QString("color:%1;font-size:28px;font-weight:700;").arg(C::TEXT));
m_title = new QLabel("", m_panel);
m_title->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;font-weight:700;").arg(C::TEXT));
m_title->setAlignment(Qt::AlignCenter);
lay->addWidget(m_title);
lay->addSpacing(60); // 标题与正文之间空一行
m_body = new QLabel("", panel);
m_body->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT));
m_body = new QLabel("", m_panel);
m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;").arg(C::TEXT));
m_body->setAlignment(Qt::AlignCenter);
m_body->setWordWrap(true);
lay->addWidget(m_body, 1);
lay->addWidget(m_body); // 不再拉伸:标题与正文紧挨着
lay->addStretch(1); // 剩余空间放在正文与按钮之间(标题/正文保持自然高度,标题顶到最上面)
QHBoxLayout* btnLay = new QHBoxLayout();
btnLay->addStretch(1);
QPushButton* ok = new QPushButton("确认", panel);
ok->setProperty("variant", "primary");
ok->setFixedSize(160, 72);
QPushButton* ok = new QPushButton("确认", m_panel);
// 常态灰底、选中(悬停/焦点)蓝底白字。
// 注意:该表设了 background-color,会压住全局的 variant/焦点规则,所以状态必须写在这里。
ok->setFixedSize(180, 80);
ok->setStyleSheet(QString(
"QPushButton{background-color:%1;color:%2;border:1px solid %3;border-radius:12px;font-size:60px;}"
"QPushButton:hover,QPushButton:focus{background-color:%4;color:#FFFFFF;border:2px solid #8ABFFF;}")
.arg(C::PANEL_HI, C::TEXT, C::BORDER, C::PRIMARY));
connect(ok, &QPushButton::clicked, this, &ConfirmDialog::accept);
QPushButton* cancel = new QPushButton("取消", panel);
cancel->setFixedSize(160, 72);
cancel->setStyleSheet(cancelButtonStyle(40)); // 选中时红底白字
connect(cancel, &QPushButton::clicked, this, &ConfirmDialog::reject);
m_cancel = new QPushButton("取消", m_panel);
m_cancel->setFixedSize(180, 80); // 与“确认”同尺寸
m_cancel->setStyleSheet(cancelButtonStyle(60)); // 与“确认”同字号;选中时红底白字
connect(m_cancel, &QPushButton::clicked, this, &ConfirmDialog::reject);
btnLay->addWidget(ok);
btnLay->addWidget(cancel);
btnLay->addSpacing(80); // “确认”与“取消”之间留 40px
btnLay->addWidget(m_cancel);
btnLay->addStretch(1);
lay->addSpacing(30); // 正文与按钮之间留 30px
lay->addLayout(btnLay);
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->addStretch(1);
root->addWidget(panel, 0, Qt::AlignHCenter);
root->addWidget(m_panel, 0, Qt::AlignHCenter);
root->addStretch(1);
}
@@ -70,6 +79,27 @@ bool ConfirmDialog::confirm(QWidget* parent, const QString& title, const QString
return dlg.exec() == QDialog::Accepted;
}
void ConfirmDialog::message(QWidget* parent, const QString& title, const QString& text)
{
ConfirmDialog dlg(parent);
dlg.m_panel->setFixedSize(900, 600); // 正文行数少 → 面板收窄
dlg.m_cancel->hide(); // 只保留“确认”
dlg.m_title->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;font-weight:700;").arg(C::TEXT));
dlg.m_body->setStyleSheet(QString("background:transparent;color:%1;font-size:50px;").arg(C::TEXT));
dlg.m_title->setWordWrap(true);
dlg.m_title->setContentsMargins(0, 20, 0, 0); // 标题文字距该行顶部 10px
dlg.m_body->setContentsMargins(0, 0, 0, 0);
dlg.m_body->setAlignment(Qt::AlignCenter); // 正文居中(水平+垂直)
if (QBoxLayout* lay = qobject_cast<QBoxLayout*>(dlg.m_panel->layout())) {
lay->setSpacing(0); // 统一间隔置 0,各段间距单独控制
lay->setContentsMargins(28, 28, 28, 28); // 内容距面板四边 28px
//lay->insertSpacing(lay->count() - 1, 20); // “确认”按钮上方留 20px
}
dlg.setTitle(title);
dlg.setText(text);
dlg.exec();
}
void ConfirmDialog::toast(QWidget* parent, const QString& text, int ms)
{
QDialog* d = new QDialog(parent);
+6
View File
@@ -4,6 +4,7 @@
#include <QDialog>
class QLabel;
class QPushButton;
// ===================== 确认弹窗 =====================
// 标题 + 正文 + 确认/取消;另提供 2s 自动消失的轻提示(toast)
@@ -19,12 +20,17 @@ public:
// 模态确认,返回是否“确认”
static bool confirm(QWidget* parent, const QString& title, const QString& text);
// 独立信息弹窗(仅“确认”按钮,替代系统 QMessageBox;正文 50px)
static void message(QWidget* parent, const QString& title, const QString& text);
// 轻提示:显示 text,ms 毫秒后自动消失(用于“修改成功”等)
static void toast(QWidget* parent, const QString& text, int ms = 2000);
private:
QLabel* m_title = nullptr;
QLabel* m_body = nullptr;
QWidget* m_panel = nullptr;
QPushButton* m_cancel = nullptr;
};
#endif // CONFIRMDIALOG_H
+25 -8
View File
@@ -11,14 +11,14 @@ DataCard::DataCard(const QString& title, QWidget* parent)
setProperty("card", true);
QVBoxLayout* lay = new QVBoxLayout(this);
lay->setContentsMargins(16, 0, 16, 14);
lay->setSpacing(8);
lay->setContentsMargins(24, 0, 24, 20);
lay->setSpacing(16);
// 标题栏(高 44px)
// 标题栏
m_title = new QLabel(title, this);
m_title->setFixedHeight(44);
m_title->setFixedHeight(60);
m_title->setProperty("cardTitle", true);
m_title->setStyleSheet(QString("color:%1;font-size:24px;font-weight:700;border-bottom:1px solid %2;")
m_title->setStyleSheet(QString("color:%1;font-size:42px;font-weight:700;border-bottom:1px solid %2;")
.arg(C::TEXT, C::BORDER));
lay->addWidget(m_title);
@@ -36,15 +36,15 @@ int DataCard::addField(const QString& label, const QString& value, bool big)
int index = m_values.size();
QHBoxLayout* row = new QHBoxLayout();
row->setSpacing(8);
row->setSpacing(16);
QLabel* lab = new QLabel(label + ":", this);
lab->setProperty("sub", true);
lab->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
lab->setStyleSheet(QString("color:%1;font-size:32px;").arg(C::TEXT_SUB));
QLabel* val = new QLabel(value, this);
val->setStyleSheet(QString("color:%1;%2").arg(C::TEXT,
big ? "font-size:64px;font-weight:700;" : "font-size:30px;font-weight:700;"));
big ? "font-size:50px;font-weight:700;" : "font-size:42px;font-weight:700;"));
row->addWidget(lab);
row->addWidget(val, 1);
@@ -89,3 +89,20 @@ void DataCard::setHighlight(bool on)
else
setStyleSheet(QString());
}
// 字段行文字从左缘缩进到「中间偏左」:行首弹性 2 : 数值行尾弹性 3
void DataCard::indentRows()
{
QVBoxLayout* lay = qobject_cast<QVBoxLayout*>(layout());
if (!lay)
return;
for (int i = 0; i < lay->count(); ++i) {
QHBoxLayout* row = qobject_cast<QHBoxLayout*>(lay->itemAt(i)->layout());
if (!row || row->count() != 2) // 字段行是 [标签][数值];标题与末尾 stretch 不是子布局
continue;
if (row->itemAt(0)->spacerItem()) // 已缩进过,避免重复插入
continue;
row->insertStretch(0, 2);
row->setStretch(2, 3); // 插入后 [0]=行首弹性 [1]=标签 [2]=数值
}
}
+1
View File
@@ -19,6 +19,7 @@ public:
void setField(int index, const QString& value);
void setAlarm(int index, bool on);
void setHighlight(bool on); // 主色描边突出
void indentRows(); // 字段行整体缩进到「中间偏左」(行首 : 行尾 = 2 : 3),只需调用一次
private:
QLabel* m_title = nullptr;
+116 -7
View File
@@ -5,6 +5,7 @@
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QKeyEvent>
#include "global.h"
FaultTable::FaultTable(QWidget* parent)
@@ -14,23 +15,26 @@ FaultTable::FaultTable(QWidget* parent)
lay->setContentsMargins(0, 0, 0, 0);
lay->setSpacing(10);
// 分页行(上一页 / 页码 / 下一页)
// 分页行(上一页 / 页码 / 下一页)——水平居中,字号与按钮加大
QHBoxLayout* nav = new QHBoxLayout();
nav->setSpacing(50);
nav->addStretch(1);
m_prev = new QPushButton("上一页", this);
m_prev->setFixedSize(160, 64);
m_prev->setFixedSize(240, 100);
m_prev->setStyleSheet("font-size:50px;");
connect(m_prev, &QPushButton::clicked, this, [this]() {
if (m_page > 0) { setPage(m_page - 1); emit pageChanged(m_page); }
});
m_pageLabel = new QLabel("1/1", this);
m_pageLabel->setAlignment(Qt::AlignCenter);
m_pageLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT));
m_pageLabel->setFixedWidth(140);
m_pageLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT));
m_pageLabel->setFixedWidth(180);
m_next = new QPushButton("下一页", this);
m_next->setFixedSize(160, 64);
m_next->setFixedSize(240, 100);
m_next->setStyleSheet("font-size:50px;");
connect(m_next, &QPushButton::clicked, this, [this]() {
if (m_page < pageCount() - 1) { setPage(m_page + 1); emit pageChanged(m_page); }
});
@@ -38,6 +42,7 @@ FaultTable::FaultTable(QWidget* parent)
nav->addWidget(m_prev);
nav->addWidget(m_pageLabel);
nav->addWidget(m_next);
nav->addStretch(1); // 尾部也留弹性 → 整行居中
lay->addLayout(nav);
// 表格
@@ -47,24 +52,127 @@ FaultTable::FaultTable(QWidget* parent)
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
m_table->setTabKeyNavigation(false); // 关掉视图自带的 Tab 导航,改由下方 eventFilter 按行切换
m_table->verticalHeader()->setVisible(false);
m_table->horizontalHeader()->setFocusPolicy(Qt::NoFocus); // 表头不参与 Tab
m_table->verticalHeader()->setFocusPolicy(Qt::NoFocus);
m_table->setAlternatingRowColors(true);
m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
m_table->setColumnWidth(0, 120);
m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
m_table->verticalHeader()->setDefaultSectionSize(100);
m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
m_table->horizontalHeader()->setFixedHeight(kHeaderHeight); // 固定表头高度(供对齐)
// 每页 6 行等分,平均填满表格可用高度
m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) {
int global = m_page * m_pageSize + row;
if (global >= 0 && global < m_data.size())
emit rowClicked(global);
});
m_table->installEventFilter(this); // 拦截 Tab,实现按行切换
m_table->viewport()->setMouseTracking(true); // 悬停整行高亮
m_table->viewport()->installEventFilter(this); // 鼠标移出视口 → 取消高亮
connect(m_table, &QAbstractItemView::entered, this,
[this](const QModelIndex& idx) { setHoverRow(idx.row()); });
lay->addWidget(m_table, 1);
refresh();
}
QWidget* FaultTable::view() const { return m_table; }
QWidget* FaultTable::prevButton() const { return m_prev; }
QWidget* FaultTable::nextButton() const { return m_next; }
bool FaultTable::eventFilter(QObject* obj, QEvent* ev)
{
if (obj == m_table->viewport() && ev->type() == QEvent::Leave) {
setHoverRow(-1); // 鼠标移出表格 → 取消悬停高亮
return QWidget::eventFilter(obj, ev);
}
if (obj != m_table)
return QWidget::eventFilter(obj, ev);
// 焦点进入表格时选中一行:正向(Tab)进末行→回首行、否则选首行;反向(Shift+Tab)同理选末行
if (ev->type() == QEvent::FocusIn) {
QFocusEvent* fe = static_cast<QFocusEvent*>(ev);
const int last = m_table->rowCount() - 1;
const int col = qMax(0, m_table->currentColumn());
int row = -1;
if (fe->reason() == Qt::TabFocusReason) {
if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() >= last))
row = 0;
} else if (fe->reason() == Qt::BacktabFocusReason) {
if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() <= 0))
row = last;
}
if (row >= 0) {
m_table->setCurrentCell(row, col);
m_table->selectRow(row); // 显式整行选中(只设当前单元格时可能只高亮一格)
}
}
// 仅键盘(Tab/Shift+Tab)移走焦点时清除选中;鼠标点击/弹窗抢焦点时保留,便于看清当前行
if (ev->type() == QEvent::FocusOut) {
QFocusEvent* fe = static_cast<QFocusEvent*>(ev);
if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason)
m_table->clearSelection();
return QWidget::eventFilter(obj, ev);
}
// Tab / Shift+Tab:按行切换选中(每一行作为一个单位);回车/空格:等同鼠标点击
if (ev->type() == QEvent::KeyPress) {
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
// 方向键在本机是"旋钮"键位(只给 ParamControl 调值),表格里不参与导航:
// 换行只走 Tab/Shift+Tab,激活只走回车/空格
if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down
|| ke->key() == Qt::Key_Left || ke->key() == Qt::Key_Right)
return true;
if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
const bool back = (ke->key() == Qt::Key_Backtab) || (ke->modifiers() & Qt::ShiftModifier);
const int next = m_table->currentRow() + (back ? -1 : 1);
if (next >= 0 && next < m_table->rowCount()) {
m_table->setCurrentCell(next, qMax(0, m_table->currentColumn()));
m_table->selectRow(next); // 整行选中
return true; // 消费事件,焦点留在表格内
}
// 已在首/末行:不拦截,焦点按 Tab 顺序移到下一个控件
} else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter
|| ke->key() == Qt::Key_Space) {
// 键盘激活:与鼠标 cellClicked 走同一个信号,弹窗逻辑只有一份
const int row = m_table->currentRow();
const int global = m_page * m_pageSize + row;
if (row >= 0 && global >= 0 && global < m_data.size()) {
emit rowClicked(global);
return true;
}
}
}
return QWidget::eventFilter(obj, ev);
}
// 鼠标悬停的整行高亮:颜色与“选中”一致(主色蓝底白字)
void FaultTable::setHoverRow(int row)
{
if (row == m_hoverRow)
return;
if (m_hoverRow >= 0) {
for (int c = 0; c < m_table->columnCount(); ++c)
if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) {
it->setData(Qt::BackgroundRole, QVariant());
it->setData(Qt::ForegroundRole, QVariant());
}
}
m_hoverRow = row;
if (m_hoverRow >= 0) {
for (int c = 0; c < m_table->columnCount(); ++c)
if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) {
it->setData(Qt::BackgroundRole, QColor(C::PRIMARY));
it->setData(Qt::ForegroundRole, QColor("#FFFFFF"));
}
}
}
void FaultTable::setData(const QVector<FaultRecord>& data)
{
m_data = data;
@@ -87,6 +195,7 @@ int FaultTable::pageCount() const
void FaultTable::refresh()
{
m_hoverRow = -1; // 单元格会重建,悬停行失效
m_table->setRowCount(0);
m_table->setRowCount(m_pageSize);
+13
View File
@@ -17,17 +17,29 @@ class FaultTable : public QWidget
public:
explicit FaultTable(QWidget* parent = nullptr);
// 表头高度:同页其他控件可据此与表格内容区对齐
static const int kHeaderHeight = 70;
void setData(const QVector<FaultRecord>& data);
void setPage(int p);
int page() const { return m_page; }
int pageCount() const;
// 供外部设置 Tab 顺序用(定义在 .cpp,避免用到未完整声明的类型)
QWidget* view() const;
QWidget* prevButton() const;
QWidget* nextButton() const;
signals:
void pageChanged(int page);
void rowClicked(int globalIndex);
protected:
bool eventFilter(QObject* obj, QEvent* ev) override; // 拦截 Tab(按行切换选中)、回车/空格(等同点击)
private:
void refresh();
void setHoverRow(int row); // 鼠标悬停的整行高亮
QTableWidget* m_table = nullptr;
QLabel* m_pageLabel = nullptr;
@@ -36,6 +48,7 @@ private:
QVector<FaultRecord> m_data;
int m_page = 0;
int m_pageSize = 6;
int m_hoverRow = -1;
};
#endif // FAULTTABLE_H
+15 -8
View File
@@ -9,9 +9,11 @@ QSize LedIndicator::sizeHint() const
QFont f = font();
f.setPixelSize(m_textPx);
const QFontMetrics fm(f);
const int textW = fm.horizontalAdvance(m_text);
if (m_dotAfter)
return QSize(textW + 10 + m_radius * 2 + 10, 52); // 文字 + 间距 + 圆点区 + 右侧 10px
// 宽度 = 文字起点(圆点区 + 左侧 10px) + 文字宽度 + 右侧 10px
const int w = textLeft() + fm.horizontalAdvance(m_text) + 10;
return QSize(w, 52); // 高度与构造函数 setFixedHeight(52) 一致
return QSize(textLeft() + textW + 10, 52); // 高度与构造函数 setFixedHeight(52) 一致
}
LedIndicator::LedIndicator(const QString& text, QWidget* parent)
@@ -55,6 +57,13 @@ void LedIndicator::paintEvent(QPaintEvent*)
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// 文字度量:圆点位置取决于文字宽度(圆点在文字后时)
QFont f = p.font();
f.setPixelSize(m_textPx);
p.setFont(f);
const QFontMetrics fm(f);
const int textW = fm.horizontalAdvance(m_text);
// 中心圆点颜色
QColor color = QColor(C::DISABLED); // 灰
switch (m_state) {
@@ -70,7 +79,7 @@ void LedIndicator::paintEvent(QPaintEvent*)
if ((m_state == RedBlink || m_state == YellowBlink) && !m_on)
color.setAlpha(102);
const QPointF c(m_radius + 8, height() / 2.0);
const QPointF c(m_dotAfter ? textW + 10 + m_radius : m_radius + 8, height() / 2.0);
// 外发光
if (color.alpha() > 0) {
@@ -93,11 +102,9 @@ void LedIndicator::paintEvent(QPaintEvent*)
p.setPen(QPen(QColor(C::BORDER), 1));
p.drawEllipse(c, m_radius, m_radius);
// 文字
// 文字(圆点在文字后时,文字从控件左边缘开始)
p.setPen(QColor(C::TEXT));
QFont f = p.font();
f.setPixelSize(m_textPx);
p.setFont(f);
const QRectF textRect(textLeft(), 0, width() - textLeft() - 10, height());
const QRectF textRect(m_dotAfter ? 0 : textLeft(), 0,
m_dotAfter ? textW : width() - textLeft() - 10, height());
p.drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, m_text);
}
+2
View File
@@ -19,6 +19,7 @@ public:
void setText(const QString& t);
void setTextPixelSize(int px) { m_textPx = px; updateGeometry(); update(); } // 文字字号
void setRadius(int r) { m_radius = r; updateGeometry(); update(); } // 圆形指示点半径
void setDotAfterText(bool after) { m_dotAfter = after; updateGeometry(); update(); } // 圆点画在文字后面
State state() const { return m_state; }
QSize sizeHint() const override; // 宽度按文字实际宽度计算,避免文字被裁
@@ -35,6 +36,7 @@ private:
bool m_on = true;
int m_radius = 16; // 圆形指示点 32×32
int m_textPx = 24; // 文字字号
bool m_dotAfter = false; // false=圆点在文字前,true=圆点在文字后
};
#endif // LEDINDICATOR_H
+142 -61
View File
@@ -18,101 +18,81 @@ NumPadDialog::NumPadDialog(QWidget* parent)
if (parent) setGeometry(parent->rect());
setStyleSheet("background-color: rgba(0,0,0,102);");
// 面板(居中)
QWidget* panel = new QWidget(this);
panel->setObjectName("NumPadPanel");
panel->setFixedSize(600, 730);
panel->setStyleSheet(QString(
// 面板(默认居中,可用 setAnchorWidget 定位到某个控件旁边)
m_panel = new QWidget(this);
m_panel->setObjectName("NumPadPanel");
m_panel->setStyleSheet(QString(
"QWidget#NumPadPanel{background-color:%1;border:4px solid %2;border-radius:16px;}"
"QPushButton[numpad=\"true\"]{background-color:#2D3947;color:#E6EDF3;border:1px solid #3D4A59;border-radius:12px;font-size:40px;}"
"QPushButton[numpad=\"true\"]:pressed{background-color:#2E7BE6;color:#FFFFFF;}"
"QPushButton[numpad=\"true\"]:focus{background-color:#3D8BFD;color:#FFFFFF;border:2px solid #8ABFFF;}")
.arg(C::PANEL, C::BORDER));
QVBoxLayout* panelLay = new QVBoxLayout(panel);
panelLay->setContentsMargins(24, 40, 24, 50);
panelLay->setSpacing(50);
m_panelLay = new QVBoxLayout(m_panel);
// 输入回显
m_echo = new QLineEdit(panel);
m_echo = new QLineEdit(m_panel);
m_echo->setReadOnly(true);
m_echo->setEchoMode(QLineEdit::Password);
m_echo->setAlignment(Qt::AlignCenter);
m_echo->setFixedSize(510, 100);
m_echo->setStyleSheet("font-size:70px;");
m_echo->setFocusPolicy(Qt::NoFocus); // 纯显示回显:不参与 Tab 选中
m_echo->setFocusPolicy(Qt::NoFocus); // 纯显示回显:不参与 Tab 选中
m_echo->setAttribute(Qt::WA_TransparentForMouseEvents); // 且点击穿透,不响应鼠标
panelLay->addWidget(m_echo, 0, Qt::AlignHCenter);
m_panelLay->addWidget(m_echo, 0, Qt::AlignHCenter);
// 数字键区(1-9, 0, 退格, 清除)
QGridLayout* grid = new QGridLayout();
grid->setSpacing(30);
// 数字键区(1-9, 0, 退格, 清除/小数点)
m_grid = new QGridLayout();
const QString digits[3][3] = {{"1","2","3"},{"4","5","6"},{"7","8","9"}};
for (int r = 0; r < 3; ++r)
for (int c = 0; c < 3; ++c) {
QPushButton* b = new QPushButton(digits[r][c], panel);
QPushButton* b = new QPushButton(digits[r][c], m_panel);
b->setProperty("numpad", true);
b->setStyleSheet("font-size:50px;");
b->setFixedSize(150, 120);
connect(b, &QPushButton::clicked, this, [this, d = digits[r][c]](){ onDigit(d); });
grid->addWidget(b, r, c);
m_grid->addWidget(b, r, c);
m_digits << b;
}
QPushButton* b0 = new QPushButton("0", panel);
QPushButton* b0 = new QPushButton("0", m_panel);
b0->setProperty("numpad", true);
b0->setStyleSheet("font-size:50px;");
b0->setFixedSize(150, 120);
connect(b0, &QPushButton::clicked, this, [this](){ onDigit("0"); });
grid->addWidget(b0, 3, 1);
m_grid->addWidget(b0, 3, 1);
m_digits << b0;
QPushButton* bBack = new QPushButton("退格", panel);
bBack->setProperty("numpad", true);
bBack->setStyleSheet("font-size:40px;");
bBack->setFixedSize(150, 120);
connect(bBack, &QPushButton::clicked, this, &NumPadDialog::onBackspace);
grid->addWidget(bBack, 3, 0);
m_backKey = new QPushButton("退格", m_panel);
m_backKey->setProperty("numpad", true);
connect(m_backKey, &QPushButton::clicked, this, &NumPadDialog::onBackspace);
m_grid->addWidget(m_backKey, 3, 0);
QPushButton* bClear = new QPushButton("清除", panel);
bClear->setProperty("numpad", true);
bClear->setStyleSheet("font-size:40px;");
bClear->setFixedSize(150, 108);
connect(bClear, &QPushButton::clicked, this, &NumPadDialog::onClear);
grid->addWidget(bClear, 3, 2);
m_clearKey = new QPushButton("清除", m_panel);
m_clearKey->setProperty("numpad", true);
connect(m_clearKey, &QPushButton::clicked, this, [this]() {
if (m_paramMode) onDot();
else onClear();
});
m_grid->addWidget(m_clearKey, 3, 2);
// 确认 / 取消(与数字键一致的灰底,选中时发亮)
QPushButton* bOk = new QPushButton("确认", panel);
bOk->setProperty("numpad", true);
bOk->setStyleSheet("font-size:60px;");
bOk->setFixedSize(160, 140);
connect(bOk, &QPushButton::clicked, this, &NumPadDialog::accept);
// 确认 / 取消
m_okKey = new QPushButton("确认", m_panel);
m_okKey->setProperty("numpad", true);
connect(m_okKey, &QPushButton::clicked, this, &NumPadDialog::accept);
QPushButton* bCancel = new QPushButton("取消", panel);
bCancel->setProperty("numpad", true);
bCancel->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
bCancel->setFixedSize(160, 140);
connect(bCancel, &QPushButton::clicked, this, &NumPadDialog::reject);
m_cancelKey = new QPushButton("取消", m_panel);
m_cancelKey->setProperty("numpad", true);
connect(m_cancelKey, &QPushButton::clicked, this, &NumPadDialog::reject);
// 确认 / 取消:单独一行、水平居中,并与数字键盘留出间隔
QHBoxLayout* okRow = new QHBoxLayout();
okRow->setSpacing(80);
okRow->addStretch(1);
okRow->addWidget(bOk);
okRow->addWidget(bCancel);
okRow->addWidget(m_okKey);
okRow->addWidget(m_cancelKey);
okRow->addStretch(1);
panelLay->addLayout(grid);
panelLay->setAlignment(grid, Qt::AlignHCenter);
//panelLay->addSpacing(20);
panelLay->addLayout(okRow);
panelLay->addStretch(1);
// 居中放置面板
QVBoxLayout* root = new QVBoxLayout(this);
root->setContentsMargins(0, 0, 0, 0);
root->addStretch(1);
root->addWidget(panel, 0, Qt::AlignHCenter);
root->addStretch(1);
m_panelLay->addLayout(m_grid);
m_panelLay->setAlignment(m_grid, Qt::AlignHCenter);
m_panelLay->addLayout(okRow);
m_panelLay->addStretch(1);
applySizes(); // 应用尺寸(默认大字版)并摆放面板
refreshEcho();
}
@@ -128,6 +108,107 @@ void NumPadDialog::clear()
refreshEcho();
}
// 整体不透明度(0.0~1.0)
void NumPadDialog::setOpacity(qreal opacity)
{
setWindowOpacity(opacity);
}
// 面板贴在参照控件左侧 gap 像素处弹出(垂直对齐其中心,并钳位到屏幕内)
void NumPadDialog::setAnchorWidget(QWidget* ref, int gap)
{
m_anchor = ref;
m_gap = gap;
placePanel();
}
// 参数输入模式:回显显示实际数字(不掩码),“清除”键换成“小数点”
void NumPadDialog::setParamMode(bool on)
{
m_paramMode = on;
m_echo->setEchoMode(on ? QLineEdit::Normal : QLineEdit::Password);
m_clearKey->setText(on ? "." : "清除");
applySizes(); // 重设“小数点/清除”键的字号
}
// 紧凑尺寸(按键与面板更小),默认 false = 大字版
void NumPadDialog::setCompact(bool on)
{
m_compact = on;
applySizes();
}
// 按当前尺寸模式设置面板/按键的大小与字号,并重新摆放面板
void NumPadDialog::applySizes()
{
const int digitFont = m_compact ? 40 : 50;
const int fnFont = m_compact ? 30 : 40;
const int dotFont = m_compact ? 46 : 60;
const int okFont = m_compact ? 44 : 60;
const QSize digitSz = m_compact ? QSize(120, 70) : QSize(150, 120);
const QSize clearSz = m_compact ? QSize(120, 70) : QSize(150, 108);
const QSize okSz = m_compact ? QSize(130, 88) : QSize(160, 140);
m_panel->setFixedSize(m_compact ? QSize(460, 600) : QSize(600, 730));
m_panelLay->setContentsMargins(m_compact ? 20 : 24, m_compact ? 24 : 40,
m_compact ? 20 : 24, m_compact ? 26 : 50);
m_panelLay->setSpacing(m_compact ? 26 : 50);
m_grid->setSpacing(m_compact ? 18 : 30);
m_echo->setFixedSize(m_compact ? QSize(400, 70) : QSize(510, 100));
m_echo->setStyleSheet(QString("font-size:%1px;").arg(m_compact ? 48 : 70));
for (QPushButton* b : m_digits) {
b->setFixedSize(digitSz);
b->setStyleSheet(QString("font-size:%1px;").arg(digitFont));
}
m_backKey->setFixedSize(digitSz);
m_backKey->setStyleSheet(QString("font-size:%1px;").arg(fnFont));
m_clearKey->setFixedSize(clearSz);
m_clearKey->setStyleSheet(QString("font-size:%1px;").arg(m_paramMode ? dotFont : fnFont));
m_okKey->setFixedSize(okSz);
m_okKey->setStyleSheet(QString("font-size:%1px;").arg(okFont));
m_cancelKey->setFixedSize(okSz);
m_cancelKey->setStyleSheet(cancelButtonStyle(okFont)); // 选中时红底白字
placePanel();
}
// 计算面板位置:有参照控件 → 其左侧;否则居中
void NumPadDialog::placePanel()
{
const int w = m_panel->width();
const int h = m_panel->height();
int x = (width() - w) / 2;
int y = (height() - h) / 2;
if (m_anchor && m_anchor->window()) {
// 面板放在参照控件左侧;对话框覆盖整个窗口,其局部坐标即窗口坐标
const QPoint p = m_anchor->mapTo(m_anchor->window(), QPoint(0, 0));
x = p.x() - m_gap - w;
y = p.y() + m_anchor->height() / 2 - h / 2;
}
x = qBound(0, x, qMax(0, width() - w));
y = qBound(0, y, qMax(0, height() - h));
m_panel->move(x, y);
}
// 小数点:仅允许一个,空值时补前导 0
void NumPadDialog::onDot()
{
if (m_value.contains('.'))
return;
if (m_value.isEmpty())
m_value = "0";
if (m_value.size() >= m_maxLen)
return;
m_value += '.';
refreshEcho();
}
void NumPadDialog::onDigit(const QString& d)
{
if (m_value.size() >= m_maxLen)
+29
View File
@@ -2,11 +2,16 @@
#define NUMPADDIALOG_H
#include <QDialog>
#include <QVector>
class QLineEdit;
class QPushButton;
class QGridLayout;
class QVBoxLayout;
// ===================== 数字键盘弹窗(模态) =====================
// 半透明遮罩 + 键盘面板:顶部输入回显,数字键 1-9、0,退格、清除,确认/取消
// 两种尺寸:默认大字版(密码页用)、紧凑版(参数输入用,setCompact(true))
class NumPadDialog : public QDialog
{
Q_OBJECT
@@ -18,17 +23,41 @@ public:
QString value() const { return m_value; }
void clear();
void setOpacity(qreal opacity); // 整体不透明度(默认 1.0)
void setAnchorWidget(QWidget* ref, int gap = 20); // 面板贴参照控件左侧弹出
// 参数输入模式:回显显示实际数字(不掩码),并把“清除”键换成“小数点”
void setParamMode(bool on);
// 紧凑尺寸(按键/面板更小);默认 false = 大字版
void setCompact(bool on);
private slots:
void onDigit(const QString& d);
void onBackspace();
void onClear();
void onDot();
private:
void refreshEcho();
void placePanel();
void applySizes();
QWidget* m_panel = nullptr;
QWidget* m_anchor = nullptr; // 定位参照控件(如参数输入框)
QGridLayout* m_grid = nullptr;
QVBoxLayout* m_panelLay = nullptr;
QLineEdit* m_echo = nullptr;
QVector<QPushButton*> m_digits; // 数字键 0-9
QPushButton* m_backKey = nullptr;
QPushButton* m_clearKey = nullptr;
QPushButton* m_okKey = nullptr;
QPushButton* m_cancelKey = nullptr;
QString m_value;
int m_maxLen = 4;
int m_gap = 20;
bool m_paramMode = false;
bool m_compact = false;
};
#endif // NUMPADDIALOG_H
+113 -58
View File
@@ -1,8 +1,11 @@
#include "paramcontrol.h"
#include "numpaddialog.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QKeyEvent>
#include "global.h"
ParamControl::ParamControl(const QString& name, const QString& unit,
@@ -16,56 +19,50 @@ ParamControl::ParamControl(const QString& name, const QString& unit,
lay->setSpacing(6);
// 参数名
QLabel* nameLabel = new QLabel(name, this);
nameLabel->setProperty("sub", true);
nameLabel->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
lay->addWidget(nameLabel);
m_nameLabel = new QLabel(name, this);
m_nameLabel->setProperty("sub", true);
m_nameLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB));
lay->addWidget(m_nameLabel);
// 数值 + 加减
QHBoxLayout* row = new QHBoxLayout();
row->setSpacing(12);
// 数值输入框 + 单位(同一行;框内只显示数值)
m_valRow = new QHBoxLayout();
m_valRow->setSpacing(12);
QPushButton* minus = new QPushButton("-", this);
minus->setFixedSize(96, 96);
minus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;")
.arg(C::PANEL_HI, C::TEXT, C::BORDER));
connect(minus, &QPushButton::clicked, this, &ParamControl::onMinus);
m_valueEdit = new QLineEdit(this);
m_valueEdit->setReadOnly(true);
m_valueEdit->setAlignment(Qt::AlignCenter);
m_valueEdit->setFocusPolicy(Qt::StrongFocus);
m_valueEdit->setFixedWidth(150); // 输入框长度(按需调整)
m_valueEdit->setStyleSheet("font-size:40px;font-weight:700;");
m_valueEdit->installEventFilter(this);
m_valRow->addStretch(0); // 行首弹性空间:setCentered(true) 时置 1,数值行即居中
m_valRow->addWidget(m_valueEdit);
m_valueLabel = new QLabel(this);
m_valueLabel->setAlignment(Qt::AlignCenter);
m_valueLabel->setStyleSheet(QString("color:%1;font-size:52px;font-weight:700;").arg(C::TEXT));
m_valueLabel->setMinimumWidth(180);
m_unitLabel = new QLabel(unit, this);
m_unitLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB));
// 同一行右侧还有按钮时,给单位固定占位,保证各参数的按钮 x 一致
// (110 取全项目最长单位“L/min”在 40px 字号下的宽度约 101px)
if (withSave || withFactory)
m_unitLabel->setFixedWidth(110);
m_valRow->addWidget(m_unitLabel);
QPushButton* plus = new QPushButton("+", this);
plus->setFixedSize(96, 96);
plus->setStyleSheet(QString("font-size:44px;background-color:%1;color:%2;border:1px solid %3;border-radius:12px;")
.arg(C::PANEL_HI, C::TEXT, C::BORDER));
connect(plus, &QPushButton::clicked, this, &ParamControl::onPlus);
row->addWidget(minus);
row->addWidget(m_valueLabel, 1);
row->addWidget(plus);
lay->addLayout(row);
// 保存 / 恢复出厂值(可选)
if (withSave || withFactory) {
QHBoxLayout* btnRow = new QHBoxLayout();
btnRow->setSpacing(12);
if (withSave) {
m_save = new QPushButton("保存", this);
m_save->setProperty("variant", "primary");
m_save->setMinimumHeight(56);
connect(m_save, &QPushButton::clicked, this, &ParamControl::saveClicked);
btnRow->addWidget(m_save);
}
if (withFactory) {
m_factory = new QPushButton("恢复出厂值", this);
m_factory->setMinimumHeight(56);
connect(m_factory, &QPushButton::clicked, this, &ParamControl::factoryResetClicked);
btnRow->addWidget(m_factory);
}
lay->addLayout(btnRow);
// 保存 / 恢复出厂值(可选):跟在单位后面,与输入框同一行、整体左对齐
if (withSave) {
m_save = new QPushButton("保存", this);
m_save->setProperty("variant", "primary");
m_save->setFixedSize(140, 60); // 固定宽高,不再随布局拉伸
connect(m_save, &QPushButton::clicked, this, &ParamControl::saveClicked);
m_valRow->addWidget(m_save);
}
if (withFactory) {
m_factory = new QPushButton("恢复出厂值", this);
m_factory->setMinimumHeight(56);
connect(m_factory, &QPushButton::clicked, this, &ParamControl::factoryResetClicked);
m_valRow->addWidget(m_factory);
}
m_valRow->addStretch(1);
lay->addLayout(m_valRow);
refresh();
}
@@ -76,32 +73,90 @@ void ParamControl::setValue(double v)
refresh();
}
QWidget* ParamControl::editor() const
{
return m_valueEdit;
}
void ParamControl::setEnabled(bool enabled)
{
QWidget::setEnabled(enabled);
}
void ParamControl::onMinus()
void ParamControl::setCentered(bool on)
{
m_value = qMax(m_min, m_value - m_step);
refresh();
emit valueChanged(m_value);
m_nameLabel->setAlignment(on ? Qt::AlignCenter : (Qt::AlignLeft | Qt::AlignVCenter));
m_valRow->setStretch(0, on ? 1 : 0); // 行首弹性空间与行尾均分 → 整行居中(含保存按钮)
}
void ParamControl::onPlus()
// 旋钮接口:steps > 0 调大、< 0 调小(每格 = m_step,自动钳位到 [min, max])
// 目前由数值框上的 ↑/↓ 键驱动,后续接面板机械旋钮也走这里
void ParamControl::adjustByKnob(int steps)
{
m_value = qMin(m_max, m_value + m_step);
refresh();
emit valueChanged(m_value);
if (steps == 0)
return;
const double old = m_value;
setValue(old + steps * m_step);
if (m_value != old)
emit valueChanged(m_value);
}
// 点击数值框 / 回车 → 弹出数字键盘(面板靠左、不透明度 80%);↑/↓ → 每档加减一次
bool ParamControl::eventFilter(QObject* obj, QEvent* ev)
{
if (obj == m_valueEdit) {
if (ev->type() == QEvent::MouseButtonPress) {
openNumPad();
return true;
}
if (ev->type() == QEvent::KeyPress) {
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
openNumPad();
return true;
}
// 旋钮档:↑ 加一档、↓ 减一档(每档 = m_step)。Tab/Shift+Tab 走焦点链、
// 空格激活按钮,各不重叠;按住方向键由按键自动重复继续调
if (ke->key() == Qt::Key_Up) {
adjustByKnob(1);
return true;
}
if (ke->key() == Qt::Key_Down) {
adjustByKnob(-1);
return true;
}
}
}
return QWidget::eventFilter(obj, ev);
}
void ParamControl::openNumPad()
{
NumPadDialog dlg(window()); // 以顶层窗口为父:遮罩覆盖整屏
dlg.setParamMode(true); // 参数模式:回显显示实际数字,“清除”换成“小数点”
dlg.setCompact(true); // 紧凑尺寸(不影响密码页的大键盘)
dlg.setAnchorWidget(m_valueEdit, 20); // 面板贴在输入框左侧 20px 处弹出
dlg.setMaxLen(6);
dlg.setValue(m_step < 1.0 ? QString::number(m_value, 'f', 1)
: QString::number(m_value, 'f', 0));
if (dlg.exec() != QDialog::Accepted)
return;
bool ok = false;
const double v = dlg.value().toDouble(&ok);
if (!ok)
return;
const double old = m_value;
setValue(v);
if (m_value != old)
emit valueChanged(m_value);
}
void ParamControl::refresh()
{
// 单位格式:整数不显示小数
QString s;
// 框内只显示数值(单位在框后面的标签里)
if (m_step < 1.0)
s = QString("%1").arg(m_value, 0, 'f', 1);
m_valueEdit->setText(QString::number(m_value, 'f', 1));
else
s = QString("%1").arg(m_value, 0, 'f', 0);
m_valueLabel->setText(s + " " + m_unit);
m_valueEdit->setText(QString::number(m_value, 'f', 0));
}
+17 -5
View File
@@ -4,10 +4,13 @@
#include <QWidget>
class QLabel;
class QLineEdit;
class QPushButton;
class QHBoxLayout;
// ===================== 参数调节控件 =====================
// 参数名 + 大数值显示 + 减/加按钮 + 保存按钮(可选)+ 恢复出厂值按钮(可选)
// 参数名 + 数值输入框(点击/回车弹出数字键盘,↑/↓ 按档加减)+ 保存按钮(可选)+ 恢复出厂值按钮(可选)
// 旋钮接口 adjustByKnob():调大/调小,数值框的 ↑/↓ 走的就是这条路径
class ParamControl : public QWidget
{
Q_OBJECT
@@ -19,18 +22,24 @@ public:
void setValue(double v);
double value() const { return m_value; }
void setEnabled(bool enabled);
void setCentered(bool on); // 参数名与数值行水平居中(默认左对齐)
QWidget* editor() const; // 数值输入框(供页面编排 Tab 顺序)
public slots:
// 旋钮接口:steps > 0 右旋调大、steps < 0 左旋调小;每格 = m_step
void adjustByKnob(int steps);
signals:
void valueChanged(double v);
void saveClicked();
void factoryResetClicked();
private slots:
void onMinus();
void onPlus();
protected:
bool eventFilter(QObject* obj, QEvent* ev) override; // 数值框:点击/回车 → 数字键盘,↑/↓ → 按档调值
private:
void refresh();
void openNumPad();
QString m_name;
QString m_unit;
@@ -38,7 +47,10 @@ private:
double m_min;
double m_max;
double m_value;
QLabel* m_valueLabel = nullptr;
QLineEdit* m_valueEdit = nullptr;
QLabel* m_unitLabel = nullptr;
QLabel* m_nameLabel = nullptr;
QHBoxLayout* m_valRow = nullptr;
QPushButton* m_save = nullptr;
QPushButton* m_factory = nullptr;
};
+38 -35
View File
@@ -1,6 +1,6 @@
#include "statusstrip.h"
#include "appcontext.h"
#include "udpcommunication.h"
#include "car.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
@@ -10,54 +10,51 @@ StatusStrip::StatusStrip(AppContext* ctx, QWidget* parent)
: QWidget(parent), m_ctx(ctx)
{
setObjectName("StatusStrip");
setFixedHeight(94);
setFixedHeight(120);
setStyleSheet(QString("QWidget#StatusStrip{background-color:%1;border-top:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
QHBoxLayout* lay = new QHBoxLayout(this);
lay->setContentsMargins(36, 0, 24, 0);
lay->setSpacing(48);
lay->setSpacing(40);
auto makeVal = [this](const QString& label) {
QLabel* l = new QLabel(this);
l->setStyleSheet(QString("color:%1;font-size:22px;").arg(C::TEXT_SUB));
l->setProperty("label", label);
return l;
};
// 底部不再显示底盘/液位数据(改由各页面卡片展示),只保留模式徽标 + 返回按钮
//auto makeVal = [this](const QString& label) {
// QLabel* l = new QLabel(this);
// l->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT_SUB));
// l->setProperty("label", label);
// return l;
//};
//m_battery = makeVal("底盘电量");
//m_voltage = makeVal("底盘电压");
//m_levelA = makeVal("清水液位");
//m_levelFoam = makeVal("泡沫液位");
//lay->addWidget(m_battery);
//lay->addWidget(m_voltage);
//lay->addWidget(m_levelA);
//lay->addWidget(m_levelFoam);
m_battery = makeVal("底盘电量");
m_voltage = makeVal("底盘电压");
m_levelA = makeVal("清水液位");
m_levelFoam = makeVal("泡沫液位");
lay->addWidget(m_battery);
lay->addWidget(m_voltage);
lay->addWidget(m_levelA);
lay->addWidget(m_levelFoam);
lay->addStretch(1);
// 模式徽标(点击可切换行车/机械臂模式,模拟面板机械旋钮)
m_mode = new QPushButton("行车模式", this);
m_mode->setFixedSize(180, 48);
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;border:none;")
// 模式徽标(仅显示:行车/机械臂由遥控器拨钮经串口回传,界面不参与切换)
m_mode = new QLabel("行车模式", this);
m_mode->setFixedSize(260, 80);
m_mode->setAlignment(Qt::AlignCenter);
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;")
.arg(C::SUCCESS));
m_mode->setCursor(Qt::PointingHandCursor);
connect(m_mode, &QPushButton::clicked, this, [this]() {
Mode next = (m_ctx->mode == Mode::Drive) ? Mode::Arm : Mode::Drive;
m_ctx->setMode(next);
});
lay->addWidget(m_mode);
// 返回按钮
m_back = new QPushButton("返回", this);
m_back->setFixedSize(144, 76);
m_back->setFixedSize(200, 80);
m_back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字
connect(m_back, &QPushButton::clicked, this, &StatusStrip::backClicked);
lay->addWidget(m_back);
connect(m_ctx->comm(), &UdpCommunication::telemetryUpdated, this, &StatusStrip::refresh);
connect(m_ctx->comm(), &CarCommunication::telemetryUpdated, this, &StatusStrip::refresh);
connect(m_ctx, &AppContext::modeChanged, this, [this](Mode m) {
m_mode->setText(m == Mode::Drive ? "行车模式" : "机械臂模式");
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:24px;font-weight:700;")
m_mode->setStyleSheet(QString("background-color:%1;color:#FFFFFF;border-radius:8px;font-size:40px;font-weight:700;")
.arg(m == Mode::Drive ? C::SUCCESS : C::WARNING));
});
refresh();
@@ -73,11 +70,17 @@ void StatusStrip::setBackEnabled(bool enabled)
m_back->setEnabled(enabled);
}
void StatusStrip::setModeVisible(bool visible)
{
m_mode->setVisible(visible);
}
void StatusStrip::refresh()
{
const Telemetry& t = m_ctx->telemetry;
m_battery->setText(QString("底盘电量:%1%").arg(t.soc));
m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0));
m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0));
m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0));
// 底部已不显示遥测数据,保留空实现(仍连着 telemetryUpdated,方便以后加显示项)
//const Telemetry& t = m_ctx->telemetry;
//m_battery->setText(QString("底盘电量:%1%").arg(t.soc));
//m_voltage->setText(QString("底盘电压:%1V").arg(t.volt, 0, 'f', 0));
//m_levelA->setText(QString("清水液位:%1L").arg(t.levelA, 0, 'f', 0));
//m_levelFoam->setText(QString("泡沫液位:%1L").arg(t.levelFoam, 0, 'f', 0));
}
+8 -7
View File
@@ -7,8 +7,8 @@ class QLabel;
class QPushButton;
class AppContext;
// ===================== 底部状态条(高 94px) =====================
// 底盘电量、底盘电压、清水液位、泡沫液位 + 模式徽标 + 返回按钮
// ===================== 底部状态条 =====================
// 只保留:模式徽标(仅显示)+ 返回按钮。行车/机械臂由遥控器拨钮决定,界面不参与切换。
class StatusStrip : public QWidget
{
Q_OBJECT
@@ -17,6 +17,7 @@ public:
void setBackVisible(bool visible);
void setBackEnabled(bool enabled);
void setModeVisible(bool visible); // 固定行车模式的页面(如底盘页)可隐藏模式徽标
signals:
void backClicked();
@@ -26,11 +27,11 @@ private slots:
private:
AppContext* m_ctx;
QLabel* m_battery = nullptr;
QLabel* m_voltage = nullptr;
QLabel* m_levelA = nullptr;
QLabel* m_levelFoam = nullptr;
QPushButton* m_mode = nullptr;
//QLabel* m_battery = nullptr; // 底部不再显示数据
//QLabel* m_voltage = nullptr;
//QLabel* m_levelA = nullptr;
//QLabel* m_levelFoam = nullptr;
QLabel* m_mode = nullptr; // 模式徽标(只显示,不参与切换)
QPushButton* m_back = nullptr;
};
+35 -55
View File
@@ -1,14 +1,10 @@
#include "titlebar.h"
#include "ledindicator.h"
#include "appcontext.h"
#include "udpcommunication.h"
#include "confirmdialog.h"
#include "car.h"
#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTime>
#include "global.h"
#include <QStyle>
TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
: QWidget(parent), m_ctx(ctx)
@@ -18,34 +14,38 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
setStyleSheet(QString("QWidget#TitleBar{background-color:%1;border-bottom:1px solid %2;}")
.arg(C::PANEL, C::BORDER));
// 左侧区(通信状态)
// 左侧区(车载端通信状态)
QWidget* leftBox = new QWidget(this);
QHBoxLayout* leftLay = new QHBoxLayout(leftBox);
leftLay->setContentsMargins(0, 0, 0, 0);
leftLay->setContentsMargins(260, 0, 0, 0);
leftLay->setSpacing(20);
m_comm = new LedIndicator("通信正常", leftBox);
m_comm->setFixedHeight(80); // 容纳放大后的文字,避免上下裁切
m_comm->setTextPixelSize(45);
m_comm->setState(LedIndicator::Green);
leftLay->addWidget(m_comm);
leftLay->addStretch(1);
// 右侧区(时间 + 软急停)
// 右侧区(遥控器状态:链路 + 电量)
QWidget* rightBox = new QWidget(this);
QHBoxLayout* rightLay = new QHBoxLayout(rightBox);
rightLay->setContentsMargins(0, 0, 0, 0);
rightLay->setSpacing(20);
m_time = new QLabel("", rightBox);
m_time->setStyleSheet(QString("color:%1;font-size:60px;").arg(C::TEXT_SUB));
m_time->setAlignment(Qt::AlignCenter);
m_estop = new QPushButton("急停", rightBox);
m_estop->setProperty("variant", "estop");
m_estop->setStyleSheet("font-size:60px;");
m_estop->setFixedSize(180, 80);
rightLay->setContentsMargins(0, 0, 20, 0);
rightLay->setSpacing(30);
rightLay->addStretch(1);
rightLay->addWidget(m_time);
rightLay->addWidget(m_estop);
m_rc = new LedIndicator("遥控器断开", rightBox);
m_rc->setFixedHeight(80);
m_rc->setTextPixelSize(45);
m_rc->setState(LedIndicator::RedBlink);
rightLay->addWidget(m_rc);
m_rcBat = new QLabel("--%", rightBox);
m_rcBat->setStyleSheet(QString("color:%1;font-size:60px;").arg(C::TEXT_SUB));
m_rcBat->setAlignment(Qt::AlignCenter);
rightLay->addWidget(m_rcBat);
// 左右两侧等宽 → 中间标题恰好居中于屏幕
const int sideW = 380; // 需 >= 较宽一侧所需宽度(右侧:时间 + 急停)
// 左区需 260(左边距)+ 240(圆点区与留白 60 + 文字 4×45)= 500
// 右区需 285(圆点区与留白 60 + 文字 5×45)+ 30 + 130(电量)+ 20 = 465
const int sideW = 520;
leftBox->setFixedWidth(sideW);
rightBox->setFixedWidth(sideW);
@@ -61,30 +61,16 @@ TitleBar::TitleBar(AppContext* ctx, QWidget* parent)
lay->addWidget(m_title, 1);
lay->addWidget(rightBox);
connect(m_estop, &QPushButton::clicked, this, [this]() {
if (m_ctx->estopTriggered) {
if (ConfirmDialog::confirm(window(), "急停复位", "确定复位急停并恢复系统?"))
m_ctx->resetEstop();
} else {
m_ctx->triggerEstop();
}
});
// 通信状态刷新
connect(m_ctx->comm(), &UdpCommunication::commOkChanged, this, [this](bool ok) {
// 车载端通信状态刷新
connect(m_ctx->comm(), &CarCommunication::commOkChanged, this, [this](bool ok) {
if (ok) { m_comm->setText("通信正常"); m_comm->setState(LedIndicator::Green); }
else { m_comm->setText("通信异常"); m_comm->setState(LedIndicator::RedBlink); }
});
// 急停状态刷新
connect(m_ctx, &AppContext::estopChanged, this, &TitleBar::updateEstop);
updateEstop();
// 时钟
m_clock = new QTimer(this);
connect(m_clock, &QTimer::timeout, this, &TitleBar::updateTime);
m_clock->start(1000);
updateTime();
// 遥控器链路与电量刷新
connect(m_ctx->rc(), &RcCommunication::linkChanged, this, &TitleBar::onRcLink);
connect(m_ctx->rc(), &RcCommunication::frameReceived, this, &TitleBar::onRcFrame);
onRcLink(m_ctx->rc()->linkOk());
}
void TitleBar::setTitle(const QString& title)
@@ -92,25 +78,19 @@ void TitleBar::setTitle(const QString& title)
m_title->setText(title);
}
void TitleBar::setEstopVisible(bool visible)
void TitleBar::onRcLink(bool ok)
{
m_estop->setVisible(visible);
}
void TitleBar::updateEstop()
{
if (m_ctx->estopTriggered) {
m_estop->setText("复位");
m_estop->setProperty("variant", "danger");
if (ok) {
m_rc->setText("遥控器正常");
m_rc->setState(LedIndicator::Green);
} else {
m_estop->setText("急停");
m_estop->setProperty("variant", "estop");
m_rc->setText("遥控器断开");
m_rc->setState(LedIndicator::RedBlink);
m_rcBat->setText("--%");
}
m_estop->style()->unpolish(m_estop);
m_estop->style()->polish(m_estop);
}
void TitleBar::updateTime()
void TitleBar::onRcFrame(const RcFrame& f)
{
m_time->setText(QTime::currentTime().toString("HH:mm"));
m_rcBat->setText(QString("%1%").arg(f.battery));
}
+7 -10
View File
@@ -2,15 +2,14 @@
#define TITLEBAR_H
#include <QWidget>
#include <QTimer>
#include "uart.h"
class QLabel;
class QPushButton;
class AppContext;
class LedIndicator;
// ===================== 顶条组件(高 98px) =====================
// 左侧“通信状态:●正常/异常”,中央页面标题,右侧时间 + 软急停按钮
// 左侧“车载端通信:●正常/异常”,中央页面标题,右侧“遥控器:●正常/断开 + 电量”
class TitleBar : public QWidget
{
Q_OBJECT
@@ -18,19 +17,17 @@ public:
explicit TitleBar(AppContext* ctx, QWidget* parent = nullptr);
void setTitle(const QString& title);
void setEstopVisible(bool visible);
private slots:
void updateEstop();
void updateTime();
void onRcLink(bool ok);
void onRcFrame(const RcFrame& f);
private:
AppContext* m_ctx;
LedIndicator* m_comm;
LedIndicator* m_comm = nullptr;
LedIndicator* m_rc = nullptr;
QLabel* m_rcBat = nullptr;
QLabel* m_title = nullptr;
QLabel* m_time = nullptr;
QPushButton* m_estop = nullptr;
QTimer* m_clock = nullptr;
};
#endif // TITLEBAR_H