first commit
This commit is contained in:
+19
@@ -0,0 +1,19 @@
|
|||||||
|
# Build artifacts
|
||||||
|
*.o
|
||||||
|
moc_*.cpp
|
||||||
|
moc_predefs.h
|
||||||
|
qrc_*.cpp
|
||||||
|
Makefile
|
||||||
|
VoiletCStudio
|
||||||
|
.qmake.stash
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.user
|
||||||
|
*.pro.user
|
||||||
|
|
||||||
|
# Installer output
|
||||||
|
*.exe
|
||||||
|
VoiletCStudio-Setup-*.exe
|
||||||
|
installer/build/
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
# VoiletCStudio - 紫罗兰 C 工具箱
|
||||||
|
|
||||||
|
Qt5 跨平台 C 工程配置器,自动生成 CMakeLists.txt
|
||||||
|
|
||||||
|
> 版本:v1.1 · 2026-04-28
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 功能特性
|
||||||
|
|
||||||
|
### 1. 工程配置管理
|
||||||
|
- ✅ JSON 格式配置文件
|
||||||
|
- ✅ 新建/打开/保存工程
|
||||||
|
- ✅ 拖拽 JSON 文件打开
|
||||||
|
- ✅ Ctrl+S 快速保存
|
||||||
|
|
||||||
|
### 2. 编译工具链配置
|
||||||
|
- ✅ 编译器路径选择
|
||||||
|
- ✅ 汇编器路径选择
|
||||||
|
- ✅ 链接器路径选择
|
||||||
|
- ✅ 支持 MinGW / GCC / Clang
|
||||||
|
- ✅ Windows 下自动识别 MinGW 工具链
|
||||||
|
|
||||||
|
### 3. 文件管理
|
||||||
|
- ✅ 虚拟目录(类似 MDK)
|
||||||
|
- ✅ .c 源文件分类管理
|
||||||
|
- ✅ .h 包含目录管理
|
||||||
|
- ✅ 库文件管理(.a/.so/.lib)
|
||||||
|
|
||||||
|
### 4. 编译配置
|
||||||
|
- ✅ 编译宏定义
|
||||||
|
- ✅ 自定义编译选项
|
||||||
|
- ✅ Debug / Release 模式
|
||||||
|
|
||||||
|
### 5. CMake 生成
|
||||||
|
- ✅ 自动生成 CMakeLists.txt
|
||||||
|
- ✅ 编译器路径在 `project()` 之前设置,避免默认回退到 NMake
|
||||||
|
- ✅ Windows 下自动指定 `-G "MinGW Makefiles"` 生成器
|
||||||
|
- ✅ 内置一键编译(Debug / Release)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 使用方法
|
||||||
|
|
||||||
|
### 编译 VoiletCStudio
|
||||||
|
|
||||||
|
**Linux:**
|
||||||
|
```bash
|
||||||
|
cd VoiletCStudio
|
||||||
|
qmake
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows (MSYS2/MinGW):**
|
||||||
|
```bash
|
||||||
|
cd VoiletCStudio
|
||||||
|
qmake
|
||||||
|
mingw32-make
|
||||||
|
```
|
||||||
|
|
||||||
|
### 运行
|
||||||
|
```bash
|
||||||
|
./VoiletCStudio # Linux
|
||||||
|
VoiletCStudio.exe # Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用流程
|
||||||
|
1. 新建工程 → 输入工程名
|
||||||
|
2. 配置编译器路径(Windows 选 `gcc.exe`)
|
||||||
|
3. 添加虚拟目录和 .c 源文件
|
||||||
|
4. 添加包含目录和库文件
|
||||||
|
5. 添加编译宏和选项
|
||||||
|
6. 保存工程(JSON)
|
||||||
|
7. 点击「生成 CMake」→ 自动生成 CMakeLists.txt
|
||||||
|
8. 点击「编译 Debug / Release」→ 一键编译
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ CMake 使用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux / macOS
|
||||||
|
cmake -B build
|
||||||
|
make debug # 编译 Debug
|
||||||
|
make release # 编译 Release
|
||||||
|
|
||||||
|
# Windows (MinGW)
|
||||||
|
cmake -G "MinGW Makefiles" -B build
|
||||||
|
mingw32-make debug
|
||||||
|
mingw32-make release
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
VoiletCStudio/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.cpp # 主程序入口
|
||||||
|
│ ├── mainwindow.cpp/h # 主窗口(含编译流程)
|
||||||
|
│ ├── projectconfig.cpp/h # 配置管理
|
||||||
|
│ └── cmakegenerator.cpp/h # CMake 生成
|
||||||
|
├── VoiletCStudio.pro # Qt 项目文件
|
||||||
|
├── README.md # 说明文档
|
||||||
|
└── 需求规格说明书.md # 详细需求文档
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 跨平台支持
|
||||||
|
|
||||||
|
| 平台 | 编译器 | 生成器 | 状态 |
|
||||||
|
|------|--------|--------|------|
|
||||||
|
| Windows | MinGW (MSYS2) | MinGW Makefiles | ✅ |
|
||||||
|
| Windows | MSVC | NMake | ⚠️ 未测试 |
|
||||||
|
| Linux | GCC | Unix Makefiles | ✅ |
|
||||||
|
| macOS | Clang | Unix Makefiles | ✅ |
|
||||||
|
|
||||||
|
### Windows 特别注意
|
||||||
|
- 必须安装 **MSYS2** 并安装 `mingw-w64-gcc`、`mingw-w64-cmake`
|
||||||
|
- 编译器路径示例:`C:/msys2/mingw64/bin/gcc.exe`
|
||||||
|
- CMake 生成时程序自动使用 `-G "MinGW Makefiles"`
|
||||||
|
- 构建时使用 `mingw32-make` 而非 `make`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 配置文件格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectName": "MyProject",
|
||||||
|
"projectPath": "/path/to/project",
|
||||||
|
"outputDir": "./build",
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"assemblerPath": "/usr/bin/gcc",
|
||||||
|
"linkerPath": "/usr/bin/gcc",
|
||||||
|
"virtualDirs": {
|
||||||
|
"App": {
|
||||||
|
"name": "App",
|
||||||
|
"files": ["src/main.c"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"includeDirs": ["./include"],
|
||||||
|
"libraries": ["libmylib.a"],
|
||||||
|
"defines": ["DEBUG"],
|
||||||
|
"compilerOptions": ["-Wall"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 技术要点
|
||||||
|
|
||||||
|
### 编译器设置顺序
|
||||||
|
CMakeLists.txt 中 `CMAKE_C_COMPILER` **必须在 `project()` 之前设置**,否则 CMake 会在 `project()` 时自动检测编译器并可能回退到不存在的 NMake。
|
||||||
|
|
||||||
|
### Windows 生成器
|
||||||
|
程序通过 `#ifdef Q_OS_WIN32` 编译期检测平台,在 Windows 上自动:
|
||||||
|
- CMake 添加 `-G "MinGW Makefiles"` 参数
|
||||||
|
- 构建时使用 `mingw32-make` 替代 `make`
|
||||||
|
- 自动从编译器路径推导 `CMAKE_MAKE_PROGRAM`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 特色
|
||||||
|
|
||||||
|
- **类 MDK 虚拟目录**:像 Keil MDK 一样管理源文件
|
||||||
|
- **一键生成 CMake**:自动生成完整的 CMakeLists.txt
|
||||||
|
- **真正跨平台**:Windows (MinGW) / Linux / macOS 全支持,自动化平台适配
|
||||||
|
- **轻量级**:纯 Qt5 实现,无额外依赖
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**作者:虾哥**
|
||||||
|
**日期:2026-04-09 · 更新:2026-04-28**
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
QT += core gui widgets
|
||||||
|
|
||||||
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
|
|
||||||
|
CONFIG += c++11
|
||||||
|
|
||||||
|
TARGET = VoiletCStudio
|
||||||
|
TEMPLATE = app
|
||||||
|
|
||||||
|
# 源文件
|
||||||
|
SOURCES += \
|
||||||
|
src/main.cpp \
|
||||||
|
src/mainwindow.cpp \
|
||||||
|
src/projectconfig.cpp \
|
||||||
|
src/cmakegenerator.cpp
|
||||||
|
|
||||||
|
# 头文件
|
||||||
|
HEADERS += \
|
||||||
|
src/mainwindow.h \
|
||||||
|
src/projectconfig.h \
|
||||||
|
src/cmakegenerator.h
|
||||||
|
|
||||||
|
# 资源文件
|
||||||
|
RESOURCES += \
|
||||||
|
resources.qrc
|
||||||
|
|
||||||
|
# 默认部署规则
|
||||||
|
qnx: target.path = /tmp/$${TARGET}/bin
|
||||||
|
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||||
|
!isEmpty(target.path): INSTALLS += target
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =====================================================
|
||||||
|
# VoiletCStudio Linux 安装脚本
|
||||||
|
# 用法: sudo bash installer/install.sh
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_NAME="VoiletCStudio"
|
||||||
|
VERSION="1.2"
|
||||||
|
INSTALL_DIR="/opt/${APP_NAME}"
|
||||||
|
BIN_DIR="/usr/local/bin"
|
||||||
|
DESKTOP_DIR="/usr/share/applications"
|
||||||
|
MIME_DIR="/usr/share/mime/packages"
|
||||||
|
ICON_DIR="/usr/share/icons/hicolor/256x256/apps"
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo " VoiletCStudio v${VERSION} - Linux 安装程序"
|
||||||
|
echo "========================================="
|
||||||
|
|
||||||
|
# 检查 root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "请使用 sudo 运行: sudo bash installer/install.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[1/5] 编译程序..."
|
||||||
|
mkdir -p build && cd build
|
||||||
|
qmake ../VoiletCStudio.pro && make -j$(nproc)
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[2/5] 安装到 ${INSTALL_DIR}..."
|
||||||
|
mkdir -p "${INSTALL_DIR}"
|
||||||
|
cp build/VoiletCStudio "${INSTALL_DIR}/"
|
||||||
|
chmod +x "${INSTALL_DIR}/VoiletCStudio"
|
||||||
|
|
||||||
|
# 创建命令行软链接
|
||||||
|
ln -sf "${INSTALL_DIR}/VoiletCStudio" "${BIN_DIR}/voiletcstudio"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[3/5] 安装图标..."
|
||||||
|
mkdir -p "${ICON_DIR}"
|
||||||
|
cp resources/voiletcstudio.png "${ICON_DIR}/voiletcstudio.png"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[4/5] 注册 MIME 类型和 .desktop..."
|
||||||
|
mkdir -p "${MIME_DIR}"
|
||||||
|
cat > "${MIME_DIR}/voiletcstudio.xml" << 'MIMEXML'
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-voiletcstudio">
|
||||||
|
<comment>VoiletCStudio Project</comment>
|
||||||
|
<comment xml:lang="zh_CN">VoiletCStudio 工程文件</comment>
|
||||||
|
<glob pattern="*.VSC"/>
|
||||||
|
<glob pattern="*.vsc"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
|
MIMEXML
|
||||||
|
|
||||||
|
mkdir -p "${DESKTOP_DIR}"
|
||||||
|
cat > "${DESKTOP_DIR}/voiletcstudio.desktop" << DESKTOPEOF
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=VoiletCStudio
|
||||||
|
Name[zh_CN]=紫罗兰 C 工程配置器
|
||||||
|
Comment=C Project Configurator with CMake
|
||||||
|
Comment[zh_CN]=C 语言工程配置器,自动生成 CMakeLists.txt
|
||||||
|
Exec=${INSTALL_DIR}/VoiletCStudio %f
|
||||||
|
Icon=voiletcstudio
|
||||||
|
MimeType=application/x-voiletcstudio;
|
||||||
|
Categories=Development;IDE;
|
||||||
|
Terminal=false
|
||||||
|
DESKTOPEOF
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "[5/5] 更新系统数据库..."
|
||||||
|
update-mime-database /usr/share/mime
|
||||||
|
update-desktop-database
|
||||||
|
gtk-update-icon-cache /usr/share/icons/hicolor 2>/dev/null || true
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo " ✅ VoiletCStudio 安装完成!"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
echo " 命令行: voiletcstudio"
|
||||||
|
echo " 或双击 .VSC 工程文件自动打开"
|
||||||
|
echo ""
|
||||||
|
echo " 卸载: sudo bash installer/uninstall.sh"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =====================================================
|
||||||
|
# VoiletCStudio Linux 卸载脚本
|
||||||
|
# =====================================================
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo "请使用 sudo 运行"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
APP_NAME="VoiletCStudio"
|
||||||
|
|
||||||
|
echo "正在卸载 ${APP_NAME}..."
|
||||||
|
|
||||||
|
rm -rf "/opt/${APP_NAME}"
|
||||||
|
rm -f "/usr/local/bin/voiletcstudio"
|
||||||
|
rm -f "/usr/share/applications/voiletcstudio.desktop"
|
||||||
|
rm -f "/usr/share/mime/packages/voiletcstudio.xml"
|
||||||
|
rm -f "/usr/share/icons/hicolor/256x256/apps/voiletcstudio.png"
|
||||||
|
|
||||||
|
update-mime-database /usr/share/mime 2>/dev/null || true
|
||||||
|
update-desktop-database 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "✅ ${APP_NAME} 已卸载"
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
; =====================================================
|
||||||
|
; VoiletCStudio Windows 安装脚本 (NSIS)
|
||||||
|
; 用法: makensis installer/voiletcstudio.nsi
|
||||||
|
; =====================================================
|
||||||
|
|
||||||
|
!define PRODUCT_NAME "VoiletCStudio"
|
||||||
|
!define PRODUCT_VERSION "1.2"
|
||||||
|
!define PRODUCT_PUBLISHER "LinuxAcme"
|
||||||
|
!define PRODUCT_EXE "VoiletCStudio.exe"
|
||||||
|
|
||||||
|
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||||
|
OutFile "VoiletCStudio-Setup-${PRODUCT_VERSION}.exe"
|
||||||
|
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
|
||||||
|
RequestExecutionLevel admin
|
||||||
|
|
||||||
|
; ----- 安装页面 -----
|
||||||
|
Page directory
|
||||||
|
Page instfiles
|
||||||
|
|
||||||
|
; ----- 默认安装路径 -----
|
||||||
|
Section "Install"
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
|
||||||
|
; 复制主程序
|
||||||
|
File "VoiletCStudio.exe"
|
||||||
|
|
||||||
|
; 复制 Qt 运行时 DLL(需要提前准备好)
|
||||||
|
; File /r "Qt5Core.dll"
|
||||||
|
; File /r "Qt5Gui.dll"
|
||||||
|
; File /r "Qt5Widgets.dll"
|
||||||
|
|
||||||
|
; 复制 MinGW 运行时
|
||||||
|
; File /r "libgcc_s_seh-1.dll"
|
||||||
|
; File /r "libstdc++-6.dll"
|
||||||
|
; File /r "libwinpthread-1.dll"
|
||||||
|
|
||||||
|
; 创建卸载程序
|
||||||
|
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
; 注册 .VSC 文件关联
|
||||||
|
WriteRegStr HKCR ".VSC" "" "VoiletCStudio.VSC"
|
||||||
|
WriteRegStr HKCR "VoiletCStudio.VSC" "" "VoiletCStudio 工程文件"
|
||||||
|
WriteRegStr HKCR "VoiletCStudio.VSC\DefaultIcon" "" "$INSTDIR\${PRODUCT_EXE},0"
|
||||||
|
WriteRegStr HKCR "VoiletCStudio.VSC\shell\open\command" "" '"$INSTDIR\${PRODUCT_EXE}" "%1"'
|
||||||
|
|
||||||
|
; 注册表卸载信息
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayName" "${PRODUCT_NAME}"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||||
|
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||||
|
|
||||||
|
; 开始菜单快捷方式
|
||||||
|
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\VoiletCStudio.lnk" "$INSTDIR\${PRODUCT_EXE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\卸载.lnk" "$INSTDIR\uninstall.exe"
|
||||||
|
|
||||||
|
; 桌面快捷方式
|
||||||
|
CreateShortCut "$DESKTOP\VoiletCStudio.lnk" "$INSTDIR\${PRODUCT_EXE}"
|
||||||
|
|
||||||
|
; 通知系统刷新文件关联
|
||||||
|
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
; ----- 卸载 -----
|
||||||
|
Section "Uninstall"
|
||||||
|
; 删除程序
|
||||||
|
Delete "$INSTDIR\${PRODUCT_EXE}"
|
||||||
|
Delete "$INSTDIR\uninstall.exe"
|
||||||
|
RMDir "$INSTDIR"
|
||||||
|
|
||||||
|
; 删除文件关联
|
||||||
|
DeleteRegKey HKCR ".VSC"
|
||||||
|
DeleteRegKey HKCR "VoiletCStudio.VSC"
|
||||||
|
|
||||||
|
; 删除注册表
|
||||||
|
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
||||||
|
|
||||||
|
; 删除快捷方式
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\VoiletCStudio.lnk"
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\卸载.lnk"
|
||||||
|
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||||
|
Delete "$DESKTOP\VoiletCStudio.lnk"
|
||||||
|
|
||||||
|
System::Call 'shell32.dll::SHChangeNotify(i, i, i, i) v (0x08000000, 0, 0, 0)'
|
||||||
|
SectionEnd
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>resources/voiletcstudio.png</file>
|
||||||
|
<file>resources/voiletcstudio.ico</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=VoiletCStudio
|
||||||
|
Name[zh_CN]=紫罗兰 C 工程配置器
|
||||||
|
Comment=C Project Configurator with CMake
|
||||||
|
Comment[zh_CN]=C 语言工程配置器,自动生成 CMakeLists.txt
|
||||||
|
Exec=VoiletCStudio %f
|
||||||
|
Icon=voiletcstudio
|
||||||
|
MimeType=application/x-voiletcstudio;
|
||||||
|
Categories=Development;IDE;
|
||||||
|
Terminal=false
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
||||||
|
<defs>
|
||||||
|
<!-- Background gradient -->
|
||||||
|
<linearGradient id="bgGrad" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#8E24AA"/>
|
||||||
|
<stop offset="100%" stop-color="#4A148C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<!-- Inner glow -->
|
||||||
|
<radialGradient id="innerGlow" cx="0.5" cy="0.4" r="0.5">
|
||||||
|
<stop offset="0%" stop-color="#AB47BC" stop-opacity="0.5"/>
|
||||||
|
<stop offset="100%" stop-color="#4A148C" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Shadow -->
|
||||||
|
<rect x="8" y="10" width="240" height="240" rx="40" ry="40" fill="#00000033"/>
|
||||||
|
|
||||||
|
<!-- Background rounded rectangle -->
|
||||||
|
<rect x="4" y="4" width="248" height="248" rx="38" ry="38" fill="url(#bgGrad)"/>
|
||||||
|
|
||||||
|
<!-- Inner glow overlay -->
|
||||||
|
<rect x="4" y="4" width="248" height="248" rx="38" ry="38" fill="url(#innerGlow)"/>
|
||||||
|
|
||||||
|
<!-- Subtle border highlight -->
|
||||||
|
<rect x="6" y="6" width="244" height="244" rx="36" ry="36" fill="none" stroke="#CE93D8" stroke-width="1.5" stroke-opacity="0.4"/>
|
||||||
|
|
||||||
|
<!-- ===== C Letter (geometric path, no text dependency) ===== -->
|
||||||
|
<!-- Modern bold C: outer arc + inner arc as a path -->
|
||||||
|
<g transform="translate(128, 128)" fill="#FFFFFF">
|
||||||
|
<!-- Large C - drawn as a thick partial circle -->
|
||||||
|
<path d="
|
||||||
|
M -40,-60
|
||||||
|
A 70,70 0 0,1 50,-40
|
||||||
|
L 30,-15
|
||||||
|
A 45,45 0 0,0 -20,-40
|
||||||
|
L -20,40
|
||||||
|
A 45,45 0 0,0 30,15
|
||||||
|
L 50,40
|
||||||
|
A 70,70 0 0,1 -40,60
|
||||||
|
Z
|
||||||
|
" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- ===== Small angle brackets (code element) ===== -->
|
||||||
|
<g fill="#FFD54F" opacity="0.9">
|
||||||
|
<!-- < bracket top-left -->
|
||||||
|
<path d="M 62,108 L 78,98 L 78,106 L 68,118 L 78,130 L 78,138 Z"/>
|
||||||
|
<!-- > bracket top-right -->
|
||||||
|
<path d="M 194,108 L 178,98 L 178,106 L 188,118 L 178,130 L 178,138 Z"/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<!-- Small dots (syntax element) -->
|
||||||
|
<circle cx="128" cy="195" r="4" fill="#FFD54F" opacity="0.7"/>
|
||||||
|
|
||||||
|
<!-- "C" underscore accent -->
|
||||||
|
<line x1="102" y1="185" x2="154" y2="185" stroke="#FFD54F" stroke-width="3" stroke-linecap="round" opacity="0.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,316 @@
|
|||||||
|
#include "cmakegenerator.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
CMakeGenerator::CMakeGenerator(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CMakeGenerator::generate(const ProjectConfig *config, const QString &outputPath)
|
||||||
|
{
|
||||||
|
if (!config) {
|
||||||
|
emit errorOccurred("配置对象为空");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString content = generateContent(config);
|
||||||
|
|
||||||
|
QFileInfo fileInfo(outputPath);
|
||||||
|
QDir dir = fileInfo.absoluteDir();
|
||||||
|
if (!dir.exists()) {
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(outputPath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||||
|
emit errorOccurred("无法创建 CMakeLists.txt: " + file.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream out(&file);
|
||||||
|
out.setCodec("UTF-8");
|
||||||
|
out << content;
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
m_lastGeneratedPath = outputPath;
|
||||||
|
emit cmakeGenerated(outputPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::getLastGeneratedPath() const
|
||||||
|
{
|
||||||
|
return m_lastGeneratedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateContent(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
|
||||||
|
content += generateHeader(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateProjectConfig(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateSourceFiles(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateIncludeDirs(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateLibraries(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateDefines(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateCompilerOptions(config);
|
||||||
|
content += "\n";
|
||||||
|
content += generateBuildTargets(config);
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateHeader(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString header;
|
||||||
|
header += "# ========================================\n";
|
||||||
|
header += "# CMakeLists.txt - VoiletCStudio 自动生成\n";
|
||||||
|
header += "# 工程名称:" + config->getProjectName() + "\n";
|
||||||
|
header += "# 生成时间:" + QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss") + "\n";
|
||||||
|
header += "# 跨平台支持:Windows (MinGW) / Linux (GCC) / macOS (Clang)\n";
|
||||||
|
header += "# ========================================\n\n";
|
||||||
|
|
||||||
|
header += "cmake_minimum_required(VERSION 3.10)\n\n";
|
||||||
|
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateProjectConfig(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
|
||||||
|
QString projectName = config->getProjectName();
|
||||||
|
if (projectName.isEmpty()) {
|
||||||
|
projectName = "UntitledProject";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 编译器必须在 project() 之前设置,否则 CMake 会默认用 NMake =====
|
||||||
|
content += "# ========================================\n";
|
||||||
|
content += "# 编译器配置(必须在 project() 之前)\n";
|
||||||
|
content += "# ========================================\n\n";
|
||||||
|
|
||||||
|
QString compilerPath = config->getCompilerPath();
|
||||||
|
QString assemblerPath = config->getAssemblerPath();
|
||||||
|
QString linkerPath = config->getLinkerPath();
|
||||||
|
|
||||||
|
if (!compilerPath.isEmpty() && compilerPath != "gcc") {
|
||||||
|
content += "set(CMAKE_C_COMPILER \"" + compilerPath + "\")\n";
|
||||||
|
|
||||||
|
// 如果编译器是 MinGW 路径,自动推导 make 程序目录
|
||||||
|
// 例如:C:/msys2/mingw64/bin/gcc.exe → C:/msys2/mingw64/bin/mingw32-make.exe
|
||||||
|
if (compilerPath.contains("mingw", Qt::CaseInsensitive)) {
|
||||||
|
QString makeDir = QFileInfo(compilerPath).path(); // path() 正确保留绝对路径的盘符
|
||||||
|
content += "set(CMAKE_MAKE_PROGRAM \"" + makeDir + "/mingw32-make.exe\")\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assemblerPath.isEmpty() && assemblerPath != "gcc") {
|
||||||
|
content += "set(CMAKE_ASM_COMPILER \"" + assemblerPath + "\")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!linkerPath.isEmpty() && linkerPath != "gcc" && linkerPath != compilerPath) {
|
||||||
|
content += "set(CMAKE_C_LINK_EXECUTABLE \"" + linkerPath + " <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>\")\n";
|
||||||
|
}
|
||||||
|
content += "\n";
|
||||||
|
|
||||||
|
content += "# 项目名称(编译器已设置,不再尝试检测)\n";
|
||||||
|
content += "project(" + projectName + " C)\n\n";
|
||||||
|
|
||||||
|
content += "# C 标准\n";
|
||||||
|
content += "set(CMAKE_C_STANDARD 11)\n";
|
||||||
|
content += "set(CMAKE_C_STANDARD_REQUIRED ON)\n\n";
|
||||||
|
|
||||||
|
// 输出目录配置
|
||||||
|
QString outputDir = config->getOutputDir();
|
||||||
|
if (outputDir.isEmpty()) {
|
||||||
|
outputDir = "./build";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "# 输出目录配置\n";
|
||||||
|
content += "set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n";
|
||||||
|
content += "set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n";
|
||||||
|
content += "set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/" + outputDir + ")\n\n";
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateSourceFiles(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
content += "# ========================================\n";
|
||||||
|
content += "# 源文件配置(虚拟目录结构)\n";
|
||||||
|
content += "# ========================================\n\n";
|
||||||
|
|
||||||
|
QMap<QString, VirtualDir> virtualDirs = config->getVirtualDirs();
|
||||||
|
|
||||||
|
if (virtualDirs.isEmpty()) {
|
||||||
|
content += "# 无源文件\n";
|
||||||
|
} else {
|
||||||
|
// 遍历虚拟目录
|
||||||
|
for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) {
|
||||||
|
content += "# 分组:" + it.key() + "\n";
|
||||||
|
QString safeName = it.key();
|
||||||
|
safeName.replace(" ", "_");
|
||||||
|
safeName.replace("/", "_");
|
||||||
|
content += "set(SRCS_" + safeName + "\n";
|
||||||
|
for (const QString &file : it->files) {
|
||||||
|
content += " " + file + "\n";
|
||||||
|
}
|
||||||
|
content += ")\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并所有源文件
|
||||||
|
content += "# 合并所有源文件\n";
|
||||||
|
content += "set(SOURCES\n";
|
||||||
|
for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) {
|
||||||
|
for (const QString &file : it->files) {
|
||||||
|
content += " " + file + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content += ")\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "# 创建可执行文件\n";
|
||||||
|
QString outputName = config->getProjectName();
|
||||||
|
if (outputName.isEmpty()) {
|
||||||
|
outputName = "output";
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "add_executable(" + outputName + " ${SOURCES})\n\n";
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateIncludeDirs(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
content += "# 包含目录\n";
|
||||||
|
|
||||||
|
QStringList includeDirs = config->getIncludeDirs();
|
||||||
|
if (includeDirs.isEmpty()) {
|
||||||
|
content += "# 无额外包含目录\n";
|
||||||
|
} else {
|
||||||
|
content += "target_include_directories(" + config->getProjectName() + " PRIVATE\n";
|
||||||
|
for (const QString &dir : includeDirs) {
|
||||||
|
content += " " + dir + "\n";
|
||||||
|
}
|
||||||
|
content += ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateLibraries(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
content += "# 链接库\n";
|
||||||
|
|
||||||
|
QStringList libraries = config->getLibraries();
|
||||||
|
if (libraries.isEmpty()) {
|
||||||
|
content += "# 无外部库文件\n";
|
||||||
|
} else {
|
||||||
|
content += "target_link_libraries(" + config->getProjectName() + " PRIVATE\n";
|
||||||
|
for (const QString &lib : libraries) {
|
||||||
|
content += " " + lib + "\n";
|
||||||
|
}
|
||||||
|
content += ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateDefines(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
content += "# 预编译宏定义\n";
|
||||||
|
|
||||||
|
QStringList defines = config->getDefines();
|
||||||
|
if (defines.isEmpty()) {
|
||||||
|
content += "# 无额外宏定义\n";
|
||||||
|
} else {
|
||||||
|
content += "target_compile_definitions(" + config->getProjectName() + " PRIVATE\n";
|
||||||
|
for (const QString &def : defines) {
|
||||||
|
content += " " + def + "\n";
|
||||||
|
}
|
||||||
|
content += ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateCompilerOptions(const ProjectConfig *config)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
content += "# 自定义编译选项\n";
|
||||||
|
|
||||||
|
QStringList options = config->getCompilerOptions();
|
||||||
|
if (options.isEmpty()) {
|
||||||
|
content += "# 无自定义编译选项\n";
|
||||||
|
} else {
|
||||||
|
content += "target_compile_options(" + config->getProjectName() + " PRIVATE\n";
|
||||||
|
for (const QString &opt : options) {
|
||||||
|
content += " " + opt + "\n";
|
||||||
|
}
|
||||||
|
content += ")\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString CMakeGenerator::generateBuildTargets(const ProjectConfig * /*config*/)
|
||||||
|
{
|
||||||
|
QString content;
|
||||||
|
|
||||||
|
content += "# ========================================\n";
|
||||||
|
content += "# 构建类型配置\n";
|
||||||
|
content += "# ========================================\n\n";
|
||||||
|
|
||||||
|
content += "# 默认构建类型:Debug\n";
|
||||||
|
content += "if(NOT CMAKE_BUILD_TYPE)\n";
|
||||||
|
content += " set(CMAKE_BUILD_TYPE Debug CACHE STRING \"Build type\" FORCE)\n";
|
||||||
|
content += "endif()\n\n";
|
||||||
|
|
||||||
|
content += "# Debug 模式配置(自动添加 DEBUG 宏)\n";
|
||||||
|
content += "set(CMAKE_C_FLAGS_DEBUG \"-g -O0 -DDEBUG\" CACHE STRING \"Debug flags\" FORCE)\n\n";
|
||||||
|
|
||||||
|
content += "# Release 模式配置\n";
|
||||||
|
content += "set(CMAKE_C_FLAGS_RELEASE \"-O2 -DNDEBUG\" CACHE STRING \"Release flags\" FORCE)\n\n";
|
||||||
|
|
||||||
|
content += "# ========================================\n";
|
||||||
|
content += "# 自定义 Make 目标:make debug / make release\n";
|
||||||
|
content += "# ========================================\n\n";
|
||||||
|
|
||||||
|
content += "# make debug - 编译 Debug 版本(自动添加 DEBUG 宏)\n";
|
||||||
|
content += "add_custom_target(debug\n";
|
||||||
|
content += " COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR}\n";
|
||||||
|
content += " COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Debug\n";
|
||||||
|
content += " COMMENT \"Building Debug version\"\n";
|
||||||
|
content += " VERBATIM\n";
|
||||||
|
content += ")\n\n";
|
||||||
|
|
||||||
|
content += "# make release - 编译 Release 版本\n";
|
||||||
|
content += "add_custom_target(release\n";
|
||||||
|
content += " COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_SOURCE_DIR}\n";
|
||||||
|
content += " COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Release\n";
|
||||||
|
content += " COMMENT \"Building Release version\"\n";
|
||||||
|
content += " VERBATIM\n";
|
||||||
|
content += ")\n\n";
|
||||||
|
|
||||||
|
content += "# 使用说明:\n";
|
||||||
|
content += "# cmake -B build # 配置(默认 Debug)\n";
|
||||||
|
content += "# cmake --build build # 编译(使用当前 CMAKE_BUILD_TYPE)\n";
|
||||||
|
content += "# make debug # 编译 Debug 版本(自动添加 DEBUG 宏)\n";
|
||||||
|
content += "# make release # 编译 Release 版本\n";
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#ifndef CMAKEGENERATOR_H
|
||||||
|
#define CMAKEGENERATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include "projectconfig.h"
|
||||||
|
|
||||||
|
class CMakeGenerator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CMakeGenerator(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// 生成 CMakeLists.txt
|
||||||
|
bool generate(const ProjectConfig *config, const QString &outputPath);
|
||||||
|
|
||||||
|
// 获取最后生成的路径
|
||||||
|
QString getLastGeneratedPath() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void cmakeGenerated(const QString &path);
|
||||||
|
void errorOccurred(const QString &error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString generateContent(const ProjectConfig *config);
|
||||||
|
QString generateHeader(const ProjectConfig *config);
|
||||||
|
QString generateProjectConfig(const ProjectConfig *config);
|
||||||
|
QString generateSourceFiles(const ProjectConfig *config);
|
||||||
|
QString generateIncludeDirs(const ProjectConfig *config);
|
||||||
|
QString generateLibraries(const ProjectConfig *config);
|
||||||
|
QString generateDefines(const ProjectConfig *config);
|
||||||
|
QString generateCompilerOptions(const ProjectConfig *config);
|
||||||
|
QString generateBuildTargets(const ProjectConfig *config);
|
||||||
|
|
||||||
|
QString m_lastGeneratedPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CMAKEGENERATOR_H
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
#include <QApplication>
|
||||||
|
#include <QStyleFactory>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "mainwindow.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ===== 文件关联注册 =====
|
||||||
|
static void registerFileAssociation()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
QString appPath = QCoreApplication::applicationFilePath();
|
||||||
|
appPath.replace("/", "\\");
|
||||||
|
|
||||||
|
// 注册 .VSC 扩展名
|
||||||
|
QSettings settings("HKEY_CLASSES_ROOT\\.VSC", QSettings::NativeFormat);
|
||||||
|
settings.setValue("Default", "VoiletCStudio.VSC");
|
||||||
|
settings.sync();
|
||||||
|
|
||||||
|
// 注册文件类型描述
|
||||||
|
QSettings typeSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC", QSettings::NativeFormat);
|
||||||
|
typeSettings.setValue("Default", "VoiletCStudio 工程文件");
|
||||||
|
typeSettings.sync();
|
||||||
|
|
||||||
|
// 注册图标
|
||||||
|
QSettings iconSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC\\DefaultIcon", QSettings::NativeFormat);
|
||||||
|
iconSettings.setValue("Default", appPath + ",0");
|
||||||
|
iconSettings.sync();
|
||||||
|
|
||||||
|
// 注册打开命令
|
||||||
|
QSettings cmdSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC\\shell\\open\\command", QSettings::NativeFormat);
|
||||||
|
cmdSettings.setValue("Default", "\"" + appPath + "\" \"%1\"");
|
||||||
|
cmdSettings.sync();
|
||||||
|
|
||||||
|
// 通知系统刷新
|
||||||
|
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||||
|
|
||||||
|
qDebug() << "[OK] .VSC file association registered on Windows";
|
||||||
|
#else
|
||||||
|
// Linux: 写入 MIME 类型定义 + 更新 desktop 数据库
|
||||||
|
QString mimeDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime/packages";
|
||||||
|
QDir().mkpath(mimeDir);
|
||||||
|
|
||||||
|
QString mimeFile = mimeDir + "/voiletcstudio.xml";
|
||||||
|
QFile f(mimeFile);
|
||||||
|
if (f.open(QIODevice::WriteOnly)) {
|
||||||
|
f.write(R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-voiletcstudio">
|
||||||
|
<comment>VoiletCStudio Project</comment>
|
||||||
|
<comment xml:lang="zh_CN">VoiletCStudio 工程文件</comment>
|
||||||
|
<glob pattern="*.VSC"/>
|
||||||
|
<glob pattern="*.vsc"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
|
)");
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入 .desktop 文件
|
||||||
|
QString appsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation);
|
||||||
|
QDir().mkpath(appsDir);
|
||||||
|
|
||||||
|
QString desktopFile = appsDir + "/voiletcstudio.desktop";
|
||||||
|
QFile df(desktopFile);
|
||||||
|
if (df.open(QIODevice::WriteOnly)) {
|
||||||
|
df.write(R"([Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=VoiletCStudio
|
||||||
|
Name[zh_CN]=紫罗兰 C 工程配置器
|
||||||
|
Comment=C Project Configurator
|
||||||
|
Comment[zh_CN]=C 语言工程配置器
|
||||||
|
Exec=)" + QCoreApplication::applicationFilePath().toUtf8() + R"( %f
|
||||||
|
Icon=voiletcstudio
|
||||||
|
MimeType=application/x-voiletcstudio;
|
||||||
|
Categories=Development;IDE;
|
||||||
|
Terminal=false
|
||||||
|
)");
|
||||||
|
df.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 MIME 数据库
|
||||||
|
QProcess::execute("update-mime-database", QStringList() <<
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime");
|
||||||
|
QProcess::execute("update-desktop-database", QStringList());
|
||||||
|
|
||||||
|
qDebug() << "[OK] .VSC file association registered on Linux";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unregisterFileAssociation()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
QSettings settings("HKEY_CLASSES_ROOT\\.VSC", QSettings::NativeFormat);
|
||||||
|
settings.remove("Default");
|
||||||
|
settings.sync();
|
||||||
|
|
||||||
|
QSettings("HKEY_CLASSES_ROOT\\VoiletCStudio.VSC", QSettings::NativeFormat).remove("");
|
||||||
|
SHDeleteKey(HKEY_CLASSES_ROOT, L"VoiletCStudio.VSC");
|
||||||
|
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||||
|
|
||||||
|
qDebug() << "[OK] .VSC file association removed";
|
||||||
|
#else
|
||||||
|
QString mimeFile = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime/packages/voiletcstudio.xml";
|
||||||
|
QFile::remove(mimeFile);
|
||||||
|
QProcess::execute("update-mime-database", QStringList() <<
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/mime");
|
||||||
|
qDebug() << "[OK] .VSC file association removed";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
// 设置应用图标
|
||||||
|
app.setWindowIcon(QIcon(":/resources/voiletcstudio.png"));
|
||||||
|
|
||||||
|
// 设置应用程序信息
|
||||||
|
app.setApplicationName("VoiletCStudio");
|
||||||
|
app.setApplicationVersion("1.2");
|
||||||
|
app.setOrganizationName("LinuxAcme");
|
||||||
|
|
||||||
|
// 命令行参数解析
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription("紫罗兰 C 工程配置器 - 自动生成 CMakeLists.txt");
|
||||||
|
parser.addHelpOption();
|
||||||
|
parser.addVersionOption();
|
||||||
|
|
||||||
|
QCommandLineOption registerOpt("register", "注册 .VSC 文件关联到系统");
|
||||||
|
QCommandLineOption unregisterOpt("unregister", "取消 .VSC 文件关联");
|
||||||
|
parser.addOption(registerOpt);
|
||||||
|
parser.addOption(unregisterOpt);
|
||||||
|
|
||||||
|
// 接受 .VSC 文件作为位置参数(双击打开)
|
||||||
|
parser.addPositionalArgument("file", "要打开的 .VSC 工程文件");
|
||||||
|
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
// 处理注册/取消注册
|
||||||
|
if (parser.isSet(registerOpt)) {
|
||||||
|
registerFileAssociation();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (parser.isSet(unregisterOpt)) {
|
||||||
|
unregisterFileAssociation();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置样式
|
||||||
|
app.setStyle(QStyleFactory::create("Fusion"));
|
||||||
|
|
||||||
|
MainWindow window;
|
||||||
|
window.show();
|
||||||
|
|
||||||
|
// 通过命令行传入的文件自动打开
|
||||||
|
QStringList posArgs = parser.positionalArguments();
|
||||||
|
if (!posArgs.isEmpty()) {
|
||||||
|
QString filePath = posArgs.first();
|
||||||
|
if (filePath.endsWith(".VSC", Qt::CaseInsensitive) || filePath.endsWith(".json", Qt::CaseInsensitive)) {
|
||||||
|
window.openProjectFile(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
@@ -0,0 +1,922 @@
|
|||||||
|
#include "mainwindow.h"
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QToolBar>
|
||||||
|
#include <QStatusBar>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QKeySequence>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QScrollBar>
|
||||||
|
#include <QMimeData>
|
||||||
|
|
||||||
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
|
: QMainWindow(parent)
|
||||||
|
, modified(false)
|
||||||
|
, showCMakeSuccessMsg(true)
|
||||||
|
{
|
||||||
|
config = new ProjectConfig(this);
|
||||||
|
cmakeGenerator = new CMakeGenerator(this);
|
||||||
|
|
||||||
|
setupUI();
|
||||||
|
setupConnections();
|
||||||
|
updateWindowTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setupUI()
|
||||||
|
{
|
||||||
|
setWindowTitle("VoiletCStudio - C 工程配置器");
|
||||||
|
resize(1400, 900);
|
||||||
|
|
||||||
|
// 创建操作按钮栏
|
||||||
|
QGroupBox *actionGroup = new QGroupBox("⚡ 快捷操作");
|
||||||
|
QHBoxLayout *actionLayout = new QHBoxLayout(actionGroup);
|
||||||
|
|
||||||
|
QPushButton *openBtn = new QPushButton("📂 打开工程");
|
||||||
|
openBtn->setMinimumHeight(35);
|
||||||
|
openBtn->setStyleSheet("QPushButton { background: #1565C0; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(openBtn, &QPushButton::clicked, this, &MainWindow::openProject);
|
||||||
|
actionLayout->addWidget(openBtn);
|
||||||
|
|
||||||
|
QPushButton *newBtn = new QPushButton("📄 新建工程");
|
||||||
|
newBtn->setMinimumHeight(35);
|
||||||
|
newBtn->setStyleSheet("QPushButton { background: #2E7D32; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(newBtn, &QPushButton::clicked, this, &MainWindow::newProject);
|
||||||
|
actionLayout->addWidget(newBtn);
|
||||||
|
|
||||||
|
saveButton = new QPushButton("💾 保存工程 (Ctrl+S)");
|
||||||
|
saveButton->setMinimumHeight(35);
|
||||||
|
saveButton->setStyleSheet("QPushButton { background: #E65100; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(saveButton, &QPushButton::clicked, this, &MainWindow::saveProject);
|
||||||
|
actionLayout->addWidget(saveButton);
|
||||||
|
|
||||||
|
generateButton = new QPushButton("🔨 生成 CMake (Ctrl+G)");
|
||||||
|
generateButton->setMinimumHeight(35);
|
||||||
|
generateButton->setStyleSheet("QPushButton { background: #6A1B9A; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(generateButton, &QPushButton::clicked, this, &MainWindow::generateCMake);
|
||||||
|
actionLayout->addWidget(generateButton);
|
||||||
|
|
||||||
|
QPushButton *debugBtn = new QPushButton("🐛 编译 Debug");
|
||||||
|
debugBtn->setMinimumHeight(35);
|
||||||
|
debugBtn->setStyleSheet("QPushButton { background: #00838F; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(debugBtn, &QPushButton::clicked, this, &MainWindow::compileDebug);
|
||||||
|
actionLayout->addWidget(debugBtn);
|
||||||
|
this->debugBtn = debugBtn;
|
||||||
|
|
||||||
|
QPushButton *releaseBtn = new QPushButton("🚀 编译 Release");
|
||||||
|
releaseBtn->setMinimumHeight(35);
|
||||||
|
releaseBtn->setStyleSheet("QPushButton { background: #AD1457; color: white; font-weight: bold; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
connect(releaseBtn, &QPushButton::clicked, this, &MainWindow::compileRelease);
|
||||||
|
actionLayout->addWidget(releaseBtn);
|
||||||
|
this->releaseBtn = releaseBtn;
|
||||||
|
|
||||||
|
actionLayout->addStretch();
|
||||||
|
|
||||||
|
// 创建中心部件
|
||||||
|
QWidget *central = new QWidget(this);
|
||||||
|
setCentralWidget(central);
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout(central);
|
||||||
|
mainLayout->addWidget(actionGroup);
|
||||||
|
|
||||||
|
// 工程基本信息
|
||||||
|
QGroupBox *basicGroup = new QGroupBox("📁 工程基本信息");
|
||||||
|
QHBoxLayout *basicLayout = new QHBoxLayout(basicGroup);
|
||||||
|
|
||||||
|
basicLayout->addWidget(new QLabel("📁 工程名:"));
|
||||||
|
projectNameEdit = new QLineEdit();
|
||||||
|
projectNameEdit->setPlaceholderText("输入工程名称");
|
||||||
|
basicLayout->addWidget(projectNameEdit);
|
||||||
|
|
||||||
|
basicLayout->addStretch();
|
||||||
|
basicLayout->addWidget(new QLabel("📂 工程路径:同 VSC 工程文件所在目录"));
|
||||||
|
|
||||||
|
mainLayout->addWidget(basicGroup);
|
||||||
|
|
||||||
|
// 编译工具链
|
||||||
|
QGroupBox *toolchainGroup = new QGroupBox("🔧 编译工具链");
|
||||||
|
QHBoxLayout *toolchainLayout = new QHBoxLayout(toolchainGroup);
|
||||||
|
|
||||||
|
toolchainLayout->addWidget(new QLabel("编译器:"));
|
||||||
|
compilerPathEdit = new QLineEdit();
|
||||||
|
compilerPathEdit->setPlaceholderText("/usr/bin/gcc");
|
||||||
|
toolchainLayout->addWidget(compilerPathEdit);
|
||||||
|
|
||||||
|
QPushButton *compilerBtn = new QPushButton("浏览");
|
||||||
|
connect(compilerBtn, &QPushButton::clicked, this, &MainWindow::browseCompilerPath);
|
||||||
|
toolchainLayout->addWidget(compilerBtn);
|
||||||
|
|
||||||
|
toolchainLayout->addWidget(new QLabel("汇编器:"));
|
||||||
|
assemblerPathEdit = new QLineEdit();
|
||||||
|
assemblerPathEdit->setPlaceholderText("/usr/bin/gcc");
|
||||||
|
toolchainLayout->addWidget(assemblerPathEdit);
|
||||||
|
|
||||||
|
QPushButton *assemblerBtn = new QPushButton("浏览");
|
||||||
|
connect(assemblerBtn, &QPushButton::clicked, this, &MainWindow::browseAssemblerPath);
|
||||||
|
toolchainLayout->addWidget(assemblerBtn);
|
||||||
|
|
||||||
|
toolchainLayout->addWidget(new QLabel("链接器:"));
|
||||||
|
linkerPathEdit = new QLineEdit();
|
||||||
|
linkerPathEdit->setPlaceholderText("/usr/bin/gcc");
|
||||||
|
toolchainLayout->addWidget(linkerPathEdit);
|
||||||
|
|
||||||
|
QPushButton *linkerBtn = new QPushButton("浏览");
|
||||||
|
connect(linkerBtn, &QPushButton::clicked, this, &MainWindow::browseLinkerPath);
|
||||||
|
toolchainLayout->addWidget(linkerBtn);
|
||||||
|
|
||||||
|
mainLayout->addWidget(toolchainGroup);
|
||||||
|
|
||||||
|
// 主分割器 - 左右布局
|
||||||
|
QSplitter *mainSplitter = new QSplitter(Qt::Horizontal);
|
||||||
|
|
||||||
|
// 左侧 - 文件管理
|
||||||
|
QWidget *leftPanel = new QWidget();
|
||||||
|
QVBoxLayout *leftLayout = new QVBoxLayout(leftPanel);
|
||||||
|
leftLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 源文件(虚拟目录)
|
||||||
|
QGroupBox *sourceGroup = new QGroupBox("📄 源文件 (.c) - 虚拟目录");
|
||||||
|
QVBoxLayout *sourceLayout = new QVBoxLayout(sourceGroup);
|
||||||
|
|
||||||
|
QHBoxLayout *sourceBtnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *addDirBtn = new QPushButton("➕ 目录");
|
||||||
|
connect(addDirBtn, &QPushButton::clicked, this, &MainWindow::addVirtualDir);
|
||||||
|
sourceBtnLayout->addWidget(addDirBtn);
|
||||||
|
|
||||||
|
QPushButton *addFileBtn = new QPushButton("➕ 文件");
|
||||||
|
connect(addFileBtn, &QPushButton::clicked, this, &MainWindow::addSourceFile);
|
||||||
|
sourceBtnLayout->addWidget(addFileBtn);
|
||||||
|
|
||||||
|
QPushButton *removeFileBtn = new QPushButton("➖ 移除");
|
||||||
|
connect(removeFileBtn, &QPushButton::clicked, this, &MainWindow::removeSourceFile);
|
||||||
|
sourceBtnLayout->addWidget(removeFileBtn);
|
||||||
|
sourceBtnLayout->addStretch();
|
||||||
|
|
||||||
|
sourceLayout->addLayout(sourceBtnLayout);
|
||||||
|
|
||||||
|
sourceTree = new QTreeWidget();
|
||||||
|
sourceTree->setHeaderLabels(QStringList() << "📁 源文件 (虚拟目录)");
|
||||||
|
sourceTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||||
|
sourceLayout->addWidget(sourceTree);
|
||||||
|
|
||||||
|
leftLayout->addWidget(sourceGroup);
|
||||||
|
|
||||||
|
// 包含目录
|
||||||
|
QGroupBox *includeGroup = new QGroupBox("📚 包含目录 (.h)");
|
||||||
|
QVBoxLayout *includeLayout = new QVBoxLayout(includeGroup);
|
||||||
|
|
||||||
|
QHBoxLayout *includeBtnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *addIncludeBtn = new QPushButton("➕ 添加");
|
||||||
|
connect(addIncludeBtn, &QPushButton::clicked, this, &MainWindow::addIncludeDir);
|
||||||
|
includeBtnLayout->addWidget(addIncludeBtn);
|
||||||
|
|
||||||
|
QPushButton *removeIncludeBtn = new QPushButton("➖ 移除");
|
||||||
|
connect(removeIncludeBtn, &QPushButton::clicked, this, &MainWindow::removeIncludeDir);
|
||||||
|
includeBtnLayout->addWidget(removeIncludeBtn);
|
||||||
|
includeBtnLayout->addStretch();
|
||||||
|
|
||||||
|
includeLayout->addLayout(includeBtnLayout);
|
||||||
|
|
||||||
|
includeDirList = new QListWidget();
|
||||||
|
includeLayout->addWidget(includeDirList);
|
||||||
|
|
||||||
|
leftLayout->addWidget(includeGroup);
|
||||||
|
|
||||||
|
mainSplitter->addWidget(leftPanel);
|
||||||
|
|
||||||
|
// 右侧 - 编译配置
|
||||||
|
QWidget *rightPanel = new QWidget();
|
||||||
|
QVBoxLayout *rightLayout = new QVBoxLayout(rightPanel);
|
||||||
|
rightLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// 库文件
|
||||||
|
QGroupBox *libGroup = new QGroupBox("📦 库文件 (.a/.so/.lib)");
|
||||||
|
QVBoxLayout *libLayout = new QVBoxLayout(libGroup);
|
||||||
|
|
||||||
|
QHBoxLayout *libBtnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *addLibBtn = new QPushButton("➕ 添加");
|
||||||
|
connect(addLibBtn, &QPushButton::clicked, this, &MainWindow::addLibrary);
|
||||||
|
libBtnLayout->addWidget(addLibBtn);
|
||||||
|
|
||||||
|
QPushButton *removeLibBtn = new QPushButton("➖ 移除");
|
||||||
|
connect(removeLibBtn, &QPushButton::clicked, this, &MainWindow::removeLibrary);
|
||||||
|
libBtnLayout->addWidget(removeLibBtn);
|
||||||
|
libBtnLayout->addStretch();
|
||||||
|
|
||||||
|
libLayout->addLayout(libBtnLayout);
|
||||||
|
|
||||||
|
libraryList = new QListWidget();
|
||||||
|
libLayout->addWidget(libraryList);
|
||||||
|
|
||||||
|
rightLayout->addWidget(libGroup);
|
||||||
|
|
||||||
|
// 编译宏
|
||||||
|
QGroupBox *defineGroup = new QGroupBox("🏷️ 编译宏定义");
|
||||||
|
QVBoxLayout *defineLayout = new QVBoxLayout(defineGroup);
|
||||||
|
|
||||||
|
QHBoxLayout *defineBtnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *addDefineBtn = new QPushButton("➕ 添加");
|
||||||
|
connect(addDefineBtn, &QPushButton::clicked, this, &MainWindow::addDefine);
|
||||||
|
defineBtnLayout->addWidget(addDefineBtn);
|
||||||
|
|
||||||
|
QPushButton *removeDefineBtn = new QPushButton("➖ 移除");
|
||||||
|
connect(removeDefineBtn, &QPushButton::clicked, this, &MainWindow::removeDefine);
|
||||||
|
defineBtnLayout->addWidget(removeDefineBtn);
|
||||||
|
defineBtnLayout->addStretch();
|
||||||
|
|
||||||
|
defineLayout->addLayout(defineBtnLayout);
|
||||||
|
|
||||||
|
defineList = new QListWidget();
|
||||||
|
defineLayout->addWidget(defineList);
|
||||||
|
|
||||||
|
rightLayout->addWidget(defineGroup);
|
||||||
|
|
||||||
|
// 编译选项
|
||||||
|
QGroupBox *optionGroup = new QGroupBox("⚙️ 自定义编译选项");
|
||||||
|
QVBoxLayout *optionLayout = new QVBoxLayout(optionGroup);
|
||||||
|
|
||||||
|
QHBoxLayout *optionBtnLayout = new QHBoxLayout();
|
||||||
|
QPushButton *addOptionBtn = new QPushButton("➕ 添加");
|
||||||
|
connect(addOptionBtn, &QPushButton::clicked, this, &MainWindow::addCompilerOption);
|
||||||
|
optionBtnLayout->addWidget(addOptionBtn);
|
||||||
|
|
||||||
|
QPushButton *removeOptionBtn = new QPushButton("➖ 移除");
|
||||||
|
connect(removeOptionBtn, &QPushButton::clicked, this, &MainWindow::removeCompilerOption);
|
||||||
|
optionBtnLayout->addWidget(removeOptionBtn);
|
||||||
|
optionBtnLayout->addStretch();
|
||||||
|
|
||||||
|
optionLayout->addLayout(optionBtnLayout);
|
||||||
|
|
||||||
|
optionList = new QListWidget();
|
||||||
|
optionLayout->addWidget(optionList);
|
||||||
|
|
||||||
|
rightLayout->addWidget(optionGroup);
|
||||||
|
|
||||||
|
mainSplitter->addWidget(rightPanel);
|
||||||
|
mainSplitter->setStretchFactor(0, 1);
|
||||||
|
mainSplitter->setStretchFactor(1, 1);
|
||||||
|
|
||||||
|
mainLayout->addWidget(mainSplitter);
|
||||||
|
|
||||||
|
// 状态栏
|
||||||
|
statusBar()->showMessage("就绪");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::setupConnections()
|
||||||
|
{
|
||||||
|
connect(projectNameEdit, &QLineEdit::textChanged, this, &MainWindow::onProjectNameChanged);
|
||||||
|
connect(compilerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onCompilerPathChanged);
|
||||||
|
connect(assemblerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onAssemblerPathChanged);
|
||||||
|
connect(linkerPathEdit, &QLineEdit::textChanged, this, &MainWindow::onLinkerPathChanged);
|
||||||
|
|
||||||
|
connect(config, &ProjectConfig::configChanged, [this]() {
|
||||||
|
modified = true;
|
||||||
|
updateWindowTitle();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(cmakeGenerator, &CMakeGenerator::cmakeGenerated, [this](const QString &path) {
|
||||||
|
if (showCMakeSuccessMsg) {
|
||||||
|
statusBar()->showMessage("CMake 生成成功:" + path, 3000);
|
||||||
|
QMessageBox::information(this, "✅ CMake 生成成功",
|
||||||
|
"CMakeLists.txt 已生成!\n\n"
|
||||||
|
"📁 位置:" + path + "\n\n"
|
||||||
|
"🔨 编译方式:\n"
|
||||||
|
" cd build\n"
|
||||||
|
" make debug # 编译 Debug 版本\n"
|
||||||
|
" make release # 编译 Release 版本");
|
||||||
|
}
|
||||||
|
showCMakeSuccessMsg = true; // 重置为 true,下次生成时还会弹窗
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(cmakeGenerator, &CMakeGenerator::errorOccurred, [this](const QString &error) {
|
||||||
|
statusBar()->showMessage("错误:" + error, 5000);
|
||||||
|
QMessageBox::critical(this, "错误", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateWindowTitle()
|
||||||
|
{
|
||||||
|
QString title = "VoiletCStudio - " + config->getProjectName();
|
||||||
|
if (modified) {
|
||||||
|
title += " [*]";
|
||||||
|
}
|
||||||
|
if (!currentFilePath.isEmpty()) {
|
||||||
|
title += " - " + currentFilePath;
|
||||||
|
}
|
||||||
|
setWindowTitle(title);
|
||||||
|
setWindowModified(modified);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::maybeSave()
|
||||||
|
{
|
||||||
|
if (!modified) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::StandardButton ret = QMessageBox::warning(this, "VoiletCStudio",
|
||||||
|
"工程已修改,是否保存?",
|
||||||
|
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
|
||||||
|
|
||||||
|
if (ret == QMessageBox::Save) {
|
||||||
|
return saveProject();
|
||||||
|
} else if (ret == QMessageBox::Cancel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::newProject()
|
||||||
|
{
|
||||||
|
if (!maybeSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config = new ProjectConfig(this);
|
||||||
|
currentFilePath.clear();
|
||||||
|
modified = false;
|
||||||
|
|
||||||
|
projectNameEdit->clear();
|
||||||
|
compilerPathEdit->clear();
|
||||||
|
assemblerPathEdit->clear();
|
||||||
|
linkerPathEdit->clear();
|
||||||
|
sourceTree->clear();
|
||||||
|
includeDirList->clear();
|
||||||
|
libraryList->clear();
|
||||||
|
defineList->clear();
|
||||||
|
optionList->clear();
|
||||||
|
|
||||||
|
updateWindowTitle();
|
||||||
|
statusBar()->showMessage("已创建新工程", 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::openProject()
|
||||||
|
{
|
||||||
|
if (!maybeSave()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filePath = QFileDialog::getOpenFileName(this, "打开工程", "",
|
||||||
|
"VSC 工程文件 (*.VSC);;所有文件 (*)");
|
||||||
|
|
||||||
|
if (filePath.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->loadConfig(filePath)) {
|
||||||
|
currentFilePath = filePath;
|
||||||
|
modified = false;
|
||||||
|
|
||||||
|
projectNameEdit->setText(config->getProjectName());
|
||||||
|
compilerPathEdit->setText(config->getCompilerPath());
|
||||||
|
assemblerPathEdit->setText(config->getAssemblerPath());
|
||||||
|
linkerPathEdit->setText(config->getLinkerPath());
|
||||||
|
|
||||||
|
sourceTree->clear();
|
||||||
|
QMap<QString, VirtualDir> dirs = config->getVirtualDirs();
|
||||||
|
for (auto it = dirs.begin(); it != dirs.end(); ++it) {
|
||||||
|
QTreeWidgetItem *dirItem = new QTreeWidgetItem(sourceTree);
|
||||||
|
dirItem->setText(0, it.key());
|
||||||
|
for (const QString &file : it->files) {
|
||||||
|
QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem);
|
||||||
|
fileItem->setText(0, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
includeDirList->clear();
|
||||||
|
includeDirList->addItems(config->getIncludeDirs());
|
||||||
|
|
||||||
|
libraryList->clear();
|
||||||
|
libraryList->addItems(config->getLibraries());
|
||||||
|
|
||||||
|
defineList->clear();
|
||||||
|
defineList->addItems(config->getDefines());
|
||||||
|
|
||||||
|
optionList->clear();
|
||||||
|
optionList->addItems(config->getCompilerOptions());
|
||||||
|
|
||||||
|
updateWindowTitle();
|
||||||
|
statusBar()->showMessage("工程已加载:" + filePath, 3000);
|
||||||
|
} else {
|
||||||
|
QMessageBox::critical(this, "错误", "无法加载工程文件:" + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::openProjectFile(const QString &filePath)
|
||||||
|
{
|
||||||
|
// 直接打开指定文件(用于命令行和双击打开),跳过 maybeSave
|
||||||
|
if (!QFile::exists(filePath)) {
|
||||||
|
QMessageBox::critical(this, "错误", "文件不存在:" + filePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->loadConfig(filePath)) {
|
||||||
|
currentFilePath = filePath;
|
||||||
|
modified = false;
|
||||||
|
|
||||||
|
projectNameEdit->setText(config->getProjectName());
|
||||||
|
compilerPathEdit->setText(config->getCompilerPath());
|
||||||
|
assemblerPathEdit->setText(config->getAssemblerPath());
|
||||||
|
linkerPathEdit->setText(config->getLinkerPath());
|
||||||
|
|
||||||
|
sourceTree->clear();
|
||||||
|
QMap<QString, VirtualDir> dirs = config->getVirtualDirs();
|
||||||
|
for (auto it = dirs.begin(); it != dirs.end(); ++it) {
|
||||||
|
QTreeWidgetItem *dirItem = new QTreeWidgetItem(sourceTree);
|
||||||
|
dirItem->setText(0, it.key());
|
||||||
|
for (const QString &file : it->files) {
|
||||||
|
QTreeWidgetItem *fileItem = new QTreeWidgetItem(dirItem);
|
||||||
|
fileItem->setText(0, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourceTree->expandAll();
|
||||||
|
|
||||||
|
includeDirList->clear();
|
||||||
|
includeDirList->addItems(config->getIncludeDirs());
|
||||||
|
|
||||||
|
libraryList->clear();
|
||||||
|
libraryList->addItems(config->getLibraries());
|
||||||
|
|
||||||
|
defineList->clear();
|
||||||
|
defineList->addItems(config->getDefines());
|
||||||
|
|
||||||
|
optionList->clear();
|
||||||
|
optionList->addItems(config->getCompilerOptions());
|
||||||
|
|
||||||
|
updateWindowTitle();
|
||||||
|
statusBar()->showMessage("✅ 工程已加载:" + filePath, 5000);
|
||||||
|
} else {
|
||||||
|
QMessageBox::critical(this, "错误", "无法加载工程文件:" + filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::saveProject()
|
||||||
|
{
|
||||||
|
if (currentFilePath.isEmpty()) {
|
||||||
|
return saveProjectAs();
|
||||||
|
}
|
||||||
|
|
||||||
|
config->setProjectName(projectNameEdit->text());
|
||||||
|
config->setCompilerPath(compilerPathEdit->text());
|
||||||
|
config->setAssemblerPath(assemblerPathEdit->text());
|
||||||
|
config->setLinkerPath(linkerPathEdit->text());
|
||||||
|
|
||||||
|
if (config->saveConfig(currentFilePath)) {
|
||||||
|
modified = false;
|
||||||
|
updateWindowTitle();
|
||||||
|
statusBar()->showMessage("工程已保存:" + currentFilePath, 3000);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
QMessageBox::critical(this, "错误", "无法保存工程文件");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::saveProjectAs()
|
||||||
|
{
|
||||||
|
QString filePath = QFileDialog::getSaveFileName(this, "保存工程", "",
|
||||||
|
"VSC 工程文件 (*.VSC);;所有文件 (*)");
|
||||||
|
|
||||||
|
if (filePath.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentFilePath = filePath;
|
||||||
|
return saveProject();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::generateCMake()
|
||||||
|
{
|
||||||
|
// 先保存工程
|
||||||
|
if (!saveProject()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取工程目录(VSC 工程文件所在目录)
|
||||||
|
QString projectDir = QFileInfo(currentFilePath).absolutePath();
|
||||||
|
QString buildDir = projectDir + "/build";
|
||||||
|
QString cmakePath = projectDir + "/CMakeLists.txt";
|
||||||
|
|
||||||
|
// 删除旧的 build 目录
|
||||||
|
QDir buildPath(buildDir);
|
||||||
|
if (buildPath.exists()) {
|
||||||
|
buildPath.removeRecursively();
|
||||||
|
statusBar()->showMessage("已删除旧 build 目录", 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重新创建 build 目录
|
||||||
|
buildPath.mkpath(buildDir);
|
||||||
|
|
||||||
|
// 生成 CMake 文件
|
||||||
|
if (cmakeGenerator->generate(config, cmakePath)) {
|
||||||
|
statusBar()->showMessage("CMake 已生成,正在编译...", 2000);
|
||||||
|
|
||||||
|
// 在 build 目录执行 cmake
|
||||||
|
QProcess process;
|
||||||
|
process.setWorkingDirectory(buildDir);
|
||||||
|
|
||||||
|
// 跨平台:Windows 必须指定 MinGW Makefiles 生成器
|
||||||
|
QStringList cmakeArgs;
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
cmakeArgs << "-G" << "MinGW Makefiles" << "..";
|
||||||
|
#else
|
||||||
|
cmakeArgs << "..";
|
||||||
|
#endif
|
||||||
|
process.start("cmake", cmakeArgs);
|
||||||
|
process.waitForFinished(10000);
|
||||||
|
|
||||||
|
QString output = process.readAllStandardOutput();
|
||||||
|
QString error = process.readAllStandardError();
|
||||||
|
|
||||||
|
if (process.exitCode() == 0) {
|
||||||
|
statusBar()->showMessage("CMake 配置成功!", 3000);
|
||||||
|
// 不弹窗了,编译按钮会直接打开编译窗口
|
||||||
|
} else {
|
||||||
|
statusBar()->showMessage("CMake 配置失败", 3000);
|
||||||
|
QMessageBox::warning(this, "警告",
|
||||||
|
"CMake 配置完成但有警告:\n" + error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void MainWindow::onProjectNameChanged(const QString &text)
|
||||||
|
{
|
||||||
|
config->setProjectName(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onCompilerPathChanged()
|
||||||
|
{
|
||||||
|
config->setCompilerPath(compilerPathEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onAssemblerPathChanged()
|
||||||
|
{
|
||||||
|
config->setAssemblerPath(assemblerPathEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onLinkerPathChanged()
|
||||||
|
{
|
||||||
|
config->setLinkerPath(linkerPathEdit->text());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addVirtualDir()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString dirName = QInputDialog::getText(this, "添加虚拟目录",
|
||||||
|
"目录名称:", QLineEdit::Normal, "", &ok);
|
||||||
|
|
||||||
|
if (ok && !dirName.isEmpty()) {
|
||||||
|
config->addVirtualDir(dirName);
|
||||||
|
|
||||||
|
QTreeWidgetItem *item = new QTreeWidgetItem(sourceTree);
|
||||||
|
item->setText(0, dirName);
|
||||||
|
sourceTree->expandAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addSourceFile()
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *currentItem = sourceTree->currentItem();
|
||||||
|
if (!currentItem) {
|
||||||
|
QMessageBox::warning(this, "警告", "请先选择虚拟目录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString dirName = currentItem->text(0);
|
||||||
|
|
||||||
|
// 使用 JSON 文件所在目录作为初始目录
|
||||||
|
QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath();
|
||||||
|
|
||||||
|
QStringList files = QFileDialog::getOpenFileNames(this, "选择源文件", initialDir,
|
||||||
|
"C 源文件 (*.c);;所有文件 (*)");
|
||||||
|
|
||||||
|
for (const QString &file : files) {
|
||||||
|
config->addSourceFile(dirName, file);
|
||||||
|
|
||||||
|
QTreeWidgetItem *fileItem = new QTreeWidgetItem(currentItem);
|
||||||
|
fileItem->setText(0, file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeSourceFile()
|
||||||
|
{
|
||||||
|
QTreeWidgetItem *currentItem = sourceTree->currentItem();
|
||||||
|
if (!currentItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString dirName = currentItem->parent() ? currentItem->parent()->text(0) : currentItem->text(0);
|
||||||
|
QString fileName = currentItem->text(0);
|
||||||
|
|
||||||
|
if (currentItem->parent()) {
|
||||||
|
config->removeSourceFile(dirName, fileName);
|
||||||
|
delete currentItem;
|
||||||
|
} else {
|
||||||
|
config->clearVirtualDirs();
|
||||||
|
delete currentItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addIncludeDir()
|
||||||
|
{
|
||||||
|
// 使用 JSON 文件所在目录作为初始目录
|
||||||
|
QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath();
|
||||||
|
|
||||||
|
QString dir = QFileDialog::getExistingDirectory(this, "选择包含目录", initialDir);
|
||||||
|
if (!dir.isEmpty()) {
|
||||||
|
config->addIncludeDir(dir);
|
||||||
|
includeDirList->addItem(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeIncludeDir()
|
||||||
|
{
|
||||||
|
int row = includeDirList->currentRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
QString dir = includeDirList->takeItem(row)->text();
|
||||||
|
config->removeIncludeDir(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addLibrary()
|
||||||
|
{
|
||||||
|
// 使用 JSON 文件所在目录作为初始目录
|
||||||
|
QString initialDir = currentFilePath.isEmpty() ? "" : QFileInfo(currentFilePath).absolutePath();
|
||||||
|
|
||||||
|
QStringList files = QFileDialog::getOpenFileNames(this, "选择库文件", initialDir,
|
||||||
|
"库文件 (*.a *.so *.lib *.dll);;所有文件 (*)");
|
||||||
|
|
||||||
|
for (const QString &file : files) {
|
||||||
|
config->addLibrary(file);
|
||||||
|
libraryList->addItem(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeLibrary()
|
||||||
|
{
|
||||||
|
int row = libraryList->currentRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
QString lib = libraryList->takeItem(row)->text();
|
||||||
|
config->removeLibrary(lib);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addDefine()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString macro = QInputDialog::getText(this, "添加编译宏",
|
||||||
|
"宏定义 (例如 DEBUG):", QLineEdit::Normal, "", &ok);
|
||||||
|
|
||||||
|
if (ok && !macro.isEmpty()) {
|
||||||
|
config->addDefine(macro);
|
||||||
|
defineList->addItem(macro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeDefine()
|
||||||
|
{
|
||||||
|
int row = defineList->currentRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
QString macro = defineList->takeItem(row)->text();
|
||||||
|
config->removeDefine(macro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::addCompilerOption()
|
||||||
|
{
|
||||||
|
bool ok;
|
||||||
|
QString option = QInputDialog::getText(this, "添加编译选项",
|
||||||
|
"编译选项 (例如 -Wall):", QLineEdit::Normal, "", &ok);
|
||||||
|
|
||||||
|
if (ok && !option.isEmpty()) {
|
||||||
|
config->addCompilerOption(option);
|
||||||
|
optionList->addItem(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::removeCompilerOption()
|
||||||
|
{
|
||||||
|
int row = optionList->currentRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
QString option = optionList->takeItem(row)->text();
|
||||||
|
config->removeCompilerOption(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::browseCompilerPath()
|
||||||
|
{
|
||||||
|
QString path = QFileDialog::getOpenFileName(this, "选择编译器", "",
|
||||||
|
"可执行文件 (*)");
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
compilerPathEdit->setText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::browseAssemblerPath()
|
||||||
|
{
|
||||||
|
QString path = QFileDialog::getOpenFileName(this, "选择汇编器", "",
|
||||||
|
"可执行文件 (*)");
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
assemblerPathEdit->setText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::browseLinkerPath()
|
||||||
|
{
|
||||||
|
QString path = QFileDialog::getOpenFileName(this, "选择链接器", "",
|
||||||
|
"可执行文件 (*)");
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
linkerPathEdit->setText(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::compileDebug()
|
||||||
|
{
|
||||||
|
showCMakeSuccessMsg = false; // 编译时不弹 CMake 成功窗
|
||||||
|
compileProject("Debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::compileRelease()
|
||||||
|
{
|
||||||
|
showCMakeSuccessMsg = false; // 编译时不弹 CMake 成功窗
|
||||||
|
compileProject("Release");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::compileProject(const QString &buildType)
|
||||||
|
{
|
||||||
|
// 先校验
|
||||||
|
if (currentFilePath.isEmpty()) {
|
||||||
|
QMessageBox::warning(this, "警告", "请先保存工程!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString projectDir = QFileInfo(currentFilePath).absolutePath();
|
||||||
|
|
||||||
|
// ★ 第一时间弹窗,让用户知道开始工作了
|
||||||
|
QDialog *compileDialog = new QDialog(this);
|
||||||
|
compileDialog->setWindowTitle("编译 " + buildType);
|
||||||
|
compileDialog->resize(800, 600);
|
||||||
|
compileDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
QVBoxLayout *dialogLayout = new QVBoxLayout(compileDialog);
|
||||||
|
|
||||||
|
QLabel *titleLabel = new QLabel("🔨 正在准备编译 " + buildType + "...");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #333;");
|
||||||
|
dialogLayout->addWidget(titleLabel);
|
||||||
|
|
||||||
|
QTextEdit *outputEdit = new QTextEdit();
|
||||||
|
outputEdit->setReadOnly(true);
|
||||||
|
outputEdit->setFont(QFont("Consolas", 10));
|
||||||
|
outputEdit->setStyleSheet("background: #1e1e1e; color: #d4d4d4;");
|
||||||
|
dialogLayout->addWidget(outputEdit);
|
||||||
|
|
||||||
|
QPushButton *closeBtn = new QPushButton("❌ 关闭");
|
||||||
|
closeBtn->setMinimumHeight(35);
|
||||||
|
closeBtn->setStyleSheet("QPushButton { background: #C62828; color: white; font-weight: bold; font-size: 14px; border: none; border-radius: 4px; padding: 6px 16px; }");
|
||||||
|
closeBtn->setEnabled(false); // 编译未完成时不可关闭
|
||||||
|
dialogLayout->addWidget(closeBtn);
|
||||||
|
|
||||||
|
// ★ 锁死编译按钮
|
||||||
|
debugBtn->setEnabled(false);
|
||||||
|
debugBtn->setText("🔒 编译中...");
|
||||||
|
releaseBtn->setEnabled(false);
|
||||||
|
releaseBtn->setText("🔒 编译中...");
|
||||||
|
|
||||||
|
// 弹窗关闭时解锁按钮
|
||||||
|
connect(compileDialog, &QDialog::finished, this, [this]() {
|
||||||
|
debugBtn->setEnabled(true);
|
||||||
|
debugBtn->setText("🐛 编译 Debug");
|
||||||
|
releaseBtn->setEnabled(true);
|
||||||
|
releaseBtn->setText("🚀 编译 Release");
|
||||||
|
});
|
||||||
|
|
||||||
|
compileDialog->show();
|
||||||
|
QApplication::processEvents(); // 立即渲染弹窗
|
||||||
|
|
||||||
|
// ===== 阶段 1:保存工程 =====
|
||||||
|
outputEdit->append("💾 正在保存工程配置...");
|
||||||
|
if (!saveProject()) {
|
||||||
|
outputEdit->append("❌ 保存失败!");
|
||||||
|
titleLabel->setText("❌ 保存失败");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;");
|
||||||
|
closeBtn->setEnabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
outputEdit->append("✅ 工程已保存\n");
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// ===== 阶段 2:清理旧 build =====
|
||||||
|
QString buildDir = projectDir + "/build";
|
||||||
|
outputEdit->append("🧹 正在清理旧的构建目录...");
|
||||||
|
QDir buildPath(buildDir);
|
||||||
|
if (buildPath.exists()) {
|
||||||
|
buildPath.removeRecursively();
|
||||||
|
outputEdit->append("✅ 已删除旧 build 目录\n");
|
||||||
|
} else {
|
||||||
|
outputEdit->append("✅ build 目录不存在,跳过清理\n");
|
||||||
|
}
|
||||||
|
buildPath.mkpath(buildDir);
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// ===== 阶段 3:生成 CMakeLists.txt =====
|
||||||
|
outputEdit->append("📄 正在生成 CMakeLists.txt...");
|
||||||
|
QString cmakePath = projectDir + "/CMakeLists.txt";
|
||||||
|
if (!cmakeGenerator->generate(config, cmakePath)) {
|
||||||
|
outputEdit->append("❌ 生成 CMake 失败!");
|
||||||
|
titleLabel->setText("❌ CMake 生成失败");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;");
|
||||||
|
closeBtn->setEnabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
outputEdit->append("✅ CMakeLists.txt 已生成\n");
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// ===== 阶段 4:CMake 配置 =====
|
||||||
|
outputEdit->append("📋 正在配置 CMake...");
|
||||||
|
QProcess cmakeProcess;
|
||||||
|
cmakeProcess.setWorkingDirectory(buildDir);
|
||||||
|
|
||||||
|
QStringList cmakeArgs;
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
cmakeArgs << "-G" << "MinGW Makefiles" << "-DCMAKE_BUILD_TYPE=" + buildType << "..";
|
||||||
|
#else
|
||||||
|
cmakeArgs << "-DCMAKE_BUILD_TYPE=" + buildType << "..";
|
||||||
|
#endif
|
||||||
|
cmakeProcess.start("cmake", cmakeArgs);
|
||||||
|
cmakeProcess.waitForFinished(30000);
|
||||||
|
|
||||||
|
QString cmakeOutput = cmakeProcess.readAllStandardOutput();
|
||||||
|
if (!cmakeOutput.isEmpty()) {
|
||||||
|
outputEdit->append(cmakeOutput);
|
||||||
|
}
|
||||||
|
QString cmakeError = cmakeProcess.readAllStandardError();
|
||||||
|
if (!cmakeError.isEmpty()) {
|
||||||
|
outputEdit->append("⚠️ CMake 警告/错误:\n" + cmakeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmakeProcess.exitCode() != 0) {
|
||||||
|
outputEdit->append("\n❌ CMake 配置失败!错误代码:" + QString::number(cmakeProcess.exitCode()));
|
||||||
|
titleLabel->setText("❌ CMake 配置失败");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;");
|
||||||
|
closeBtn->setEnabled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
outputEdit->append("✅ CMake 配置完成\n");
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
// ===== 阶段 5:编译 =====
|
||||||
|
outputEdit->append("🔨 正在编译 " + buildType + "...\n");
|
||||||
|
titleLabel->setText("🔨 编译 " + buildType + " 中...");
|
||||||
|
QApplication::processEvents();
|
||||||
|
|
||||||
|
QProcess makeProcess;
|
||||||
|
makeProcess.setWorkingDirectory(buildDir);
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
QString makeCmd = "mingw32-make";
|
||||||
|
#else
|
||||||
|
QString makeCmd = "make";
|
||||||
|
#endif
|
||||||
|
makeProcess.start(makeCmd, QStringList() << buildType.toLower());
|
||||||
|
|
||||||
|
// 实时输出
|
||||||
|
connect(&makeProcess, &QProcess::readyReadStandardOutput, [&outputEdit, &makeProcess]() {
|
||||||
|
outputEdit->append(makeProcess.readAllStandardOutput());
|
||||||
|
outputEdit->verticalScrollBar()->setValue(outputEdit->verticalScrollBar()->maximum());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(&makeProcess, &QProcess::readyReadStandardError, [&outputEdit, &makeProcess]() {
|
||||||
|
outputEdit->append(makeProcess.readAllStandardError());
|
||||||
|
outputEdit->verticalScrollBar()->setValue(outputEdit->verticalScrollBar()->maximum());
|
||||||
|
});
|
||||||
|
|
||||||
|
makeProcess.waitForFinished(300000); // 5 分钟超时
|
||||||
|
|
||||||
|
if (makeProcess.exitCode() == 0) {
|
||||||
|
outputEdit->append("\n✅ 编译成功!");
|
||||||
|
titleLabel->setText("✅ 编译 " + buildType + " 成功!");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #4CAF50;");
|
||||||
|
} else {
|
||||||
|
outputEdit->append("\n❌ 编译失败!错误代码:" + QString::number(makeProcess.exitCode()));
|
||||||
|
titleLabel->setText("❌ 编译 " + buildType + " 失败!");
|
||||||
|
titleLabel->setStyleSheet("font-size: 18px; font-weight: bold; color: #f44336;");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ★ 编译完成,允许关闭和重新编译
|
||||||
|
closeBtn->setEnabled(true);
|
||||||
|
connect(closeBtn, &QPushButton::clicked, compileDialog, &QDialog::accept);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
#ifndef MAINWINDOW_H
|
||||||
|
#define MAINWINDOW_H
|
||||||
|
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QTreeWidget>
|
||||||
|
#include <QTabWidget>
|
||||||
|
#include "projectconfig.h"
|
||||||
|
#include "cmakegenerator.h"
|
||||||
|
|
||||||
|
class MainWindow : public QMainWindow
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
MainWindow(QWidget *parent = nullptr);
|
||||||
|
~MainWindow();
|
||||||
|
|
||||||
|
// 通过命令行/双击打开工程文件
|
||||||
|
void openProjectFile(const QString &filePath);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// 文件操作
|
||||||
|
void newProject();
|
||||||
|
void openProject();
|
||||||
|
bool saveProject();
|
||||||
|
bool saveProjectAs();
|
||||||
|
|
||||||
|
// 生成 CMake
|
||||||
|
void generateCMake();
|
||||||
|
|
||||||
|
// 编译
|
||||||
|
void compileDebug();
|
||||||
|
void compileRelease();
|
||||||
|
void compileProject(const QString &buildType);
|
||||||
|
|
||||||
|
// UI 更新
|
||||||
|
void onProjectNameChanged(const QString &text);
|
||||||
|
void onCompilerPathChanged();
|
||||||
|
void onAssemblerPathChanged();
|
||||||
|
void onLinkerPathChanged();
|
||||||
|
|
||||||
|
// 虚拟目录操作
|
||||||
|
void addVirtualDir();
|
||||||
|
void addSourceFile();
|
||||||
|
void removeSourceFile();
|
||||||
|
|
||||||
|
// 包含目录操作
|
||||||
|
void addIncludeDir();
|
||||||
|
void removeIncludeDir();
|
||||||
|
|
||||||
|
// 库文件操作
|
||||||
|
void addLibrary();
|
||||||
|
void removeLibrary();
|
||||||
|
|
||||||
|
// 编译宏操作
|
||||||
|
void addDefine();
|
||||||
|
void removeDefine();
|
||||||
|
|
||||||
|
// 编译选项操作
|
||||||
|
void addCompilerOption();
|
||||||
|
void removeCompilerOption();
|
||||||
|
|
||||||
|
// 浏览文件
|
||||||
|
void browseCompilerPath();
|
||||||
|
void browseAssemblerPath();
|
||||||
|
void browseLinkerPath();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUI();
|
||||||
|
void setupConnections();
|
||||||
|
void updateWindowTitle();
|
||||||
|
bool maybeSave();
|
||||||
|
|
||||||
|
// 组件
|
||||||
|
QLineEdit *projectNameEdit;
|
||||||
|
QLineEdit *compilerPathEdit;
|
||||||
|
QLineEdit *assemblerPathEdit;
|
||||||
|
QLineEdit *linkerPathEdit;
|
||||||
|
|
||||||
|
QTreeWidget *sourceTree; // 虚拟目录树
|
||||||
|
QListWidget *includeDirList; // 包含目录列表
|
||||||
|
QListWidget *libraryList; // 库文件列表
|
||||||
|
QListWidget *defineList; // 编译宏列表
|
||||||
|
QListWidget *optionList; // 编译选项列表
|
||||||
|
|
||||||
|
QPushButton *saveButton;
|
||||||
|
QPushButton *generateButton;
|
||||||
|
QPushButton *debugBtn;
|
||||||
|
QPushButton *releaseBtn;
|
||||||
|
|
||||||
|
// 配置和生成器
|
||||||
|
ProjectConfig *config;
|
||||||
|
CMakeGenerator *cmakeGenerator;
|
||||||
|
|
||||||
|
QString currentFilePath;
|
||||||
|
bool modified;
|
||||||
|
bool showCMakeSuccessMsg; // 控制是否显示 CMake 成功弹窗
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MAINWINDOW_H
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
#include "projectconfig.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QDir>
|
||||||
|
|
||||||
|
ProjectConfig::ProjectConfig(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
m_projectName = "Untitled";
|
||||||
|
m_projectPath = "";
|
||||||
|
m_compilerPath = "gcc";
|
||||||
|
m_assemblerPath = "gcc";
|
||||||
|
m_linkerPath = "gcc";
|
||||||
|
m_outputDir = "./build";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectConfig::loadConfig(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (doc.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fromJson(doc.object());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProjectConfig::saveConfig(const QString &filePath)
|
||||||
|
{
|
||||||
|
QFile file(filePath);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument doc(toJson());
|
||||||
|
file.write(doc.toJson());
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ProjectConfig::toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject json;
|
||||||
|
|
||||||
|
// 基本信息
|
||||||
|
json["projectName"] = m_projectName;
|
||||||
|
json["projectPath"] = m_projectPath;
|
||||||
|
json["outputDir"] = m_outputDir;
|
||||||
|
|
||||||
|
// 工具链
|
||||||
|
json["compilerPath"] = m_compilerPath;
|
||||||
|
json["assemblerPath"] = m_assemblerPath;
|
||||||
|
json["linkerPath"] = m_linkerPath;
|
||||||
|
|
||||||
|
// 虚拟目录
|
||||||
|
QJsonObject virtualDirs;
|
||||||
|
for (auto it = m_virtualDirs.begin(); it != m_virtualDirs.end(); ++it) {
|
||||||
|
QJsonObject dirObj;
|
||||||
|
dirObj["name"] = it->name;
|
||||||
|
QJsonArray files;
|
||||||
|
for (const QString &f : it->files) {
|
||||||
|
files.append(f);
|
||||||
|
}
|
||||||
|
dirObj["files"] = files;
|
||||||
|
virtualDirs[it.key()] = dirObj;
|
||||||
|
}
|
||||||
|
json["virtualDirs"] = virtualDirs;
|
||||||
|
|
||||||
|
// 包含目录
|
||||||
|
QJsonArray includeDirs;
|
||||||
|
for (const QString &dir : m_includeDirs) {
|
||||||
|
includeDirs.append(dir);
|
||||||
|
}
|
||||||
|
json["includeDirs"] = includeDirs;
|
||||||
|
|
||||||
|
// 库文件
|
||||||
|
QJsonArray libraries;
|
||||||
|
for (const QString &lib : m_libraries) {
|
||||||
|
libraries.append(lib);
|
||||||
|
}
|
||||||
|
json["libraries"] = libraries;
|
||||||
|
|
||||||
|
// 编译宏
|
||||||
|
QJsonArray defines;
|
||||||
|
for (const QString &def : m_defines) {
|
||||||
|
defines.append(def);
|
||||||
|
}
|
||||||
|
json["defines"] = defines;
|
||||||
|
|
||||||
|
// 编译选项
|
||||||
|
QJsonArray options;
|
||||||
|
for (const QString &opt : m_compilerOptions) {
|
||||||
|
options.append(opt);
|
||||||
|
}
|
||||||
|
json["compilerOptions"] = options;
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::fromJson(const QJsonObject &json)
|
||||||
|
{
|
||||||
|
m_projectName = json["projectName"].toString();
|
||||||
|
m_projectPath = json["projectPath"].toString();
|
||||||
|
m_outputDir = json["outputDir"].toString();
|
||||||
|
m_compilerPath = json["compilerPath"].toString();
|
||||||
|
m_assemblerPath = json["assemblerPath"].toString();
|
||||||
|
m_linkerPath = json["linkerPath"].toString();
|
||||||
|
|
||||||
|
// 虚拟目录
|
||||||
|
m_virtualDirs.clear();
|
||||||
|
QJsonObject virtualDirs = json["virtualDirs"].toObject();
|
||||||
|
for (auto it = virtualDirs.begin(); it != virtualDirs.end(); ++it) {
|
||||||
|
VirtualDir dir;
|
||||||
|
dir.name = it->toObject()["name"].toString();
|
||||||
|
QJsonArray files = it->toObject()["files"].toArray();
|
||||||
|
for (const QJsonValue &f : files) {
|
||||||
|
dir.files.append(f.toString());
|
||||||
|
}
|
||||||
|
m_virtualDirs[it.key()] = dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含目录
|
||||||
|
m_includeDirs.clear();
|
||||||
|
QJsonArray includeDirs = json["includeDirs"].toArray();
|
||||||
|
for (const QJsonValue &dir : includeDirs) {
|
||||||
|
m_includeDirs.append(dir.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 库文件
|
||||||
|
m_libraries.clear();
|
||||||
|
QJsonArray libraries = json["libraries"].toArray();
|
||||||
|
for (const QJsonValue &lib : libraries) {
|
||||||
|
m_libraries.append(lib.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译宏
|
||||||
|
m_defines.clear();
|
||||||
|
QJsonArray defines = json["defines"].toArray();
|
||||||
|
for (const QJsonValue &def : defines) {
|
||||||
|
m_defines.append(def.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译选项
|
||||||
|
m_compilerOptions.clear();
|
||||||
|
QJsonArray options = json["compilerOptions"].toArray();
|
||||||
|
for (const QJsonValue &opt : options) {
|
||||||
|
m_compilerOptions.append(opt.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter/Setter 实现
|
||||||
|
void ProjectConfig::setProjectName(const QString &name) {
|
||||||
|
if (m_projectName != name) {
|
||||||
|
m_projectName = name;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getProjectName() const {
|
||||||
|
return m_projectName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setProjectPath(const QString &path) {
|
||||||
|
if (m_projectPath != path) {
|
||||||
|
m_projectPath = path;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getProjectPath() const {
|
||||||
|
return m_projectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setCompilerPath(const QString &path) {
|
||||||
|
if (m_compilerPath != path) {
|
||||||
|
m_compilerPath = path;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getCompilerPath() const {
|
||||||
|
return m_compilerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setAssemblerPath(const QString &path) {
|
||||||
|
if (m_assemblerPath != path) {
|
||||||
|
m_assemblerPath = path;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getAssemblerPath() const {
|
||||||
|
return m_assemblerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setLinkerPath(const QString &path) {
|
||||||
|
if (m_linkerPath != path) {
|
||||||
|
m_linkerPath = path;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getLinkerPath() const {
|
||||||
|
return m_linkerPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::setOutputDir(const QString &dir) {
|
||||||
|
if (m_outputDir != dir) {
|
||||||
|
m_outputDir = dir;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ProjectConfig::getOutputDir() const {
|
||||||
|
return m_outputDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 虚拟目录操作
|
||||||
|
void ProjectConfig::addVirtualDir(const QString &dirName) {
|
||||||
|
if (!m_virtualDirs.contains(dirName)) {
|
||||||
|
VirtualDir dir;
|
||||||
|
dir.name = dirName;
|
||||||
|
m_virtualDirs[dirName] = dir;
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::addSourceFile(const QString &dirName, const QString &filePath) {
|
||||||
|
if (m_virtualDirs.contains(dirName)) {
|
||||||
|
if (!m_virtualDirs[dirName].files.contains(filePath)) {
|
||||||
|
m_virtualDirs[dirName].files.append(filePath);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::removeSourceFile(const QString &dirName, const QString &filePath) {
|
||||||
|
if (m_virtualDirs.contains(dirName)) {
|
||||||
|
m_virtualDirs[dirName].files.removeAll(filePath);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QString, VirtualDir> ProjectConfig::getVirtualDirs() const {
|
||||||
|
return m_virtualDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::clearVirtualDirs() {
|
||||||
|
m_virtualDirs.clear();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含目录操作
|
||||||
|
void ProjectConfig::addIncludeDir(const QString &dir) {
|
||||||
|
if (!m_includeDirs.contains(dir)) {
|
||||||
|
m_includeDirs.append(dir);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::removeIncludeDir(const QString &dir) {
|
||||||
|
m_includeDirs.removeAll(dir);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ProjectConfig::getIncludeDirs() const {
|
||||||
|
return m_includeDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::clearIncludeDirs() {
|
||||||
|
m_includeDirs.clear();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 库文件操作
|
||||||
|
void ProjectConfig::addLibrary(const QString &libPath) {
|
||||||
|
if (!m_libraries.contains(libPath)) {
|
||||||
|
m_libraries.append(libPath);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::removeLibrary(const QString &libPath) {
|
||||||
|
m_libraries.removeAll(libPath);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ProjectConfig::getLibraries() const {
|
||||||
|
return m_libraries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::clearLibraries() {
|
||||||
|
m_libraries.clear();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译宏操作
|
||||||
|
void ProjectConfig::addDefine(const QString ¯o) {
|
||||||
|
if (!m_defines.contains(macro)) {
|
||||||
|
m_defines.append(macro);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::removeDefine(const QString ¯o) {
|
||||||
|
m_defines.removeAll(macro);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ProjectConfig::getDefines() const {
|
||||||
|
return m_defines;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::clearDefines() {
|
||||||
|
m_defines.clear();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译选项操作
|
||||||
|
void ProjectConfig::addCompilerOption(const QString &option) {
|
||||||
|
if (!m_compilerOptions.contains(option)) {
|
||||||
|
m_compilerOptions.append(option);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::removeCompilerOption(const QString &option) {
|
||||||
|
m_compilerOptions.removeAll(option);
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList ProjectConfig::getCompilerOptions() const {
|
||||||
|
return m_compilerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProjectConfig::clearCompilerOptions() {
|
||||||
|
m_compilerOptions.clear();
|
||||||
|
emit configChanged();
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#ifndef PROJECTCONFIG_H
|
||||||
|
#define PROJECTCONFIG_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
// 虚拟目录结构
|
||||||
|
struct VirtualDir {
|
||||||
|
QString name;
|
||||||
|
QStringList files; // .c 文件列表
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProjectConfig : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ProjectConfig(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
// 加载/保存配置
|
||||||
|
bool loadConfig(const QString &filePath);
|
||||||
|
bool saveConfig(const QString &filePath);
|
||||||
|
|
||||||
|
// 工程基本信息
|
||||||
|
void setProjectName(const QString &name);
|
||||||
|
QString getProjectName() const;
|
||||||
|
|
||||||
|
void setProjectPath(const QString &path);
|
||||||
|
QString getProjectPath() const;
|
||||||
|
|
||||||
|
// 编译工具链
|
||||||
|
void setCompilerPath(const QString &path);
|
||||||
|
QString getCompilerPath() const;
|
||||||
|
|
||||||
|
void setAssemblerPath(const QString &path);
|
||||||
|
QString getAssemblerPath() const;
|
||||||
|
|
||||||
|
void setLinkerPath(const QString &path);
|
||||||
|
QString getLinkerPath() const;
|
||||||
|
|
||||||
|
// 输出目录
|
||||||
|
void setOutputDir(const QString &dir);
|
||||||
|
QString getOutputDir() const;
|
||||||
|
|
||||||
|
// 虚拟目录(.c 文件)
|
||||||
|
void addVirtualDir(const QString &dirName);
|
||||||
|
void addSourceFile(const QString &dirName, const QString &filePath);
|
||||||
|
void removeSourceFile(const QString &dirName, const QString &filePath);
|
||||||
|
QMap<QString, VirtualDir> getVirtualDirs() const;
|
||||||
|
void clearVirtualDirs();
|
||||||
|
|
||||||
|
// 包含目录(.h 文件)
|
||||||
|
void addIncludeDir(const QString &dir);
|
||||||
|
void removeIncludeDir(const QString &dir);
|
||||||
|
QStringList getIncludeDirs() const;
|
||||||
|
void clearIncludeDirs();
|
||||||
|
|
||||||
|
// 库文件
|
||||||
|
void addLibrary(const QString &libPath);
|
||||||
|
void removeLibrary(const QString &libPath);
|
||||||
|
QStringList getLibraries() const;
|
||||||
|
void clearLibraries();
|
||||||
|
|
||||||
|
// 编译宏
|
||||||
|
void addDefine(const QString ¯o);
|
||||||
|
void removeDefine(const QString ¯o);
|
||||||
|
QStringList getDefines() const;
|
||||||
|
void clearDefines();
|
||||||
|
|
||||||
|
// 自定义编译选项
|
||||||
|
void addCompilerOption(const QString &option);
|
||||||
|
void removeCompilerOption(const QString &option);
|
||||||
|
QStringList getCompilerOptions() const;
|
||||||
|
void clearCompilerOptions();
|
||||||
|
|
||||||
|
// 转换为 JSON
|
||||||
|
QJsonObject toJson() const;
|
||||||
|
void fromJson(const QJsonObject &json);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void configChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_projectName;
|
||||||
|
QString m_projectPath;
|
||||||
|
QString m_compilerPath;
|
||||||
|
QString m_assemblerPath;
|
||||||
|
QString m_linkerPath;
|
||||||
|
QString m_outputDir;
|
||||||
|
|
||||||
|
QMap<QString, VirtualDir> m_virtualDirs; // 虚拟目录
|
||||||
|
QStringList m_includeDirs; // 包含目录
|
||||||
|
QStringList m_libraries; // 库文件
|
||||||
|
QStringList m_defines; // 编译宏
|
||||||
|
QStringList m_compilerOptions; // 自定义编译选项
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PROJECTCONFIG_H
|
||||||
+542
@@ -0,0 +1,542 @@
|
|||||||
|
# VoiletCStudio 需求规格说明书
|
||||||
|
|
||||||
|
**版本号:** v1.1
|
||||||
|
**项目名称:** 紫罗兰 C 工程配置器(VoiletCStudio)
|
||||||
|
**开发日期:** 2026-04-09
|
||||||
|
**最后更新:** 2026-04-28
|
||||||
|
**作者:** 虾哥
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 目录
|
||||||
|
|
||||||
|
1. [项目概述](#1-项目概述)
|
||||||
|
2. [功能需求](#2-功能需求)
|
||||||
|
3. [界面设计](#3-界面设计)
|
||||||
|
4. [技术规格](#4-技术规格)
|
||||||
|
5. [使用说明](#5-使用说明)
|
||||||
|
6. [文件结构](#6-文件结构)
|
||||||
|
7. [配置文件格式](#7-配置文件格式)
|
||||||
|
8. [编译流程](#8-编译流程)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
### 1.1 项目背景
|
||||||
|
|
||||||
|
VoiletCStudio 是一款跨平台的 C 工程配置工具,旨在简化 C 语言项目的构建流程。通过图形化界面管理工程配置,自动生成 CMakeLists.txt 文件,支持一键编译 Debug 和 Release 版本。
|
||||||
|
|
||||||
|
### 1.2 目标用户
|
||||||
|
|
||||||
|
- C 语言开发者
|
||||||
|
- 嵌入式系统开发人员
|
||||||
|
- 需要跨平台编译的项目团队
|
||||||
|
- 从 IDE 迁移到 CMake 的开发者
|
||||||
|
|
||||||
|
### 1.3 核心价值
|
||||||
|
|
||||||
|
- **简化配置**:图形化界面替代手动编写 CMakeLists.txt
|
||||||
|
- **跨平台支持**:Windows (MinGW) / Linux (GCC) / macOS (Clang)
|
||||||
|
- **提高效率**:一键生成和编译,减少重复劳动
|
||||||
|
- **易于上手**:类似 MDK 的虚拟目录管理,降低学习成本
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 功能需求
|
||||||
|
|
||||||
|
### 2.1 工程文件管理
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-001 | 新建工程 | 创建新的 C 工程配置 | 高 |
|
||||||
|
| F-002 | 打开工程 | 加载已保存的 JSON 配置文件 | 高 |
|
||||||
|
| F-003 | 保存工程 | 保存当前配置到 JSON 文件 | 高 |
|
||||||
|
| F-004 | 另存为 | 将工程保存到指定位置 | 中 |
|
||||||
|
| F-005 | 拖拽打开 | 拖拽 JSON 文件到窗口打开工程 | 中 |
|
||||||
|
|
||||||
|
### 2.2 编译工具链配置
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-010 | 编译器配置 | 设置 C 编译器路径(gcc/clang 等) | 高 |
|
||||||
|
| F-011 | 汇编器配置 | 设置汇编器路径 | 中 |
|
||||||
|
| F-012 | 链接器配置 | 设置链接器路径 | 中 |
|
||||||
|
|
||||||
|
### 2.3 文件管理
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-020 | 虚拟目录 | 创建类似 MDK 的虚拟文件夹 | 高 |
|
||||||
|
| F-021 | 源文件管理 | 添加/移除 .c 源文件 | 高 |
|
||||||
|
| F-022 | 包含目录 | 添加/移除 .h 头文件目录 | 高 |
|
||||||
|
| F-023 | 库文件管理 | 添加/移除 .a/.so/.lib 库文件 | 高 |
|
||||||
|
|
||||||
|
### 2.4 编译配置
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-030 | 编译宏 | 添加/移除预编译宏定义 | 高 |
|
||||||
|
| F-031 | 编译选项 | 添加/移除自定义编译参数 | 中 |
|
||||||
|
|
||||||
|
### 2.5 CMake 生成
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-040 | 生成 CMake | 自动生成 CMakeLists.txt 文件 | 高 |
|
||||||
|
| F-041 | 命令提示 | 显示编译命令使用说明 | 高 |
|
||||||
|
|
||||||
|
### 2.6 编译功能
|
||||||
|
|
||||||
|
| 功能 ID | 功能名称 | 功能描述 | 优先级 |
|
||||||
|
|--------|---------|---------|--------|
|
||||||
|
| F-050 | 编译 Debug | 编译 Debug 版本(带调试信息) | 高 |
|
||||||
|
| F-051 | 编译 Release | 编译 Release 版本(优化发布) | 高 |
|
||||||
|
| F-052 | 实时输出 | 显示编译过程实时日志 | 高 |
|
||||||
|
| F-053 | 自动清理 | 编译前自动清空 build 目录 | 高 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 界面设计
|
||||||
|
|
||||||
|
### 3.1 主界面布局
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ VoiletCStudio - C 工程配置器 │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ ⚡ 快捷操作 │
|
||||||
|
│ [📂 打开] [📄 新建] [💾 保存] [🔨 生成 CMake] [🐛 编译 Debug] [🚀 编译 Release] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 📁 工程基本信息 │
|
||||||
|
│ 工程名:[____________] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 🔧 编译工具链 │
|
||||||
|
│ 编译器:[____] 汇编器:[____] 链接器:[____] │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 📁 源文件 │ 📦 库文件 │
|
||||||
|
│ ├─ 📁 App │ ├─ libmylib.a │
|
||||||
|
│ │ ├─ main.c │ 🏷️ 编译宏 │
|
||||||
|
│ │ └─ utils.c │ ├─ DEBUG │
|
||||||
|
│ 📚 包含目录 │ ⚙️ 编译选项 │
|
||||||
|
│ ├─ ./include │ ├─ -Wall │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ 就绪 [状态栏] │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 按钮说明
|
||||||
|
|
||||||
|
| 按钮 | 颜色 | 功能 | 快捷键 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| 📂 打开工程 | 🔵 蓝色 | 打开已有工程 | Ctrl+O |
|
||||||
|
| 📄 新建工程 | 🟢 绿色 | 创建新工程 | Ctrl+N |
|
||||||
|
| 💾 保存工程 | 🟠 橙色 | 保存配置 | Ctrl+S |
|
||||||
|
| 🔨 生成 CMake | 🟣 紫色 | 生成 CMakeLists.txt | Ctrl+G |
|
||||||
|
| 🐛 编译 Debug | 🔵 青色 | 编译 Debug 版本 | - |
|
||||||
|
| 🚀 编译 Release | 🌸 粉色 | 编译 Release 版本 | - |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 技术规格
|
||||||
|
|
||||||
|
### 4.1 开发环境
|
||||||
|
|
||||||
|
| 项目 | 规格 |
|
||||||
|
|------|------|
|
||||||
|
| 开发语言 | C++ 11 |
|
||||||
|
| GUI 框架 | Qt 5 |
|
||||||
|
| 构建系统 | qmake |
|
||||||
|
| 目标平台 | Windows / Linux / macOS |
|
||||||
|
|
||||||
|
### 4.2 系统要求
|
||||||
|
|
||||||
|
| 项目 | 最低要求 | 推荐配置 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| 操作系统 | Windows 7 / Ubuntu 18.04 / macOS 10.14 | Windows 10 / Ubuntu 20.04 / macOS 11+ |
|
||||||
|
| 内存 | 512 MB | 2 GB |
|
||||||
|
| 磁盘空间 | 100 MB | 500 MB |
|
||||||
|
| 编译器 | GCC 5.0+ / MinGW / Clang | GCC 9.0+ / MinGW-w64 / Clang 10+ |
|
||||||
|
|
||||||
|
### 4.3 依赖库
|
||||||
|
|
||||||
|
| 库名称 | 版本 | 用途 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Qt Core | 5.12+ | 核心功能 |
|
||||||
|
| Qt GUI | 5.12+ | 图形界面 |
|
||||||
|
| Qt Widgets | 5.12+ | 窗口组件 |
|
||||||
|
| CMake | 3.10+ | 构建系统 |
|
||||||
|
| GCC/MinGW | 5.0+ | C 编译器 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 使用说明
|
||||||
|
|
||||||
|
### 5.1 快速开始
|
||||||
|
|
||||||
|
#### 步骤 1:新建工程
|
||||||
|
|
||||||
|
1. 点击【📄 新建工程】按钮
|
||||||
|
2. 输入工程名称
|
||||||
|
3. 点击【💾 保存工程】,选择保存位置
|
||||||
|
|
||||||
|
#### 步骤 2:配置工具链
|
||||||
|
|
||||||
|
1. 点击编译器旁的【浏览】按钮
|
||||||
|
2. 选择编译器路径(如 `/usr/bin/gcc`)
|
||||||
|
3. 同样配置汇编器和链接器(通常与编译器相同)
|
||||||
|
|
||||||
|
#### 步骤 3:添加源文件
|
||||||
|
|
||||||
|
1. 点击【➕ 目录】创建虚拟文件夹(如 "App")
|
||||||
|
2. 选中目录,点击【➕ 文件】添加 .c 文件
|
||||||
|
3. 重复添加所有源文件
|
||||||
|
|
||||||
|
#### 步骤 4:配置包含目录
|
||||||
|
|
||||||
|
1. 切换到右侧"包含目录"面板
|
||||||
|
2. 点击【➕ 添加】选择头文件目录
|
||||||
|
3. 添加所有需要的包含路径
|
||||||
|
|
||||||
|
#### 步骤 5:添加库文件(可选)
|
||||||
|
|
||||||
|
1. 切换到"库文件"面板
|
||||||
|
2. 点击【➕ 添加】选择 .a/.so/.lib 文件
|
||||||
|
3. 添加所有依赖库
|
||||||
|
|
||||||
|
#### 步骤 6:配置编译选项
|
||||||
|
|
||||||
|
1. 切换到"编译宏"面板
|
||||||
|
2. 点击【➕ 添加】输入宏定义(如 `DEBUG`)
|
||||||
|
3. 切换到"编译选项"面板
|
||||||
|
4. 添加自定义编译参数(如 `-Wall`)
|
||||||
|
|
||||||
|
#### 步骤 7:生成 CMake
|
||||||
|
|
||||||
|
1. 点击【🔨 生成 CMake】按钮
|
||||||
|
2. 查看弹出的编译命令提示
|
||||||
|
3. CMakeLists.txt 已生成到工程目录
|
||||||
|
|
||||||
|
#### 步骤 8:编译工程
|
||||||
|
|
||||||
|
**方式一:使用内置编译**
|
||||||
|
1. 点击【🐛 编译 Debug】或【🚀 编译 Release】
|
||||||
|
2. 查看编译输出窗口
|
||||||
|
3. 编译完成后查看结果
|
||||||
|
|
||||||
|
**方式二:手动编译**
|
||||||
|
```bash
|
||||||
|
cd build
|
||||||
|
make debug # 编译 Debug 版本
|
||||||
|
make release # 编译 Release 版本
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 文件结构
|
||||||
|
|
||||||
|
### 6.1 工程目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
MyProject/
|
||||||
|
├── config.json # 工程配置文件
|
||||||
|
├── CMakeLists.txt # CMake 配置文件(自动生成)
|
||||||
|
├── build/ # 编译输出目录(自动生成)
|
||||||
|
│ ├── Makefile # Makefile(自动生成)
|
||||||
|
│ ├── debug/ # Debug 编译产物
|
||||||
|
│ │ └── MyProject # Debug 可执行文件
|
||||||
|
│ └── release/ # Release 编译产物
|
||||||
|
│ └── MyProject # Release 可执行文件
|
||||||
|
├── src/ # 源文件目录(用户创建)
|
||||||
|
│ ├── main.c
|
||||||
|
│ └── utils.c
|
||||||
|
└── include/ # 头文件目录(用户创建)
|
||||||
|
└── utils.h
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 源代码结构
|
||||||
|
|
||||||
|
```
|
||||||
|
VoiletCStudio/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.cpp # 程序入口
|
||||||
|
│ ├── mainwindow.cpp/h # 主窗口实现
|
||||||
|
│ ├── projectconfig.cpp/h # 配置管理
|
||||||
|
│ └── cmakegenerator.cpp/h # CMake 生成
|
||||||
|
├── VoiletCStudio.pro # Qt 项目文件
|
||||||
|
├── README.md # 使用说明
|
||||||
|
└── 需求规格说明书.md # 本文档
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 配置文件格式
|
||||||
|
|
||||||
|
### 7.1 JSON 配置示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectName": "MyProject",
|
||||||
|
"projectPath": "/home/anonymous/Desktop/MyProject",
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"assemblerPath": "/usr/bin/gcc",
|
||||||
|
"linkerPath": "/usr/bin/gcc",
|
||||||
|
"outputDir": "./build",
|
||||||
|
"virtualDirs": {
|
||||||
|
"App": {
|
||||||
|
"name": "App",
|
||||||
|
"files": [
|
||||||
|
"/home/anonymous/Desktop/MyProject/src/main.c",
|
||||||
|
"/home/anonymous/Desktop/MyProject/src/utils.c"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Drivers": {
|
||||||
|
"name": "Drivers",
|
||||||
|
"files": [
|
||||||
|
"/home/anonymous/Desktop/MyProject/src/drivers/driver.c"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"includeDirs": [
|
||||||
|
"/home/anonymous/Desktop/MyProject/include"
|
||||||
|
],
|
||||||
|
"libraries": [
|
||||||
|
"/usr/lib/libmylib.a"
|
||||||
|
],
|
||||||
|
"defines": [
|
||||||
|
"DEBUG",
|
||||||
|
"VERSION=1.0"
|
||||||
|
],
|
||||||
|
"compilerOptions": [
|
||||||
|
"-Wall",
|
||||||
|
"-Wextra"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 字段说明
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 | 必填 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| projectName | string | 工程名称 | 是 |
|
||||||
|
| projectPath | string | 工程路径 | 是 |
|
||||||
|
| compilerPath | string | 编译器路径 | 是 |
|
||||||
|
| assemblerPath | string | 汇编器路径 | 否 |
|
||||||
|
| linkerPath | string | 链接器路径 | 否 |
|
||||||
|
| outputDir | string | 输出目录 | 否 |
|
||||||
|
| virtualDirs | object | 虚拟目录配置 | 是 |
|
||||||
|
| includeDirs | array | 包含目录列表 | 否 |
|
||||||
|
| libraries | array | 库文件列表 | 否 |
|
||||||
|
| defines | array | 编译宏列表 | 否 |
|
||||||
|
| compilerOptions | array | 编译选项列表 | 否 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 编译流程
|
||||||
|
|
||||||
|
### 8.1 CMake 生成流程
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击【生成 CMake】
|
||||||
|
↓
|
||||||
|
读取工程配置(JSON)
|
||||||
|
↓
|
||||||
|
生成 CMakeLists.txt
|
||||||
|
↓
|
||||||
|
显示编译命令提示
|
||||||
|
↓
|
||||||
|
完成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 编译执行流程
|
||||||
|
|
||||||
|
```
|
||||||
|
用户点击【编译 Debug/Release】
|
||||||
|
↓
|
||||||
|
保存工程配置
|
||||||
|
↓
|
||||||
|
删除旧的 build 目录
|
||||||
|
↓
|
||||||
|
创建新的 build 目录
|
||||||
|
↓
|
||||||
|
生成 CMakeLists.txt
|
||||||
|
↓
|
||||||
|
执行 cmake -DCMAKE_BUILD_TYPE=Debug/Release ..
|
||||||
|
↓
|
||||||
|
执行 make debug/release
|
||||||
|
↓
|
||||||
|
实时显示编译输出
|
||||||
|
↓
|
||||||
|
显示编译结果
|
||||||
|
↓
|
||||||
|
完成
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 CMakeLists.txt 结构
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# ========================================
|
||||||
|
# CMakeLists.txt - VoiletCStudio 自动生成
|
||||||
|
# 工程名称:MyProject
|
||||||
|
# 生成时间:2026-04-09 10:00:00
|
||||||
|
# 跨平台支持:Windows (MinGW) / Linux (GCC) / macOS (Clang)
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
# 项目名称
|
||||||
|
project(MyProject C)
|
||||||
|
|
||||||
|
# C 标准
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# 输出目录配置
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build)
|
||||||
|
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build)
|
||||||
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/./build)
|
||||||
|
|
||||||
|
# 编译器配置
|
||||||
|
set(CMAKE_C_COMPILER "/usr/bin/gcc")
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 源文件配置(虚拟目录结构)
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# 分组:App
|
||||||
|
set(SRCS_App
|
||||||
|
/home/anonymous/Desktop/MyProject/src/main.c
|
||||||
|
/home/anonymous/Desktop/MyProject/src/utils.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# 分组:Drivers
|
||||||
|
set(SRCS_Drivers
|
||||||
|
/home/anonymous/Desktop/MyProject/src/drivers/driver.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# 合并所有源文件
|
||||||
|
set(SOURCES
|
||||||
|
/home/anonymous/Desktop/MyProject/src/main.c
|
||||||
|
/home/anonymous/Desktop/MyProject/src/utils.c
|
||||||
|
/home/anonymous/Desktop/MyProject/src/drivers/driver.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建可执行文件
|
||||||
|
add_executable(MyProject ${SOURCES})
|
||||||
|
|
||||||
|
# 包含目录
|
||||||
|
target_include_directories(MyProject PRIVATE
|
||||||
|
/home/anonymous/Desktop/MyProject/include
|
||||||
|
)
|
||||||
|
|
||||||
|
# 链接库
|
||||||
|
target_link_libraries(MyProject PRIVATE
|
||||||
|
/usr/lib/libmylib.a
|
||||||
|
)
|
||||||
|
|
||||||
|
# 预编译宏定义
|
||||||
|
target_compile_definitions(MyProject PRIVATE
|
||||||
|
DEBUG
|
||||||
|
VERSION=1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
# 自定义编译选项
|
||||||
|
target_compile_options(MyProject PRIVATE
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 构建类型配置
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# 默认构建类型:Debug
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Debug 模式配置(自动添加 DEBUG 宏)
|
||||||
|
set(CMAKE_C_FLAGS_DEBUG "-g -O0 -DDEBUG" CACHE STRING "Debug flags" FORCE)
|
||||||
|
|
||||||
|
# Release 模式配置
|
||||||
|
set(CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG" CACHE STRING "Release flags" FORCE)
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# 自定义 Make 目标:make debug / make release
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# make debug - 编译 Debug 版本(自动添加 DEBUG 宏)
|
||||||
|
add_custom_target(debug
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Debug
|
||||||
|
COMMENT "Building Debug version"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
# make release - 编译 Release 版本
|
||||||
|
add_custom_target(release
|
||||||
|
COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Release ${CMAKE_SOURCE_DIR}
|
||||||
|
COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --config Release
|
||||||
|
COMMENT "Building Release version"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
|
||||||
|
# 使用说明:
|
||||||
|
# cmake -B build # 配置(默认 Debug)
|
||||||
|
# cmake --build build # 编译(使用当前 CMAKE_BUILD_TYPE)
|
||||||
|
# make debug # 编译 Debug 版本(自动添加 DEBUG 宏)
|
||||||
|
# make release # 编译 Release 版本
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 常见问题
|
||||||
|
|
||||||
|
### 9.1 编译失败
|
||||||
|
|
||||||
|
**问题:** 点击编译后提示找不到编译器
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
1. 检查编译器路径是否正确
|
||||||
|
2. 确认编译器已安装(`gcc --version`)
|
||||||
|
3. Windows 用户确认 MinGW 已正确安装
|
||||||
|
|
||||||
|
### 9.2 CMake 生成失败
|
||||||
|
|
||||||
|
**问题:** 点击生成 CMake 无反应
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
1. 检查工程是否已保存
|
||||||
|
2. 确认保存路径有写入权限
|
||||||
|
3. 查看状态栏错误提示
|
||||||
|
|
||||||
|
### 9.3 拖拽无反应
|
||||||
|
|
||||||
|
**问题:** 拖拽 JSON 文件到窗口没反应
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
1. 确认文件扩展名为 `.json`
|
||||||
|
2. 确认文件是有效的工程配置
|
||||||
|
3. 尝试使用"打开工程"按钮
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 版本历史
|
||||||
|
|
||||||
|
| 版本 | 日期 | 更新内容 |
|
||||||
|
|------|------|---------|
|
||||||
|
| v1.0 | 2026-04-09 | 初始版本,完整功能发布 |
|
||||||
|
| v1.1 | 2026-04-28 | 修复 Windows 跨平台兼容性:CMAKE_C_COMPILER 设置提前到 project() 之前;自动添加 MinGW Makefiles 生成器;mingw32-make 替代 make;按钮高对比度配色优化 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 联系方式
|
||||||
|
|
||||||
|
- **作者:** 虾哥
|
||||||
|
- **邮箱:** anonymous@linuxacme.com
|
||||||
|
- **项目地址:** https://git.linuxacme.com/iorebuild/VoiletCStudio.git
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本:** v1.0
|
||||||
|
**最后更新:** 2026-04-09
|
||||||
Reference in New Issue
Block a user