调试初版
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cd \"//192.168.1.188/Ui_wuhan\" && ls -la comm/ && echo \"=== 各文件里的 0x18xxxxxx ===\" && grep -rn \"0x18[0-9A-Fa-f]\\\\{6\\\\}\" --include=*.cpp --include=*.h . | grep -v \"^./comm/car.cpp\" | head -30)",
|
||||
"Read(///192.168.1.188/Ui_wuhan//**)",
|
||||
"Bash(awk -F, ' *)",
|
||||
"Bash(awk -F',' '$4 ~ /^0x/ {print $25}' \"洗消机器人遥控器通信协议20260810.csv\")",
|
||||
"Bash(awk -F',' '$4 ~ /^0x/ {n[$25]++; if\\($6!=p[$25] && p[$25]!=\"\"\\){} ; printf \"%s %s %s %s\\\\n\", $25, $2, $4, $6}' \"洗消机器人遥控器通信协议20260810.csv\")",
|
||||
"Bash(cd \"//192.168.1.188/Ui_wuhan\" && echo \"=== onHeartbeatTimeout / setCommOk ===\" && grep -n \"onHeartbeatTimeout\" -A 8 comm/car.cpp && echo && echo \"=== rcLinkOk / linkChanged / linkOk 的消费方 ===\" && grep -rn \"rcLinkOk\\\\|linkChanged\\\\|linkOk\\(\\)\" --include=*.cpp --include=*.h . | grep -v \"comm/uart\")",
|
||||
"Bash(cd \"//192.168.1.188/Ui_wuhan\" && grep -n \"onRcLinkChanged\" -A 10 app/appcontext.cpp | head -20 && echo \"=== commOk 消费方 ===\" && grep -rn \"commOk\" --include=*.cpp pages/ widgets/ | head -20)",
|
||||
"Bash(cd \"//192.168.1.188/Ui_wuhan\" && grep -n \"setFixedHeight\\\\|setMinimumHeight\\\\|kHeight\\\\|setFixedSize\" widgets/titlebar.cpp widgets/alarmbanner.cpp | head -20)",
|
||||
"Bash(cd \"//192.168.1.188/Ui_wuhan\" && echo \"=== washpage.cpp:63 ===\" && sed -n '63p' pages/washpage.cpp && echo \"=== car.cpp:499-503 ===\" && sed -n '499,503p' comm/car.cpp && echo \"=== global.h:85 ===\" && sed -n '85p' app/global.h && echo \"=== passwordmgmt.cpp 关键行 ===\" && sed -n '11,12p;76,79p;88,92p' pages/passwordmgmt.cpp && echo && echo \"=== mtime ===\" && ls -l --time-style=full-iso pages/washpage.cpp comm/car.cpp app/global.h pages/passwordmgmt.cpp && echo && echo \"=== date ===\" && date)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,10 @@ CMakeLists.txt.user
|
||||
|
||||
# 编译输出目录(Qt Creator 默认生成的外部 build 文件夹)
|
||||
build-*/
|
||||
build/
|
||||
|
||||
# Qt Creator 本地工程状态(与 *.user 同类,因个人环境而异)
|
||||
.qtcreator/
|
||||
|
||||
# 临时编译文件与中间件
|
||||
*.o
|
||||
|
||||
@@ -1,34 +1,29 @@
|
||||
# 图数一体机遥控端 QT 界面(wuhan)
|
||||
|
||||
依据《QT界面详细设计说明书.md》实现的“玉兔工业无线遥控器(YTZT6K8EART2-aL25)”遥控端界面,
|
||||
覆盖 ③开机密码(含密码警告/密码管理登录/密码管理/密码重置)④自检 ⑤系统首页面 ⑥作业选择 ⑦机器人底盘
|
||||
⑧机械臂 ⑨气体检测仪 ⑩消除作业 ⑪消毒作业 ⑫调试维护 ⑬故障查询 共 11 个界面,
|
||||
并配套 **CAN 报文收发**的通信层(19 条报文经 UDP 收发,遥控器另占串口 `/dev/ttyS7`)。
|
||||
|
||||
## 环境与构建
|
||||
|
||||
- Qt 5.15.2 MinGW 32-bit(`F:\QT\5.15.2\mingw81_32`),编译套件 `Desktop Qt 5.15.2 MinGW 32-bit`。
|
||||
- 用 Qt Creator 打开 `wuhan.pro` 直接构建运行即可(qmake 工程,`QT += serialport`)。
|
||||
- 目标运行环境:主板(Linux / aarch64),Qt 5.15.2。
|
||||
- qmake 工程 `wuhan.pro`,依赖 Qt 模块 `core gui widgets network serialport`。
|
||||
|
||||
命令行构建示例:
|
||||
在主板本机构建:
|
||||
|
||||
```bash
|
||||
export PATH="/f/QT/Tools/mingw810_32/bin:/f/QT/5.15.2/mingw81_32/bin:$PATH"
|
||||
mkdir -p build && cd build
|
||||
qmake ../wuhan.pro -spec win32-g++ "CONFIG+=debug"
|
||||
mingw32-make -j4
|
||||
```
|
||||
|
||||
运行:`debug/wuhan.exe`(1920×1080 全屏无边框)。
|
||||
qmake wuhan.pro
|
||||
make -j4
|
||||
```
|
||||
|
||||
## 运行说明
|
||||
|
||||
- 开机密码默认 **6789**(可在“忘记密码/修改密码 → 密码管理登录(管理员密码 **9527**)→ 密码管理 → 重置密码”中修改);管理员密码为固定值 **9527**,不可修改。
|
||||
- 密码正确后进入自检,自检完成 2 秒自动进入“作业选择”;自检异常时可“忽略故障”继续。
|
||||
- 底部状态条的模式徽标只作**显示**(不响应点击);行车模式操作底盘,机械臂模式操作机械臂。
|
||||
演示用快捷键 **M** 可切换模式,但**遥控器在线时模式由遥控器报文决定,M 被忽略**。
|
||||
模式由**遥控器报文**决定。
|
||||
- 软急停由**遥控器 bit4** 下发:上升沿触发急停、下降沿复位,面板不提供急停/复位控件
|
||||
(顶条原有的“急停”按钮已移除)。触发后页面之上出现全屏红条“急停已触发,请用遥控器复位!”。
|
||||
触发后页面之上出现全屏红条“急停已触发,请用遥控器复位!”。
|
||||
- ⑨⑩ 运行中“返回”无效(需先停止);调试维护页常显红色警告。
|
||||
|
||||
## 通信(19 条 CAN 报文,经 /dev/ttyS7 串口收发)
|
||||
@@ -96,9 +91,9 @@ mingw32-make -j4
|
||||
### 已知缺口
|
||||
|
||||
- **自检 10 项无对应 CAN 报文**,`sendSelfCheckStart()` 暂由本端跑完序列(见 `onSelfCheckTick`
|
||||
的 TODO),仅为维持 ④自检 → ⑤作业选择 流程不中断;拿到真实报文后替换该本地驱动。
|
||||
的 TODO),仅为维持 ④自检 → ⑤系统首页面 流程不中断;拿到真实报文后替换该本地驱动。
|
||||
- **行车/机械臂模式**没有对应的下行信号:协议 12 条反馈里没有该字段,`telemetry.driveMode`
|
||||
无处可填,故模式只由遥控器报文决定(遥控器离线时保持上次值,可用演示快捷键 **M** 切换)。
|
||||
无处可填,故模式只由遥控器报文决定(遥控器离线时保持上次值)。
|
||||
- **`Vehi_spd` 按协议取 8 位**(bit24、精度 0.01 → 0~2.55 km/h)。协议表里该信号与其后的
|
||||
`Vehi_RdyStatus`(32)/`Reserve`(34)/`All_Comm_FaultCode`(40) 起始位自洽,按 8 位读才对齐;
|
||||
若实测车速需要 0~5 km/h,说明该字段实为 16 位、其后信号整体后移 8 位,需同步调整。
|
||||
@@ -157,10 +152,21 @@ mingw32-make -j4
|
||||
|
||||
- `app/` 全局常量、配色、应用上下文(AppContext)、QSS 样式、闪烁工具
|
||||
- `comm/` 车端 CAN 通信层(`car`,19 条报文经 UDP 收 9000 / 发 9001)+ 遥控器串口通道(`uart`,`/dev/ttyS7`)
|
||||
- `widgets/` 公共组件:TitleBar / StatusStrip / LedIndicator / NumPadDialog /
|
||||
ConfirmDialog / AlarmBanner / ParamControl / DataCard / FaultTable
|
||||
- `pages/` 各界面(继承 BasePage),对应关系如下:
|
||||
`widgets/` 公共组件,对应关系如下:
|
||||
|
||||
| 组件 | 文件(.h / .cpp) | 类名 |
|
||||
|---|---|---|
|
||||
| 顶条(高 98px) | titlebar | TitleBar |
|
||||
| 底部状态条 | statusstrip | StatusStrip |
|
||||
| 指示灯(自绘圆形渐变 + 外发光) | ledindicator | LedIndicator |
|
||||
| 数字键盘弹窗(模态) | numpaddialog | NumPadDialog |
|
||||
| 确认弹窗 | confirmdialog | ConfirmDialog |
|
||||
| 告警横幅 | alarmbanner | AlarmBanner |
|
||||
| 参数调节控件 | paramcontrol | ParamControl |
|
||||
| 数据卡片 | datacard | DataCard |
|
||||
| 故障分页表格 | faulttable | FaultTable |
|
||||
|
||||
- `pages/` 各界面(继承 BasePage),对应关系如下:
|
||||
| 界面 | 文件(.h / .cpp) | 类名 |
|
||||
|---|---|---|
|
||||
| 页面基类 | base | BasePage |
|
||||
|
||||
+47
-9
@@ -5,6 +5,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
|
||||
// 启动 500ms 闪烁:每 500ms 切换一次动态属性,QSS 据此在 40%↔100% 透明度间切换
|
||||
@@ -49,29 +50,67 @@ QString cancelButtonStyle(int fontPx)
|
||||
.arg(fontPx).arg(C::DANGER);
|
||||
}
|
||||
|
||||
// ===================== 按钮焦点光圈 =====================
|
||||
// ===================== 按钮焦点(选中)光圈 =====================
|
||||
ButtonFocusGlow::ButtonFocusGlow(const QColor& glow, QObject* parent)
|
||||
: QObject(parent), m_glow(glow)
|
||||
{
|
||||
}
|
||||
|
||||
// 触摸屏上手指抬起后指针并不会跟着移开,Qt 会把刚点过的按钮一直留在悬停(:hover)态。
|
||||
// 本项目的悬停样式与选中样式是同一套(蓝底/亮边),残留的悬停看起来就是"还选中着",
|
||||
// 此时再按 Tab 选中另一个按钮,屏幕上就出现了两个"选中"。选中态一律以焦点为准,
|
||||
// 故每次产生新的选中(点击/触摸按下、焦点移入)时,把其它按钮残留的悬停态清掉。
|
||||
static void clearStaleHover(QWidget* keep)
|
||||
{
|
||||
// 用 allWidgets 而非 qApp->findChildren:顶层窗口并非 QApplication 的子对象
|
||||
const QWidgetList all = QApplication::allWidgets();
|
||||
for (QWidget* w : all) {
|
||||
if (w == keep || !w->underMouse() || !qobject_cast<QAbstractButton*>(w))
|
||||
continue;
|
||||
w->setAttribute(Qt::WA_UnderMouse, false);
|
||||
w->update();
|
||||
}
|
||||
}
|
||||
|
||||
bool ButtonFocusGlow::eventFilter(QObject* obj, QEvent* ev)
|
||||
{
|
||||
if (ev->type() == QEvent::FocusIn || ev->type() == QEvent::FocusOut) {
|
||||
QWidget* w = qobject_cast<QWidget*>(obj);
|
||||
if (w && qobject_cast<QPushButton*>(w)) {
|
||||
if (ev->type() == QEvent::FocusIn) {
|
||||
if (!w->graphicsEffect()) { // 挂一圈发光(偏移 0 → 四周光晕)
|
||||
QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(w);
|
||||
if (!w)
|
||||
return QObject::eventFilter(obj, ev);
|
||||
|
||||
switch (ev->type()) {
|
||||
case QEvent::FocusIn:
|
||||
clearStaleHover(w); // 新的选中出现 → 旧按钮的悬停残影一并清掉
|
||||
if (qobject_cast<QPushButton*>(w) && !w->graphicsEffect()) {
|
||||
QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(w); // 挂一圈发光(偏移 0 → 四周光晕)
|
||||
g->setBlurRadius(40); // 光晕大小(越大越强)
|
||||
g->setOffset(0, 0);
|
||||
g->setColor(m_glow);
|
||||
w->setGraphicsEffect(g);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
case QEvent::FocusOut:
|
||||
if (qobject_cast<QPushButton*>(w) && w->graphicsEffect()) {
|
||||
// 光晕带模糊、偏移为 0,会溢出按钮矩形画到父控件上。
|
||||
// 只重绘按钮自身清不掉溢出的那圈,Tab 移走焦点后仍会留下蓝色残影,
|
||||
// 所以把父控件整块标脏,连同按钮一起重绘。
|
||||
QWidget* parent = w->parentWidget();
|
||||
w->setGraphicsEffect(nullptr); // Qt 会删除旧效果
|
||||
w->update();
|
||||
if (parent)
|
||||
parent->update(parent->rect());
|
||||
}
|
||||
break;
|
||||
case QEvent::MouseButtonPress:
|
||||
// 触摸/点击与 Tab 归一到同一个焦点:谁最后操作,谁就是唯一的选中
|
||||
if (static_cast<QMouseEvent*>(ev)->button() == Qt::LeftButton
|
||||
&& (w->focusPolicy() & Qt::ClickFocus)) {
|
||||
w->setFocus(Qt::MouseFocusReason);
|
||||
clearStaleHover(w);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QObject::eventFilter(obj, ev);
|
||||
}
|
||||
@@ -95,8 +134,7 @@ bool ArrowKeyGuard::eventFilter(QObject* obj, QEvent* ev)
|
||||
QWidget* fw = QApplication::focusWidget();
|
||||
if (fw && qobject_cast<QAbstractButton*>(fw)) {
|
||||
const int k = static_cast<QKeyEvent*>(ev)->key();
|
||||
if (k == Qt::Key_Up || k == Qt::Key_Down
|
||||
|| k == Qt::Key_Left || k == Qt::Key_Right)
|
||||
if (Keys::up(k) || Keys::down(k))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-5
@@ -82,7 +82,7 @@ struct Telemetry {
|
||||
bool armHomed = false; // 是否已回位(用于底盘“机械臂未回位”告警)
|
||||
|
||||
// 洗消
|
||||
QString washMode = "--"; // 消除作业 / 消毒作业
|
||||
QString washMode = "--"; // 手动消除 / 手动消毒
|
||||
bool washStart = false; // 洗消启停反馈
|
||||
double flowA = 0.0; // A 药剂流量 L/min
|
||||
double flowB = 0.0; // B 药剂流量 L/min
|
||||
@@ -150,8 +150,22 @@ void stopBlink(QWidget* w, const char* prop = "blink");
|
||||
// 退出/取消按钮预设样式:被选中(悬停/按下/焦点)时红底白字
|
||||
QString cancelButtonStyle(int fontPx = 60);
|
||||
|
||||
// ===================== 按钮焦点光圈 =====================
|
||||
// 全局事件过滤器:任意 QPushButton 获得焦点时,在其四周加一圈发光(QSS 无法实现光晕)
|
||||
// ===================== 本机按键 =====================
|
||||
// 面板只提供 5 个键:Tab、Shift+Tab、空格、方向上、方向下。
|
||||
// 新增按键或调整键位只改这一处。
|
||||
namespace Keys {
|
||||
inline bool tab(int k) { return k == Qt::Key_Tab; }
|
||||
inline bool backtab(int k) { return k == Qt::Key_Backtab; } // Shift+Tab
|
||||
inline bool shifted(int mods) { return (mods & Qt::ShiftModifier) != 0; }
|
||||
inline bool space(int k) { return k == Qt::Key_Space; }
|
||||
inline bool up(int k) { return k == Qt::Key_Up; }
|
||||
inline bool down(int k) { return k == Qt::Key_Down; }
|
||||
}
|
||||
|
||||
// ===================== 按钮焦点(选中)光圈 =====================
|
||||
// 全局事件过滤器:任意 QPushButton 获得焦点时,在其四周加一圈发光;
|
||||
// 同时把"触摸/点击"与"Tab"归一到同一个焦点,并清除触摸残留在按钮上的悬停态,
|
||||
// 保证任意时刻只有一个按钮呈选中样式(新的操作覆盖原来的)。
|
||||
class ButtonFocusGlow : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -169,8 +183,7 @@ private:
|
||||
void installButtonFocusGlow(QApplication* app);
|
||||
|
||||
// ===================== 方向键(旋钮)隔离 =====================
|
||||
// 方向键在本机是"旋钮"键位,只由 ParamControl 用来调值。但 QAbstractButton 会把
|
||||
// 方向键当成沿焦点链换焦(等同 Tab/Shift+Tab),停在按钮上时按方向键焦点会乱跳。
|
||||
// 上/下方向键在本机是"旋钮"键位,只由 ParamControl 用来调值。
|
||||
// 这个全局过滤器把"焦点在按钮上时"的方向键吞掉,方向键只做调值、换焦只走 Tab。
|
||||
class ArrowKeyGuard : public QObject
|
||||
{
|
||||
|
||||
+3
-3
@@ -30,7 +30,7 @@ const int kDataLen = 8;
|
||||
// 车端 CAN 的 UDP 端口:本机 9000 收(车端反馈),上行命令发往 9001
|
||||
const quint16 kCanRxPort = 9000;
|
||||
const quint16 kCanTxPort = 9001;
|
||||
const char* kCanTxHost = "127.0.0.1";
|
||||
const char* kCanTxHost = "0.0.0.0";
|
||||
|
||||
quint8 canChecksum(quint32 canId, quint8 dlc, const quint8* data)
|
||||
{
|
||||
@@ -497,8 +497,8 @@ void CarCommunication::handleFrame(quint32 canId, const quint8* d)
|
||||
// ---------- 0x18368180 洗消工作状态1:模式 / 泵 / 球阀 / 液位 / 流量 ----------
|
||||
case kIdDecontamFb1: {
|
||||
const quint8 mode = quint8(intelBits(d, 0, 2));
|
||||
m_tel->washMode = (mode == 3) ? QString::fromUtf8("消毒作业")
|
||||
: (mode == 2) ? QString::fromUtf8("消除作业")
|
||||
m_tel->washMode = (mode == 3) ? QString::fromUtf8("手动消毒")
|
||||
: (mode == 2) ? QString::fromUtf8("手动消除")
|
||||
: QString("--");
|
||||
m_tel->washStart = intelBits(d, 2, 1) != 0;
|
||||
m_tel->plunger = intelBits(d, 3, 1) != 0;
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ struct CanFault {
|
||||
// 下行 分类二/三 12 条(车端 → 遥控器):底盘 4 条、机械臂 4 条、洗消 2 条、气体检测仪 2 条,
|
||||
// 按 canId 分流转 handleFrame() 写入 Telemetry。
|
||||
// 全部为 29 位扩展帧、信号一律 Intel(小端)位序。
|
||||
// 传输走 UDP:bind 本机 9000 端口收下行反馈,上行命令发往 127.0.0.1:9001。
|
||||
// 传输走 UDP:bind 本机 9000 端口收下行反馈,上行命令发往 0.0.0.0:9001。
|
||||
// UDP 载荷统一为 18 字节:帧头 0x5AA5FF00 + canId + DLC + 8 字节数据 + 校验和。
|
||||
// 通信超时判定:连续 2s 未收到任何合法帧则判为通信异常(最慢的下行周期 1000ms)。
|
||||
class CarCommunication : public QObject
|
||||
|
||||
@@ -37,7 +37,6 @@ Q_DECLARE_METATYPE(RcFrame)
|
||||
|
||||
// ===================== 遥控器串口接收层 =====================
|
||||
// 独占 rc/device(默认 /dev/ttyS7),115200 / 8 数据位 / 1 停止位 / 无校验。
|
||||
// 车端 CAN 改走 UDP(见 car.h,收 9000 / 发 9001),不再占用串口。
|
||||
// 只负责收包、校验、解析与链路超时判定,不决定遥控器数据的用途。
|
||||
class RcCommunication : public QObject
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ int main(int argc, char *argv[])
|
||||
QApplication a(argc, argv);
|
||||
|
||||
a.setStyleSheet(appStyleSheet());
|
||||
installButtonFocusGlow(&a); // 按钮获得焦点时显示光圈
|
||||
installButtonFocusGlow(&a); // 按钮获得焦点时显示光圈 + 触摸/点击与 Tab 共用同一个选中
|
||||
installArrowKeyGuard(&a); // 方向键只做旋钮调值,不在按钮间换焦
|
||||
|
||||
MainWindow w;
|
||||
|
||||
+8
-21
@@ -22,7 +22,6 @@
|
||||
#include <QStackedWidget>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QKeyEvent>
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QMainWindow(parent)
|
||||
@@ -55,12 +54,8 @@ MainWindow::MainWindow(QWidget* parent)
|
||||
m_ctx->comm()->start();
|
||||
m_ctx->rc()->start(); // 遥控器接收(独立端口)
|
||||
|
||||
// 初始页面:开机密码
|
||||
// m_stack->setCurrentIndex(PageBoot);
|
||||
// 免密码直接进自检页
|
||||
// m_stack->setCurrentIndex(PageSelfCheck);
|
||||
// 免登录直接进作业选择页(恢复原流程时改回上面的 PageBoot)
|
||||
m_stack->setCurrentIndex(PageJob);
|
||||
// 初始页面:开机密码 →(验证通过)自检 →(自检结束)系统首页面
|
||||
m_stack->setCurrentIndex(PageBoot);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@@ -94,6 +89,7 @@ void MainWindow::buildPages()
|
||||
add(PageFault, new FaultQueryPage(m_ctx));
|
||||
|
||||
m_faultPage = qobject_cast<FaultQueryPage*>(page(PageFault));
|
||||
m_debugPage = qobject_cast<DebugMaintainPage*>(page(PageDebug));
|
||||
}
|
||||
|
||||
void MainWindow::onNavigate(PageId id)
|
||||
@@ -106,6 +102,11 @@ void MainWindow::onNavigate(PageId id)
|
||||
else
|
||||
m_faultPage->setReturnPage(PageJob);
|
||||
}
|
||||
// 调试维护有两个入口(系统首页 / 作业选择),同样原路返回
|
||||
if (id == PageDebug && m_debugPage) {
|
||||
PageId cur = static_cast<PageId>(m_stack->currentIndex());
|
||||
m_debugPage->setReturnPage(cur == PageHome ? PageHome : PageJob);
|
||||
}
|
||||
if (id >= 0 && id < PageCount)
|
||||
m_stack->setCurrentIndex(id);
|
||||
}
|
||||
@@ -115,17 +116,3 @@ void MainWindow::onEstopChanged(bool triggered)
|
||||
m_estopBanner->setVisible(triggered);
|
||||
m_estopBanner->raise();
|
||||
}
|
||||
|
||||
void MainWindow::keyPressEvent(QKeyEvent* ev)
|
||||
{
|
||||
// 演示用快捷键:M 切换行车/机械臂模式。
|
||||
// 遥控器在线时模式由遥控器报文决定,此处不抢(与底盘页 showEvent 同一规则)。
|
||||
switch (ev->key()) {
|
||||
case Qt::Key_M:
|
||||
if (!m_ctx->rc()->linkOk())
|
||||
m_ctx->setMode(m_ctx->mode == Mode::Drive ? Mode::Arm : Mode::Drive);
|
||||
break;
|
||||
default:
|
||||
QMainWindow::keyPressEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -10,6 +10,7 @@ class QLabel;
|
||||
class AppContext;
|
||||
class BasePage;
|
||||
class FaultQueryPage;
|
||||
class DebugMaintainPage;
|
||||
|
||||
// ===================== 主窗口(全屏无边框 + 页面路由) =====================
|
||||
class MainWindow : public QMainWindow
|
||||
@@ -19,9 +20,6 @@ public:
|
||||
explicit MainWindow(QWidget* parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* ev) override;
|
||||
|
||||
private slots:
|
||||
void onNavigate(PageId id);
|
||||
void onEstopChanged(bool triggered);
|
||||
@@ -35,6 +33,7 @@ private:
|
||||
QLabel* m_estopBanner = nullptr;
|
||||
QVector<BasePage*> m_pages;
|
||||
FaultQueryPage* m_faultPage = nullptr;
|
||||
DebugMaintainPage* m_debugPage = nullptr;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
||||
@@ -36,7 +36,7 @@ BootPasswordPage::BootPasswordPage(AppContext* ctx, QWidget* parent)
|
||||
m_input->setEchoMode(QLineEdit::Password);
|
||||
m_input->setAlignment(Qt::AlignCenter);
|
||||
m_input->setFixedSize(450, 120);
|
||||
m_input->setStyleSheet("font-size:80px");
|
||||
m_input->setStyleSheet("font-size:50px");
|
||||
m_input->setProperty("clickable", true);
|
||||
lay->addWidget(m_input, 0, Qt::AlignHCenter);
|
||||
//lay->addSpacing(20);
|
||||
@@ -55,11 +55,11 @@ BootPasswordPage::BootPasswordPage(AppContext* ctx, QWidget* parent)
|
||||
btnRow->setSpacing(80);
|
||||
btnRow->addStretch(1);
|
||||
QPushButton* ok = button("确认", "", col);
|
||||
ok->setFixedSize(160, 140);
|
||||
ok->setFixedSize(160, 100);
|
||||
ok->setStyleSheet("font-size:60px;");
|
||||
connect(ok, &QPushButton::clicked, this, &BootPasswordPage::onConfirm);
|
||||
QPushButton* cancel = button("取消", "", col);
|
||||
cancel->setFixedSize(160, 140);
|
||||
QPushButton* cancel = button("清空", "", col);
|
||||
cancel->setFixedSize(160, 100);
|
||||
cancel->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
|
||||
connect(cancel, &QPushButton::clicked, this, &BootPasswordPage::onCancel);
|
||||
btnRow->addWidget(ok);
|
||||
@@ -93,7 +93,8 @@ bool BootPasswordPage::eventFilter(QObject* obj, QEvent* ev)
|
||||
}
|
||||
if (ev->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
// 空格当“点击”:Tab 聚焦后按空格弹出数字键盘
|
||||
if (Keys::space(ke->key())) {
|
||||
onInputClicked();
|
||||
return true;
|
||||
}
|
||||
@@ -155,5 +156,5 @@ void BootPasswordPage::refreshInput()
|
||||
// 空态:无论是否被选中都显示提示文字(灰字);有内容:显示密码圆点
|
||||
m_input->setEchoMode(empty ? QLineEdit::Normal : QLineEdit::Password);
|
||||
m_input->setText(empty ? "点击输入密码" : m_value);
|
||||
m_input->setStyleSheet(empty ? "font-size:50px;color:#9AA7B4;" : "font-size:80px;");
|
||||
m_input->setStyleSheet(empty ? "font-size:50px;color:#9AA7B4;" : "font-size:50px;");
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
|
||||
QPushButton* back = button("返回", "", bottom);
|
||||
back->setFixedSize(160, 76);
|
||||
back->setStyleSheet(cancelButtonStyle(40)); // 选中(悬停/按下/焦点)红底白字
|
||||
connect(back, &QPushButton::clicked, this, [this]() { emit navigate(PageJob); });
|
||||
connect(back, &QPushButton::clicked, this, [this]() { emit navigate(m_returnPage); });
|
||||
bottomLay->addWidget(back);
|
||||
root->addWidget(bottom);
|
||||
|
||||
@@ -129,8 +129,6 @@ DebugMaintainPage::DebugMaintainPage(AppContext* ctx, QWidget* parent)
|
||||
});
|
||||
|
||||
// Tab 顺序(循环):返回 → D1控制 → D2控制 → 各泵(转速输入 → 启动 → 停止) → 回到返回。
|
||||
// 这些按钮在控件级样式表里写了 :checked,而控件级样式表的优先级高于全局
|
||||
// QPushButton:focus,默认焦点链下容易看起来“停不上去”,故显式排一遍保证都能停住
|
||||
QWidget* tabChain[] = {
|
||||
m_valve1Btn, m_valve2Btn,
|
||||
m_airRpm->editor(), m_airStart, m_airStop,
|
||||
|
||||
@@ -19,6 +19,8 @@ class DebugMaintainPage : public BasePage
|
||||
public:
|
||||
explicit DebugMaintainPage(AppContext* ctx, QWidget* parent = nullptr);
|
||||
|
||||
void setReturnPage(PageId id) { m_returnPage = id; }
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
|
||||
@@ -64,6 +66,9 @@ private:
|
||||
|
||||
// 是否收到过车端遥测:未收到时球阀到位灯保持“开启=绿、关闭=红”的缺省显示
|
||||
bool m_telemetrySeen = false;
|
||||
|
||||
// “返回”去向:本页有两个入口(系统首页 / 作业选择),由 MainWindow 记录来源后原路返回
|
||||
PageId m_returnPage = PageJob;
|
||||
};
|
||||
|
||||
#endif // DEBUGMAINTAINPAGE_H
|
||||
|
||||
+4
-4
@@ -41,7 +41,7 @@ HomePage::HomePage(AppContext* ctx, QWidget* parent)
|
||||
leftLay->setSpacing(100);
|
||||
auto addMenu = [&](const QString& t, PageId id) {
|
||||
QPushButton* b = button(t, "", left);
|
||||
b->setFixedSize(260, 100); // 固定尺寸(改这里同时调整 2 个菜单按钮)
|
||||
b->setFixedSize(260, 120); // 固定尺寸(改这里同时调整 2 个菜单按钮)
|
||||
b->setStyleSheet(menuBtnStyle);
|
||||
connect(b, &QPushButton::clicked, this, [this, id]() { emit navigate(id); });
|
||||
leftLay->addWidget(b, 0, Qt::AlignHCenter); // 不占拉伸因子,余量交给上下 stretch 均分
|
||||
@@ -89,15 +89,15 @@ HomePage::HomePage(AppContext* ctx, QWidget* parent)
|
||||
rightLay->setContentsMargins(0, 0, 0, 0);
|
||||
rightLay->setSpacing(24);
|
||||
QPushButton* debug = button("调试维护", "", right);
|
||||
debug->setFixedSize(240, 100);
|
||||
debug->setFixedSize(240, 120);
|
||||
debug->setStyleSheet(actionBtnStyle);
|
||||
connect(debug, &QPushButton::clicked, this, [this]() { emit navigate(PageDebug); });
|
||||
QPushButton* help = button("操作说明", "", right);
|
||||
help->setFixedSize(240, 100);
|
||||
help->setFixedSize(240, 120);
|
||||
help->setStyleSheet(actionBtnStyle);
|
||||
connect(help, &QPushButton::clicked, this, &HomePage::onHelp);
|
||||
QPushButton* spare = button("备用", "", right);
|
||||
spare->setFixedSize(240, 100);
|
||||
spare->setFixedSize(240, 120);
|
||||
spare->setStyleSheet(actionBtnStyle);
|
||||
spare->setEnabled(false); // 预留功能:置灰禁用,不接任何跳转
|
||||
rightLay->addStretch(1);
|
||||
|
||||
+57
-5
@@ -4,8 +4,53 @@
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QEvent>
|
||||
#include <QColor>
|
||||
#include <QGraphicsDropShadowEffect>
|
||||
#include "global.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class CardFocusGlow : public QObject
|
||||
{
|
||||
public:
|
||||
CardFocusGlow(QWidget* card, const QString& normalQss, const QString& focusQss,
|
||||
const QColor& glow, QObject* parent = nullptr)
|
||||
: QObject(parent), m_card(card), m_normal(normalQss), m_focus(focusQss), m_glow(glow) {}
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* ev) override
|
||||
{
|
||||
if (ev->type() == QEvent::FocusIn) {
|
||||
m_card->setStyleSheet(m_focus);
|
||||
if (!m_card->graphicsEffect()) {
|
||||
QGraphicsDropShadowEffect* g = new QGraphicsDropShadowEffect(m_card); // 偏移 0 → 四周光晕
|
||||
g->setBlurRadius(40);
|
||||
g->setOffset(0, 0);
|
||||
g->setColor(m_glow);
|
||||
m_card->setGraphicsEffect(g);
|
||||
}
|
||||
} else if (ev->type() == QEvent::FocusOut) {
|
||||
m_card->setStyleSheet(m_normal);
|
||||
QWidget* parent = m_card->parentWidget();
|
||||
// 光晕溢出卡片矩形,只重绘卡片自身会留下残影,把父控件一并标脏
|
||||
m_card->setGraphicsEffect(nullptr); // Qt 会删除旧效果
|
||||
m_card->update();
|
||||
if (parent)
|
||||
parent->update(parent->rect());
|
||||
}
|
||||
return QObject::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
private:
|
||||
QWidget* m_card;
|
||||
QString m_normal;
|
||||
QString m_focus;
|
||||
QColor m_glow;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PasswordMgmtPage::PasswordMgmtPage(AppContext* ctx, QWidget* parent)
|
||||
: BasePage(ctx, parent)
|
||||
{
|
||||
@@ -25,16 +70,23 @@ PasswordMgmtPage::PasswordMgmtPage(AppContext* ctx, QWidget* parent)
|
||||
// 功能选项列表
|
||||
QWidget* option = new QWidget(col);
|
||||
option->setProperty("card", true);
|
||||
option->setStyleSheet(QString("QWidget[card=\"true\"]{background-color:%1;border:2px solid %2;border-radius:12px;}")
|
||||
.arg(C::PANEL, C::BORDER));
|
||||
const QString cardQss = QString("QWidget[card=\"true\"]{background-color:%1;border:2px solid %2;border-radius:12px;}")
|
||||
.arg(C::PANEL, C::BORDER);
|
||||
const QString cardFocusQss = QString("QWidget[card=\"true\"]{background-color:%1;border:3px solid %2;border-radius:12px;}")
|
||||
.arg(C::PANEL, "#8ABFFF");
|
||||
option->setStyleSheet(cardQss);
|
||||
QVBoxLayout* optLay = new QVBoxLayout(option);
|
||||
m_reset = new QCheckBox("重置密码功能", option);
|
||||
m_reset->setChecked(true);
|
||||
m_reset->setStyleSheet(QString(
|
||||
"QCheckBox{color:%1;font-size:40px;spacing:16px;}"
|
||||
"QCheckBox::indicator{width:40px;height:40px;border:2px solid %2;border-radius:6px;background-color:%3;}"
|
||||
"QCheckBox::indicator:checked{background-color:%4;border-color:%4;image:none;}")
|
||||
"QCheckBox::indicator:checked{background-color:%4;border-color:%4;image:none;}"
|
||||
"QCheckBox:focus{color:#FFFFFF;}"
|
||||
"QCheckBox:focus::indicator{border:4px solid #8ABFFF;}")
|
||||
.arg(C::TEXT, C::BORDER, C::BG, C::PRIMARY));
|
||||
// 复选框获得焦点(Tab 选中)时点亮整个卡片
|
||||
m_reset->installEventFilter(new CardFocusGlow(option, cardQss, cardFocusQss, QColor("#7FBBFF"), m_reset));
|
||||
optLay->addWidget(m_reset);
|
||||
lay->addWidget(option);
|
||||
|
||||
@@ -43,11 +95,11 @@ PasswordMgmtPage::PasswordMgmtPage(AppContext* ctx, QWidget* parent)
|
||||
btnRow->setSpacing(80);
|
||||
btnRow->addStretch(1);
|
||||
QPushButton* ok = button("确认", "", col);
|
||||
ok->setFixedSize(160, 140);
|
||||
ok->setFixedSize(160, 120);
|
||||
ok->setStyleSheet("font-size:60px;");
|
||||
connect(ok, &QPushButton::clicked, this, [this]() { emit navigate(PageReset); });
|
||||
QPushButton* exit = button("退出", "", col);
|
||||
exit->setFixedSize(160, 140);
|
||||
exit->setFixedSize(160, 120);
|
||||
exit->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
|
||||
connect(exit, &QPushButton::clicked, this, [this]() { emit navigate(PageBoot); });
|
||||
btnRow->addWidget(ok);
|
||||
|
||||
@@ -49,11 +49,11 @@ PasswordMgmtLoginPage::PasswordMgmtLoginPage(AppContext* ctx, QWidget* parent)
|
||||
btnRow->setSpacing(80);
|
||||
btnRow->addStretch(1);
|
||||
QPushButton* ok = button("确认", "", col);
|
||||
ok->setFixedSize(160, 140);
|
||||
ok->setFixedSize(160, 120);
|
||||
ok->setStyleSheet("font-size:60px;");
|
||||
connect(ok, &QPushButton::clicked, this, &PasswordMgmtLoginPage::onConfirm);
|
||||
QPushButton* cancel = button("取消", "", col);
|
||||
cancel->setFixedSize(160, 140);
|
||||
cancel->setFixedSize(160, 120);
|
||||
cancel->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
|
||||
connect(cancel, &QPushButton::clicked, this, &PasswordMgmtLoginPage::onCancel);
|
||||
btnRow->addWidget(ok);
|
||||
@@ -74,7 +74,8 @@ bool PasswordMgmtLoginPage::eventFilter(QObject* obj, QEvent* ev)
|
||||
}
|
||||
if (ev->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
// 空格当“点击”:Tab 聚焦后按空格弹出数字键盘
|
||||
if (Keys::space(ke->key())) {
|
||||
onInputClicked();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -44,15 +44,15 @@ PasswordResetPage::PasswordResetPage(AppContext* ctx, QWidget* parent)
|
||||
btnRow->setSpacing(80);
|
||||
btnRow->addStretch(1);
|
||||
QPushButton* ok = button("确认", "", col);
|
||||
ok->setFixedSize(160, 140);
|
||||
ok->setFixedSize(160, 120);
|
||||
ok->setStyleSheet("font-size:60px;");
|
||||
connect(ok, &QPushButton::clicked, this, &PasswordResetPage::onConfirm);
|
||||
QPushButton* cancel = button("取消", "", col);
|
||||
cancel->setFixedSize(160, 140);
|
||||
cancel->setFixedSize(160, 120);
|
||||
cancel->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
|
||||
connect(cancel, &QPushButton::clicked, this, &PasswordResetPage::onBack);
|
||||
QPushButton* exit = button("退出", "", col);
|
||||
exit->setFixedSize(160, 140);
|
||||
exit->setFixedSize(160, 120);
|
||||
exit->setStyleSheet(cancelButtonStyle(60)); // 选中时红底白字
|
||||
connect(exit, &QPushButton::clicked, this, &PasswordResetPage::onExit);
|
||||
btnRow->addWidget(ok);
|
||||
@@ -74,7 +74,8 @@ bool PasswordResetPage::eventFilter(QObject* obj, QEvent* ev)
|
||||
}
|
||||
if (ev->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
// 空格当“点击”:Tab 聚焦后按空格弹出数字键盘
|
||||
if (Keys::space(ke->key())) {
|
||||
onInputClicked();
|
||||
return true;
|
||||
}
|
||||
|
||||
+15
-12
@@ -28,18 +28,17 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent)
|
||||
main->setContentsMargins(20, 30, 20, 30);
|
||||
main->setSpacing(40);
|
||||
|
||||
// 两侧按钮统一样式:字号在此统一调整;按钮会拉伸填满整列(sideBtnH 为最小高度)
|
||||
const int sideBtnH = 130;
|
||||
// 两侧按钮统一样式:左右两列 5 个按钮统一高度 sideBtnH,字号在此统一调整
|
||||
const int sideBtnH = 120;
|
||||
const QString sideBtnStyle = "font-size:60px;";
|
||||
auto makeSideBtn = [&](const QString& text, const QString& variant, QWidget* parent) {
|
||||
QPushButton* b = button(text, variant, parent);
|
||||
b->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); // 纵向可拉伸
|
||||
b->setMinimumHeight(sideBtnH);
|
||||
b->setFixedHeight(sideBtnH); // 左右两列共用同一高度值
|
||||
b->setStyleSheet(sideBtnStyle);
|
||||
return b;
|
||||
};
|
||||
|
||||
// 左按钮列(按钮按列高平分,填满整列)
|
||||
// 左按钮列(高度统一为 sideBtnH,整列垂直居中)
|
||||
QWidget* left = new QWidget(this);
|
||||
QVBoxLayout* leftLay = new QVBoxLayout(left);
|
||||
leftLay->setContentsMargins(20, 220, 20, 220);
|
||||
@@ -49,8 +48,10 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent)
|
||||
m_ignore = makeSideBtn("忽略故障", "", left);
|
||||
connect(m_ignore, &QPushButton::clicked, this, &SelfCheckPage::onIgnoreFault);
|
||||
m_ignore->setEnabled(false);
|
||||
leftLay->addWidget(m_faultQuery, 1);
|
||||
leftLay->addWidget(m_ignore, 1);
|
||||
leftLay->addStretch(1);
|
||||
leftLay->addWidget(m_faultQuery);
|
||||
leftLay->addWidget(m_ignore);
|
||||
leftLay->addStretch(1);
|
||||
left->setFixedWidth(300);
|
||||
main->addWidget(left);
|
||||
|
||||
@@ -106,11 +107,11 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent)
|
||||
|
||||
main->addWidget(center, 1);
|
||||
|
||||
// 右按钮列(按钮按列高平分,填满整列)
|
||||
// 右按钮列(高度统一为 sideBtnH,整列垂直居中)
|
||||
QWidget* right = new QWidget(this);
|
||||
QVBoxLayout* rightLay = new QVBoxLayout(right);
|
||||
rightLay->setContentsMargins(20, 140, 20, 140);
|
||||
rightLay->setSpacing(140);
|
||||
rightLay->setSpacing(100);
|
||||
QPushButton* help = makeSideBtn("操作说明", "", right);
|
||||
connect(help, &QPushButton::clicked, this, &SelfCheckPage::onHelp);
|
||||
QPushButton* start = makeSideBtn("启动自检", "", right);
|
||||
@@ -118,9 +119,11 @@ SelfCheckPage::SelfCheckPage(AppContext* ctx, QWidget* parent)
|
||||
m_job = makeSideBtn("作业选择", "", right);
|
||||
connect(m_job, &QPushButton::clicked, this, &SelfCheckPage::onJobSelect);
|
||||
m_job->setEnabled(false);
|
||||
rightLay->addWidget(help, 1);
|
||||
rightLay->addWidget(start, 1);
|
||||
rightLay->addWidget(m_job, 1);
|
||||
rightLay->addStretch(1);
|
||||
rightLay->addWidget(help);
|
||||
rightLay->addWidget(start);
|
||||
rightLay->addWidget(m_job);
|
||||
rightLay->addStretch(1);
|
||||
right->setFixedWidth(300);
|
||||
main->addWidget(right);
|
||||
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ WashPage::WashPage(AppContext* ctx, Mode mode, QWidget* parent)
|
||||
main->addWidget(left);
|
||||
|
||||
// 中央洗消卡
|
||||
m_washCard = new DataCard(clean ? "洗消(消除作业)" : "洗消(消毒作业)", this);
|
||||
m_washCard = new DataCard("洗消", this);
|
||||
m_fMode = m_washCard->addField("洗消模式", "--");
|
||||
m_fLevelA = m_washCard->addField("A组分液位", "100L");
|
||||
m_fLevelFoam = m_washCard->addField("泡沫箱液位", "30L");
|
||||
|
||||
@@ -14,9 +14,10 @@ DataCard::DataCard(const QString& title, QWidget* parent)
|
||||
lay->setContentsMargins(24, 0, 24, 20);
|
||||
lay->setSpacing(16);
|
||||
|
||||
// 标题栏
|
||||
// 标题栏(标题在卡片内居中,下沿一条分割线)
|
||||
m_title = new QLabel(title, this);
|
||||
m_title->setFixedHeight(60);
|
||||
m_title->setAlignment(Qt::AlignCenter);
|
||||
m_title->setProperty("cardTitle", true);
|
||||
m_title->setStyleSheet(QString("color:%1;font-size:42px;font-weight:700;border-bottom:1px solid %2;")
|
||||
.arg(C::TEXT, C::BORDER));
|
||||
|
||||
+31
-11
@@ -62,8 +62,13 @@ FaultTable::FaultTable(QWidget* parent)
|
||||
m_table->setColumnWidth(0, 120);
|
||||
m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);
|
||||
m_table->horizontalHeader()->setFixedHeight(kHeaderHeight); // 固定表头高度(供对齐)
|
||||
// 每页 6 行等分,平均填满表格可用高度
|
||||
m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);
|
||||
// 每页 6 行等分填满表格可用高度,行高由视口高度直接算出(见 updateRowHeights)。
|
||||
// 不能用 Stretch:它不会把行压到 minimumSectionSize 之下,而该值默认取自字体度量,
|
||||
// 主板上 QSS 指定的中文字体(微软雅黑等)都不存在、回退字体的行高更大,6 行塞不进
|
||||
// 视口就会出滚动条、只看得到 5 行。改为固定行高后与字体无关。
|
||||
m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
|
||||
m_table->verticalHeader()->setMinimumSectionSize(1);
|
||||
m_table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) {
|
||||
int global = m_page * m_pageSize + row;
|
||||
if (global >= 0 && global < m_data.size())
|
||||
@@ -85,6 +90,13 @@ QWidget* FaultTable::nextButton() const { return m_next; }
|
||||
|
||||
bool FaultTable::eventFilter(QObject* obj, QEvent* ev)
|
||||
{
|
||||
// 视口尺寸变化(含首次布局)后重算行高,保证 6 行恰好铺满。
|
||||
// 必须挂在 viewport 上:m_table 收到 Resize 时它的 viewport 还没重排,
|
||||
// 那时读到的 viewport 高度是旧值(首次为 0)。
|
||||
if (obj == m_table->viewport() && ev->type() == QEvent::Resize) {
|
||||
updateRowHeights();
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
if (obj == m_table->viewport() && ev->type() == QEvent::Leave) {
|
||||
setHoverRow(-1); // 鼠标移出表格 → 取消悬停高亮
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
@@ -119,17 +131,17 @@ bool FaultTable::eventFilter(QObject* obj, QEvent* ev)
|
||||
return QWidget::eventFilter(obj, ev);
|
||||
}
|
||||
|
||||
// Tab / Shift+Tab:按行切换选中(每一行作为一个单位);回车/空格:等同鼠标点击
|
||||
// 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)
|
||||
const int k = ke->key();
|
||||
// 上/下方向键在本机是"旋钮"键位(只给 ParamControl 调值),表格里不参与导航:
|
||||
// 换行只走 Tab/Shift+Tab,激活只走空格
|
||||
if (Keys::up(k) || Keys::down(k))
|
||||
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);
|
||||
if (Keys::tab(k) || Keys::backtab(k)) {
|
||||
const bool back = Keys::backtab(k) || Keys::shifted(ke->modifiers());
|
||||
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()));
|
||||
@@ -137,8 +149,7 @@ bool FaultTable::eventFilter(QObject* obj, QEvent* ev)
|
||||
return true; // 消费事件,焦点留在表格内
|
||||
}
|
||||
// 已在首/末行:不拦截,焦点按 Tab 顺序移到下一个控件
|
||||
} else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter
|
||||
|| ke->key() == Qt::Key_Space) {
|
||||
} else if (Keys::space(k)) {
|
||||
// 键盘激活:与鼠标 cellClicked 走同一个信号,弹窗逻辑只有一份
|
||||
const int row = m_table->currentRow();
|
||||
const int global = m_page * m_pageSize + row;
|
||||
@@ -225,3 +236,12 @@ void FaultTable::refresh()
|
||||
m_prev->setEnabled(m_page > 0);
|
||||
m_next->setEnabled(m_page < pageCount() - 1);
|
||||
}
|
||||
|
||||
// 行高 = 视口高度 / 每页行数:6 行恰好铺满,不出现滚动条,也不受字体度量影响
|
||||
void FaultTable::updateRowHeights()
|
||||
{
|
||||
const int h = m_table->viewport()->height();
|
||||
if (h <= 0 || m_pageSize <= 0)
|
||||
return;
|
||||
m_table->verticalHeader()->setDefaultSectionSize(qMax(1, h / m_pageSize));
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ protected:
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void updateRowHeights(); // 行高 = 视口高度 / 每页行数,保证 6 行铺满且无滚动条
|
||||
void setHoverRow(int row); // 鼠标悬停的整行高亮
|
||||
|
||||
QTableWidget* m_table = nullptr;
|
||||
|
||||
+16
-14
@@ -139,24 +139,26 @@ void NumPadDialog::setCompact(bool on)
|
||||
}
|
||||
|
||||
// 按当前尺寸模式设置面板/按键的大小与字号,并重新摆放面板
|
||||
// 面板高度必须 ≥ 内容高度(上下边距 + 回显 + 键区 + 确认行 + 各间距),
|
||||
// 否则 QVBoxLayout 只能压缩间距,各行会互相压住。
|
||||
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);
|
||||
const int digitFont = m_compact ? 40 : 44;
|
||||
const int fnFont = m_compact ? 30 : 34;
|
||||
const int dotFont = m_compact ? 46 : 52;
|
||||
const int okFont = m_compact ? 44 : 52;
|
||||
const QSize digitSz = m_compact ? QSize(120, 70) : QSize(132, 84);
|
||||
const QSize clearSz = m_compact ? QSize(120, 70) : QSize(132, 84);
|
||||
const QSize okSz = m_compact ? QSize(130, 88) : QSize(140, 104);
|
||||
|
||||
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_panel->setFixedSize(m_compact ? QSize(460, 600) : QSize(488, 688));
|
||||
m_panelLay->setContentsMargins(m_compact ? 20 : 24, m_compact ? 24 : 28,
|
||||
m_compact ? 20 : 24, m_compact ? 26 : 32);
|
||||
m_panelLay->setSpacing(m_compact ? 26 : 26);
|
||||
m_grid->setSpacing(m_compact ? 18 : 16);
|
||||
|
||||
m_echo->setFixedSize(m_compact ? QSize(400, 70) : QSize(510, 100));
|
||||
m_echo->setStyleSheet(QString("font-size:%1px;").arg(m_compact ? 48 : 70));
|
||||
m_echo->setFixedSize(m_compact ? QSize(400, 70) : QSize(440, 88));
|
||||
m_echo->setStyleSheet(QString("font-size:%1px;").arg(m_compact ? 48 : 56));
|
||||
|
||||
for (QPushButton* b : m_digits) {
|
||||
b->setFixedSize(digitSz);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "paramcontrol.h"
|
||||
#include "numpaddialog.h"
|
||||
#include "global.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
@@ -101,7 +102,7 @@ void ParamControl::adjustByKnob(int steps)
|
||||
emit valueChanged(m_value);
|
||||
}
|
||||
|
||||
// 点击数值框 / 回车 → 弹出数字键盘(面板靠左、不透明度 80%);↑/↓ → 每档加减一次
|
||||
// 点击数值框 / 空格 → 弹出数字键盘(面板靠左、不透明度 80%);↑/↓ → 每档加减一次
|
||||
bool ParamControl::eventFilter(QObject* obj, QEvent* ev)
|
||||
{
|
||||
if (obj == m_valueEdit) {
|
||||
@@ -111,17 +112,18 @@ bool ParamControl::eventFilter(QObject* obj, QEvent* ev)
|
||||
}
|
||||
if (ev->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
|
||||
if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
||||
const int k = ke->key();
|
||||
if (Keys::space(k)) {
|
||||
openNumPad();
|
||||
return true;
|
||||
}
|
||||
// 旋钮档:↑ 加一档、↓ 减一档(每档 = m_step)。Tab/Shift+Tab 走焦点链、
|
||||
// 空格激活按钮,各不重叠;按住方向键由按键自动重复继续调
|
||||
if (ke->key() == Qt::Key_Up) {
|
||||
// 旋钮档:↑ 加一档、↓ 减一档(每档 = m_step)。Tab/Shift+Tab 走焦点链,
|
||||
// 各不重叠;按住方向键由按键自动重复继续调
|
||||
if (Keys::up(k)) {
|
||||
adjustByKnob(1);
|
||||
return true;
|
||||
}
|
||||
if (ke->key() == Qt::Key_Down) {
|
||||
if (Keys::down(k)) {
|
||||
adjustByKnob(-1);
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user