Merge pull request '合并' (#7) from main into huaijin
commit
d25af828cb
@ -0,0 +1,49 @@
|
|||||||
|
# 编译生成文件 (Build artifacts)
|
||||||
|
build/
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
*.dll
|
||||||
|
*.exe
|
||||||
|
moc_*.cpp
|
||||||
|
ui_*.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Makefile
|
||||||
|
|
||||||
|
# Qt临时文件 (Qt temporary files)
|
||||||
|
*.pro.user
|
||||||
|
*.pro.user.*
|
||||||
|
.qmake.stash
|
||||||
|
|
||||||
|
# 个人配置文件 (Personal configuration files)
|
||||||
|
# 忽略个人数据库配置,避免团队成员间的配置冲突
|
||||||
|
# Ignore personal database configuration to avoid conflicts between team members
|
||||||
|
src/Client/config/database.ini
|
||||||
|
|
||||||
|
# 环境配置文件 (Environment configuration files)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# 日志文件 (Log files)
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# 临时文件 (Temporary files)
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
*~
|
||||||
|
|
||||||
|
# IDE配置文件 (IDE configuration files)
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# 系统文件 (System files)
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# 备份文件 (Backup files)
|
||||||
|
*.bak
|
||||||
|
*.backup
|
@ -0,0 +1,126 @@
|
|||||||
|
<execution>
|
||||||
|
<constraint>
|
||||||
|
## 学术项目界面约束
|
||||||
|
- **评分时间限制**:界面需要在短时间内给老师留下深刻印象
|
||||||
|
- **演示环境约束**:需要适应课堂投影和不同显示设备
|
||||||
|
- **功能展示要求**:界面必须能清晰展现所有核心功能
|
||||||
|
- **团队协作体现**:界面需要体现团队分工和技术整合
|
||||||
|
- **文档配合约束**:界面设计需要与技术文档保持一致
|
||||||
|
</constraint>
|
||||||
|
|
||||||
|
<rule>
|
||||||
|
## 学术界面强制标准
|
||||||
|
- **功能完整性优先**:所有要求功能必须有对应界面入口
|
||||||
|
- **专业性体现必须**:界面必须体现学生的技术水平
|
||||||
|
- **演示友好性**:界面必须便于课堂演示和功能展示
|
||||||
|
- **创新点突出**:必须有超出基本要求的设计亮点
|
||||||
|
- **稳定性保证**:演示过程中不能出现界面错误
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
<guideline>
|
||||||
|
## 学术界面设计指南
|
||||||
|
- **第一印象优化**:应用启动后的首屏要专业美观
|
||||||
|
- **核心功能突出**:主要功能入口要显眼易找
|
||||||
|
- **技术深度展现**:通过界面细节体现技术实力
|
||||||
|
- **用户引导清晰**:操作流程要直观易懂
|
||||||
|
- **错误处理完善**:异常情况要有友好提示
|
||||||
|
</guideline>
|
||||||
|
|
||||||
|
<process>
|
||||||
|
## 学术界面标准化流程
|
||||||
|
|
||||||
|
### 评分标准对齐检查
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
A[功能完整性检查] --> B[专业美观度评估]
|
||||||
|
B --> C[用户体验测试]
|
||||||
|
C --> D[技术深度体现]
|
||||||
|
D --> E[创新亮点识别]
|
||||||
|
E --> F[演示效果验证]
|
||||||
|
F --> G{达到学术标准?}
|
||||||
|
G -->|是| H[标准合格]
|
||||||
|
G -->|否| I[针对性改进]
|
||||||
|
I --> A
|
||||||
|
```
|
||||||
|
|
||||||
|
### 老师评分视角模拟
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
A[应用启动] --> B[第一印象评分]
|
||||||
|
B --> C[功能演示]
|
||||||
|
C --> D[交互体验]
|
||||||
|
D --> E[技术亮点]
|
||||||
|
E --> F[整体评价]
|
||||||
|
F --> G[最终评分]
|
||||||
|
```
|
||||||
|
|
||||||
|
**评分关键节点:**
|
||||||
|
1. **启动印象** (20%):应用启动速度和首屏效果
|
||||||
|
2. **功能展示** (30%):核心功能的界面表现
|
||||||
|
3. **交互体验** (25%):操作流程的流畅度
|
||||||
|
4. **技术深度** (15%):界面体现的技术水平
|
||||||
|
5. **创新亮点** (10%):超出预期的设计创新
|
||||||
|
|
||||||
|
### 差异化竞争策略
|
||||||
|
```mermaid
|
||||||
|
mindmap
|
||||||
|
root((竞争优势))
|
||||||
|
技术深度
|
||||||
|
复杂QSS样式
|
||||||
|
自定义控件
|
||||||
|
高级布局技巧
|
||||||
|
视觉设计
|
||||||
|
现代化UI风格
|
||||||
|
专业配色方案
|
||||||
|
精致图标设计
|
||||||
|
交互创新
|
||||||
|
流畅动画效果
|
||||||
|
智能操作引导
|
||||||
|
个性化设置
|
||||||
|
功能完整
|
||||||
|
全面功能覆盖
|
||||||
|
异常处理完善
|
||||||
|
性能优化到位
|
||||||
|
```
|
||||||
|
</process>
|
||||||
|
|
||||||
|
<criteria>
|
||||||
|
## 学术界面评分标准
|
||||||
|
|
||||||
|
### 功能完整性 (30分)
|
||||||
|
- ✅ 所有要求功能都有界面入口 (10分)
|
||||||
|
- ✅ 功能操作流程完整清晰 (10分)
|
||||||
|
- ✅ 异常情况处理完善 (5分)
|
||||||
|
- ✅ 界面与功能逻辑一致 (5分)
|
||||||
|
|
||||||
|
### 专业美观度 (25分)
|
||||||
|
- ✅ 整体视觉设计专业 (8分)
|
||||||
|
- ✅ 色彩搭配协调统一 (6分)
|
||||||
|
- ✅ 控件样式现代美观 (6分)
|
||||||
|
- ✅ 布局合理有序 (5分)
|
||||||
|
|
||||||
|
### 用户体验 (20分)
|
||||||
|
- ✅ 操作流程直观简洁 (8分)
|
||||||
|
- ✅ 界面响应及时准确 (6分)
|
||||||
|
- ✅ 错误提示友好明确 (3分)
|
||||||
|
- ✅ 学习成本低 (3分)
|
||||||
|
|
||||||
|
### 技术深度 (15分)
|
||||||
|
- ✅ Qt技术运用熟练 (6分)
|
||||||
|
- ✅ 代码结构清晰规范 (4分)
|
||||||
|
- ✅ 性能优化到位 (3分)
|
||||||
|
- ✅ 跨平台兼容性好 (2分)
|
||||||
|
|
||||||
|
### 创新亮点 (10分)
|
||||||
|
- ✅ 有超出基本要求的设计 (4分)
|
||||||
|
- ✅ 技术实现有创新性 (3分)
|
||||||
|
- ✅ 用户体验有独特之处 (2分)
|
||||||
|
- ✅ 整体方案有思考深度 (1分)
|
||||||
|
|
||||||
|
### 演示效果加分项
|
||||||
|
- 🌟 界面启动给人惊艳感 (+2分)
|
||||||
|
- 🌟 功能演示流畅无卡顿 (+2分)
|
||||||
|
- 🌟 细节处理体现工匠精神 (+1分)
|
||||||
|
- 🌟 整体方案体现团队协作 (+1分)
|
||||||
|
</criteria>
|
||||||
|
</execution>
|
@ -0,0 +1,266 @@
|
|||||||
|
# Qt 5.15 界面开发核心知识
|
||||||
|
|
||||||
|
## QSS样式表精通
|
||||||
|
|
||||||
|
### 基础语法结构
|
||||||
|
```css
|
||||||
|
/* 选择器语法 */
|
||||||
|
QWidget { background-color: #f0f0f0; }
|
||||||
|
QPushButton#myButton { color: blue; }
|
||||||
|
QLabel[class="title"] { font-size: 18px; }
|
||||||
|
|
||||||
|
/* 伪状态选择器 */
|
||||||
|
QPushButton:hover { background-color: #e0e0e0; }
|
||||||
|
QPushButton:pressed { background-color: #d0d0d0; }
|
||||||
|
QPushButton:disabled { color: gray; }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 现代化按钮样式
|
||||||
|
```css
|
||||||
|
QPushButton {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #3d8b40;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 输入框美化
|
||||||
|
```css
|
||||||
|
QLineEdit {
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLineEdit:focus {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 布局管理器精通
|
||||||
|
|
||||||
|
### QVBoxLayout 垂直布局
|
||||||
|
```cpp
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout;
|
||||||
|
layout->addWidget(titleLabel);
|
||||||
|
layout->addSpacing(10); // 添加间距
|
||||||
|
layout->addWidget(contentWidget);
|
||||||
|
layout->addStretch(); // 添加弹性空间
|
||||||
|
layout->setContentsMargins(20, 20, 20, 20); // 设置边距
|
||||||
|
```
|
||||||
|
|
||||||
|
### QGridLayout 网格布局
|
||||||
|
```cpp
|
||||||
|
QGridLayout *gridLayout = new QGridLayout;
|
||||||
|
gridLayout->addWidget(label1, 0, 0);
|
||||||
|
gridLayout->addWidget(lineEdit1, 0, 1);
|
||||||
|
gridLayout->addWidget(label2, 1, 0);
|
||||||
|
gridLayout->addWidget(lineEdit2, 1, 1);
|
||||||
|
gridLayout->setColumnStretch(1, 1); // 第二列可拉伸
|
||||||
|
```
|
||||||
|
|
||||||
|
### QHBoxLayout 水平布局
|
||||||
|
```cpp
|
||||||
|
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||||
|
buttonLayout->addStretch();
|
||||||
|
buttonLayout->addWidget(okButton);
|
||||||
|
buttonLayout->addWidget(cancelButton);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 控件美化技巧
|
||||||
|
|
||||||
|
### QTableWidget 表格美化
|
||||||
|
```css
|
||||||
|
QTableWidget {
|
||||||
|
gridline-color: #e0e0e0;
|
||||||
|
background-color: white;
|
||||||
|
alternate-background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget::item {
|
||||||
|
padding: 8px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget::item:selected {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHeaderView::section {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### QComboBox 下拉框美化
|
||||||
|
```css
|
||||||
|
QComboBox {
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox::drop-down {
|
||||||
|
border: none;
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox::down-arrow {
|
||||||
|
image: url(:/icons/down-arrow.png);
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 自定义控件开发
|
||||||
|
|
||||||
|
### 自定义按钮类
|
||||||
|
```cpp
|
||||||
|
class CustomButton : public QPushButton {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
CustomButton(const QString &text, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent *event) override;
|
||||||
|
void enterEvent(QEvent *event) override;
|
||||||
|
void leaveEvent(QEvent *event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_hovered;
|
||||||
|
QPropertyAnimation *m_animation;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 渐变背景实现
|
||||||
|
```cpp
|
||||||
|
void CustomWidget::paintEvent(QPaintEvent *event) {
|
||||||
|
QPainter painter(this);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
QLinearGradient gradient(0, 0, 0, height());
|
||||||
|
gradient.setColorAt(0, QColor(240, 240, 240));
|
||||||
|
gradient.setColorAt(1, QColor(220, 220, 220));
|
||||||
|
|
||||||
|
painter.fillRect(rect(), gradient);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 信号槽机制优化
|
||||||
|
|
||||||
|
### Lambda表达式连接
|
||||||
|
```cpp
|
||||||
|
connect(button, &QPushButton::clicked, [this]() {
|
||||||
|
// 处理点击事件
|
||||||
|
updateUI();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义信号定义
|
||||||
|
```cpp
|
||||||
|
class CustomWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void dataChanged(const QString &data);
|
||||||
|
void statusUpdated(int status);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void onDataReceived(const QByteArray &data);
|
||||||
|
void onStatusChanged(bool connected);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化技巧
|
||||||
|
|
||||||
|
### 减少重绘次数
|
||||||
|
```cpp
|
||||||
|
// 批量更新时禁用重绘
|
||||||
|
widget->setUpdatesEnabled(false);
|
||||||
|
// 执行多个更新操作
|
||||||
|
widget->setUpdatesEnabled(true);
|
||||||
|
widget->update(); // 手动触发重绘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用样式表缓存
|
||||||
|
```cpp
|
||||||
|
// 在类初始化时设置样式表,避免重复设置
|
||||||
|
static const QString buttonStyle =
|
||||||
|
"QPushButton { background-color: #4CAF50; }";
|
||||||
|
button->setStyleSheet(buttonStyle);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 响应式设计
|
||||||
|
|
||||||
|
### 自适应布局
|
||||||
|
```cpp
|
||||||
|
void MainWindow::resizeEvent(QResizeEvent *event) {
|
||||||
|
QMainWindow::resizeEvent(event);
|
||||||
|
|
||||||
|
// 根据窗口大小调整布局
|
||||||
|
if (width() < 800) {
|
||||||
|
// 小屏幕布局
|
||||||
|
switchToCompactLayout();
|
||||||
|
} else {
|
||||||
|
// 大屏幕布局
|
||||||
|
switchToNormalLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DPI适配
|
||||||
|
```cpp
|
||||||
|
// 获取系统DPI缩放比例
|
||||||
|
qreal dpiScale = qApp->devicePixelRatio();
|
||||||
|
int scaledSize = static_cast<int>(16 * dpiScale);
|
||||||
|
font.setPixelSize(scaledSize);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 国际化支持
|
||||||
|
|
||||||
|
### 文本国际化
|
||||||
|
```cpp
|
||||||
|
// 在代码中使用tr()函数
|
||||||
|
button->setText(tr("确定"));
|
||||||
|
label->setText(tr("用户名:"));
|
||||||
|
|
||||||
|
// 在.pro文件中添加
|
||||||
|
TRANSLATIONS += app_zh_CN.ts app_en_US.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## 调试和测试
|
||||||
|
|
||||||
|
### 样式表调试
|
||||||
|
```cpp
|
||||||
|
// 运行时修改样式表进行调试
|
||||||
|
#ifdef QT_DEBUG
|
||||||
|
widget->setStyleSheet("border: 1px solid red;"); // 调试边框
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### 性能监控
|
||||||
|
```cpp
|
||||||
|
// 使用QElapsedTimer监控性能
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
// 执行耗时操作
|
||||||
|
qDebug() << "操作耗时:" << timer.elapsed() << "ms";
|
||||||
|
```
|
@ -0,0 +1,19 @@
|
|||||||
|
<role>
|
||||||
|
<personality>
|
||||||
|
@!thought://remember
|
||||||
|
@!thought://recall
|
||||||
|
@!thought://ui-design-thinking
|
||||||
|
@!thought://academic-standards-awareness
|
||||||
|
</personality>
|
||||||
|
|
||||||
|
<principle>
|
||||||
|
@!execution://qt-optimization-workflow
|
||||||
|
@!execution://academic-ui-standards
|
||||||
|
</principle>
|
||||||
|
|
||||||
|
<knowledge>
|
||||||
|
@!knowledge://qt-ui-development
|
||||||
|
@!knowledge://ui-ux-principles
|
||||||
|
@!knowledge://academic-project-standards
|
||||||
|
</knowledge>
|
||||||
|
</role>
|
Binary file not shown.
@ -0,0 +1,14 @@
|
|||||||
|
cmake_minimum_required(VERSION 2.14)
|
||||||
|
project(faceLightClient2)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -O3 -march=native -Wall")
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include_directories(include)
|
||||||
|
link_directories(lib)
|
||||||
|
|
||||||
|
add_executable(faceLightClient2 main2.cpp)
|
||||||
|
target_link_libraries(faceLightClient2 libfaceLight_SDK_arm64.so)
|
||||||
|
|
||||||
|
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
|
@ -1,63 +0,0 @@
|
|||||||
# Qt/C++ build files
|
|
||||||
build/
|
|
||||||
bin/
|
|
||||||
*.o
|
|
||||||
*.obj
|
|
||||||
*.so
|
|
||||||
*.dll
|
|
||||||
*.dylib
|
|
||||||
*.a
|
|
||||||
*.lib
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
# Qt specific files
|
|
||||||
moc_*.cpp
|
|
||||||
moc_*.h
|
|
||||||
qrc_*.cpp
|
|
||||||
ui_*.h
|
|
||||||
Makefile*
|
|
||||||
*.pro.user
|
|
||||||
*.pro.user.*
|
|
||||||
|
|
||||||
# IDE files
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
*.kate-swp
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Temporary files
|
|
||||||
*.tmp
|
|
||||||
*.temp
|
|
||||||
.DS_Store
|
|
||||||
Thumbs.db
|
|
||||||
|
|
||||||
# Debug/Release directories
|
|
||||||
debug/
|
|
||||||
release/
|
|
||||||
Debug/
|
|
||||||
Release/
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
CMakeCache.txt
|
|
||||||
CMakeFiles/
|
|
||||||
cmake_install.cmake
|
|
||||||
|
|
||||||
# Android build files
|
|
||||||
android/build/
|
|
||||||
android/.gradle/
|
|
||||||
android/local.properties
|
|
||||||
|
|
||||||
# Backup files
|
|
||||||
*.bak
|
|
||||||
*.backup
|
|
||||||
*~
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
*.log
|
|
||||||
|
|
||||||
# Core dumps
|
|
||||||
core
|
|
||||||
core.*
|
|
||||||
|
|
||||||
# Documentation directory
|
|
||||||
doc/
|
|
Binary file not shown.
@ -0,0 +1,94 @@
|
|||||||
|
#ifndef FACELIGHTCONTROL_H
|
||||||
|
#define FACELIGHTCONTROL_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QProgressBar>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QFormLayout>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
|
namespace Ui {
|
||||||
|
class FaceLightControl;
|
||||||
|
}
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
class FaceLightControl : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
FaceLightControl(QWidget *parent = nullptr);
|
||||||
|
~FaceLightControl();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// 基础控制按钮
|
||||||
|
void on_setAllLeds_clicked();
|
||||||
|
void on_setSingleLed_clicked();
|
||||||
|
void on_startColorCycle_clicked();
|
||||||
|
void on_stopColorCycle_clicked();
|
||||||
|
void on_applyCustomPattern_clicked();
|
||||||
|
void on_turnOffAllLeds_clicked();
|
||||||
|
|
||||||
|
// SSH连接设置
|
||||||
|
void on_saveSshSettings_clicked();
|
||||||
|
void on_testConnection_clicked();
|
||||||
|
|
||||||
|
// 高级控制
|
||||||
|
void on_rgbAlternating_clicked();
|
||||||
|
void on_quickPresets_clicked();
|
||||||
|
|
||||||
|
// SSH进程处理
|
||||||
|
void onSshProcessFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
void onSshProcessError(QProcess::ProcessError error);
|
||||||
|
|
||||||
|
// 颜色循环定时器
|
||||||
|
void onColorCycleTimer();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::FaceLightControl *ui;
|
||||||
|
QProcess *sshProcess;
|
||||||
|
QString currentCommand;
|
||||||
|
|
||||||
|
// SSH连接信息
|
||||||
|
QString m_sshHost;
|
||||||
|
QString m_sshUser;
|
||||||
|
QString m_sshPassword;
|
||||||
|
QString m_jumpHost;
|
||||||
|
QString m_jumpUser;
|
||||||
|
QString m_jumpPassword;
|
||||||
|
|
||||||
|
// 颜色循环控制
|
||||||
|
QTimer *colorCycleTimer;
|
||||||
|
QStringList cycleColors;
|
||||||
|
int currentColorIndex;
|
||||||
|
bool isCycling;
|
||||||
|
|
||||||
|
// 核心方法
|
||||||
|
void executeSSHCommand(const QString &command, const QString &description);
|
||||||
|
void updateSshSettings();
|
||||||
|
|
||||||
|
// UI设置和状态更新
|
||||||
|
void setupUI();
|
||||||
|
void updateStatus(const QString &message, bool isError = false);
|
||||||
|
void setupColorCycle();
|
||||||
|
|
||||||
|
// 命令构建方法
|
||||||
|
QString buildFaceLightCommand(const QString &mode, const QString &color = "",
|
||||||
|
int ledIndex = -1, int delay = 2000,
|
||||||
|
int times = -1, const QString &pattern = "");
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FACELIGHTCONTROL_H
|
@ -0,0 +1,702 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>FaceLightControl</class>
|
||||||
|
<widget class="QMainWindow" name="FaceLightControl">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1000</width>
|
||||||
|
<height>900</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>机器狗面部灯光控制系统</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QMainWindow {
|
||||||
|
background-color: rgb(24, 33, 45);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton {
|
||||||
|
background-color: rgb(30, 44, 62);
|
||||||
|
color: rgb(220, 230, 240);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.5);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
min-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: rgb(50, 70, 95);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: rgb(40, 60, 85);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel {
|
||||||
|
color: rgb(220, 230, 240);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox, QSpinBox, QLineEdit {
|
||||||
|
background-color: rgb(30, 44, 62);
|
||||||
|
color: rgb(220, 230, 240);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextEdit {
|
||||||
|
background-color: rgb(15, 22, 32);
|
||||||
|
color: rgb(220, 230, 240);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.3);
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: "Courier New", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProgressBar {
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgb(30, 44, 62);
|
||||||
|
color: rgb(220, 230, 240);
|
||||||
|
}
|
||||||
|
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: rgba(0, 168, 255, 0.8);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QGroupBox {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgb(0, 168, 255);
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.4);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QGroupBox::title {
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
subcontrol-position: top center;
|
||||||
|
padding: 0 15px;
|
||||||
|
background-color: rgb(24, 33, 45);
|
||||||
|
}</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="spacing">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>30</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>20</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="titleLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>💡 机器狗面部灯光控制系统</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment">
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QLabel {
|
||||||
|
color: rgb(0, 168, 255);
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 20px;
|
||||||
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
|
||||||
|
stop:0 rgba(0, 168, 255, 0.1),
|
||||||
|
stop:1 rgba(0, 120, 180, 0.1));
|
||||||
|
border: 2px solid rgba(0, 168, 255, 0.3);
|
||||||
|
border-radius: 15px;
|
||||||
|
}</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="sshSettingsGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>🔗 SSH连接设置</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="sshLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="connectionLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="jumpHostGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>跳板机设置</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QGroupBox { font-size: 14px; color: rgb(180, 190, 200); }</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="jumpFormLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="jumpIpLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>跳板机IP:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditJumpIp">
|
||||||
|
<property name="text">
|
||||||
|
<string>192.168.12.1</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="jumpUserLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>用户名:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditJumpUsername">
|
||||||
|
<property name="text">
|
||||||
|
<string>pi</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="jumpPasswordLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>密码:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditJumpPassword">
|
||||||
|
<property name="text">
|
||||||
|
<string>123</string>
|
||||||
|
</property>
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::Password</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="targetHostGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>目标机器狗设置</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QGroupBox { font-size: 14px; color: rgb(180, 190, 200); }</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QFormLayout" name="targetFormLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="targetIpLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>机器狗IP:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditTargetIp">
|
||||||
|
<property name="text">
|
||||||
|
<string>192.168.123.13</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="targetUserLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>用户名:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditTargetUsername">
|
||||||
|
<property name="text">
|
||||||
|
<string>unitree</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="targetPasswordLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>密码:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="lineEditTargetPassword">
|
||||||
|
<property name="text">
|
||||||
|
<string>123</string>
|
||||||
|
</property>
|
||||||
|
<property name="echoMode">
|
||||||
|
<enum>QLineEdit::Password</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="sshButtonLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="saveSshSettings">
|
||||||
|
<property name="text">
|
||||||
|
<string>💾 保存设置</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="testConnection">
|
||||||
|
<property name="text">
|
||||||
|
<string>🔍 测试连接</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="connectionStatusLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>连接状态: 等待设置...</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">color: rgb(160, 170, 180); font-size: 12px;</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="mainControlLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="basicControlGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>🎨 基础控制</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="basicControlLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="colorFormLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="colorLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>颜色选择:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="colorComboBox">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Red</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Green</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Blue</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Yellow</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>White</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Black</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="ledIndexLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>LED索引:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="ledIndexSpinBox">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>11</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="setAllLeds">
|
||||||
|
<property name="text">
|
||||||
|
<string>💡 设置所有LED</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(45, 125, 65);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(65, 145, 85); }
|
||||||
|
QPushButton:pressed { background-color: rgb(55, 135, 75); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="setSingleLed">
|
||||||
|
<property name="text">
|
||||||
|
<string>🔆 设置单个LED</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(85, 125, 165);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(105, 145, 185); }
|
||||||
|
QPushButton:pressed { background-color: rgb(95, 135, 175); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="turnOffAllLeds">
|
||||||
|
<property name="text">
|
||||||
|
<string>🌑 关闭所有LED</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(120, 60, 60);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(140, 80, 80); }
|
||||||
|
QPushButton:pressed { background-color: rgb(130, 70, 70); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="advancedControlGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>⚡ 高级控制</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="advancedControlLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="cycleLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>🔄 颜色循环设置:</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">font-weight: bold; color: rgb(0, 168, 255);</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="cycleFormLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="cycleDelayLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>延时(ms):</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QSpinBox" name="cycleDelaySpinBox">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>10000</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>1000</number>
|
||||||
|
</property>
|
||||||
|
<property name="suffix">
|
||||||
|
<string> ms</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="cycleTimesLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>循环次数:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="cycleTimesSpinBox">
|
||||||
|
<property name="minimum">
|
||||||
|
<number>-1</number>
|
||||||
|
</property>
|
||||||
|
<property name="maximum">
|
||||||
|
<number>100</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>5</number>
|
||||||
|
</property>
|
||||||
|
<property name="specialValueText">
|
||||||
|
<string>无限循环</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="cycleButtonLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="startColorCycle">
|
||||||
|
<property name="text">
|
||||||
|
<string>▶️ 开始循环</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(45, 125, 65);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(65, 145, 85); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="stopColorCycle">
|
||||||
|
<property name="text">
|
||||||
|
<string>⏹️ 停止循环</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(165, 85, 45);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(185, 105, 65); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="patternLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>🎭 自定义图案:</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">font-weight: bold; color: rgb(0, 168, 255);</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="patternLineEdit">
|
||||||
|
<property name="placeholderText">
|
||||||
|
<string>例如: 0:red,3:green,6:blue</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="applyCustomPattern">
|
||||||
|
<property name="text">
|
||||||
|
<string>🎨 应用自定义图案</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="rgbAlternating">
|
||||||
|
<property name="text">
|
||||||
|
<string>🌈 RGB交替模式</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="presetGroup">
|
||||||
|
<property name="title">
|
||||||
|
<string>⚡ 快速预设</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="presetLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="preset1">
|
||||||
|
<property name="text">
|
||||||
|
<string>🔴 预设1: 红色</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(180, 60, 60);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(200, 80, 80); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="preset2">
|
||||||
|
<property name="text">
|
||||||
|
<string>🔵 预设2: 蓝色</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(60, 60, 180);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(80, 80, 200); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="preset3">
|
||||||
|
<property name="text">
|
||||||
|
<string>🟢 预设3: 绿色</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(60, 180, 60);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(80, 200, 80); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="preset4">
|
||||||
|
<property name="text">
|
||||||
|
<string>⚪ 预设4: 白色</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">QPushButton {
|
||||||
|
background-color: rgb(140, 140, 140);
|
||||||
|
color: rgb(30, 30, 30);
|
||||||
|
}
|
||||||
|
QPushButton:hover { background-color: rgb(160, 160, 160); }</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<spacer name="presetSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar">
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="textVisible">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="logLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>📋 执行日志:</string>
|
||||||
|
</property>
|
||||||
|
<property name="styleSheet">
|
||||||
|
<string notr="true">font-size: 16px; font-weight: bold; color: rgb(0, 168, 255);</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<widget class="QTextEdit" name="logTextEdit">
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>200</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="readOnly">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
|
||||||
|
<widget class="QMenuBar" name="menubar">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>1000</width>
|
||||||
|
<height>22</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
|
||||||
|
<widget class="QStatusBar" name="statusbar"/>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -0,0 +1,47 @@
|
|||||||
|
# 战场探索系统数据库配置文件示例
|
||||||
|
# Database Configuration Example for BattlefieldExplorationSystem
|
||||||
|
#
|
||||||
|
# 这是一个示例配置文件,展示了常见的配置选项
|
||||||
|
# This is an example configuration file showing common configuration options
|
||||||
|
|
||||||
|
[Database]
|
||||||
|
# 数据库服务器地址 (Database server host)
|
||||||
|
host=localhost
|
||||||
|
|
||||||
|
# 数据库端口 (Database port)
|
||||||
|
port=3306
|
||||||
|
|
||||||
|
# 数据库名称 (Database name)
|
||||||
|
databaseName=Client
|
||||||
|
|
||||||
|
# 数据库用户名 (Database username)
|
||||||
|
username=root
|
||||||
|
|
||||||
|
# 数据库密码 (Database password)
|
||||||
|
password=example_password
|
||||||
|
|
||||||
|
# 连接超时时间,单位毫秒 (Connection timeout in milliseconds)
|
||||||
|
connectionTimeout=30000
|
||||||
|
|
||||||
|
# 数据库驱动名称 (Database driver name)
|
||||||
|
driverName=QMYSQL
|
||||||
|
|
||||||
|
# 其他常见配置示例:
|
||||||
|
# Other common configuration examples:
|
||||||
|
|
||||||
|
# 使用不同的数据库服务器
|
||||||
|
# Using different database server
|
||||||
|
# host=192.168.1.100
|
||||||
|
# port=3306
|
||||||
|
|
||||||
|
# 使用不同的数据库名称
|
||||||
|
# Using different database name
|
||||||
|
# databaseName=BattlefieldSystem
|
||||||
|
|
||||||
|
# 使用不同的用户名
|
||||||
|
# Using different username
|
||||||
|
# username=battlefield_user
|
||||||
|
|
||||||
|
# 更长的连接超时时间
|
||||||
|
# Longer connection timeout
|
||||||
|
# connectionTimeout=60000
|
@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* @file DatabaseConfig.cpp
|
||||||
|
* @brief 数据库配置类实现
|
||||||
|
* @author BattlefieldExplorationSystem Team
|
||||||
|
* @date 2024-01-01
|
||||||
|
* @version 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/database/DatabaseConfig.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
// 静态成员初始化
|
||||||
|
DatabaseConfig* DatabaseConfig::m_instance = nullptr;
|
||||||
|
|
||||||
|
DatabaseConfig* DatabaseConfig::getInstance()
|
||||||
|
{
|
||||||
|
if (m_instance == nullptr) {
|
||||||
|
m_instance = new DatabaseConfig();
|
||||||
|
}
|
||||||
|
return m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConfig::DatabaseConfig()
|
||||||
|
: m_host("localhost")
|
||||||
|
, m_port(3306)
|
||||||
|
, m_databaseName("Client")
|
||||||
|
, m_username("root")
|
||||||
|
, m_password("hzk200407140238")
|
||||||
|
, m_connectTimeout(30)
|
||||||
|
, m_maxConnections(10)
|
||||||
|
, m_settings(nullptr)
|
||||||
|
{
|
||||||
|
initDefaultConfig();
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConfig::~DatabaseConfig()
|
||||||
|
{
|
||||||
|
if (m_settings) {
|
||||||
|
delete m_settings;
|
||||||
|
m_settings = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseConfig::initDefaultConfig()
|
||||||
|
{
|
||||||
|
// 初始化默认数据库配置
|
||||||
|
m_host = "localhost";
|
||||||
|
m_port = 3306;
|
||||||
|
m_databaseName = "Client";
|
||||||
|
m_username = "root";
|
||||||
|
m_password = "hzk200407140238";
|
||||||
|
m_connectTimeout = 30;
|
||||||
|
m_maxConnections = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseConfig::getHost() const
|
||||||
|
{
|
||||||
|
return m_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatabaseConfig::getPort() const
|
||||||
|
{
|
||||||
|
return m_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseConfig::getDatabaseName() const
|
||||||
|
{
|
||||||
|
return m_databaseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseConfig::getUsername() const
|
||||||
|
{
|
||||||
|
return m_username;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseConfig::getPassword() const
|
||||||
|
{
|
||||||
|
return m_password;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatabaseConfig::getConnectTimeout() const
|
||||||
|
{
|
||||||
|
return m_connectTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DatabaseConfig::getMaxConnections() const
|
||||||
|
{
|
||||||
|
return m_maxConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseConfig::setDatabaseConfig(const QString& host, int port, const QString& dbName,
|
||||||
|
const QString& username, const QString& password)
|
||||||
|
{
|
||||||
|
m_host = host;
|
||||||
|
m_port = port;
|
||||||
|
m_databaseName = dbName;
|
||||||
|
m_username = username;
|
||||||
|
m_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConfig::loadConfig(const QString& configPath)
|
||||||
|
{
|
||||||
|
QString configFile = configPath;
|
||||||
|
if (configFile.isEmpty()) {
|
||||||
|
// 使用默认配置文件路径
|
||||||
|
configFile = QCoreApplication::applicationDirPath() + "/config/database.ini";
|
||||||
|
|
||||||
|
// 如果应用程序目录没有配置文件,尝试用户配置目录
|
||||||
|
if (!QDir(configFile).exists()) {
|
||||||
|
QString userConfigDir = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
|
||||||
|
configFile = userConfigDir + "/BattlefieldExplorationSystem/database.ini";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settings) {
|
||||||
|
delete m_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_settings = new QSettings(configFile, QSettings::IniFormat);
|
||||||
|
|
||||||
|
if (!QDir(configFile).exists()) {
|
||||||
|
qDebug() << "Database config file not found, using defaults:" << configFile;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取数据库配置
|
||||||
|
m_settings->beginGroup("Database");
|
||||||
|
m_host = m_settings->value("host", m_host).toString();
|
||||||
|
m_port = m_settings->value("port", m_port).toInt();
|
||||||
|
m_databaseName = m_settings->value("databaseName", m_databaseName).toString();
|
||||||
|
m_username = m_settings->value("username", m_username).toString();
|
||||||
|
m_password = m_settings->value("password", m_password).toString();
|
||||||
|
m_settings->endGroup();
|
||||||
|
|
||||||
|
// 读取连接池配置
|
||||||
|
m_settings->beginGroup("ConnectionPool");
|
||||||
|
m_connectTimeout = m_settings->value("connectTimeout", m_connectTimeout).toInt();
|
||||||
|
m_maxConnections = m_settings->value("maxConnections", m_maxConnections).toInt();
|
||||||
|
m_settings->endGroup();
|
||||||
|
|
||||||
|
qDebug() << "Database config loaded from:" << configFile;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConfig::saveConfig(const QString& configPath)
|
||||||
|
{
|
||||||
|
QString configFile = configPath;
|
||||||
|
if (configFile.isEmpty()) {
|
||||||
|
configFile = QCoreApplication::applicationDirPath() + "/config/database.ini";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保配置目录存在
|
||||||
|
QDir configDir = QFileInfo(configFile).absoluteDir();
|
||||||
|
if (!configDir.exists()) {
|
||||||
|
configDir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_settings) {
|
||||||
|
delete m_settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_settings = new QSettings(configFile, QSettings::IniFormat);
|
||||||
|
|
||||||
|
// 保存数据库配置
|
||||||
|
m_settings->beginGroup("Database");
|
||||||
|
m_settings->setValue("host", m_host);
|
||||||
|
m_settings->setValue("port", m_port);
|
||||||
|
m_settings->setValue("databaseName", m_databaseName);
|
||||||
|
m_settings->setValue("username", m_username);
|
||||||
|
m_settings->setValue("password", m_password);
|
||||||
|
m_settings->endGroup();
|
||||||
|
|
||||||
|
// 保存连接池配置
|
||||||
|
m_settings->beginGroup("ConnectionPool");
|
||||||
|
m_settings->setValue("connectTimeout", m_connectTimeout);
|
||||||
|
m_settings->setValue("maxConnections", m_maxConnections);
|
||||||
|
m_settings->endGroup();
|
||||||
|
|
||||||
|
m_settings->sync();
|
||||||
|
|
||||||
|
qDebug() << "Database config saved to:" << configFile;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConnectionInfo DatabaseConfig::getConnectionInfo() const
|
||||||
|
{
|
||||||
|
DatabaseConnectionInfo info;
|
||||||
|
info.hostName = m_host;
|
||||||
|
info.port = m_port;
|
||||||
|
info.databaseName = m_databaseName;
|
||||||
|
info.username = m_username;
|
||||||
|
info.password = m_password;
|
||||||
|
return info;
|
||||||
|
}
|
@ -0,0 +1,348 @@
|
|||||||
|
/**
|
||||||
|
* @file DatabaseHelper.cpp
|
||||||
|
* @brief 数据库助手类实现
|
||||||
|
* @author BattlefieldExplorationSystem Team
|
||||||
|
* @date 2024-01-01
|
||||||
|
* @version 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/database/DatabaseHelper.h"
|
||||||
|
#include "core/database/DatabaseConfig.h"
|
||||||
|
#include <QSqlDriver>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
// 静态成员初始化
|
||||||
|
DatabaseHelper* DatabaseHelper::m_instance = nullptr;
|
||||||
|
QMutex DatabaseHelper::m_mutex;
|
||||||
|
QMap<QString, QSqlDatabase> DatabaseHelper::m_connections;
|
||||||
|
int DatabaseHelper::m_connectionCounter = 0;
|
||||||
|
|
||||||
|
DatabaseHelper* DatabaseHelper::getInstance()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
if (m_instance == nullptr) {
|
||||||
|
m_instance = new DatabaseHelper();
|
||||||
|
}
|
||||||
|
return m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseHelper::DatabaseHelper()
|
||||||
|
{
|
||||||
|
// 初始化数据库助手
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseHelper::~DatabaseHelper()
|
||||||
|
{
|
||||||
|
closeAllConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseHelper::generateConnectionName()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
return QString("Connection_%1_%2").arg(++m_connectionCounter).arg(reinterpret_cast<quintptr>(QThread::currentThreadId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase DatabaseHelper::createConnection(const QString& connectionName)
|
||||||
|
{
|
||||||
|
QString connName = connectionName;
|
||||||
|
if (connName.isEmpty()) {
|
||||||
|
connName = generateConnectionName();
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
|
// 如果连接已存在且有效,直接返回
|
||||||
|
if (m_connections.contains(connName)) {
|
||||||
|
QSqlDatabase existingDb = m_connections[connName];
|
||||||
|
if (existingDb.isValid() && existingDb.isOpen()) {
|
||||||
|
return existingDb;
|
||||||
|
} else {
|
||||||
|
// 移除无效连接
|
||||||
|
m_connections.remove(connName);
|
||||||
|
QSqlDatabase::removeDatabase(connName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新连接
|
||||||
|
QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", connName);
|
||||||
|
|
||||||
|
if (!configureConnection(db)) {
|
||||||
|
qWarning() << "Failed to configure database connection:" << connName;
|
||||||
|
QSqlDatabase::removeDatabase(connName);
|
||||||
|
return QSqlDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!db.open()) {
|
||||||
|
qWarning() << "Failed to open database connection:" << connName << db.lastError().text();
|
||||||
|
QSqlDatabase::removeDatabase(connName);
|
||||||
|
return QSqlDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_connections[connName] = db;
|
||||||
|
qDebug() << "Database connection created:" << connName;
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase DatabaseHelper::createTempConnection(const QString& connectionName)
|
||||||
|
{
|
||||||
|
QString tempConnName = connectionName + "_" + QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
|
return createConnection(tempConnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::configureConnection(QSqlDatabase& db)
|
||||||
|
{
|
||||||
|
DatabaseConfig* config = DatabaseConfig::getInstance();
|
||||||
|
|
||||||
|
db.setHostName(config->getHost());
|
||||||
|
db.setPort(config->getPort());
|
||||||
|
db.setDatabaseName(config->getDatabaseName());
|
||||||
|
db.setUserName(config->getUsername());
|
||||||
|
db.setPassword(config->getPassword());
|
||||||
|
|
||||||
|
// 设置连接选项
|
||||||
|
db.setConnectOptions("MYSQL_OPT_CONNECT_TIMEOUT=" + QString::number(config->getConnectTimeout()) +
|
||||||
|
";MYSQL_OPT_RECONNECT=1");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseHelper::closeConnection(const QString& connectionName)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
|
if (m_connections.contains(connectionName)) {
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
if (db.isOpen()) {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
m_connections.remove(connectionName);
|
||||||
|
QSqlDatabase::removeDatabase(connectionName);
|
||||||
|
qDebug() << "Database connection closed:" << connectionName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseHelper::closeAllConnections()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
|
QStringList connectionNames = m_connections.keys();
|
||||||
|
for (const QString& name : connectionNames) {
|
||||||
|
QSqlDatabase db = m_connections[name];
|
||||||
|
if (db.isOpen()) {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
QSqlDatabase::removeDatabase(name);
|
||||||
|
}
|
||||||
|
m_connections.clear();
|
||||||
|
qDebug() << "All database connections closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::isConnectionValid(const QString& connectionName)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
||||||
|
if (!m_connections.contains(connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
return db.isValid() && db.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery DatabaseHelper::executeQuery(const QString& query, const QString& connectionName)
|
||||||
|
{
|
||||||
|
QString connName = connectionName;
|
||||||
|
if (connName.isEmpty()) {
|
||||||
|
connName = generateConnectionName();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = createConnection(connName);
|
||||||
|
if (!db.isValid()) {
|
||||||
|
qWarning() << "Invalid database connection for query execution";
|
||||||
|
return QSqlQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlQuery sqlQuery(db);
|
||||||
|
if (!sqlQuery.exec(query)) {
|
||||||
|
qWarning() << "Query execution failed:" << query << sqlQuery.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
return sqlQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::beginTransaction(const QString& connectionName)
|
||||||
|
{
|
||||||
|
if (!isConnectionValid(connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
return db.transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::commitTransaction(const QString& connectionName)
|
||||||
|
{
|
||||||
|
if (!isConnectionValid(connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
return db.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::rollbackTransaction(const QString& connectionName)
|
||||||
|
{
|
||||||
|
if (!isConnectionValid(connectionName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
return db.rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseHelper::getLastError(const QString& connectionName)
|
||||||
|
{
|
||||||
|
if (!isConnectionValid(connectionName)) {
|
||||||
|
return "Invalid connection";
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase db = m_connections[connectionName];
|
||||||
|
return db.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::testConnection()
|
||||||
|
{
|
||||||
|
QSqlDatabase testDb = createConnection("TestConnection");
|
||||||
|
bool success = testDb.isValid() && testDb.isOpen();
|
||||||
|
closeConnection("TestConnection");
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::initializeDatabase()
|
||||||
|
{
|
||||||
|
qDebug() << "Initializing database...";
|
||||||
|
|
||||||
|
if (!testConnection()) {
|
||||||
|
qWarning() << "Database connection test failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::createTables()
|
||||||
|
{
|
||||||
|
QSqlDatabase db = createConnection("InitConnection");
|
||||||
|
if (!db.isValid()) {
|
||||||
|
qWarning() << "Failed to create database connection for table creation";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = true;
|
||||||
|
success &= createDevicesTable(db);
|
||||||
|
success &= createOperationLogsTable(db);
|
||||||
|
success &= createSystemConfigTable(db);
|
||||||
|
|
||||||
|
closeConnection("InitConnection");
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
qDebug() << "Database tables created successfully";
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to create some database tables";
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::createDevicesTable(QSqlDatabase& db)
|
||||||
|
{
|
||||||
|
QSqlQuery query(db);
|
||||||
|
QString sql = R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS devices (
|
||||||
|
id VARCHAR(50) PRIMARY KEY,
|
||||||
|
name VARCHAR(100) NOT NULL,
|
||||||
|
device_type ENUM('uav', 'dog') NOT NULL,
|
||||||
|
ip VARCHAR(15) NOT NULL,
|
||||||
|
port INT NOT NULL,
|
||||||
|
state INT NOT NULL DEFAULT 0,
|
||||||
|
longitude DOUBLE DEFAULT 0.0,
|
||||||
|
latitude DOUBLE DEFAULT 0.0,
|
||||||
|
signal_strength INT DEFAULT 0,
|
||||||
|
battery_level INT DEFAULT 100,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_device_type (device_type),
|
||||||
|
INDEX idx_state (state)
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!query.exec(sql)) {
|
||||||
|
qWarning() << "Failed to create devices table:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::createOperationLogsTable(QSqlDatabase& db)
|
||||||
|
{
|
||||||
|
QSqlQuery query(db);
|
||||||
|
QString sql = R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS device_operation_logs (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
device_id VARCHAR(50) NOT NULL,
|
||||||
|
device_type ENUM('uav', 'dog') NOT NULL,
|
||||||
|
operation VARCHAR(100) NOT NULL,
|
||||||
|
operation_result VARCHAR(20) NOT NULL,
|
||||||
|
operator VARCHAR(50) NOT NULL,
|
||||||
|
operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_device_id (device_id),
|
||||||
|
INDEX idx_operation_time (operation_time)
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!query.exec(sql)) {
|
||||||
|
qWarning() << "Failed to create operation logs table:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseHelper::createSystemConfigTable(QSqlDatabase& db)
|
||||||
|
{
|
||||||
|
QSqlQuery query(db);
|
||||||
|
QString sql = R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS system_config (
|
||||||
|
config_key VARCHAR(100) PRIMARY KEY,
|
||||||
|
config_value TEXT,
|
||||||
|
description VARCHAR(255),
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!query.exec(sql)) {
|
||||||
|
qWarning() << "Failed to create system config table:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插入默认配置
|
||||||
|
QString insertDefaults = R"(
|
||||||
|
INSERT IGNORE INTO system_config (config_key, config_value, description) VALUES
|
||||||
|
('map.default_center_lon', '116.4', '默认地图中心经度'),
|
||||||
|
('map.default_center_lat', '39.9', '默认地图中心纬度'),
|
||||||
|
('system.version', '2.0.0', '系统版本'),
|
||||||
|
('system.name', 'BattlefieldExplorationSystem', '系统名称')
|
||||||
|
)";
|
||||||
|
|
||||||
|
if (!query.exec(insertDefaults)) {
|
||||||
|
qWarning() << "Failed to insert default config:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,475 @@
|
|||||||
|
/**
|
||||||
|
* @file DatabaseManager.cpp
|
||||||
|
* @brief 数据库连接管理器实现
|
||||||
|
* @author BattlefieldExplorationSystem Team
|
||||||
|
* @date 2025-06-30
|
||||||
|
* @version 2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core/database/DatabaseManager.h"
|
||||||
|
#include "utils/ConfigManager.h"
|
||||||
|
|
||||||
|
// C++标准库头文件
|
||||||
|
#include <chrono>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
// Qt头文件
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
// DatabaseConnection 实现
|
||||||
|
|
||||||
|
DatabaseConnection::DatabaseConnection(const QString& connectionName, bool autoReconnect)
|
||||||
|
: m_connectionName(connectionName)
|
||||||
|
, m_autoReconnect(autoReconnect)
|
||||||
|
, m_isValid(false)
|
||||||
|
{
|
||||||
|
if (QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
m_database = QSqlDatabase::database(m_connectionName);
|
||||||
|
} else {
|
||||||
|
// 创建新连接
|
||||||
|
ConfigManager& config = ConfigManager::getInstance();
|
||||||
|
|
||||||
|
m_database = QSqlDatabase::addDatabase("QMYSQL", m_connectionName);
|
||||||
|
m_database.setHostName(config.getDatabaseHost());
|
||||||
|
m_database.setPort(config.getDatabasePort());
|
||||||
|
m_database.setDatabaseName(config.getDatabaseName());
|
||||||
|
m_database.setUserName(config.getDatabaseUser());
|
||||||
|
|
||||||
|
QString password = config.getDatabasePassword();
|
||||||
|
if (password.isEmpty()) {
|
||||||
|
qCritical() << "Database password not configured! Set BES_DB_PASSWORD environment variable.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_database.setPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试打开连接
|
||||||
|
m_isValid = m_database.open();
|
||||||
|
if (!m_isValid) {
|
||||||
|
qWarning() << "Failed to open database connection:" << m_database.lastError().text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConnection::~DatabaseConnection()
|
||||||
|
{
|
||||||
|
if (m_database.isOpen()) {
|
||||||
|
m_database.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
QSqlDatabase::removeDatabase(m_connectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConnection::DatabaseConnection(DatabaseConnection&& other) noexcept
|
||||||
|
: m_connectionName(std::move(other.m_connectionName))
|
||||||
|
, m_database(std::move(other.m_database))
|
||||||
|
, m_autoReconnect(other.m_autoReconnect)
|
||||||
|
, m_isValid(other.m_isValid)
|
||||||
|
{
|
||||||
|
other.m_isValid = false;
|
||||||
|
other.m_connectionName.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseConnection& DatabaseConnection::operator=(DatabaseConnection&& other) noexcept
|
||||||
|
{
|
||||||
|
if (this != &other) {
|
||||||
|
// 清理当前资源
|
||||||
|
if (m_database.isOpen()) {
|
||||||
|
m_database.close();
|
||||||
|
}
|
||||||
|
if (!m_connectionName.isEmpty() && QSqlDatabase::contains(m_connectionName)) {
|
||||||
|
QSqlDatabase::removeDatabase(m_connectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动资源
|
||||||
|
m_connectionName = std::move(other.m_connectionName);
|
||||||
|
m_database = std::move(other.m_database);
|
||||||
|
m_autoReconnect = other.m_autoReconnect;
|
||||||
|
m_isValid = other.m_isValid;
|
||||||
|
|
||||||
|
other.m_isValid = false;
|
||||||
|
other.m_connectionName.clear();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConnection::isValid() const
|
||||||
|
{
|
||||||
|
return m_isValid && m_database.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase& DatabaseConnection::database()
|
||||||
|
{
|
||||||
|
if (!isValid()) {
|
||||||
|
if (m_autoReconnect && !reconnect()) {
|
||||||
|
throw std::runtime_error("Database connection is not available and reconnection failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_database;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QSqlDatabase& DatabaseConnection::database() const
|
||||||
|
{
|
||||||
|
if (!isValid()) {
|
||||||
|
throw std::runtime_error("Database connection is not available");
|
||||||
|
}
|
||||||
|
return m_database;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<QSqlQuery> DatabaseConnection::executeQuery(const QString& sql)
|
||||||
|
{
|
||||||
|
auto query = std::make_unique<QSqlQuery>(database());
|
||||||
|
|
||||||
|
if (!query->exec(sql)) {
|
||||||
|
qWarning() << "Query execution failed:" << query->lastError().text();
|
||||||
|
qWarning() << "SQL:" << sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<QSqlQuery> DatabaseConnection::executePreparedQuery(const QString& sql,
|
||||||
|
const QVariantList& bindings)
|
||||||
|
{
|
||||||
|
auto query = std::make_unique<QSqlQuery>(database());
|
||||||
|
|
||||||
|
if (!query->prepare(sql)) {
|
||||||
|
qWarning() << "Query preparation failed:" << query->lastError().text();
|
||||||
|
qWarning() << "SQL:" << sql;
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定参数
|
||||||
|
for (const auto& binding : bindings) {
|
||||||
|
query->addBindValue(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query->exec()) {
|
||||||
|
qWarning() << "Prepared query execution failed:" << query->lastError().text();
|
||||||
|
qWarning() << "SQL:" << sql;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConnection::beginTransaction()
|
||||||
|
{
|
||||||
|
if (!isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_database.transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConnection::commitTransaction()
|
||||||
|
{
|
||||||
|
if (!isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_database.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConnection::rollbackTransaction()
|
||||||
|
{
|
||||||
|
if (!isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return m_database.rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlError DatabaseConnection::lastError() const
|
||||||
|
{
|
||||||
|
return m_database.lastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseConnection::reconnect()
|
||||||
|
{
|
||||||
|
if (m_database.isOpen()) {
|
||||||
|
m_database.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_isValid = m_database.open();
|
||||||
|
if (!m_isValid) {
|
||||||
|
qWarning() << "Reconnection failed:" << m_database.lastError().text();
|
||||||
|
} else {
|
||||||
|
qDebug() << "Database reconnection successful for:" << m_connectionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseManager 实现
|
||||||
|
|
||||||
|
std::unique_ptr<DatabaseManager> DatabaseManager::m_instance = nullptr;
|
||||||
|
std::mutex DatabaseManager::m_instanceMutex;
|
||||||
|
|
||||||
|
DatabaseManager& DatabaseManager::getInstance()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_instanceMutex);
|
||||||
|
if (!m_instance) {
|
||||||
|
m_instance.reset(new DatabaseManager());
|
||||||
|
}
|
||||||
|
return *m_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseManager::DatabaseManager(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_maxConnections(10)
|
||||||
|
, m_connectionCounter(0)
|
||||||
|
, m_initialized(false)
|
||||||
|
, m_databaseAvailable(false)
|
||||||
|
, m_configManager(&ConfigManager::getInstance())
|
||||||
|
, m_totalConnectionsCreated(0)
|
||||||
|
, m_totalQueriesExecuted(0)
|
||||||
|
, m_failedConnections(0)
|
||||||
|
{
|
||||||
|
// 私有构造函数
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseManager::~DatabaseManager()
|
||||||
|
{
|
||||||
|
closeAllConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseManager::initialize(int maxConnections)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
if (m_initialized) {
|
||||||
|
qWarning() << "DatabaseManager already initialized";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_maxConnections = maxConnections;
|
||||||
|
|
||||||
|
// 确保配置管理器已初始化
|
||||||
|
if (!m_configManager->contains("database/host")) {
|
||||||
|
if (!m_configManager->initialize()) {
|
||||||
|
qCritical() << "Failed to initialize ConfigManager";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试初始连接
|
||||||
|
auto testConnection = getConnection("test_connection");
|
||||||
|
if (testConnection && testConnection->isValid()) {
|
||||||
|
m_databaseAvailable = true;
|
||||||
|
qDebug() << "Database is available";
|
||||||
|
} else {
|
||||||
|
m_databaseAvailable = false;
|
||||||
|
qWarning() << "Database is not available";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动健康检查
|
||||||
|
startHealthCheckTimer();
|
||||||
|
|
||||||
|
m_initialized = true;
|
||||||
|
|
||||||
|
emit databaseAvailabilityChanged(m_databaseAvailable);
|
||||||
|
|
||||||
|
qDebug() << "DatabaseManager initialized with max connections:" << m_maxConnections;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<DatabaseConnection> DatabaseManager::getConnection(const QString& connectionName)
|
||||||
|
{
|
||||||
|
std::unique_ptr<DatabaseConnection> connection;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
QString actualConnectionName = connectionName.isEmpty() ?
|
||||||
|
generateConnectionName() : connectionName;
|
||||||
|
|
||||||
|
// 检查是否超过最大连接数
|
||||||
|
if (m_activeConnections.size() >= static_cast<size_t>(m_maxConnections)) {
|
||||||
|
qWarning() << "Maximum connections reached:" << m_maxConnections;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
connection = std::make_unique<DatabaseConnection>(actualConnectionName);
|
||||||
|
|
||||||
|
if (connection->isValid()) {
|
||||||
|
m_activeConnections.push_back(actualConnectionName);
|
||||||
|
++m_totalConnectionsCreated;
|
||||||
|
} else {
|
||||||
|
++m_failedConnections;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送状态更新信号
|
||||||
|
emit poolStatusChanged(static_cast<int>(m_activeConnections.size()), m_maxConnections);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseManager::testConnection()
|
||||||
|
{
|
||||||
|
auto connection = getConnection("health_check");
|
||||||
|
if (!connection || !connection->isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto query = connection->executeQuery("SELECT 1");
|
||||||
|
return query && query->next();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseManager::getPoolStatistics() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_statsMutex);
|
||||||
|
|
||||||
|
return QString("DatabaseManager Statistics:\n"
|
||||||
|
"- Active connections: %1/%2\n"
|
||||||
|
"- Total connections created: %3\n"
|
||||||
|
"- Total queries executed: %4\n"
|
||||||
|
"- Failed connections: %5\n"
|
||||||
|
"- Database available: %6")
|
||||||
|
.arg(m_activeConnections.size())
|
||||||
|
.arg(m_maxConnections)
|
||||||
|
.arg(m_totalConnectionsCreated)
|
||||||
|
.arg(m_totalQueriesExecuted)
|
||||||
|
.arg(m_failedConnections)
|
||||||
|
.arg(m_databaseAvailable ? "Yes" : "No");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseManager::closeAllConnections()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
// 移除所有活跃连接
|
||||||
|
for (const auto& connectionName : m_activeConnections) {
|
||||||
|
if (QSqlDatabase::contains(connectionName)) {
|
||||||
|
QSqlDatabase::removeDatabase(connectionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_activeConnections.clear();
|
||||||
|
m_availableConnections.clear();
|
||||||
|
|
||||||
|
qDebug() << "All database connections closed";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseManager::isDatabaseAvailable() const
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
return m_databaseAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseManager::performHealthCheck()
|
||||||
|
{
|
||||||
|
bool wasAvailable = m_databaseAvailable;
|
||||||
|
m_databaseAvailable = testConnection();
|
||||||
|
|
||||||
|
if (wasAvailable != m_databaseAvailable) {
|
||||||
|
emit databaseAvailabilityChanged(m_databaseAvailable);
|
||||||
|
qDebug() << "Database availability changed to:" << m_databaseAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理失效的连接
|
||||||
|
cleanupIdleConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseManager::cleanupIdleConnections()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
|
||||||
|
// 移除无效的连接名称
|
||||||
|
auto it = std::remove_if(m_activeConnections.begin(), m_activeConnections.end(),
|
||||||
|
[](const QString& connectionName) {
|
||||||
|
if (!QSqlDatabase::contains(connectionName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QSqlDatabase db = QSqlDatabase::database(connectionName);
|
||||||
|
return !db.isOpen();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it != m_activeConnections.end()) {
|
||||||
|
int cleaned = static_cast<int>(std::distance(it, m_activeConnections.end()));
|
||||||
|
m_activeConnections.erase(it, m_activeConnections.end());
|
||||||
|
qDebug() << "Cleaned up" << cleaned << "idle connections";
|
||||||
|
|
||||||
|
emit poolStatusChanged(static_cast<int>(m_activeConnections.size()), m_maxConnections);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString DatabaseManager::generateConnectionName()
|
||||||
|
{
|
||||||
|
return QString("connection_%1_%2")
|
||||||
|
.arg(reinterpret_cast<qintptr>(QThread::currentThreadId()))
|
||||||
|
.arg(++m_connectionCounter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DatabaseManager::startHealthCheckTimer()
|
||||||
|
{
|
||||||
|
m_healthCheckTimer = std::make_unique<QTimer>(this);
|
||||||
|
connect(m_healthCheckTimer.get(), &QTimer::timeout,
|
||||||
|
this, &DatabaseManager::performHealthCheck);
|
||||||
|
|
||||||
|
// 每30秒检查一次
|
||||||
|
m_healthCheckTimer->start(30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseTransaction 实现
|
||||||
|
|
||||||
|
DatabaseTransaction::DatabaseTransaction(DatabaseConnection& connection)
|
||||||
|
: m_connection(connection)
|
||||||
|
, m_committed(false)
|
||||||
|
, m_active(false)
|
||||||
|
{
|
||||||
|
m_active = m_connection.beginTransaction();
|
||||||
|
if (!m_active) {
|
||||||
|
qWarning() << "Failed to begin transaction:" << m_connection.lastError().text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseTransaction::~DatabaseTransaction()
|
||||||
|
{
|
||||||
|
if (m_active && !m_committed) {
|
||||||
|
// 自动回滚未提交的事务
|
||||||
|
rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseTransaction::commit()
|
||||||
|
{
|
||||||
|
if (!m_active || m_committed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = m_connection.commitTransaction();
|
||||||
|
if (success) {
|
||||||
|
m_committed = true;
|
||||||
|
m_active = false;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Failed to commit transaction:" << m_connection.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseTransaction::rollback()
|
||||||
|
{
|
||||||
|
if (!m_active) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = m_connection.rollbackTransaction();
|
||||||
|
m_active = false;
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
qWarning() << "Failed to rollback transaction:" << m_connection.lastError().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DatabaseTransaction::isActive() const
|
||||||
|
{
|
||||||
|
return m_active;
|
||||||
|
}
|
@ -0,0 +1,545 @@
|
|||||||
|
/**
|
||||||
|
* @file DroneControlDialog.cpp
|
||||||
|
* @brief 无人机控制对话框实现
|
||||||
|
* @author Qt UI Optimizer
|
||||||
|
* @date 2024-07-04
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui/dialogs/DroneControlDialog.h"
|
||||||
|
#include "styles/ModernStyleManager.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDesktopWidget>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
DroneControlDialog::DroneControlDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_mainLayout(nullptr)
|
||||||
|
, m_contentLayout(nullptr)
|
||||||
|
, m_isMappingActive(false)
|
||||||
|
, m_isNavigationActive(false)
|
||||||
|
, m_isPhotoTransmissionActive(false)
|
||||||
|
, m_isPersonRecognitionActive(false)
|
||||||
|
, m_isFlying(false)
|
||||||
|
, m_statusUpdateTimer(new QTimer(this))
|
||||||
|
{
|
||||||
|
setupUI();
|
||||||
|
applyStyles();
|
||||||
|
connectSignals();
|
||||||
|
|
||||||
|
// 启动状态更新定时器
|
||||||
|
m_statusUpdateTimer->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
DroneControlDialog::~DroneControlDialog()
|
||||||
|
{
|
||||||
|
if (m_statusUpdateTimer) {
|
||||||
|
m_statusUpdateTimer->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::setupUI()
|
||||||
|
{
|
||||||
|
setWindowTitle("🚁 无人机控制中心");
|
||||||
|
setModal(false);
|
||||||
|
setMinimumSize(900, 700);
|
||||||
|
resize(1000, 750);
|
||||||
|
|
||||||
|
// 窗口居中显示
|
||||||
|
QRect screenGeometry = QApplication::desktop()->screenGeometry();
|
||||||
|
int x = (screenGeometry.width() - this->width()) / 2;
|
||||||
|
int y = (screenGeometry.height() - this->height()) / 2;
|
||||||
|
move(x, y);
|
||||||
|
|
||||||
|
m_mainLayout = new QVBoxLayout(this);
|
||||||
|
m_mainLayout->setSpacing(20);
|
||||||
|
m_mainLayout->setContentsMargins(20, 20, 20, 20);
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
QLabel *titleLabel = new QLabel("🚁 无人机控制中心");
|
||||||
|
titleLabel->setObjectName("DialogTitle");
|
||||||
|
titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
titleLabel->setStyleSheet(
|
||||||
|
"font-size: 24px; "
|
||||||
|
"font-weight: bold; "
|
||||||
|
"color: #0078d4; "
|
||||||
|
"padding: 10px; "
|
||||||
|
"border-bottom: 2px solid #0078d4; "
|
||||||
|
"margin-bottom: 10px;"
|
||||||
|
);
|
||||||
|
m_mainLayout->addWidget(titleLabel);
|
||||||
|
|
||||||
|
// 主内容区域
|
||||||
|
m_contentLayout = new QHBoxLayout();
|
||||||
|
m_contentLayout->setSpacing(20);
|
||||||
|
|
||||||
|
setupFlightControlModule();
|
||||||
|
setupMissionControlModule();
|
||||||
|
setupStatusMonitorModule();
|
||||||
|
|
||||||
|
m_mainLayout->addLayout(m_contentLayout);
|
||||||
|
|
||||||
|
// 底部按钮
|
||||||
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||||
|
buttonLayout->addStretch();
|
||||||
|
|
||||||
|
QPushButton *closeBtn = new QPushButton("关闭");
|
||||||
|
closeBtn->setObjectName("CloseBtn");
|
||||||
|
closeBtn->setMinimumSize(100, 40);
|
||||||
|
connect(closeBtn, &QPushButton::clicked, this, &QDialog::close);
|
||||||
|
|
||||||
|
buttonLayout->addWidget(closeBtn);
|
||||||
|
m_mainLayout->addLayout(buttonLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::setupFlightControlModule()
|
||||||
|
{
|
||||||
|
m_flightControlGroup = new QGroupBox("✈️ 飞行控制");
|
||||||
|
m_flightControlGroup->setObjectName("ControlGroup");
|
||||||
|
m_flightControlGroup->setMinimumWidth(280);
|
||||||
|
|
||||||
|
QVBoxLayout *flightLayout = new QVBoxLayout(m_flightControlGroup);
|
||||||
|
flightLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 基础飞行控制按钮
|
||||||
|
QGridLayout *basicControlLayout = new QGridLayout();
|
||||||
|
basicControlLayout->setSpacing(10);
|
||||||
|
|
||||||
|
m_takeoffBtn = new QPushButton("🚀 起飞");
|
||||||
|
m_takeoffBtn->setObjectName("PrimaryBtn");
|
||||||
|
m_takeoffBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
m_landBtn = new QPushButton("🛬 降落");
|
||||||
|
m_landBtn->setObjectName("WarningBtn");
|
||||||
|
m_landBtn->setMinimumHeight(50);
|
||||||
|
m_landBtn->setEnabled(false);
|
||||||
|
|
||||||
|
m_hoverBtn = new QPushButton("⏸️ 悬停");
|
||||||
|
m_hoverBtn->setObjectName("InfoBtn");
|
||||||
|
m_hoverBtn->setMinimumHeight(50);
|
||||||
|
m_hoverBtn->setEnabled(false);
|
||||||
|
|
||||||
|
m_returnHomeBtn = new QPushButton("🏠 返航");
|
||||||
|
m_returnHomeBtn->setObjectName("SuccessBtn");
|
||||||
|
m_returnHomeBtn->setMinimumHeight(50);
|
||||||
|
m_returnHomeBtn->setEnabled(false);
|
||||||
|
|
||||||
|
basicControlLayout->addWidget(m_takeoffBtn, 0, 0);
|
||||||
|
basicControlLayout->addWidget(m_landBtn, 0, 1);
|
||||||
|
basicControlLayout->addWidget(m_hoverBtn, 1, 0);
|
||||||
|
basicControlLayout->addWidget(m_returnHomeBtn, 1, 1);
|
||||||
|
|
||||||
|
flightLayout->addLayout(basicControlLayout);
|
||||||
|
|
||||||
|
// 高度和速度控制
|
||||||
|
QLabel *altitudeLabel = new QLabel("飞行高度 (m):");
|
||||||
|
m_altitudeSlider = new QSlider(Qt::Horizontal);
|
||||||
|
m_altitudeSlider->setRange(1, 100);
|
||||||
|
m_altitudeSlider->setValue(10);
|
||||||
|
m_altitudeSlider->setEnabled(false);
|
||||||
|
|
||||||
|
QLabel *speedLabel = new QLabel("飞行速度 (m/s):");
|
||||||
|
m_speedSlider = new QSlider(Qt::Horizontal);
|
||||||
|
m_speedSlider->setRange(1, 20);
|
||||||
|
m_speedSlider->setValue(5);
|
||||||
|
m_speedSlider->setEnabled(false);
|
||||||
|
|
||||||
|
flightLayout->addWidget(altitudeLabel);
|
||||||
|
flightLayout->addWidget(m_altitudeSlider);
|
||||||
|
flightLayout->addWidget(speedLabel);
|
||||||
|
flightLayout->addWidget(m_speedSlider);
|
||||||
|
|
||||||
|
// 紧急停止按钮
|
||||||
|
m_emergencyStopBtn = new QPushButton("🚨 紧急停止");
|
||||||
|
m_emergencyStopBtn->setObjectName("DangerBtn");
|
||||||
|
m_emergencyStopBtn->setMinimumHeight(60);
|
||||||
|
m_emergencyStopBtn->setStyleSheet(
|
||||||
|
"QPushButton {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #e74c3c, stop:1 #c0392b);"
|
||||||
|
" color: white;"
|
||||||
|
" font-size: 16px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" border: 2px solid #e74c3c;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
"}"
|
||||||
|
"QPushButton:hover {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #c0392b, stop:1 #a93226);"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
flightLayout->addWidget(m_emergencyStopBtn);
|
||||||
|
flightLayout->addStretch();
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_flightControlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::setupMissionControlModule()
|
||||||
|
{
|
||||||
|
m_missionControlGroup = new QGroupBox("🎯 任务控制");
|
||||||
|
m_missionControlGroup->setObjectName("ControlGroup");
|
||||||
|
m_missionControlGroup->setMinimumWidth(280);
|
||||||
|
|
||||||
|
QVBoxLayout *missionLayout = new QVBoxLayout(m_missionControlGroup);
|
||||||
|
missionLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 任务模式选择
|
||||||
|
QLabel *modeLabel = new QLabel("任务模式:");
|
||||||
|
m_missionModeCombo = new QComboBox();
|
||||||
|
m_missionModeCombo->addItems({"手动控制", "自主巡航", "目标跟踪", "区域扫描"});
|
||||||
|
m_missionModeCombo->setMinimumHeight(35);
|
||||||
|
|
||||||
|
missionLayout->addWidget(modeLabel);
|
||||||
|
missionLayout->addWidget(m_missionModeCombo);
|
||||||
|
|
||||||
|
// 功能控制按钮
|
||||||
|
m_mappingBtn = new QPushButton("🗺️ 开始建图");
|
||||||
|
m_mappingBtn->setObjectName("FunctionBtn");
|
||||||
|
m_mappingBtn->setMinimumHeight(50);
|
||||||
|
m_mappingBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_navigationBtn = new QPushButton("🧭 导航避障");
|
||||||
|
m_navigationBtn->setObjectName("FunctionBtn");
|
||||||
|
m_navigationBtn->setMinimumHeight(50);
|
||||||
|
m_navigationBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_photoBtn = new QPushButton("📸 照片传输");
|
||||||
|
m_photoBtn->setObjectName("FunctionBtn");
|
||||||
|
m_photoBtn->setMinimumHeight(50);
|
||||||
|
m_photoBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_recognitionBtn = new QPushButton("👁️ 人物识别");
|
||||||
|
m_recognitionBtn->setObjectName("FunctionBtn");
|
||||||
|
m_recognitionBtn->setMinimumHeight(50);
|
||||||
|
m_recognitionBtn->setCheckable(true);
|
||||||
|
|
||||||
|
missionLayout->addWidget(m_mappingBtn);
|
||||||
|
missionLayout->addWidget(m_navigationBtn);
|
||||||
|
missionLayout->addWidget(m_photoBtn);
|
||||||
|
missionLayout->addWidget(m_recognitionBtn);
|
||||||
|
missionLayout->addStretch();
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_missionControlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::setupStatusMonitorModule()
|
||||||
|
{
|
||||||
|
m_statusGroup = new QGroupBox("📊 状态监控");
|
||||||
|
m_statusGroup->setObjectName("ControlGroup");
|
||||||
|
m_statusGroup->setMinimumWidth(320);
|
||||||
|
|
||||||
|
QVBoxLayout *statusLayout = new QVBoxLayout(m_statusGroup);
|
||||||
|
statusLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 电池状态
|
||||||
|
QHBoxLayout *batteryLayout = new QHBoxLayout();
|
||||||
|
m_batteryLabel = new QLabel("电池电量:");
|
||||||
|
m_batteryProgress = new QProgressBar();
|
||||||
|
m_batteryProgress->setRange(0, 100);
|
||||||
|
m_batteryProgress->setValue(85);
|
||||||
|
m_batteryProgress->setTextVisible(true);
|
||||||
|
m_batteryProgress->setFormat("%p%");
|
||||||
|
|
||||||
|
batteryLayout->addWidget(m_batteryLabel);
|
||||||
|
batteryLayout->addWidget(m_batteryProgress);
|
||||||
|
statusLayout->addLayout(batteryLayout);
|
||||||
|
|
||||||
|
// 飞行参数
|
||||||
|
QGridLayout *paramLayout = new QGridLayout();
|
||||||
|
paramLayout->addWidget(new QLabel("飞行高度:"), 0, 0);
|
||||||
|
m_altitudeLabel = new QLabel("0.0 m");
|
||||||
|
m_altitudeLabel->setStyleSheet("font-weight: bold; color: #0078d4;");
|
||||||
|
paramLayout->addWidget(m_altitudeLabel, 0, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("飞行速度:"), 1, 0);
|
||||||
|
m_speedLabel = new QLabel("0.0 m/s");
|
||||||
|
m_speedLabel->setStyleSheet("font-weight: bold; color: #0078d4;");
|
||||||
|
paramLayout->addWidget(m_speedLabel, 1, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("GPS状态:"), 2, 0);
|
||||||
|
m_gpsLabel = new QLabel("🔴 未连接");
|
||||||
|
paramLayout->addWidget(m_gpsLabel, 2, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("连接状态:"), 3, 0);
|
||||||
|
m_connectionLabel = new QLabel("🟢 已连接");
|
||||||
|
paramLayout->addWidget(m_connectionLabel, 3, 1);
|
||||||
|
|
||||||
|
statusLayout->addLayout(paramLayout);
|
||||||
|
|
||||||
|
// 日志显示
|
||||||
|
QLabel *logLabel = new QLabel("系统日志:");
|
||||||
|
m_logTextEdit = new QTextEdit();
|
||||||
|
m_logTextEdit->setMaximumHeight(200);
|
||||||
|
m_logTextEdit->setReadOnly(true);
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机控制系统启动").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
m_logTextEdit->append(QString("[%1] 等待连接无人机...").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
statusLayout->addWidget(logLabel);
|
||||||
|
statusLayout->addWidget(m_logTextEdit);
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_statusGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::applyStyles()
|
||||||
|
{
|
||||||
|
// 应用现代样式管理器
|
||||||
|
ModernStyleManager* styleManager = ModernStyleManager::getInstance();
|
||||||
|
|
||||||
|
// 应用按钮样式
|
||||||
|
styleManager->applyButtonStyle(m_takeoffBtn, ModernStyleManager::ButtonStyle::Primary);
|
||||||
|
styleManager->applyButtonStyle(m_landBtn, ModernStyleManager::ButtonStyle::Warning);
|
||||||
|
styleManager->applyButtonStyle(m_hoverBtn, ModernStyleManager::ButtonStyle::Info);
|
||||||
|
styleManager->applyButtonStyle(m_returnHomeBtn, ModernStyleManager::ButtonStyle::Success);
|
||||||
|
styleManager->applyButtonStyle(m_emergencyStopBtn, ModernStyleManager::ButtonStyle::Danger);
|
||||||
|
|
||||||
|
// 设置对话框样式
|
||||||
|
setStyleSheet(
|
||||||
|
"QDialog {"
|
||||||
|
" background-color: #f8f9fa;"
|
||||||
|
" border: 1px solid #dee2e6;"
|
||||||
|
"}"
|
||||||
|
"QGroupBox {"
|
||||||
|
" font-size: 16px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" color: #495057;"
|
||||||
|
" border: 2px solid #dee2e6;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
" margin-top: 10px;"
|
||||||
|
" padding-top: 10px;"
|
||||||
|
"}"
|
||||||
|
"QGroupBox::title {"
|
||||||
|
" subcontrol-origin: margin;"
|
||||||
|
" left: 10px;"
|
||||||
|
" padding: 0 8px 0 8px;"
|
||||||
|
" background-color: #f8f9fa;"
|
||||||
|
"}"
|
||||||
|
"QSlider::groove:horizontal {"
|
||||||
|
" border: 1px solid #bbb;"
|
||||||
|
" background: white;"
|
||||||
|
" height: 10px;"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
"}"
|
||||||
|
"QSlider::handle:horizontal {"
|
||||||
|
" background: #0078d4;"
|
||||||
|
" border: 1px solid #5c5c5c;"
|
||||||
|
" width: 18px;"
|
||||||
|
" margin: -2px 0;"
|
||||||
|
" border-radius: 3px;"
|
||||||
|
"}"
|
||||||
|
"QProgressBar {"
|
||||||
|
" border: 2px solid #dee2e6;"
|
||||||
|
" border-radius: 5px;"
|
||||||
|
" text-align: center;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
"}"
|
||||||
|
"QProgressBar::chunk {"
|
||||||
|
" background-color: #28a745;"
|
||||||
|
" border-radius: 3px;"
|
||||||
|
"}"
|
||||||
|
"QTextEdit {"
|
||||||
|
" border: 1px solid #dee2e6;"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" background-color: white;"
|
||||||
|
" font-family: 'Consolas', monospace;"
|
||||||
|
" font-size: 12px;"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::connectSignals()
|
||||||
|
{
|
||||||
|
// 飞行控制信号连接
|
||||||
|
connect(m_takeoffBtn, &QPushButton::clicked, this, &DroneControlDialog::onTakeoffClicked);
|
||||||
|
connect(m_landBtn, &QPushButton::clicked, this, &DroneControlDialog::onLandClicked);
|
||||||
|
connect(m_hoverBtn, &QPushButton::clicked, this, &DroneControlDialog::onHoverClicked);
|
||||||
|
connect(m_returnHomeBtn, &QPushButton::clicked, this, &DroneControlDialog::onReturnHomeClicked);
|
||||||
|
connect(m_emergencyStopBtn, &QPushButton::clicked, this, &DroneControlDialog::onEmergencyStop);
|
||||||
|
|
||||||
|
// 任务控制信号连接
|
||||||
|
connect(m_mappingBtn, &QPushButton::clicked, this, &DroneControlDialog::onMappingToggle);
|
||||||
|
connect(m_navigationBtn, &QPushButton::clicked, this, &DroneControlDialog::onNavigationToggle);
|
||||||
|
connect(m_photoBtn, &QPushButton::clicked, this, &DroneControlDialog::onPhotoTransmissionToggle);
|
||||||
|
connect(m_recognitionBtn, &QPushButton::clicked, this, &DroneControlDialog::onPersonRecognitionToggle);
|
||||||
|
|
||||||
|
// 状态更新定时器
|
||||||
|
connect(m_statusUpdateTimer, &QTimer::timeout, [this]() {
|
||||||
|
// 模拟状态更新
|
||||||
|
static int counter = 0;
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if (m_isFlying) {
|
||||||
|
// 模拟电池消耗
|
||||||
|
int currentBattery = m_batteryProgress->value();
|
||||||
|
if (currentBattery > 0 && counter % 10 == 0) {
|
||||||
|
m_batteryProgress->setValue(currentBattery - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新电池颜色
|
||||||
|
if (currentBattery > 50) {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #28a745; }");
|
||||||
|
} else if (currentBattery > 20) {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #ffc107; }");
|
||||||
|
} else {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #dc3545; }");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 槽函数实现
|
||||||
|
void DroneControlDialog::onTakeoffClicked()
|
||||||
|
{
|
||||||
|
m_isFlying = true;
|
||||||
|
m_takeoffBtn->setEnabled(false);
|
||||||
|
m_landBtn->setEnabled(true);
|
||||||
|
m_hoverBtn->setEnabled(true);
|
||||||
|
m_returnHomeBtn->setEnabled(true);
|
||||||
|
m_altitudeSlider->setEnabled(true);
|
||||||
|
m_speedSlider->setEnabled(true);
|
||||||
|
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机起飞中...").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
m_gpsLabel->setText("🟢 GPS已锁定");
|
||||||
|
|
||||||
|
// 模拟起飞过程
|
||||||
|
QTimer::singleShot(2000, [this]() {
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机起飞成功,当前高度: %2m").arg(
|
||||||
|
QDateTime::currentDateTime().toString("hh:mm:ss")).arg(m_altitudeSlider->value()));
|
||||||
|
updateDroneStatus(m_batteryProgress->value(), m_altitudeSlider->value(), m_speedSlider->value());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onLandClicked()
|
||||||
|
{
|
||||||
|
m_isFlying = false;
|
||||||
|
m_takeoffBtn->setEnabled(true);
|
||||||
|
m_landBtn->setEnabled(false);
|
||||||
|
m_hoverBtn->setEnabled(false);
|
||||||
|
m_returnHomeBtn->setEnabled(false);
|
||||||
|
m_altitudeSlider->setEnabled(false);
|
||||||
|
m_speedSlider->setEnabled(false);
|
||||||
|
|
||||||
|
// 停止所有任务
|
||||||
|
if (m_isMappingActive) onMappingToggle();
|
||||||
|
if (m_isNavigationActive) onNavigationToggle();
|
||||||
|
if (m_isPhotoTransmissionActive) onPhotoTransmissionToggle();
|
||||||
|
if (m_isPersonRecognitionActive) onPersonRecognitionToggle();
|
||||||
|
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机降落中...").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
QTimer::singleShot(3000, [this]() {
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机安全降落").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateDroneStatus(m_batteryProgress->value(), 0.0, 0.0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onHoverClicked()
|
||||||
|
{
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机进入悬停模式").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateDroneStatus(m_batteryProgress->value(), m_altitudeSlider->value(), 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onReturnHomeClicked()
|
||||||
|
{
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机开始返航").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
// 停止所有任务
|
||||||
|
if (m_isMappingActive) onMappingToggle();
|
||||||
|
if (m_isNavigationActive) onNavigationToggle();
|
||||||
|
if (m_isPhotoTransmissionActive) onPhotoTransmissionToggle();
|
||||||
|
if (m_isPersonRecognitionActive) onPersonRecognitionToggle();
|
||||||
|
|
||||||
|
QTimer::singleShot(5000, [this]() {
|
||||||
|
m_logTextEdit->append(QString("[%1] 无人机返航完成").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
onLandClicked();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onEmergencyStop()
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton reply = QMessageBox::warning(this, "紧急停止",
|
||||||
|
"确定要执行紧急停止吗?这将立即停止所有操作!",
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
m_logTextEdit->append(QString("[%1] 🚨 执行紧急停止!").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
onLandClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onMappingToggle()
|
||||||
|
{
|
||||||
|
m_isMappingActive = !m_isMappingActive;
|
||||||
|
|
||||||
|
if (m_isMappingActive) {
|
||||||
|
m_mappingBtn->setText("🗺️ 停止建图");
|
||||||
|
m_mappingBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始自主建图").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startMapping();
|
||||||
|
} else {
|
||||||
|
m_mappingBtn->setText("🗺️ 开始建图");
|
||||||
|
m_mappingBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止自主建图").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopMapping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onNavigationToggle()
|
||||||
|
{
|
||||||
|
m_isNavigationActive = !m_isNavigationActive;
|
||||||
|
|
||||||
|
if (m_isNavigationActive) {
|
||||||
|
m_navigationBtn->setText("🧭 停止导航");
|
||||||
|
m_navigationBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始导航避障").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startNavigation();
|
||||||
|
} else {
|
||||||
|
m_navigationBtn->setText("🧭 导航避障");
|
||||||
|
m_navigationBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止导航避障").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopNavigation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onPhotoTransmissionToggle()
|
||||||
|
{
|
||||||
|
m_isPhotoTransmissionActive = !m_isPhotoTransmissionActive;
|
||||||
|
|
||||||
|
if (m_isPhotoTransmissionActive) {
|
||||||
|
m_photoBtn->setText("📸 停止传输");
|
||||||
|
m_photoBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始照片传输").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startPhotoTransmission();
|
||||||
|
} else {
|
||||||
|
m_photoBtn->setText("📸 照片传输");
|
||||||
|
m_photoBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止照片传输").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopPhotoTransmission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::onPersonRecognitionToggle()
|
||||||
|
{
|
||||||
|
m_isPersonRecognitionActive = !m_isPersonRecognitionActive;
|
||||||
|
|
||||||
|
if (m_isPersonRecognitionActive) {
|
||||||
|
m_recognitionBtn->setText("👁️ 停止识别");
|
||||||
|
m_recognitionBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始人物识别").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startPersonRecognition();
|
||||||
|
} else {
|
||||||
|
m_recognitionBtn->setText("👁️ 人物识别");
|
||||||
|
m_recognitionBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止人物识别").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopPersonRecognition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DroneControlDialog::updateDroneStatus(int battery, double altitude, double speed)
|
||||||
|
{
|
||||||
|
m_batteryProgress->setValue(battery);
|
||||||
|
m_altitudeLabel->setText(QString("%1 m").arg(altitude, 0, 'f', 1));
|
||||||
|
m_speedLabel->setText(QString("%1 m/s").arg(speed, 0, 'f', 1));
|
||||||
|
}
|
@ -0,0 +1,630 @@
|
|||||||
|
/**
|
||||||
|
* @file RobotDogControlDialog.cpp
|
||||||
|
* @brief 机器狗控制对话框实现
|
||||||
|
* @author Qt UI Optimizer
|
||||||
|
* @date 2024-07-04
|
||||||
|
* @version 1.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ui/dialogs/RobotDogControlDialog.h"
|
||||||
|
#include "styles/ModernStyleManager.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDesktopWidget>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
RobotDogControlDialog::RobotDogControlDialog(QWidget *parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
, m_mainLayout(nullptr)
|
||||||
|
, m_contentLayout(nullptr)
|
||||||
|
, m_isMappingActive(false)
|
||||||
|
, m_isNavigationActive(false)
|
||||||
|
, m_isPhotoTransmissionActive(false)
|
||||||
|
, m_isPersonRecognitionActive(false)
|
||||||
|
, m_isMoving(false)
|
||||||
|
, m_currentPosture("站立")
|
||||||
|
, m_statusUpdateTimer(new QTimer(this))
|
||||||
|
{
|
||||||
|
setupUI();
|
||||||
|
applyStyles();
|
||||||
|
connectSignals();
|
||||||
|
|
||||||
|
// 启动状态更新定时器
|
||||||
|
m_statusUpdateTimer->start(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
RobotDogControlDialog::~RobotDogControlDialog()
|
||||||
|
{
|
||||||
|
if (m_statusUpdateTimer) {
|
||||||
|
m_statusUpdateTimer->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::setupUI()
|
||||||
|
{
|
||||||
|
setWindowTitle("🐕 机器狗控制中心");
|
||||||
|
setModal(false);
|
||||||
|
setMinimumSize(900, 700);
|
||||||
|
resize(1000, 750);
|
||||||
|
|
||||||
|
// 窗口居中显示
|
||||||
|
QRect screenGeometry = QApplication::desktop()->screenGeometry();
|
||||||
|
int x = (screenGeometry.width() - this->width()) / 2;
|
||||||
|
int y = (screenGeometry.height() - this->height()) / 2;
|
||||||
|
move(x, y);
|
||||||
|
|
||||||
|
m_mainLayout = new QVBoxLayout(this);
|
||||||
|
m_mainLayout->setSpacing(20);
|
||||||
|
m_mainLayout->setContentsMargins(20, 20, 20, 20);
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
QLabel *titleLabel = new QLabel("🐕 机器狗控制中心");
|
||||||
|
titleLabel->setObjectName("DialogTitle");
|
||||||
|
titleLabel->setAlignment(Qt::AlignCenter);
|
||||||
|
titleLabel->setStyleSheet(
|
||||||
|
"font-size: 24px; "
|
||||||
|
"font-weight: bold; "
|
||||||
|
"color: #16a085; "
|
||||||
|
"padding: 10px; "
|
||||||
|
"border-bottom: 2px solid #16a085; "
|
||||||
|
"margin-bottom: 10px;"
|
||||||
|
);
|
||||||
|
m_mainLayout->addWidget(titleLabel);
|
||||||
|
|
||||||
|
// 主内容区域
|
||||||
|
m_contentLayout = new QHBoxLayout();
|
||||||
|
m_contentLayout->setSpacing(20);
|
||||||
|
|
||||||
|
setupMovementControlModule();
|
||||||
|
setupMissionControlModule();
|
||||||
|
setupStatusMonitorModule();
|
||||||
|
|
||||||
|
m_mainLayout->addLayout(m_contentLayout);
|
||||||
|
|
||||||
|
// 底部按钮
|
||||||
|
QHBoxLayout *buttonLayout = new QHBoxLayout();
|
||||||
|
buttonLayout->addStretch();
|
||||||
|
|
||||||
|
QPushButton *closeBtn = new QPushButton("关闭");
|
||||||
|
closeBtn->setObjectName("CloseBtn");
|
||||||
|
closeBtn->setMinimumSize(100, 40);
|
||||||
|
connect(closeBtn, &QPushButton::clicked, this, &QDialog::close);
|
||||||
|
|
||||||
|
buttonLayout->addWidget(closeBtn);
|
||||||
|
m_mainLayout->addLayout(buttonLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::setupMovementControlModule()
|
||||||
|
{
|
||||||
|
m_movementControlGroup = new QGroupBox("🎮 运动控制");
|
||||||
|
m_movementControlGroup->setObjectName("ControlGroup");
|
||||||
|
m_movementControlGroup->setMinimumWidth(280);
|
||||||
|
|
||||||
|
QVBoxLayout *movementLayout = new QVBoxLayout(m_movementControlGroup);
|
||||||
|
movementLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 方向控制按钮 - 十字布局
|
||||||
|
QGridLayout *directionLayout = new QGridLayout();
|
||||||
|
directionLayout->setSpacing(10);
|
||||||
|
|
||||||
|
m_forwardBtn = new QPushButton("⬆️ 前进");
|
||||||
|
m_forwardBtn->setObjectName("DirectionBtn");
|
||||||
|
m_forwardBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
m_backwardBtn = new QPushButton("⬇️ 后退");
|
||||||
|
m_backwardBtn->setObjectName("DirectionBtn");
|
||||||
|
m_backwardBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
m_leftBtn = new QPushButton("⬅️ 左转");
|
||||||
|
m_leftBtn->setObjectName("DirectionBtn");
|
||||||
|
m_leftBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
m_rightBtn = new QPushButton("➡️ 右转");
|
||||||
|
m_rightBtn->setObjectName("DirectionBtn");
|
||||||
|
m_rightBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
m_stopBtn = new QPushButton("⏹️ 停止");
|
||||||
|
m_stopBtn->setObjectName("StopBtn");
|
||||||
|
m_stopBtn->setMinimumHeight(50);
|
||||||
|
|
||||||
|
// 十字布局
|
||||||
|
directionLayout->addWidget(m_forwardBtn, 0, 1);
|
||||||
|
directionLayout->addWidget(m_leftBtn, 1, 0);
|
||||||
|
directionLayout->addWidget(m_stopBtn, 1, 1);
|
||||||
|
directionLayout->addWidget(m_rightBtn, 1, 2);
|
||||||
|
directionLayout->addWidget(m_backwardBtn, 2, 1);
|
||||||
|
|
||||||
|
movementLayout->addLayout(directionLayout);
|
||||||
|
|
||||||
|
// 姿态控制按钮
|
||||||
|
QLabel *postureLabel = new QLabel("姿态控制:");
|
||||||
|
movementLayout->addWidget(postureLabel);
|
||||||
|
|
||||||
|
QHBoxLayout *postureLayout = new QHBoxLayout();
|
||||||
|
postureLayout->setSpacing(10);
|
||||||
|
|
||||||
|
m_standBtn = new QPushButton("🧍 站立");
|
||||||
|
m_standBtn->setObjectName("PostureBtn");
|
||||||
|
m_standBtn->setMinimumHeight(45);
|
||||||
|
|
||||||
|
m_lieDownBtn = new QPushButton("🛌 趴下");
|
||||||
|
m_lieDownBtn->setObjectName("PostureBtn");
|
||||||
|
m_lieDownBtn->setMinimumHeight(45);
|
||||||
|
|
||||||
|
m_jumpBtn = new QPushButton("🦘 跳跃");
|
||||||
|
m_jumpBtn->setObjectName("PostureBtn");
|
||||||
|
m_jumpBtn->setMinimumHeight(45);
|
||||||
|
|
||||||
|
postureLayout->addWidget(m_standBtn);
|
||||||
|
postureLayout->addWidget(m_lieDownBtn);
|
||||||
|
postureLayout->addWidget(m_jumpBtn);
|
||||||
|
movementLayout->addLayout(postureLayout);
|
||||||
|
|
||||||
|
// 速度控制
|
||||||
|
QLabel *speedLabel = new QLabel("移动速度:");
|
||||||
|
m_speedSlider = new QSlider(Qt::Horizontal);
|
||||||
|
m_speedSlider->setRange(1, 10);
|
||||||
|
m_speedSlider->setValue(5);
|
||||||
|
|
||||||
|
movementLayout->addWidget(speedLabel);
|
||||||
|
movementLayout->addWidget(m_speedSlider);
|
||||||
|
|
||||||
|
// 紧急停止按钮
|
||||||
|
m_emergencyStopBtn = new QPushButton("🚨 紧急停止");
|
||||||
|
m_emergencyStopBtn->setObjectName("DangerBtn");
|
||||||
|
m_emergencyStopBtn->setMinimumHeight(60);
|
||||||
|
m_emergencyStopBtn->setStyleSheet(
|
||||||
|
"QPushButton {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #e74c3c, stop:1 #c0392b);"
|
||||||
|
" color: white;"
|
||||||
|
" font-size: 16px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" border: 2px solid #e74c3c;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
"}"
|
||||||
|
"QPushButton:hover {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #c0392b, stop:1 #a93226);"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
|
||||||
|
movementLayout->addWidget(m_emergencyStopBtn);
|
||||||
|
movementLayout->addStretch();
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_movementControlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::setupMissionControlModule()
|
||||||
|
{
|
||||||
|
m_missionControlGroup = new QGroupBox("🎯 任务控制");
|
||||||
|
m_missionControlGroup->setObjectName("ControlGroup");
|
||||||
|
m_missionControlGroup->setMinimumWidth(280);
|
||||||
|
|
||||||
|
QVBoxLayout *missionLayout = new QVBoxLayout(m_missionControlGroup);
|
||||||
|
missionLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 任务模式选择
|
||||||
|
QLabel *modeLabel = new QLabel("任务模式:");
|
||||||
|
m_missionModeCombo = new QComboBox();
|
||||||
|
m_missionModeCombo->addItems({"手动控制", "自主巡逻", "目标跟踪", "区域探索", "护卫模式"});
|
||||||
|
m_missionModeCombo->setMinimumHeight(35);
|
||||||
|
|
||||||
|
missionLayout->addWidget(modeLabel);
|
||||||
|
missionLayout->addWidget(m_missionModeCombo);
|
||||||
|
|
||||||
|
// 功能控制按钮
|
||||||
|
m_mappingBtn = new QPushButton("🗺️ 开始建图");
|
||||||
|
m_mappingBtn->setObjectName("FunctionBtn");
|
||||||
|
m_mappingBtn->setMinimumHeight(50);
|
||||||
|
m_mappingBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_navigationBtn = new QPushButton("🧭 导航避障");
|
||||||
|
m_navigationBtn->setObjectName("FunctionBtn");
|
||||||
|
m_navigationBtn->setMinimumHeight(50);
|
||||||
|
m_navigationBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_photoBtn = new QPushButton("📸 照片传输");
|
||||||
|
m_photoBtn->setObjectName("FunctionBtn");
|
||||||
|
m_photoBtn->setMinimumHeight(50);
|
||||||
|
m_photoBtn->setCheckable(true);
|
||||||
|
|
||||||
|
m_recognitionBtn = new QPushButton("👁️ 人物识别");
|
||||||
|
m_recognitionBtn->setObjectName("FunctionBtn");
|
||||||
|
m_recognitionBtn->setMinimumHeight(50);
|
||||||
|
m_recognitionBtn->setCheckable(true);
|
||||||
|
|
||||||
|
missionLayout->addWidget(m_mappingBtn);
|
||||||
|
missionLayout->addWidget(m_navigationBtn);
|
||||||
|
missionLayout->addWidget(m_photoBtn);
|
||||||
|
missionLayout->addWidget(m_recognitionBtn);
|
||||||
|
missionLayout->addStretch();
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_missionControlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::setupStatusMonitorModule()
|
||||||
|
{
|
||||||
|
m_statusGroup = new QGroupBox("📊 状态监控");
|
||||||
|
m_statusGroup->setObjectName("ControlGroup");
|
||||||
|
m_statusGroup->setMinimumWidth(320);
|
||||||
|
|
||||||
|
QVBoxLayout *statusLayout = new QVBoxLayout(m_statusGroup);
|
||||||
|
statusLayout->setSpacing(15);
|
||||||
|
|
||||||
|
// 电池状态
|
||||||
|
QHBoxLayout *batteryLayout = new QHBoxLayout();
|
||||||
|
m_batteryLabel = new QLabel("电池电量:");
|
||||||
|
m_batteryProgress = new QProgressBar();
|
||||||
|
m_batteryProgress->setRange(0, 100);
|
||||||
|
m_batteryProgress->setValue(90);
|
||||||
|
m_batteryProgress->setTextVisible(true);
|
||||||
|
m_batteryProgress->setFormat("%p%");
|
||||||
|
|
||||||
|
batteryLayout->addWidget(m_batteryLabel);
|
||||||
|
batteryLayout->addWidget(m_batteryProgress);
|
||||||
|
statusLayout->addLayout(batteryLayout);
|
||||||
|
|
||||||
|
// 运行参数
|
||||||
|
QGridLayout *paramLayout = new QGridLayout();
|
||||||
|
paramLayout->addWidget(new QLabel("移动速度:"), 0, 0);
|
||||||
|
m_speedLabel = new QLabel("0.0 m/s");
|
||||||
|
m_speedLabel->setStyleSheet("font-weight: bold; color: #16a085;");
|
||||||
|
paramLayout->addWidget(m_speedLabel, 0, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("设备温度:"), 1, 0);
|
||||||
|
m_temperatureLabel = new QLabel("35.2°C");
|
||||||
|
m_temperatureLabel->setStyleSheet("font-weight: bold; color: #16a085;");
|
||||||
|
paramLayout->addWidget(m_temperatureLabel, 1, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("当前姿态:"), 2, 0);
|
||||||
|
m_postureLabel = new QLabel("🧍 站立");
|
||||||
|
paramLayout->addWidget(m_postureLabel, 2, 1);
|
||||||
|
|
||||||
|
paramLayout->addWidget(new QLabel("连接状态:"), 3, 0);
|
||||||
|
m_connectionLabel = new QLabel("🟢 已连接");
|
||||||
|
paramLayout->addWidget(m_connectionLabel, 3, 1);
|
||||||
|
|
||||||
|
statusLayout->addLayout(paramLayout);
|
||||||
|
|
||||||
|
// 日志显示
|
||||||
|
QLabel *logLabel = new QLabel("系统日志:");
|
||||||
|
m_logTextEdit = new QTextEdit();
|
||||||
|
m_logTextEdit->setMaximumHeight(200);
|
||||||
|
m_logTextEdit->setReadOnly(true);
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗控制系统启动").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗已连接,当前状态:待命").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
statusLayout->addWidget(logLabel);
|
||||||
|
statusLayout->addWidget(m_logTextEdit);
|
||||||
|
|
||||||
|
m_contentLayout->addWidget(m_statusGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::applyStyles()
|
||||||
|
{
|
||||||
|
// 应用现代样式管理器
|
||||||
|
ModernStyleManager* styleManager = ModernStyleManager::getInstance();
|
||||||
|
|
||||||
|
// 设置对话框样式
|
||||||
|
setStyleSheet(
|
||||||
|
"QDialog {"
|
||||||
|
" background-color: #f8f9fa;"
|
||||||
|
" border: 1px solid #dee2e6;"
|
||||||
|
"}"
|
||||||
|
"QGroupBox {"
|
||||||
|
" font-size: 16px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" color: #495057;"
|
||||||
|
" border: 2px solid #dee2e6;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
" margin-top: 10px;"
|
||||||
|
" padding-top: 10px;"
|
||||||
|
"}"
|
||||||
|
"QGroupBox::title {"
|
||||||
|
" subcontrol-origin: margin;"
|
||||||
|
" left: 10px;"
|
||||||
|
" padding: 0 8px 0 8px;"
|
||||||
|
" background-color: #f8f9fa;"
|
||||||
|
"}"
|
||||||
|
"QPushButton#DirectionBtn {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #16a085, stop:1 #138d75);"
|
||||||
|
" color: white;"
|
||||||
|
" font-size: 14px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" border: 2px solid #16a085;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
" padding: 8px;"
|
||||||
|
"}"
|
||||||
|
"QPushButton#DirectionBtn:hover {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #138d75, stop:1 #117a65);"
|
||||||
|
"}"
|
||||||
|
"QPushButton#DirectionBtn:pressed {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #117a65, stop:1 #0e6b5d);"
|
||||||
|
"}"
|
||||||
|
"QPushButton#StopBtn {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #e67e22, stop:1 #d35400);"
|
||||||
|
" color: white;"
|
||||||
|
" font-size: 14px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" border: 2px solid #e67e22;"
|
||||||
|
" border-radius: 8px;"
|
||||||
|
" padding: 8px;"
|
||||||
|
"}"
|
||||||
|
"QPushButton#PostureBtn {"
|
||||||
|
" background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0,"
|
||||||
|
" stop:0 #3498db, stop:1 #2980b9);"
|
||||||
|
" color: white;"
|
||||||
|
" font-size: 13px;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
" border: 2px solid #3498db;"
|
||||||
|
" border-radius: 6px;"
|
||||||
|
" padding: 6px;"
|
||||||
|
"}"
|
||||||
|
"QSlider::groove:horizontal {"
|
||||||
|
" border: 1px solid #bbb;"
|
||||||
|
" background: white;"
|
||||||
|
" height: 10px;"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
"}"
|
||||||
|
"QSlider::handle:horizontal {"
|
||||||
|
" background: #16a085;"
|
||||||
|
" border: 1px solid #5c5c5c;"
|
||||||
|
" width: 18px;"
|
||||||
|
" margin: -2px 0;"
|
||||||
|
" border-radius: 3px;"
|
||||||
|
"}"
|
||||||
|
"QProgressBar {"
|
||||||
|
" border: 2px solid #dee2e6;"
|
||||||
|
" border-radius: 5px;"
|
||||||
|
" text-align: center;"
|
||||||
|
" font-weight: bold;"
|
||||||
|
"}"
|
||||||
|
"QProgressBar::chunk {"
|
||||||
|
" background-color: #28a745;"
|
||||||
|
" border-radius: 3px;"
|
||||||
|
"}"
|
||||||
|
"QTextEdit {"
|
||||||
|
" border: 1px solid #dee2e6;"
|
||||||
|
" border-radius: 4px;"
|
||||||
|
" background-color: white;"
|
||||||
|
" font-family: 'Consolas', monospace;"
|
||||||
|
" font-size: 12px;"
|
||||||
|
"}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::connectSignals()
|
||||||
|
{
|
||||||
|
// 运动控制信号连接
|
||||||
|
connect(m_forwardBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onMoveForwardClicked);
|
||||||
|
connect(m_backwardBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onMoveBackwardClicked);
|
||||||
|
connect(m_leftBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onTurnLeftClicked);
|
||||||
|
connect(m_rightBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onTurnRightClicked);
|
||||||
|
connect(m_stopBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onStopClicked);
|
||||||
|
|
||||||
|
// 姿态控制信号连接
|
||||||
|
connect(m_standBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onStandClicked);
|
||||||
|
connect(m_lieDownBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onLieDownClicked);
|
||||||
|
connect(m_jumpBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onJumpClicked);
|
||||||
|
|
||||||
|
// 紧急停止
|
||||||
|
connect(m_emergencyStopBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onEmergencyStop);
|
||||||
|
|
||||||
|
// 任务控制信号连接
|
||||||
|
connect(m_mappingBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onMappingToggle);
|
||||||
|
connect(m_navigationBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onNavigationToggle);
|
||||||
|
connect(m_photoBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onPhotoTransmissionToggle);
|
||||||
|
connect(m_recognitionBtn, &QPushButton::clicked, this, &RobotDogControlDialog::onPersonRecognitionToggle);
|
||||||
|
|
||||||
|
// 速度滑块
|
||||||
|
connect(m_speedSlider, &QSlider::valueChanged, [this](int value) {
|
||||||
|
m_logTextEdit->append(QString("[%1] 速度设置为: %2").arg(
|
||||||
|
QDateTime::currentDateTime().toString("hh:mm:ss")).arg(value));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 状态更新定时器
|
||||||
|
connect(m_statusUpdateTimer, &QTimer::timeout, [this]() {
|
||||||
|
// 模拟状态更新
|
||||||
|
static int counter = 0;
|
||||||
|
counter++;
|
||||||
|
|
||||||
|
if (m_isMoving) {
|
||||||
|
// 模拟电池消耗
|
||||||
|
int currentBattery = m_batteryProgress->value();
|
||||||
|
if (currentBattery > 0 && counter % 15 == 0) {
|
||||||
|
m_batteryProgress->setValue(currentBattery - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新电池颜色
|
||||||
|
if (currentBattery > 50) {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #28a745; }");
|
||||||
|
} else if (currentBattery > 20) {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #ffc107; }");
|
||||||
|
} else {
|
||||||
|
m_batteryProgress->setStyleSheet("QProgressBar::chunk { background-color: #dc3545; }");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟温度变化
|
||||||
|
static double temperature = 35.2;
|
||||||
|
temperature += (qrand() % 21 - 10) * 0.1; // ±1度随机变化
|
||||||
|
if (temperature < 30.0) temperature = 30.0;
|
||||||
|
if (temperature > 45.0) temperature = 45.0;
|
||||||
|
m_temperatureLabel->setText(QString("%1°C").arg(temperature, 0, 'f', 1));
|
||||||
|
|
||||||
|
// 温度颜色警告
|
||||||
|
if (temperature > 40.0) {
|
||||||
|
m_temperatureLabel->setStyleSheet("font-weight: bold; color: #dc3545;");
|
||||||
|
} else if (temperature > 38.0) {
|
||||||
|
m_temperatureLabel->setStyleSheet("font-weight: bold; color: #ffc107;");
|
||||||
|
} else {
|
||||||
|
m_temperatureLabel->setStyleSheet("font-weight: bold; color: #16a085;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运动控制槽函数实现
|
||||||
|
void RobotDogControlDialog::onMoveForwardClicked()
|
||||||
|
{
|
||||||
|
m_isMoving = true;
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗开始前进").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateRobotStatus(m_batteryProgress->value(), m_speedSlider->value() * 0.5, 35.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onMoveBackwardClicked()
|
||||||
|
{
|
||||||
|
m_isMoving = true;
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗开始后退").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateRobotStatus(m_batteryProgress->value(), m_speedSlider->value() * 0.3, 35.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onTurnLeftClicked()
|
||||||
|
{
|
||||||
|
m_isMoving = true;
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗开始左转").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateRobotStatus(m_batteryProgress->value(), m_speedSlider->value() * 0.2, 35.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onTurnRightClicked()
|
||||||
|
{
|
||||||
|
m_isMoving = true;
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗开始右转").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateRobotStatus(m_batteryProgress->value(), m_speedSlider->value() * 0.2, 35.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onStopClicked()
|
||||||
|
{
|
||||||
|
m_isMoving = false;
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗停止移动").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
updateRobotStatus(m_batteryProgress->value(), 0.0, 34.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onStandClicked()
|
||||||
|
{
|
||||||
|
m_currentPosture = "站立";
|
||||||
|
m_postureLabel->setText("🧍 站立");
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗切换到站立姿态").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onLieDownClicked()
|
||||||
|
{
|
||||||
|
m_currentPosture = "趴下";
|
||||||
|
m_postureLabel->setText("🛌 趴下");
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗切换到趴下姿态").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
// 趴下时停止移动
|
||||||
|
if (m_isMoving) {
|
||||||
|
onStopClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onJumpClicked()
|
||||||
|
{
|
||||||
|
m_logTextEdit->append(QString("[%1] 机器狗执行跳跃动作").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
|
||||||
|
// 跳跃后恢复站立姿态
|
||||||
|
QTimer::singleShot(1000, [this]() {
|
||||||
|
onStandClicked();
|
||||||
|
m_logTextEdit->append(QString("[%1] 跳跃动作完成").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onEmergencyStop()
|
||||||
|
{
|
||||||
|
QMessageBox::StandardButton reply = QMessageBox::warning(this, "紧急停止",
|
||||||
|
"确定要执行紧急停止吗?这将立即停止所有操作!",
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
m_logTextEdit->append(QString("[%1] 🚨 执行紧急停止!").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
onStopClicked();
|
||||||
|
|
||||||
|
// 停止所有任务
|
||||||
|
if (m_isMappingActive) onMappingToggle();
|
||||||
|
if (m_isNavigationActive) onNavigationToggle();
|
||||||
|
if (m_isPhotoTransmissionActive) onPhotoTransmissionToggle();
|
||||||
|
if (m_isPersonRecognitionActive) onPersonRecognitionToggle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务控制槽函数实现
|
||||||
|
void RobotDogControlDialog::onMappingToggle()
|
||||||
|
{
|
||||||
|
m_isMappingActive = !m_isMappingActive;
|
||||||
|
|
||||||
|
if (m_isMappingActive) {
|
||||||
|
m_mappingBtn->setText("🗺️ 停止建图");
|
||||||
|
m_mappingBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始自主建图").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startMapping();
|
||||||
|
} else {
|
||||||
|
m_mappingBtn->setText("🗺️ 开始建图");
|
||||||
|
m_mappingBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止自主建图").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopMapping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onNavigationToggle()
|
||||||
|
{
|
||||||
|
m_isNavigationActive = !m_isNavigationActive;
|
||||||
|
|
||||||
|
if (m_isNavigationActive) {
|
||||||
|
m_navigationBtn->setText("🧭 停止导航");
|
||||||
|
m_navigationBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始导航避障").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startNavigation();
|
||||||
|
} else {
|
||||||
|
m_navigationBtn->setText("🧭 导航避障");
|
||||||
|
m_navigationBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止导航避障").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopNavigation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onPhotoTransmissionToggle()
|
||||||
|
{
|
||||||
|
m_isPhotoTransmissionActive = !m_isPhotoTransmissionActive;
|
||||||
|
|
||||||
|
if (m_isPhotoTransmissionActive) {
|
||||||
|
m_photoBtn->setText("📸 停止传输");
|
||||||
|
m_photoBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始照片传输").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startPhotoTransmission();
|
||||||
|
} else {
|
||||||
|
m_photoBtn->setText("📸 照片传输");
|
||||||
|
m_photoBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止照片传输").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopPhotoTransmission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::onPersonRecognitionToggle()
|
||||||
|
{
|
||||||
|
m_isPersonRecognitionActive = !m_isPersonRecognitionActive;
|
||||||
|
|
||||||
|
if (m_isPersonRecognitionActive) {
|
||||||
|
m_recognitionBtn->setText("👁️ 停止识别");
|
||||||
|
m_recognitionBtn->setStyleSheet("background-color: #dc3545; color: white;");
|
||||||
|
m_logTextEdit->append(QString("[%1] 开始人物识别").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit startPersonRecognition();
|
||||||
|
} else {
|
||||||
|
m_recognitionBtn->setText("👁️ 人物识别");
|
||||||
|
m_recognitionBtn->setStyleSheet("");
|
||||||
|
m_logTextEdit->append(QString("[%1] 停止人物识别").arg(QDateTime::currentDateTime().toString("hh:mm:ss")));
|
||||||
|
emit stopPersonRecognition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RobotDogControlDialog::updateRobotStatus(int battery, double speed, double temperature)
|
||||||
|
{
|
||||||
|
m_batteryProgress->setValue(battery);
|
||||||
|
m_speedLabel->setText(QString("%1 m/s").arg(speed, 0, 'f', 1));
|
||||||
|
m_temperatureLabel->setText(QString("%1°C").arg(temperature, 0, 'f', 1));
|
||||||
|
}
|
Loading…
Reference in new issue